top of page
hand-businesswoman-touching-hand-artificial-intelligence-meaning-technology-connection-go-

How to avoid Duplicates in Step Definition

Sometimes, we get Ambiguous Step Definition or Duplicate Step Definition errors while writing Step Definition in Cucumber.

If Cucumber encounters duplicate or ambiguous Steps, all the other Steps of the Scenarios are skipped, and the Scenario is marked as a fail.

These errors are common when we have multiple Steps Definitions files or Large Projects.

You do not have to worry about this because Cucumber itself identifies it and returns an error for you, and it will also tell you the method name which is creating these problems.


let’s create two very simple and very similar feature files



When we start creating our step definition files



Next we might start creating the step definitions file for the Subtract feature and notice one step as already existing (i.e. it’s not got a highlight background colour). If you press the CTRL key and move the mouse over the step which appear to exist already (i.e. Two input values, 1 and 2) a hyperlink line will appear, click this and it will take you to the previously added step in the AddStepdefs file.


What’s happening here is that whilst we might define separate classes per feature (which may well seem a logical way to write our test code/step definitions), Cucumber is actually matching to methods based upon the RegEx within the annotations, i.e. Given Two input values, 1 and 2 maps to


Cucumber doesn’t actually take notice of the Given/When/Then annotations for matching the method to the line of Gherkin code.


Let us assume that we simply copy the missing step into the SubtractStepdefs.java file, we now have duplicate step definitions according to Cucumber, which is ofcourse correct if we think that each step is in essence globally scoped by Cucumber.

Or to put it another way, Cucumber will search through all the packages within the “Glue” package(s) to locate matching RegEx’s. If it finds more than one matching RegEx we get a duplicate step error. Here’s the (truncated) error that Cucumber will display for us when we try to run all features.




--------Handling duplicate step definitions---------

So how do we resolve a situation where we want to run all features and we have duplicate steps?


The easiest solution is, ensure you never have duplicate steps unless you intended to reuse the same step definition code – the general idea is to think of the language used to define a step in Gherkin as a specific task and another step using the same text is really the same task (at least when associated with the same package).


Ofcourse we can have duplicate steps in different packages, we just need to ensure we run features against on that package using the Glue option, i.e. do not reference all packages but just point to the one’s specific to the features.


SOME EXPERT'S OPINION

Try follow each one to get more performance and use the best practices always 🙂

1. Feature Files Should Actually be Features, Not Entire Portions of an App One feature per well named file, please, and keep the features focused.

2. Avoid Inconsistencies with Domain Language You’ll get the most benefit out of using Cucumber when your customers are involved. To that end, make sure you use their domain language when you write stories. The best course of action is to have them involved in writing the stories.

3. Organize Your Features and Scenarios with the Same Thought You Give to Organizing Your Code One useful way to organize things is by how fast they run. Use 2-3 levels of granularity for this:

  • Fast: scenarios that run very fast, e.g. under 1/10 of a second

  • Slow: scenarios that are slower but not painfully so, maybe under one second each

  • Glacial: scenarios that take a really long time to run

You can do this separation several different ways (and even some combination):

  • Put them in separate features

  • Put them in separate subdirectories

  • Tag them

4. Use Tags Tags are a great way to organize your features and scenarios in non functional ways. You could use @small, @medium and @large, or maybe @hare, @tortoise, and @sloth. Using tags let you keep a feature’s scenarios together structurally, but run them separately. It also makes it easy to move features/scenarios between groups, and to have a given feature’s scenarios split between groups. The advantage of separating them this way is that you can selectively run scenarios at different times and/or frequencies, i.e. run faster scenarios more often, or run really big/slow scenarios overnight on a schedule. Tagging has uses beyond separating scenarios into groups based on how fast they are:

  • When they should be run: on @checkin, @hourly, @daily

  • What external dependencies they have: @local, @database, @network

  • Level: @functional, @system, @smoke

  • Etc.

5. Use Rake Tasks to Run Features This provides a consistent environment for running features: this way each run uses the same set of options and parameters. This goes a long way toward maintaining deterministic results. Another benefit is that this makes for easy integration with continuous integration tools. There is a single point of entry into the spec run, with all options/parameters encapsulated.

6. Don’t Get Carried Away with Backgrounds (Stick to Givens) The larger the background, the greater the load of understanding for each scenario. Scenarios that contain all the details are self-contained and as such, can be more understandable at a glance.

7. Make Scenarios Independent and Deterministic There shouldn’t be any sort of coupling between scenarios. The main source of such coupling is state that persists between scenarios. This can be accidental, or worse, by design. For example one scenario could step through adding a record to a database, and subsequent scenarios depend on the existence of that record. This may work, but will create a problem if the order in which scenarios run changes, or they are run in parallel. Scenarios need to be completely independent. Each time a scenario runs, it should run the same, giving identical results. The purpose of a scenario is to describe how your system works. If you don’t have confidence that this is always the case, then it isn’t doing its job. If you have non-deterministic scenarios, find out why and fix them.

8. Write Scenarios for the Non-Happy-Path Cases As Well Happy path tests are easy; edge cases and failure scenarios take more thought and work. Here’s where having some good (and yet pathological) testers on the team can reap rewards. Use rcov with your full Cucumber runs to find holes in coverage.

9. Be DRY: Refactor and Reuse Step Definitions Especially look for the opportunity to make reusable step definitions that are not feature specific. As a project proceeds, you should be accumulating a library of step definitions. Ideally, you will end up with step definitions that can be used across projects. 10. Use a Library (Such as Chronic) for Parsing Time in Your Step Definitions This allows you to use time in scenarios in a natural way. This is especially useful for relative times. Background: Given a user signs up for a 30 day account Scenario: access before expiry When they login in 29 days Then they will be let in Scenario: access after expiry When they login in 31 days Then they will be asked to renew

11. Revisit, Refactor, and Improve Your Scenarios and Steps Look for opportunities to generalize your steps and reuse them. You want to accumulate a reusable library of steps so that writing additional features takes less and less effort over time.

12. Refactor Language and Steps to Reflect Better Understanding of Domain This is an extension of the previous point; as your understanding of the domain and your customer’s language/terminology improves, update the language used in your scenarios.

13. Use Compound Steps to Build Up Your Language Compound steps (calling steps from steps) can help make your features more concise while still keeping your steps general—just don’t get too carried away. For example: Given /^the user (.*) exists$/ do |name| # ...endGiven /^I log in as (.*)$/ do |name| # ...endGiven /^(.*) is logged in$/ do |name| Given "the user #{name} exists"Given "I log in as #{name}"end

14. Use Parallel Step Definitions to Support Different Implementations for Features For example, running features against Webrat and Selenium. Put these step definitions somewhere where they won’t be auto-loaded, and require them from the command line or rake task.

15. Avoid Using Conjunctive Steps Each step should do one thing. You should not generally have step patterns containing “and.” For example: Given A and B should be split into two steps: Given A And B Bye 🙂 And Happy Learning :-)



506 views0 comments

Recent Posts

See All
bottom of page