Up to now I’ve seen quite a few projects using SpecFlow for conducting automated regression tests. Even if all these projects are located in different domains and are implemented with different technologies such as Asp.net, WPF or WCF, they still have one problem in common: the step definitions are messy and hard to maintain
Driver Pattern is a powerful tool to prevent this problem. It becomes even more efficient due to its combination with SpecFlow’s Context Injection. Therefore, it is first important to understand how context injection works.
Context Injection is a simple dependency-injection framework shipped with SpecFlow. However, it is unique, as it doesn’t support Inversion of Control out of the box, as one might expect. As a result, only concrete types can be injected. At first glance, this constraint causes the feeling of incompleteness, but you will see that it is brilliant for the purpose of SpecFlow.
Basically, context Injection was designed to share data between step definitions. The two most important rules for context injection are:
- The lifetime of an injected object is limited to the scenario execution.
- Within one scenario execution, you can be sure that you will always get the same instance of the object. (It acts like a singleton)
More rules can be found in the official SpecFlow documentation
Too theoretical? Here is an example:
Story behind the example: Let’s say I’m some kind of forgetful guy and always forget to water my flowers. That’s why I bought a small sprinkler system, which luckily offers a .Net Sdk. (Sad to say that only the first sentence is correct.) I have implemented an application which allows me to start watering my flowers. I’ve generated some gherkin steps with following step definitions:
In the above example, we have two different classes with one step definition in each. These two step definitions need to share some data. That’s why the
FlowerContext is injected twice during the execution of one scenario:
- During the first time, SpecFlow creates a new instance of
FlowerContextand passes it to the Constructor of
- During the second time, as Specflow is smart enough to know that an instance of
FlowerContextwas already created, it passes this one to
I think the really cool thing about this is, that context injection works just out of the box. There is no need for registering the
FlowerContext to any IOC container or put it into any additional configuration.
Ok, but where is the driver?
Basically, a driver acts like a Data Context. The main difference between the driver and a data context is that a data context, like we have seen above, is only used for holding data. In contrast, a driver is something more complex. It isolates the automation logic, combines different tasks of this logic and may even offer some kind of state.
Many step definitions I’ve seen look something like this:
I guess you see the problem with this kind of test code. It’s hard to read, understand and also to maintain. The interesting thing, however, is that most of the developers I know would never write a production code like this. But if we talk about test code: “Yolo man, it’s green”. For an unknown reason, I still have the feeling that test code is too often seen as a kind of unloved duty by many developers. But hey, here is a little secret: If you do it right, writing test code can be quite cool. That’s why it deserves your love, too.
For this reason I created a
FlowerSprinkerDriver, which holds all the knowledge about how to perform the automation logic:
This causes, that the step definition only delegates to the correct driver functions and has some assertions:
Beside the logic of how something is done, like in the
GetLastWateredFlower() function, the driver also provides some state information in the
TodaysWateredFlowers property. This results, that the state can be safely used in different step definitions during one scenario execution.
The combination of context injection and driver pattern is a really powerful tool for cleaning up step definitions. Some of the main advantages are:
- achieving a better separation of concerns within test code: On the one hand, the step definitions concentrate on their actual task of translating human language to automation code; on the other hand, each driver has his well-defined scope of automation logic.
- There is no problem with transferring data between the step definitions as the relevant data is held by the driver itself; further, through context injection, it acts as a singleton for each scenario execution.
- step definitions will be easier to read (and maintain), as one to five meaningful driver-function calls are easier to understand compared to a wall of automation code.
About the Page Object Pattern
In this context there is also the term Page Object Pattern . This pattern works like the Driver Pattern we have seen above. With the only difference, that a Page Objects encapsulates UI (for example a webpage, or only some controls of it, etc.), while a Driver encapsulates some functions layered above the UI. If you use a lot of automated UI-testing, you can consider to group your test infrastructure by Page Objects. I will not go into depth on this topic, as there are many good posts about this out there. For example Martin Fowler wrote about it. And believe me, if Martin Fowler writes about something, you shouldn’t try to make it better. But if you use the Page Object Pattern in combination with SpecFlow, don’t forget the Context Injection.
Since this bullshitbingo can be a bit confusing, here is a short summary: a context provides common used data (state), a driver encapsulates some test automation logic, and a page object encapsulates some UI access logic. In combination with Context Injection they transform to powerful Ninjas.
- Page Objects:
- Page Objects with Selenium:
Deepen your knowledge
If you want to deepen your knowledge about BDD/ATDD and Specflow, I recommend the SpecFlow Course held by Gaspar Nagy, who is the creator and main contributor of SpecFlow. In this training concepts such as Design Pattern or Page Object Pattern are explained in detail.