Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can I specify step definition file for each feature file? #748

Closed
robsonrosa opened this issue Feb 2, 2017 · 18 comments
Closed

How can I specify step definition file for each feature file? #748

robsonrosa opened this issue Feb 2, 2017 · 18 comments

Comments

@robsonrosa
Copy link

My Goal

I am trying to create a scalable structure of features and step definitions for a large application and my first shot was trying to link step_definition files to features so that I could use the same step pattern for different step definitions.

My Code

I show my current example:

My folder structure:

/features/sample.feature
/features/example.feature
/features/step_definitions/sample_steps.js
/features/step_definitions/example_steps.js
/features/step_definitions/common/common_steps.js

In my sample.feature I have:

  Scenario: Launching Cucumber
  Given I have some step definitions
   When I check some step definition with parameter "any"
   Then I should see all green "sample"

In my example.feature I have:

  Scenario: Launching Cucumber
  Given I have some step definitions
   When I check some step definition with parameter "any"
   Then I should see all green "example"

The Given and When steps are defined at /common/common_steps.js file and works fine.

The Then step is defined both to sample_steps.js and example_steps.js but differently.

In my sample_steps.js I have:

Then('I should see all green {stringInDoubleQuotes}', (arg) => {
   if (arg !== 'sample') {
     throw 'I should see all green when the argument is "sample"';
   }
   return;
 });

And, finally, in my example_steps.js I have:

Then('I should see all green {stringInDoubleQuotes}', (arg) => {
   if (arg !== 'example') {
     throw 'I should see all green when the argument is "example"';
   }
   return;
 });

The Error

My main goal is to have all green here, but of course, it doesn't work and I get this obviouly error:

Multiple step definitions match:
   I should see all green {stringInDoubleQuotes} - features\step_definitions\example_steps.js:6
   I should see all green {stringInDoubleQuotes} - features\step_definitions\sample_steps.js:6

Cucumber-JVM

I know that in cucumber-jvm we can specify a glue attribute that links features and step_definitions and it's exactly what I'm looking for, but in cucumber-js. Example in Java:

@RunWith(Cucumber.class)
@Cucumber.Options( glue = { "com.app.stepdefinitions.common", "com.app.stepdefinitions.sample" } )
public class SampleFeature{
}

@RunWith(Cucumber.class)
@Cucumber.Options( glue = { "com.app.stepdefinitions.common", "com.app.stepdefinitions.example" } )
public class ExampleFeature{
}

Finally

How can I achieve the same as cucumbr-jvm using cucumber-js?

@charlierudolph
Copy link
Member

I am trying to create a scalable structure of features and step definitions for a large application and my first shot was trying to link step_definition files to features so that I could use the same step pattern for different step definitions.

Very interesting. cucumber-js has nothing built in like the cucumber-java example you gave. The idea I like for this type of thing, is pushing this logic switching into your world setup or instance. Either way you end up with only one step definition. You can have multiple world definitions that you switch between when defining your support code or have a single world instance that exposes different methods based on context. We don't currently expose the file of the current scenario to running steps but you could use tags to determine your context.

@leipert
Copy link

leipert commented Feb 10, 2017

We actually have same need for this. We are using nightwatch-cucumber to run selenium tests and our only solution for now is to add a prefix to each step:

Given [comp1] I click on "Open dialog"

vs

Given [comp2] I click on "Open dialog"

This helps us avoid ambiguous step definitions, but leads to really unreadable feature files.
I tried tinkering around with the cucumber.js source, but found no good hints to add support for this feature.

Could this be realized with some hooks or alternative Worlds?

@charlierudolph
Copy link
Member

@leipert what user interface are you envisioning? I believe this logic should live in the world or support for multiple world objects. Then the step definitions just deal with parsing the gherkin matches and passing them to the proper world function. We could add a CLI option for selecting which world object to use.

For the moment if you would like to have multiple worlds / step definitions you can achieve this by putting your code in separate folders and only requiring one of them per run (using the --require CLI option).

@pinxue
Copy link

pinxue commented Feb 16, 2017

Well, "the cucumber book" specifically discourage this way of designing steps. Step is shared between scenarios by design, the same sentence should has consistent meaning despite the feature using it. Once you introduce the scope of steps, it is really easy to create language trap.

So far only tags are close to your purpose in cucuber.js. But only hooks can declare they are specific to tags. If you are certain it is right way for your people, you may invent a DSL, perhaps simple as [feature name] in step pattern.

@robsonrosa
Copy link
Author

Thanks, @pinxue . Very useful response. However, I can't understand it:

Well, "the cucumber book" specifically discourage this way of designing steps.

A same action phrase could have different meanings in different contexts. But it's ok. It's good to know the options I have, Actually, we're already walking on a internal DSL to achieve it,

Thanks a lot.

@leipert
Copy link

leipert commented Feb 20, 2017

Thanks for your answers @pinxue and @robsonrosa.
Here is my reasoning for scoped step definitions:

  1. Global variables are bad and do not scale:
    We just have around 15 feature file files with 10 scenarios each, and it is already hard to structure all step definitions and feature files. For big applications (say 100 feature files with 10 scenarios with an average of 5 Steps) having all steps defined globally seems insane, as it is really hard to track which feature file uses which steps.
  2. Developers have no control over wording in feature files:
    In our use case our "management" writes feature files, it is hard to sell them: We do awesome BDD, but you have to limit yourself to an awkward custom DSL.
  3. If cucumber-jvm has the option, why doesn't cucumber-js ?
    This may be a bad argument 💃

