I’m starting yet another new test execution framework, called Dialect. I’ll be the first to admit any “dialect” posts will be self-serving in one respect: they will be my attempt to document to myself my own thought process as I go about creating the framework. That said, I definitely wish I had a resource like this when I was first starting out. So my hope is others will find value in what I’m providing, whether or not they ever use Dialect.
As I looked at certain test frameworks that are out there, I often wished that I was able to see the evolution of the framework itself. Sure, I can look at the commit history and try to understand how it was built up but that quickly becomes cumbersome since you don’t have context into any thought or design process. What I really wanted was a diary, of sorts, that indicated how the person was thinking, what ideas they discarded (and why), and some of the inherent challenges that often are beyond frustrating when going through them but eventually smoothed over by time, beyling the inherent flux that goes into any act of creation.
I do plan on having certain feature branches live on in the main Dialect repository. Those will serve as snapshots in time of certain development paths. That will also mean these posts will have relevance even down the line as Dialect changes.
Why the name Dialect? Well, a little while ago I posted some opinionated statements about naming your test tools with some thought. Am I doing so here? Not sure, to be honest. Dialect, the concept, is a particular way of communicating. Dialect, the tool, is a way of having certain test artifacts “speak” a certain dialect when the framework is utilized by those artifacts.
It makes sense when I say it in my head…
Dialect will be distributed as a Ruby gem. Why Ruby? Because of all the languages I’ve worked with, it has been the one that has allowed me the most freedom to express code the way I want to.
What does Dialect do?
The gemspec of Dialect provides a basic description:
Dialect is a framework that provides a way to describe your
application in terms of activity and page definitions. Those
definitions can then be referenced by test libraries using
the DSL that Dialect provides.
The DSL provides a fluent interface that can be used for
constructing test execution logic. This fluent interface
promotes the idea of compressibility of your test logic,
allowing for more factoring, more reuse, and less repetition.
You can use Dialect directly as an automated testing solution
or you can use it with other tools such as RSpec, Cucumber,
or my own Lucid tool.
From that you can see that a design goal of Dialect is to provide a domain-specific language for test automation. The plan is for this DSL to extend the human-readable BDD style of frameworks such as Cucumber and RSpec into the automation code itself. Part of that is via the use of page and activity definitions, which will be discussed another time, and the idea of a fluent interface.
These interfaces tend to be a bit misunderstood, I’ve found. So let’s talk briefly about what I mean based upon what I think the actual meaning is from my research.
Fluent interfaces are semantic façades. You put them on top of existing code to reduce syntactical noise and to more clearly express what the code does, ideally in a ubiquitous language that reflects the business domain that the code supports. The core idea behind building a fluent interface is one of readability, the idea being that someone reading the code should be able to understand what is being achieved without having to dig into the implementation to clarify details.
When fluent interfaces get larger and more complex, they suddenly get called Domain Specific Languages. What’s usually happening is that a fluent interface ends up being a particular pattern to use in emerging a DSL. There are various ways to do this: named parameters, method chaining, factory classes. The details of this get into implementation that I will be discussing as I figure out what my implementation is! However, I’ve certainly found that thoughts along these lines impact the API you want to provide for your test framework.
The Framework API
Let’s consider another tool for a second: Capybara. Capybara allows you to write tests and then run them in any compatible driver. Examples of compatible drivers are Selenium-WebDriver and Rack::Test. Dialect is taking a different approach: it settles upon a single driver (Watir-WebDriver) initially. Why? Watir, like Capybara, is just an API that provides a layer of abstraction on top of your actual automation library. With both Capybara and Watir, the library they use is Selenium. If it helps, think of Capybara or Watir as a type of translator. You effectively tell it to do something and it translates a (hopefully simple) command into the API of your given driver. The API of the driver can be a lot less friendly, as Selenium tends to be (although it has gotten better).
So to use this translator you need to have both a way of telling it what to do and also an automation library API for it to translate in to.
Like the fluent interface, a lot of the API is really just a bit of a façade. Dialect, like Capybara or Watir, in the best way possible, provides a lot of “syntactic sugar” around some basic building blocks. In the case of Capybara, for example, these building blocks are basically just XPath expressions to find elements on a web page and then delegate the action down to the underlying driver. With Dialect, something similar happens except without the reliance on XPath. Most of the time it makes sense to use this “sugared” API, as your code is made a lot more expressive and readable.
For Capybara, aside from the obvious benefit of “write once, run on multiple drivers”, you also get a semantically clean API that does allow for a fluent interface. But this does bring up a good point. Do I plan on supporting multiple drivers? Do I want to provide support for Watir and Selenium? Do I want to provide support for Capybara?
As just mentioned, Watir and Capybara both use Selenium as their driver. Further, both are just abstraction layers on top of Selenium to make Selenium less problematic to deal with.
There seems to be little reason to support both Watir and Capybara. So I decided to choose one and I chose Watir. Why? Largely because I’m more familiar with it. I’ve played with Capybara and I like it. But it grew up in the Rails context and while it can certainly work outside that context, I find it a little cumbersome in some cases. However, the syntactic sugar that it puts in place is undeniably nice and I could see incorporating some ideas from that into Dialect. A tool that does a good job of providing a layer around Capybara’s layer would be SitePrism.
Side note: As a designer of test frameworks, I keep finding myself drawn back to Capybara and I’m not entirely sure why that is. I think in some ways I like the ecosystem that is growing up around it. I’m pretty sure that before I get too far into Dialect-with-Watir, I’ll be doing a development spike to see what, if anything, Capybara offers me. I have to imagine that this is a concern many test solution developers face. It’s also often the last thing you want to do. At least for me it feels like I’m defocusing on the stuff I “really” want to work on. It is, however, important to consider alternatives, even if that means delaying your own implementation.
What about Selenium? There’s a gem out there called page-object that attempts to support both Selenium and Watir. The benefit of this approach is that it allows you to provide a common command API that lets you normalize interaction with Selenium and Watir. So, for example, I can make a call that selects a particular element from a select list. And even though Selenium has a very different way of doing that than Watir, page-object normalizes that interaction.
That, however, comes at a cost, which is the complexity of your framework. You now have to account for the distinct differences between the libraries (Selenium and Watir) which adds to your development burden, your testing burden, and your ability to support changes and updates to those libraries. The source code of page-object is a testament to the large amount of redundant logic you have to provide to support this kind of approach. However, there is something about the “support different drivers” approach that I like so I’m keeping that option open with Dialect. That said, I think Capybara (and perhaps even Kookaburra) have a better implementation of this idea than page-object.
So … off I go?
That, for better or worse, is the start of the diary in terms of the basis upon which I’m starting to construct Dialect. I’m really hoping to develop Dialect in an incremental and visible fashion. Even if this visibility helps no one but myself, I feel it will be worth the effort.