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

Allow multiple step patterns on the same method #957

Closed
mikeholler opened this issue Jan 28, 2016 · 25 comments
Closed

Allow multiple step patterns on the same method #957

mikeholler opened this issue Jan 28, 2016 · 25 comments
Labels
⚡ enhancement Request for new functionality

Comments

@mikeholler
Copy link

I've been working with Cucumber JVM for the past couple of months and have been loving it, but there's one feature I would love to see and I think others might as well.

Basically, I'd like to do something like this:

@When(
        value = {
                "^I perform the calculation (\\d+) plus (\\d+)$",
                "^I add the numbers (\\d+) and (\\d+)$"
        }
)
void performXPlusY(int x, int y) {
    // do the step
}

I know it's technically possible to do this via regex, but it's very ugly, uses extra capture groups, and allows for more than the two desired patterns:

@When("^(I perform the calculation|I add the numbers) (\\d+) (plus|and) (\\d+)$")
void performXPlusY(String ignored1, int x, String ignored2, int y) {
    // do the step
}

What does everyone think about this? Is it something that would be easy enough to add?

@lvinniel
Copy link

Plus 1!

@moreau-nicolas
Copy link

I think this feature would be nice to have too!

Regarding the implementation I'd rather let the annotations be @Repeatable.

Your solution might be friendlier to non-Java 8 users, though.

@mikeholler
Copy link
Author

@moreau-nicolas it would be possible to make this annotation repeatable on JVM versions that support it. You can have it so that people using Java 8 can write:

@When("^I perform the calculation (\\d+) plus (\\d+)$")
@When("^I add the numbers (\\d+) and (\\d+)$")
void performXPlusY(int x, int y) {
    // do the step
}

And people using Java < 8 could write:

@When(
        value = {
                "^I perform the calculation (\\d+) plus (\\d+)$",
                "^I add the numbers (\\d+) and (\\d+)$"
        }
)
void performXPlusY(int x, int y) {
    // do the step
}

Notice that both examples work in Java 8, but only the latter works in Java < 8.

@dkowis
Copy link
Member

dkowis commented Feb 5, 2016

I don't believe this is a feature in any of the other Cucumber implementations. You are able to delegate to a method call, having two annotated methods, 3 methods total.

Help me understand how will this feature improve the quality of your Scenarios? How will this feature improve the communication between stakeholders?

@mikeholler
Copy link
Author

Cucumber relies on the natural language capabilities of Gherkin to allow tests to be written that are supposedly easy for both managers and programmers to understand. However, it's not always one person that writes specs (Gherkin .feature files) and even if there is one person it's very easy to write two steps that mean the same thing in syntactically different ways.

Currently, the only ways to handle syntactically different but semantically the same steps are:

  • Modify the step to match a previously written (and running) step
  • Create a step handler for the syntactically new step and delegate it to the older step

Since Cucumber and Gherkin pride themselves on natural language and quality of the human-computer interface, I believe there should be an easier way than delegation to allow both syntactically different steps to exist.

@moreau-nicolas
Copy link

I completely agree with @mikeholler !

@jcannon-sovrn
Copy link

+1 - as the number of scenarios we have grows and grows - 240 and counting in one of our products - it's very possible that you end up reusing steps whose function is exactly what you want, but as part of the plain-language story you are describing with scenarios and steps, sounds a little weird. I've ended up using the regex option for is/are, a/an, plurals, etc., which works, but does make it look a little confusing to someone else who is coming up to speed on our integration tests; I'd hate to extend it much farther than it already is. It would be handy to put another @when or similar annotation on a glue function instead. I've just accepted that occasional lines in my scenario will work functionally but only "mostly" make sense from a language perspective.

@mikeholler
Copy link
Author

@jcannon-sovrn I didn't think about it at first, but pluralization is an obvious benefit to this.

@dkowis this is something I would love to see added (obviously) but am wondering if Cucumber-JVM is the best place to ask for this feature. Is there a better place to make this request, perhaps on a universal cucumber standard rather than this implementation?

@asilvis
Copy link

asilvis commented Apr 1, 2016

+1 for this.

I've some situations that means same code but diff step def. I'd love this feature.

@mikeholler
Copy link
Author

@dkowis any update on whether this is the correct place for this issue?

@hanemay
Copy link

hanemay commented May 31, 2016

@mikeholler have you found out anything regarding this issue, a workarround ?

@dkowis
Copy link
Member

dkowis commented May 31, 2016