I see the following approaches to solve the problem for us :

  1. Create a custom DSL with prefixes in step definitions
    Cons: Not nice to work with.
    Pros: needs no changes in cucumber.js
  2. Create glue for cucumber.js
    Cons: May go against the idea of "The Cucumber Book".
    Pros: Feature parity with cucumber.js
  3. Change how we run the tests
    Cons: This feature will be available to less people.
    Pros: needs no changes in cucumber.js

That being said, we probably will go with 3., if the maintainers of cucumber.js think scoped step definitions are out of scope (pun intended) for this project.

@robsonrosa I would be interested in your solution, once you have one.

@unional
Copy link

unional commented Mar 23, 2017

IMO scoping is definitely needed. As your application grows, the number of features expands and you will end up with conflicting descriptions in different contexts.

In my company, our product has hundreds of features and QA has test cases in 100k range.
If we use cucumber we will definitely run into this problem.

Yes, I think you should consider context instead of scoping.
You can have most, if not all, of your step definitions in the default context (or no-context), but there should be a way to specify the context without the need of a custom DSL.

It is the BA and QA who should be writing these tests and any custom DSL is bounded to create confusion and resistance.

The Feature/Scenario already provide contexts by definition, that's why the Gherkin syntax has the indentation.

Adding tags and custom DSL is a workaround of implementation limitation (i.e. a hack, IMO) instead of a solution.

Maybe you can consider this while considering #745 ?

How about extending Scenario from defineStep and pass {Given, When, Then} into the callback?

i.e.:

import {defineSupportCode} from 'cucumber'

defineSupportCode(({ Scenario, Given, When, Then }) => {
  Given(...)
  When(...)
  Then(...)
  Scenario('<match scenario description>', ({ Given, When, Then}) => {
    Given('<take precedent of non-contexted>', ...)
    ...
  })
})

@unional
Copy link

unional commented Mar 23, 2017

I learn BDD from http://fitnesse.org/ so I may look at BDD differently than in the cucumber community.

@mattwynne
Copy link
Member

mattwynne commented Mar 23, 2017

Paging @richardlawrence

@richardlawrence
Copy link

This is an area where Cucumber is particularly opinionated. It's built around the belief that teams should grow a ubiquitous language, where words and phrases mean exactly one thing in the context of an application and are used consistently. Keeping step defs global maintains a positive pressure to avoid ambiguity.

In the rare cases where an application has multiple distinct bounded contexts (in DDD terms), you would simply divide your Cucumber suite along the same lines your application is divided to reflect that boundary, and step defs would be global within each bounded context.

@lancerkind
Copy link

An article about working with Cucumber and creating boundaries. It implements some of the ideas on this page but doesn't present any great solutions as @richardlawrence has mentioned that you can't configure Cucumber to focus on one a particular class for step definitions. http://confessionsofanagilecoach.blogspot.com/2017/05/teaching-cucumbers-about-boundaries.html

@lancerkind
Copy link

As @leipert said, global variables are bad. I think those of us in the CucumberJVM world are only getting half the story. Cucumber (non JVM) uses a tidy World concept which is a global memory space for the duration of the Scenario. After the Scenario is executed, it is destroyed. This feels like a good solution. But... I've not seeing a good implementation of this pattern in CucumberJVM. So if Cucumber forces us to have a flat/global namespace for step definitions, and CucumberJVM had a clear design pattern for implementing the World pattern, then I'm a little happier. Up to this point, CucumberJVM + World pattern == overly complicated solutions. So far, everything I've seen is more complicated than simply letting me control which step functions go with which feature file. So far the alternatives I've seen give me nothing more valuable for all the effort/complexity. Other types of Cucumber have better World solutions.

Ultimately, even with the World pattern I'm still be unhappy because I know there will be information loss when going from feature file to steps. If I spend great effort: putting my feature files in a good organization, creating good feature file names, declaring my feature in a smart way, naming my scenarios in a smart way--all context is tossed out the window and I'm forced to work with global step definition namespace.

@eliezercazares
Copy link

I can only think of keeping the relationship out of the validations of the system and simply add wildcards on the paths for the tests, and make them work, even if it takes modifying some open source framework. might give it a shot as our project grows.

@andreasmarkussen
Copy link

andreasmarkussen commented Apr 9, 2018

I understand your goal, but I would not recommend it.
I would also recommend having explicit .feature files, with either a background statement or an additional Given step that makes this explicit.

Alternatively you could make two different configuration files.
One for the sample and another for the example.
Then you could point to different step folders.

@cristianmercado19
Copy link

@robsonrosa @leipert I share your opinion
Almost two years after original post...
Did you get any progress with that? Any workaround?

@leipert
Copy link

leipert commented Jan 10, 2019

@cristianmercado19 Sorry, no. I am not using cucumber js anymore.

@cristianmercado19
Copy link

cristianmercado19 commented Jan 10, 2019

Ups... I like the way to write feature file completely isolated from the step's implementation.
I have tried to achieve the same goal with mocha but I am not happy with.
If you got any other alternative which commits my goal, more than happy to give a try.
Appreciate your help @leipert .

@lock
Copy link

lock bot commented Jan 10, 2020

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Jan 10, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests