Ruby on Rails became one of the most popular web development frameworks thanks to its ‘convention over configuration’ motto. With its common structure, developers can easily move between projects with minimal effort. They know exactly where to find the database schema, view layouts and other files without having to go searching through the codebase.
The way that code is structured plays an important role in maintainability and productivity. While is no objectively ‘right way’ to structure code, there are known good practices that can help avoid mistakes that others have made.
In this post we’ll take a look at some popular ways to structure behavior-driven development, or BDD, automation code to keep everything organized, improve maintainability and maximize productivity.
Behavior-driven development is a set of collaborative practices designed to increase the value produced by software development. By improving communication between business and technical teams, BDD reduces rework caused by misunderstood or vague requirements, technical debt caused by a reluctance to refactor, and slow feedback cycles from silos and handovers.
The most important part of the BDD process is the deliberate discovery meeting where stakeholders, developers and test engineers meet to discuss user stories before they are pulled into development. After discussing the user story, the team creates human-readable and machine-parsable specifications that, when automated, will verify that each technical specification meets the specific business requirements.
There are plenty of resources that discuss how to get started with BDD processes, as well as write effective scenarios, but few resources discuss how to organize the automation code an application. Organization plays an important role in the ease-of-use and maintainability of the living documentation, as well as the productivity of the development team.
There are many different behavior-driven development approaches, but Cucumber and Gherkin have emerged as one of the most popular frameworks. We’ll look at how to organize Cucumber automation with Cucumber Ruby in this article, but similar principles apply to many other BDD frameworks.
Cucumber Ruby creates a `/features` directory by default to house `.feature` files. Each feature file describes a single area of functionality or requirement, such as a customer editing their profile, and contains multiple scenarios within the same file that illustrate the functionality or requirement. Scenarios contain Given-When-Then steps that describe behavior in a human-readable and machine-parsable way.
Note: The organization of Cucumber Ruby may differ from ports for other languages.
Feature files are structured like this:
Feature: A short description of the feature
In order to realize a business value
As a specific system actor
I want to gain some outcome
Scenario: Some specific situation
Given some precondition
And some other precondition
When some action is taken by the user
And some other action is taken by the user
Then some testable outcome happens
And something else happens
Scenario: Some other specific situation
Scenario: Some other specific condition
Feature files can be housed directly under the `/features` directory with a separate folder for step definitions. Many teams start out organizing their step definitions around their feature files – there are even forks of Cucumber that make this a first-class feature – but we’ve found this doesn’t work very well in the long run.
We recommend arranging step definitions by domain concept or system interface, while arranging feature files by business functionality. This is more cohesive than arranging by feature, since the body of step definitions face towards the application itself and allows things to change naturally over time.
An effective directory structure looks like this:
| |__ user_profile
| | |__ edit_user_profile.feature
| | |__ user_steps.rb
Notice how the step definitions are organised by domain concept, into a “user steps” file. This could contain other steps that work with user data that are used by other features. That way, all the code that works with user data lives in one place.
Code structure plays an important role in test maintainability and developer happiness. By keeping these best practices in mind, you can improve your team’s tests and ensure that everything is running smoothly.