How we write readable feature tests with RSpec
Chris Zetter, a developer in the FutureLearn product team, talks about how the team writes feature tests after moving from Cucumber to RSpec in the pursuit of maintainable and readable tests.
Testing is an integral part of building and maintaining a large platform. When we build new parts of the FutureLearn platform we write automated feature tests to document how the new feature should work and to let us know when it doesn’t.
Cucumber Love-hate
Cucumber is a commonly used tool for writing feature specs, and seemed like the obvious choice to use when we started the project. It let us write high level behaviour driven tests from the user’s perspective:
We liked Cucumber because it meant the tests were very easy to create from our user stories, and once written, easy to read. However, there were a few downsides to using Cucumber. Firstly, we were already using RSpec for the rest of the project, so having Cucumber meant another test framework dependency that we had to maintain. Secondly, there was a cognitive overhead of switching between them because of the different DSLs and test runners. Finally, we particularly didn’t like Cucumber’s use of regular expressions, because they made it harder to follow what code is being executed when tests are run as compared to standard method invocation in Ruby.
Writing better RSpec features
So, how could we stop using Cucumber but keep the readable tests that we liked?
We had already started using RSpec features instead of Cucumber, and often they looked like this:
These had a tendency to be quite long, making it difficult to understand what they were testing. There is also no clear separation between the Arrange, Act, Assert parts of the test (known as ‘Given’, ‘When’ and ‘Then’ in the language of Cucumber). We tried adding comments to the steps in the code, but found that these suffered the same fate as comments often do in application code: after time they get out of sync with the actual code.
Typically, a method this long elsewhere in our application code would have been a red flag, which we’d normally refactor through the therapeutic act of method extraction. So why not do just that? Let’s extract those lines and name each method in the same style as a Cucumber step:
What we found
We’ve since removed all of our Cucumber features and converted most of them to these new style RSpec feature specs. They retain the benefit of readability that Cucumber previously provided, yet are much easier to write and maintain.
We made a conscious decision not to optimise for code-reuse of our extracted methods between different feature files as we were worried that it would make the tests harder to follow. We have found that some code reuse tends to happen naturally when writing multiple scenarios around a single feature.