Practical Cucumber: Organization

This post is part of our Practical Cucumber series.

Nobody knows how to organize cucumber features and steps. Unlike our beloved web framework, there are not enforced convention. Every project has it’s own conventions–that is, if it has conventions at all. Here is a pattern that works for us.

Group steps by model

Steps should be reusable. We’ve found the best way to keep track of them is to group them by the primary model they affect. Some steps may affect multiple models, but usually there is an obvious choice. For example, the following step could either go in provider_steps.rb or license_steps.rb.

Given '"$name" is licensed in "$state"' do |name, state|
  provider = Provider.find_by_name!(name)
  Factory(:license, :provider => provider, :state => state)
end

While it is related to the Provider model, there are two good reasons it should go in license_steps.rb

  1. The step is creating a License, while it is only finding a Provider
  2. Provider is the most central model in this app. It will likely have a lot more related steps, so for findability’s sake, we will put it in license_steps.rb

Grouping steps by model makes them easy to find and encourages

Group features by…feature

Brilliant, huh? It seems obvious, but we often see apps where the features are grouped by model. There will be one massive appointment.feature model that has completely unrelated scenarios. Put each feature in it’s own file. It’s up to you to decide what a “feature” is.

Here is one subdirectory of the project that I took the above example from:

$ ls features/scheduling_appointment/
canceling_appointment.feature
client_requesting_appointments_with_insurance.feature
client_requesting_chat_appointment.feature
client_requesting_phone_appointment.feature
client_requesting_video_appointment.feature
client_responding_to_rescheduled_appointment.feature
expiring_pending_appointments.feature
marking_an_appointment_complete.feature
provider_avoiding_scheduling_conflicts.feature
provider_requesting_appointment_with_an_unknown_user.feature
provider_requesting_phone_appointment.feature
provider_responds_to_appointment_request.feature
provider_scheduleing_an_appointment.feature
rescheduling_a_scheduled_appointment.feature

Which brings up another point: don’t be afraid to put features in subdirectories. For any large app, it’s almost essential. Cucumber gets confused when you do this, but you can fix that by adding the following to the options in cucumber.yml:

--require features/step_definitions --require features/support

Thoughts?

What do you do to organize your cucumber features and steps?

This post is part of our Practical Cucumber series.

brandon@opensoul.org

Comments

  1. October 01, 2010 at 12:54 PM

    The .yml file is part of the Cucumber gem. If we add these requires it will work fine locally. How might we easily distribute the change across multiple teams and environments as an automation package? Currently we use Bundler to package all of the gems required for our automation framework. A custom cucumber gem is probably not the best solution. Can we override the .yml file?

  2. gregor007@gmail.com
    G-man
    October 03, 2010 at 20:37 PM

    I’m kind a fan of the RESTful approach for smaller projects.. I group features by Model-CRUD description, as in feature files for: company_create, company_read, company_update, and company delete.

    True, the files can get a little long, but I just keep the few @wip features I’m working on at the top, and run an overall rcov coverage test of everything at the end of the day just to keep myself honest.

    Point is, I know right away where to put a feature by just thinking RESTfully.

    Again, for small projects, the few custom steps I may have just go in an extra_steps.rb file.

    Nice blog!

  3. October 26, 2010 at 23:18 PM

    Carl Shaulis: we check the .yml file into git with our projects. You can certainly override it or add to it.

  4. ccummins55@gmail.com
    Charlie Cummins
    March 18, 2011 at 13:48 PM

    I am looking to do some stand alone testing against a Tapestry web app using cucumber. There are many roles in this application. How should I incorporate the roles in cucumber? Should I incorporate the roles in the directory structure or prefix the feature file names with the role.
    Thanks, Charlie

  5. ccummins55@gmail.com
    Charlie Cummins
    March 18, 2011 at 14:16 PM

    I am looking to do some stand alone testing against a Tapestry web app using cucumber. There are many roles in this application. How should I incorporate the roles in cucumber? Should I incorporate the roles in the directory structure or prefix the feature file names with the role.
    Thanks, Charlie

  6. March 24, 2011 at 23:05 PM

    Charlie Cummins: We’ve had good luck with directories, but if you end up with a lot of directories with only one or two files, try to group by type of action and have each role’s version of that in the directory.