The pure java implementation stands alone using annotations. Even the scala and jruby backends don't use annotations and instead use a DSL. Ruby and Javascript implementations also use a DSL. And it is limited to one regexp per step method, or an ugly complicated regex. I believe the proper solution in all of the other languages is to write methods that delegate to another method. Totaling N+1 methods for the number of regexes you want to match, or even just N methods if they delegate directly to the original.

I think this is the right place for the issue, but I don't think it's something we want to have in cucumber-java.

I think the solution is going to be:

Create a step handler for the syntactically new step and delegate it to the older step

@mikeholler
Copy link
Author

@dkowis, thanks for responding. I respectfully disagree with your position on the basis of code (glue) quality and readability, and beg you to reconsider.

It's hard to argue that delegating to an older step is more concise and readable than my proposed solution.

It seems to me the point of "glue" functions is to link a step definition with instructions for step execution. That is, to map syntax to semantic meaning. The glue should be considered semantically (this is how to do X) rather than syntactically (do X).

It's true that most other languages utilize a DSL, but even with a DSL the ability to have multiple syntactically different regexes defined for a single step is possible by allowing the DSL to accept an array of regexes in addition to a single regex. Perhaps this is a change that needs to happen on the Cucumber platform level, rather than the Cucumber-JVM level.

Thank you for reconsidering.

@brasmusson
Copy link
Contributor

I always read Andrew Premdas/@diabolo posts on the cukes group very carefully. From what he write I know that he as tons of more experience with using Cucumber(-Ruby) than I have with using any Cucumber implementation, and I want to tap into that experience as much as I can.

I common thread many of these post is to to focus on writing good helper classes to be able to interact with the system under test, and to write one line step definitions that delegate to a method on a helper class ("step definitions should only be used for translating natural language to calls"). When doing that there is no problem with having one step definition for each regex variant calling the same helper method.

If that experience with Cucumber(-Ruby) and Ruby is valid also for Cucumber-JVM and the JVM-languages, then allowing multiple step patterns on the same method is an unwanted functionality as it encourages user to put functionality in step definitions instead of delegating to well design and implemented helper classes. Which a much more experience Cucumber user than me has learned is a mistake.

@mattwynne
Copy link
Member

I've often wanted this feature, and I think it would be useful. We kinda have this in Ruby, with the one line step definitions feature which allows you to map step match patterns to method names.

@gasparnagy has added this to SpecFlow.

I think anything that encourages people to express themselves fully in Gherkin is a good idea, and I see this feature as pulling things in that direction. I agree with what @brasmusson has said about keeping step definition methods short, but I don't think this feature is incompatible with that guideline.

@moreau-nicolas
Copy link

I agree with @mattwynne : let's keep step definition methods short and provide a means to avoid methods that are just there to allow for synonyms.

@mikeholler
Copy link
Author

Yeah, I very much agree with @brasmusson as well, but also believe too that this feature is not incompatible with short, concise step code. One should always strive to write short, concise code for steps and in all other situations.

I want this feature not as a crutch so I can write bloated steps, but as a tool I can use to consolidate repetitive steps all using the same helper method into a single step with multiple annotations.

@aslakhellesoy aslakhellesoy added Java ⚡ enhancement Request for new functionality labels Aug 30, 2016
@mpkorstanje
Copy link
Contributor

mpkorstanje commented Jun 18, 2017

I've been writing my step definitions exclusively using lambda step definitions for a while now and I find that both of the desires in this discussion can be satisfied. Lambda step definitions allow for short, concise step code while also consolidating repetitive steps.

For example:

@When("^I perform the calculation (\\d+) plus (\\d+)$")
@When("^I add the numbers (\\d+) and (\\d+)$")
void performXPlusY(int x, int y) {
    // do the step
}

When there just isn't enough going on in a new set of step definitions to justify the creation of a proper helper class I find that I would write this by as:

public LambdaStepdefs() {
    When("^I perform the calculation (\\d+) plus (\\d+)$", this::performXPlusY);
    When("^I add the numbers (\\d+) and (\\d+)$", this::performXPlusY);
}
private void performXPlusY(int a, int b){
  // do the step
}

And once the step definition starts to grow in complexity I find that I extract a helper class and things end up looking like this. I don't think this can be any more concise.

public LambdaStepdefs(Calculator calculator) {
    When("^I perform the calculation (\\d+) plus (\\d+)$", calculator::performXPlusY);
    When("^I add the numbers (\\d+) and (\\d+)$", calculator::performXPlusY);
}

So is this discussion still relevant after two years? I believe that Android support for java 8 was lagging behind a bit in 2016. I don't have any experience with that. But if Android support is longer an issue I think we can close this ticket.

