Cucumber is a wonderful tool but at the same time it can get you into deep trouble if you don’t use it correctly. Over the years I tried many different styles to write Cucumber Features but in the end most projects ended up with a verbose and brittle set of features, which was a pain to maintain.
I always liked Cucumber because you can write your thoughts without translating them into a programming language. This helps me to think more about the feature at hand and gets me into the mindset of the customer. I understood the goal of Cucumber from the beginning but as a programmer I always looked at it as a testing tool. It took me a long time (multiple years) to notice that the maintenance pain is directly coupled to that thinking.
Cucumber helps you think, specify and document your application in a structured way. That we can also use this documentation to drive our tests is convenient but should not be your primary goal. The distinction you need to make, is that you write the Cucumber Features to document and not to test your application. If you apply this thinking you’ll notice that you don’t “program” with cucumber anymore. You don’t try to create highly reusable step definitions but just write a different one. The end result is that you have a lot of step definitions, which do similar things but are not the same step. The features will be much more expressive but since you don’t tackle duplication on the text-level you need to deal with it in the implementation of the steps.
After I realized that the Cucumber Features are not the right level to abstract we started using the PageObject pattern to reduce the duplication in our step implementations. A PageObject as the name says is an object, which represents a page in your application. The object has methods to interact with your application and to assert that it is in a certain state (effectively what you are doing when you are acceptance testing). The PageObject is an abstraction for your tests that you can combine and reuse in your step implementations to fit your needs. In the beginning you have to put some more work into creating the PageObjects but as time passes you will have an API to navigate your application and it will save you a lot of time. It also allows you to have many steps, which vary only slightly but can be implemented with 1 or 2 lines, which is not a problem when you need to react to change.
Another benefit of PageObjects is that you have a shared place to put assertions about your application. You could for example verify that the correct tab is selected before performing an operation.
Imagine you are writing the next big blogging engine. You could have a Feature which looks like:
1 2 3 4 5 6 7 8 9
Of course as always with examples it’s very simple but notice that we have descriptive steps, which likely won’t be reused. After writing such a Feature I got into the habit of just running it and copying all the needed step definitions:
1 2 3 4 5 6 7 8 9 10 11
Now we can apply the PageObject pattern to build an object, which represents the article page we are developing. The PageObject is very straight forward: a simple class and the necessary methods to implement our steps:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
We need to include the PageObjects module into our World that we can
access the helper method
1 2 3 4
and the step implementations:
1 2 3 4 5 6 7 8 9 10 11 12 13
Even though this a tiny example you can see an API emerge around our
page. If we need to write a feature to ensure that the comments are
ordered by date they are created we could already reuse the
1 2 3 4
1 2 3 4 5 6 7 8 9 10 11 12 13 14
The examples illustrate only the tip of the iceberg. I plan to do a few follow-up posts to describe how we use PageObjects to make our step implementations less brittle, more helpful and how you can split your PageObjects into smaller reusable pieces.
If you have experience using the PageObject pattern or use a different approach to achieve the same goal please leave a comments.