Reusable Cucumber Steps
Mike Swieton recently posted Never say “Click” advocating the use of custom steps over browser-centric steps such as When I press “submit”. I know and respect Mike and a lot of the Atomic Object team; they’re a bright bunch. I appreciate where he’s coming from, however following his suggestions you are going to create many unnecessary custom steps. And I firmly disagree with that.
You waste time debugging custom steps
Building reusable steps is one of the rare ideological goals that actually works very well in practice. Lets split up Cucumber’s architecture into three parts. You have the code under test, the cucumber scenario, and your cucumber steps. When you have a failure it can crop up at either the scenario level or the step level. Debugging steps can be a pain in the ass because a scenario can use a hodge-podge of steps that are littered across multiple files AND require context of the steps executed prior.
Some code to illustrate my point
Let’s revive our scenario from our previous post.
Given a product named "Some Product" When I add "Some Product" to my favorites Then I should see "Some Product" in my favorites
Sure when I read this scenario, it makes sense on a high level. But if I have a problem, I’m going to have an assertion error buried somewhere in the steps rather than at the top level scenario. If it was an error at the scenario level, it would be apparent to start investigating my code under test.
Given the following product exists: | Name | | Some Product | When I go to the home page And I follow "Some Product" And I press "Add to Favorites" And I follow "Favorites" Then I should see "Some Product"
Now since all steps are small and reusable, the burden of the precondition and assertion falls on the scenario itself.
What do I mean by “reusable steps”?
Are your steps only being used 3 or 4 times? Are they only being used from a single scenario? If you answer “yes” to a large number of your steps, you may be able to do better.
Here’s another test. Compare the line of code (LOC) ratio between your features and your steps using these two commands:
# Print the total lines of code in your features find features -iname "*.feature" | xargs egrep -i "(Given|When|Then|And)" | wc -l # Print the total number of steps you have defined find features/step_definitions -iname "*.rb" | xargs egrep -i "(Given|When|Then)" | wc -l
Here is the ratios on one of our client projects that I feel is does a good job reusing steps: 2045 feature LOC and 176 step LOC or 11.6:1. Let’s check out a couple other open source projects: rspec has a ratio of 18.2:1 and vcr has a ratio of 31.4:1.
Reusable steps are the true benefit of Cucumber
One argument I hear from many people new to cucumber is that it adds a lot more overhead without much benefit compared to traditional assertion frameworks like test/unit and rspec. You know what, when you approach a 1:1 feature line-to-step ratio, I completely agree with them! That doesn’t mean cucumber is bad, it means that often our first inclination is to write custom steps instead of finding ways to refactor the ones we have. Cucumber’s primary benefit is building a comprehensive test suite from reusable steps.
So in conclusion, my own code and the projects I have been part of have continue to validate that reusable steps are a big win. When testing rails stick with cucumber’s web steps, and “click” the crap out of it.
I can understand how this post can be construed as an argument against the plain-text readability of cucumber features. I don’t feel I’m arguing against that, I don’t think they are mutually exclusive goals. I do appreciate readable features, and I have recently come across relish and think the principle behind it is pretty slick. Here are the docs to both rspec and vcr, the two libraries I referenced above.
I know that some developers use Cucumber as a plain-text communication bridge between developers and non-developers, and in my experience and attempts that just has not stuck. Plain-text or not, the format of Given/When/Then still feels structured enough to give off that code smell. I still feel Cucumber is a valuable testing tool, and we use it on our own projects constantly. Even if those features are never shown to non-developers. There is the case for readability among developers, and regarding relish above I think that has merit. However adding one-use custom steps in order to convey intent to another developer misses the point, which is the purpose of this article.