@mikeholler
Copy link
Author

Thanks for the comment! You're right, lambda step defs do solve this problem quite nicely. I would love to use this on Android, however yes, this issue is still relevant.

Android support for Java 8 was announced in the past few months, but that is only for latest version of Android. As you may have heard, Android is often slowly updated, with manufacturers, carriers, and users taking a long time to upgrade.

The rule of thumb is that whenever something is announced that is not available in the backwards compatible support library, we have to wait at least two years before we can use it, since that's the time it takes for the majority of Android users to adopt a new android version announced today.

Even with the backwards compatibility of some language features from Java 8, I do not believe this syntax is available to me. We'll have to wait until full language support of Java 8 has reached an overwhelming majority of users in a few years.

@mpkorstanje
Copy link
Contributor

We're not going to fix this. I reckon that by the time we do get around to fix this Google will have added more support for java 8 and the old Android versions have been become deprecated.

@kjarrad
Copy link

kjarrad commented Jul 14, 2017

I would like to use the suggestion below by @mpkorstanje

The code compiles for me but at run-time I get ClassNotFoundException:int, with cucumber-java8 version 1.2.5

Changing the primitive int to Integer works for me. Is anyone successfully using primitive parameter types?

public LambdaStepdefs() {
    When("^I perform the calculation (\\d+) plus (\\d+)$", this::performXPlusY);
    When("^I add the numbers (\\d+) and (\\d+)$", this::performXPlusY);
}
private void performXPlusY(int a, int b){
  // do the step
}

@mpkorstanje
Copy link
Contributor

mpkorstanje commented Jul 14, 2017

Eh. I should have added that method references aren't supported in 1.2.5 and are currently somewhat supported 2.0.0-SNAPSHOT (no primitives yet) and will hopefully be fully supported once 2.0.0 is done.

