7 Steps to better Cucumber tests
When you are starting out with rails testing there are so many things you need to learn about and thus it’s very hard to have a clear picture of what goes where and what’s the best way of writing unit tests and view specs and integration tests etc.
With this post I’m hoping to shed some light or at least give you a few ideas on how to properly use cucumber to write your features. This is by no means the only way to do it but it’s a guide that I’ve tested and it seems to be working very well on a number of different projects.
1. One feature, one business goal
The feature description is that thing at the top that looks like this:
Feature: Submit order In order to buy a product As a client I want to be able to submit an order
As developers it’s sometimes helpful to think in terms of business value, that means the value that the thing we’re testing brings to the business. There’s always a business goal for every new thing you write (or at least it should be). Not only that it forces you to see the big picture and answer why you are writing the code, but it also keeps you from going into too much detail as you will quickly figure out what is important and what is not.
2. Nothing else matters
When writing down features it’s a good idea to bring the stakeholder(s) at the table and describe exactly what matters for each one of the features, don’t be afraid to get very detailed here. It’s not only useful because you can find a common language but also it can save your back the next time “but I thought… feature X does Y” comes up, and it will.
Since you’re describing exactly what’s important and in great detail, you have everything you need to start working. You also have the context (the business goal) and the stakeholder’s approval.
3. Avoid unnecessary details in your scenarios
There is always the tendency to describe too much in one scenario. That can easily lead to messy features. It’s better to try and stick to describing one thing only in each scenario.
So one example that pops up often is this:
Given I’ve not logged in.
Instead of assuming that the user already has an account but he has not logged in yet, you can simply replace that with:
Given I am on the homepage and since you don’t mention anything about logging in, it’s assumed that you haven’t gone through the login process and thus you are not logged in.
4. What’s the user’s story
For each of your scenarios you can imagine the user’s journey through your application, actually through your user interface (UI).
You want to limit the features to what the user can actually see and do using the UI. So a few obvious things to avoid here are assertions about things that are not displayed in the UI, things like saving records to the database or API calls. You can do all that in your unit tests so there’s no point of mixing those in here.
5. Clarity is more important than reusability
This is probably one of the most confusing ones because as a programmer and especially a ruby one that’s been bombarded with DRY from day one, you’re always on the lookout for things you can refactor into reusable chunks.
When testing, in general not just for integration tests, it’s a lot better to think about your tests as documentation as opposed to highly optimized code. Being more verbose is better than having to decipher what you meant three months ago or having to visit 3-4 files to figure out what the current test is saying. It will also help your colleagues understand your intent.
6. Implicit subjects are to be avoided
Always make sure that the object you are referring to is specified clearly. That means if you need to say
it, then it should be obvious what
it refers to in the current context.
This is similar to the previous step, it’s a way of making sure that you can read the test as you would read documentation. The test you have in front of you should give you all the details you need in order to figure out what the business goal is.
7. Use the declarative style
The declarative style describes behaviour at a higher level, which I think improves the readability of the feature by abstracting out the implementation details of the application. So in practice it’s easier for someone less familiar with the application to read and understand the features.
Imagine hiring a product manager six months from now and have him/her read the features. This new person should have no trouble understanding each and every one of them. It should basically serve as a high level documentation for the entire application (that should be the goal).
Here’s an example of declarative style:
Scenario: User logs in Given I am on the homepage When I log in Then I should see a login notification
versus the imperative style:
Scenario: User logs in Given I am on the homepage When I click on the "Login" button And I fill in the "Email" field with "firstname.lastname@example.org" And I fill in the "Password" field with "secret" And I click on "Submit" Then I should see "Welcome to the app, John Doe"
Of course we still have these login steps where the user fills in the form in the first (declarative) example but they are hidden in some step definition. I think the declarative style makes the test easier to read and understand.
Another thing to consider here is to stay away from specifics like IDs, class names or strings inside the
.feature file and move those into the step definitions.
Tests are all about readability. The more abstraction you add to your tests, the less readable they will appear to the next person that reads them, and that could be you six months from now. So try to keep them to the point and very readable.
If you liked the article please share it so other people can learn from it.