The Semantic Model is a vital part of a
well-designed DSL, it provides a clear separation of concerns between the core
(Selenium) model and the DSL independently, building new features into the
model before figuring out how to expose them through the language (can use Bridge). As bottom line our DSL merely acts as a
mechanism for expressing how the model is configured. Meaning that the DSL is just
a thin facade over the model (again, the model might be a testing library or
framework). So, we aim that our DSL should enhance the capabilities of that
model. The right DSL makes it easier to understand what a particular functionality
does.
Looking at the big picture, DSL supports a bare
minimum of features needed to support its domain (SUT). We can't build an entire software system in it,
but rather use a DSL for one particular aspect of a system – yeas this is the testing.
So our OOP language of choice fits perfect with the DSL as a particular way of
using this general-purpose language. A DSL script is valid code in its
general-purpose language, but only uses a subset of the language's features in
a particular style to handle one small aspect of the overall system. The result
should have the feel of a custom language, rather than its host language
(consider Fluent interface). When using the DSL, we should
have the feel of putting together whole sentences, rather than a sequence of
disconnected commands.
Let’s talk more about the Semantic Model pattern - all
the important semantic behavior is captured in a model, and the DSL's role is
to populate that model via a parsing step (from the BDD steps to the definition
ones). As already said our Semantic model is a completely normal object model,
which can be manipulated in the same way as any object model you might have. So
our Semantic model is nothing more than a library or framework that the DSL
uses. Already had our hands on the
BDD Gherkin’s style of coding we start to see the seamless
transition our DSL provides for the programming language. It’s true that
sometimes we have methods whose names make no sense in an open context, but
read properly in the context of a DSL sentence. With DSL naming, it's the
sentence that comes first; the elements are named to fit in with that context.
DSL names are written with the context of the specific DSL in mind, while
command-query names are written to work without any context (or in any context,
which is the same thing).
In my opinion the top most layer (BDD) is
really closer to the Procedural programming, than anything else. Your Step defs
simply contain a series of computational steps to be carried out by your
predefined test work flow. Any given procedure (definition) might be called at
any point during a program's execution (the runner), including by other
procedures or itself. Furthermore Modularity is generally desirable, especially in large,
complex solutions. Inputs are usually specified syntactically in the form of
arguments and the outputs delivered as return values.
So keeping things simple - a DSL is really just
an API, and you are using facilities of a language you already know. But this
DSL should be easier to learn. So keep in mind this when creating a layer of
Expression Builders over the model. The Expression Builders are relatively
straightforward to write, but most of the effort isn't in getting them to work, but in fiddling with the language so that you have something that works well.
This Expression Builder cost won't appear if you are putting the fluent methods
directly in the model, but that may lead to other costs if people find these
methods confusing compared to a command-query API. We should aim at nothing
more than a convention to use certain fluent methods to do things. One more
advantage is that it produces a relatively restricted range of what can be done.
This restriction can make it easier to understand what to do, and serves as a
barrier to bugs. If you have a DSL with strong boundaries that limits the kinds
of things you need to test for. With a general-purpose language, anything is
possible, so you have to watch the boundaries through convention and review.
By nature Selenium 2.0 aims to
provide a basic set of building blocks from which developers can create their
own Domain Specific Language. There are already good Selenium DSL examples. This fact drives us further and I can point at
next step - Method chaining (Progressive Interfaces).
As stated in the book I already mentioned – “a valuable variation to the basic
Method Chaining approach is to use multiple interfaces to drive a fixed
sequence of method-chaining calls”. This works really great with our BDD
support. We utilize the Progressive interfaces to enforce mandatory elements in
our test workflow. We can implement this by defining an interface that only
takes a single mandatory element.
Another option for mandatory clauses are the Nested Functions. Some
attention is required for the sentences finishing. It may become a problem that
pops up from time to time. A good way to go is a Nested Closure.
One more key point is the translation layer
that translates the fluent interface into the command-query API. To do so we
again can turn to the Expression Builder - an object that provides a fluent
interface which it then translates into calls on an underlying command-query API. You can consider using Composite to build sub-expressions within an overall
clause.
Finally (probably my favorite feature) DSL
advocates Metaprogramming, meaning that our program modify itself at run-time.
It’ll minimize the number of lines of BDD code need to express a solution, also
gives greater flexibility and efficiency without recompilation. Here the BDD
scenario tags will do the job just fine. I personally use
them as Fixture strategy, both Setup and Teardown. You can implement such special logic for
tagged scenarios in the event bindings, scoped bindings or step bindings by
querying the ScenarioContext.Current.ScenarioInfo.Tags
property. If done properly your DSL will turn writing the test cases in nothing more than a BDD recipe, leaving 90% automation efforts at the highest level. In time you'll use more and more WHAT steps, over HOW steps.
By now I hope that I've managed to convince you,
that our DSL is a representation of the syntax of the OOP general-purpose
language we already use. Basically, it's a stylized use of that language for the
domain-specific purpose of testing.
No comments:
Post a Comment