mpkorstanje added a commit that referenced this issue Jul 14, 2017
To determine which argument types a lambda function requires we created a
 ConstantPoolTypeIntrospector. However it has never functioned quite correctly
 (#937, #1140, #957), was prone to breaking (#912, #914) and hasn't been tested
 much (#1048).

It is important to understand that while we will get a properly functioning and
 tested replacement, TypeResolver uses the same ConstantPool and thus has the
 same potential to break. However because TypeResolver is used by a much larger
 audience I expect these problems to be shallow.

Because this change the interface of Java8StepDefinition it made sense to
 refactor all the Java8 related stuff out of cucumber-java. This will make it easier in
 the future to add things like KotlinStepDefintions without creating a separate
 KotlinBackend.

Related issues:
 - #912
 - #914
 - #937
 - #957
 - #1140
 - #1048
 - #1140
 -
mpkorstanje added a commit that referenced this issue Jul 14, 2017
To determine which argument types a lambda function requires we created a
 ConstantPoolTypeIntrospector. However it has never functioned quite correctly
 (#937, #1140, #957), was prone to breaking (#912, #914) and hasn't been tested
 much (#1048).

It is important to understand that while we will get a properly functioning and
 tested replacement, TypeResolver uses the same ConstantPool and thus has the
 same potential to break. However because TypeResolver is used by a much larger
 audience I expect these problems to be shallow.

Because this change the interface of Java8StepDefinition it made sense to
 refactor all the Java8 related stuff out of cucumber-java. This will make it easier in
 the future to add things like KotlinStepDefintions without creating a separate
 KotlinBackend.

Related issues:
 - #912
 - #914
 - #937
 - #957
 - #1140
 - #1048
 - #1140
mpkorstanje added a commit that referenced this issue Jul 14, 2017
To determine which argument types a lambda function requires we created a
 ConstantPoolTypeIntrospector. However it has never functioned quite correctly
 (#937, #1140, #957), was prone to breaking (#912, #914) and hasn't been tested
 much (#1048).

It is important to understand that while we will get a properly functioning and
 tested replacement, TypeResolver uses the same ConstantPool and thus has the
 same potential to break. However because TypeResolver is used by a much larger
 audience I expect these problems to be shallow.

Because this change the interface of Java8StepDefinition it made sense to
 refactor all the Java8 related stuff out of cucumber-java. This will make it easier in
 the future to add things like KotlinStepDefintions without creating a separate
 KotlinBackend.

Related issues:
 - #912
 - #914
 - #937
 - #957
 - #1140
 - #1048
 - #1140
mpkorstanje added a commit that referenced this issue Jul 14, 2017
To determine which argument types a lambda function requires we created a
 ConstantPoolTypeIntrospector. However it has never functioned quite correctly
 (#937, #1140, #957), was prone to breaking (#912, #914) and hasn't been tested
 much (#1048).

It is important to understand that while we will get a properly functioning and
 tested replacement, TypeResolver uses the same ConstantPool and thus has the
 same potential to break. However because TypeResolver is used by a much larger
 audience I expect these problems to be shallow.

Because this change the interface of Java8StepDefinition it made sense to
 refactor all the Java8 related stuff out of cucumber-java. This will make it easier in
 the future to add things like KotlinStepDefintions without creating a separate
 KotlinBackend.

Related issues:
 - #912
 - #914
 - #937
 - #957
 - #1140
 - #1048
 - #1140

Closes #937
mpkorstanje added a commit that referenced this issue Jul 15, 2017
To determine which argument types a lambda function requires we created a
 ConstantPoolTypeIntrospector. However it has never functioned quite correctly
 (#937, #1140, #957), was prone to breaking (#912, #914) and hasn't been tested
 much (#1048).

It is important to understand that while we will get a properly functioning and
 tested replacement, TypeResolver uses the same ConstantPool and thus has the
 same potential to break. However because TypeResolver is used by a much larger
 audience I expect these problems to be shallow.

Because this change the interface of Java8StepDefinition it made sense to
 refactor all the Java8 related stuff out of cucumber-java. This will make it easier in
 the future to add things like KotlinStepDefintions without creating a separate
 KotlinBackend.

Related issues:
 - #912
 - #914
 - #937
 - #957
 - #1140
 - #1048
 - #1140

Closes #937
Closes #1048
mpkorstanje added a commit that referenced this issue Jul 23, 2017
To determine which argument types a lambda function requires we created a
 ConstantPoolTypeIntrospector. However it has never functioned quite correctly
 (#937, #1140, #957), was prone to breaking (#912, #914) and hasn't been tested
 much (#1048).

It is important to understand that while we will get a properly functioning and
 tested replacement, TypeResolver uses the same ConstantPool and thus has the
 same potential to break. However because TypeResolver is used by a much larger
 audience I expect these problems to be shallow.

Because this change the interface of Java8StepDefinition it made sense to
 refactor all the Java8 related stuff out of cucumber-java. This will make it easier in
 the future to add things like KotlinStepDefintions without creating a separate
 KotlinBackend.

Related issues:
 - #912
 - #914
 - #937
 - #957
 - #1140
 - #1048
 - #1140

Closes #937
Closes #1048
mpkorstanje added a commit that referenced this issue Jul 29, 2017
To determine which argument types a lambda function requires we created a
 ConstantPoolTypeIntrospector. However it has never functioned quite correctly
 (#937, #1140, #957), was prone to breaking (#912, #914) and hasn't been tested
 much (#1048).

It is important to understand that while we will get a properly functioning and
 tested replacement, TypeResolver uses the same ConstantPool and thus has the
 same potential to break. However because TypeResolver is used by a much larger
 audience I expect these problems to be shallow.

Because this change the interface of Java8StepDefinition it made sense to
 refactor all the Java8 related stuff out of cucumber-java. This will make it easier in
 the future to add things like KotlinStepDefintions without creating a separate
 KotlinBackend.

Related issues:
 - #912
 - #914
 - #937
 - #957
 - #1140
 - #1048
 - #1140

Closes #937
Closes #1048
@siva90144
Copy link

siva90144 commented Sep 17, 2018

What about String values ?
ex:
Then I verify "results" dropdown on "Customer" page
And I verify "Address" textbox on "Customer" page

I am mapping as below its throwing error as mentioned @mpkorstanje
Then(""I verify "([^"])" dropdown on "([^"])" page$")
Then("^I verify "([^"])" textbox on "([^"]) page$")
public void verifyCustomerResults(String result,String pageName){
//doing something
}
I am using jdk1.8
cucumber version: 1.2.4

@mpkorstanje mpkorstanje added 🙏 help wanted Help wanted - not prioritized by core team good first issue Good for newcomers labels Sep 18, 2018
@mpkorstanje mpkorstanje reopened this Sep 18, 2018
@mpkorstanje mpkorstanje removed good first issue Good for newcomers 🙏 help wanted Help wanted - not prioritized by core team labels Sep 18, 2018
@mpkorstanje
Copy link
Contributor

Wrong issue. Oops.

@lock
Copy link

lock bot commented Sep 18, 2019

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 Sep 18, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
⚡ enhancement Request for new functionality
Projects
None yet
Development

No branches or pull requests