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

Proposal: Unambiguous Keywords #768

Closed
badeball opened this issue Oct 22, 2019 · 34 comments
Closed

Proposal: Unambiguous Keywords #768

badeball opened this issue Oct 22, 2019 · 34 comments
Labels
milestone-proposal Candidate for Cucumber cross-platform milestone 🧷 pinned Tells Stalebot not to close this issue

Comments

@badeball
Copy link
Member

Proposal Summary / Motivation

I've briefly presented this idea on Slack and in the spirit of gathering more feedback I'm posting it here as well.

The Gherkin reference specifically stipulates that keywords should not be taken into account when looking for a step definition [1].

I've personally witnessed in several projects that this can lead to deterioration in test language unless every change is strictly reviewed.

Below is an example of a hastily written patch that breaks the flow of language, but doesn't yield an error (actually taken from my Git history, but modified slightly to anonymize the domain).

       When I press the foobar button
-      Then I should see 2 events in total
       And I should see these new fields with values
         | My Field      | foo |
         | Another Field | bar |

[1] https://cucumber.io/docs/gherkin/reference/#steps

Current Behavior

Currently, the following steps would yield ambiguity error.

• Given(“I press button”)
• When(“I press button”)

I don't propose changing this.

However, it doesn't matter which keyword you use in your feature files and the example below runs just fine.

# features/step_definitions/cucumber_steps.js
import {When} from 'cucumber'

When(/^a step$/, function() {});

# features/a.feature
Feature: a feature name
  Scenario: a scenario name
    Given a step

Proposed Behavior

Given the following step definitions.

When("step a", () => {});

... and the following feature file.

Feature: a feature name
  Scenario: a scenario name
    Given a step

I propose for Cucumber to fail with a wrong-keyword error. Furthermore, and just as importantly, for this to properly work, any use of And and But keyword must be interpreted as if they were of the same type as the previous step.

I've tried to outline this behavior with some test cases here.

@badeball badeball changed the title Unambiguous Keywords Proposal: Unambiguous Keywords Oct 22, 2019
@aslakhellesoy aslakhellesoy added the milestone-proposal Candidate for Cucumber cross-platform milestone label Oct 22, 2019
@aslakhellesoy
Copy link
Contributor

I like this proposal because it encourages even less ambiguity. You can no longer mix and match Given/When/Then as you see fit.

@luke-hill
Copy link
Contributor

luke-hill commented Oct 23, 2019

The only thing I propose to this is we do a major update to all our APIs with this and only this, because I can see this either being reported 20 times or causing some grievances.

That being said, I do like it :)

@mpkorstanje
Copy link
Contributor

I feel like this should be in the realm of a linters rather then in the realm of the interpreters.

@aslakhellesoy
Copy link
Contributor

@mpkorstanje that linter would require access to more than the gherkin document - it would also need the step definitions. I think that’s too heavy for a linter. Plus, people don’t use linters.

If we add this we could always add an option to disable it, so people with years of legacy scenarios can go on with their lives.

Interested in your perspective @mattwynne @sebrose

@badeball
Copy link
Member Author

badeball commented Oct 25, 2019

I guess this would necessarily require a change in the messages, which only contains the difficult-to-interpret synonym used in tests.

@ehuelsmann
Copy link
Contributor

I very much like this proposal. It also mirrors the behaviour that has been implemented in Perl's implementation (pherkin/test-bdd-cucumber-perl) since before 2013.

@mattwynne
Copy link
Member

mattwynne commented Oct 28, 2019

I like this proposal too.

From my point of view, it's not just about language clashes, it's also about wanting to be able to do different technical stuff during context, action and outcome phases of a scenario. For example, if I'm using some kind of driver adapter to connect my steps to my app, I would like to be able to use the domain-driver during Given steps, then switch to a UI driver during the When and Then steps.

It would be super-awesome if there were messages (that front-ends could them build hooks for) that told us when we were moving from context into action, from action into outcome phases of the scenario. But keep that out of scope for now!

@davidjgoss
Copy link
Contributor

I'm a fan of this. The example in the original summary is exactly the kind of thing I have seen happening in practise.

The only thing I propose to this is we do a major update to all our APIs with this and only this, because I can see this either being reported 20 times or causing some grievances.

It could work like a deprecation - ship the change whenever, have it raise a prominent warning at runtime initially but not fail (unless you opt in early), then move to hard failure in a major release after X months?

Another minor facet: cucumber-js currently exposes the generic defineStep function (that the keywords delegate to) in its API so removing that would be a prerequisite there I guess.

@aslakhellesoy
Copy link
Contributor

I love your thinking here @mattwynne - about separating the context/action/outcome scopes, and giving the user control over how they are created. It's something @joshski and I have been discussing regularly since we paired on the cucumber electron subscond tdd code a couple of years ago. We ended up implementing this pattern ourselves, but it would be much cleaner if it were part of Cucumber itself.

@aslakhellesoy
Copy link
Contributor

@davidjgoss - regarding removing defineStep we have an open ticket for that - which has caused some grievances. cucumber/cucumber-ruby#1362 - I'd be interested in your thoughts in that ticket.

@davidjgoss
Copy link
Contributor

@aslakhellesoy I've looked through that thread but struggling to see the overlap - defineStep isn't a subroutines thing, it's just that it exposes a way to do a step definition without specifying which of "Given", "When" or "Then" it is - which would be at odds with this change.

@laeubi
Copy link

laeubi commented Nov 11, 2019

I also think a warning would be suitable as a first step, with an option to turn warning into error.
Maybe a commandline option like -strict could be added, or we adopt something similar how gcc and other compilers work by adding +/-W switches.

@stale
Copy link

stale bot commented Jan 10, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in a week if no further activity occurs.

@stale stale bot added the ⌛ stale Will soon be closed by stalebot unless there is activity label Jan 10, 2020
@stale
Copy link

stale bot commented Jan 17, 2020

This issue has been automatically closed because of inactivity. You can support the Cucumber core team on opencollective.

@stale stale bot closed this as completed Jan 17, 2020
@aslakhellesoy aslakhellesoy added the 🧷 pinned Tells Stalebot not to close this issue label Jan 17, 2020
@aslakhellesoy aslakhellesoy reopened this Jan 17, 2020
@stale stale bot removed the ⌛ stale Will soon be closed by stalebot unless there is activity label Jan 17, 2020
@charlierudolph
Copy link
Member

charlierudolph commented Mar 7, 2020

Really like this idea. Want to recap to check my understand and see if we can determining next steps

  • With step definition: Given("x", ...)
    • Use in a feature file
      • Given x -> matches step definition
      • When x -> no match, wrong keyword
      • Then x -> no match, wrong keyword
      • And x / But x -> keyword need to be transformed to last usage of Given / When / Then
  • Multiple step definitions Given("x", ...) and Given("x", ...) lead to ambiguous result

Questions:

  • Are multiple step definitions Given("x", ...) and When("x", ...) allowed?
    • These stop leading to ambiguous results and could technically be allowed after keywords are always unambiguous

Implementation Plan

  • Core changes
    • gherkin - pickle steps include a keyword type that is computed to be Given / When / Then (or context / action / outcome)
    • add example to compatibility kit
  • Language Libraries
    • Initial release
      • Warning generic step definitions will be removed (those without keyword type)
      • Warning on wrong keyword type
      • Opt-in flag to unambiguous keywords
    • Major release for unambiguous keywords only

Thoughts?

@davidjgoss
Copy link
Contributor

@charlierudolph sounds like a plan to me.

Are multiple step definitions Given("x", ...) and When("x", ...) allowed?

Still no IMO, because fundamentally Given (context), When (action) and Then (verification) should be different stuff.

There is also the * keyword which is potentially tricky with this.

@enkessler
Copy link

  • Are multiple step definitions Given("x", ...) and When("x", ...) allowed?

Presumably, yes. If the fundamental complaint is that Given("x", ...) and When("x", ...) are being treated the same and the goal of this change is to treat them as different, then I would expect that both of those definitions would be allowed to co-exist because, you know, they are different.

@luke-hill
Copy link
Contributor

Dependent on work involved, I would say maybe to start with do the smallest change (That would in theory permit both Given("x") and When("x"), and then maybe later set it up to prevent them. But I'm only spitballing at the moment.

Also shared this link with slack as we were discussing it recently.

@marnen
Copy link

marnen commented Feb 15, 2021

I would prefer that this be a configurable option. I do occasionally reuse the same steps between Given and When, and I don't really want to have to write the same step definitions twice if I can help it. At the same time, I understand that some people see value in matching the keyword, and I don't want to deny them that either.

@ehuelsmann
Copy link
Contributor

I'll be joining the stand-up coming Thursday. I'm keen to understand what will be required to bring this forward, because it's one of the last obstacles for Test::BDD::Cucumber to move to using the Gherkin library.

@ehuelsmann
Copy link
Contributor

ehuelsmann commented Jul 17, 2021

In response to @ciaranmcnulty :

If this goes ahead we'll need to consider / test the impact on i18n

Will it add to my experience to have to remember to use Given() whenever I write Gegewe in my steps?

That depends: in the Perl Cucumber variant that I maintain, translations for Given/When/Then are registered for the step files to be available (documentation: https://metacpan.org/pod/Test::BDD::Cucumber::Manual::Tutorial#Non-English-features-and-step-files). No idea how other implementations would (be able to) deal with this though.

@sebrose
Copy link
Member

sebrose commented Aug 5, 2021

I really don't like this suggestion:

  • IMO it encourages ambiguous business domain language, with automation code modified by preceding keyword
  • Gherkin currently supports steps being preceded by '*'
  • when conjunctions are used (And, But) the reader will have to identify the preceding G/W/T keyword

However, a number of Gherkin implementations do work in this way (SpecFlow, Perl, Behat?). SpecFlow introduced the @StepDefinition annotation to emulate core Cucumber implementations.

I support the passing of the keyword to the pickle.

@mpkorstanje
Copy link
Contributor

I also support passing of the keyword to the pickle.

But I do not support making the keyword part of the step definition. Major breaking change with no value.

@ehuelsmann
Copy link
Contributor

Discussed on the weekly Cucumber team call; consensus was that since some implementations have had the step-definition-per-keyword behavior (mentioned during the meeting: SpecFlow and Test::BDD::Cucumber (Perl)), it's necessary to add the keywords to the pickles. Additionally, since Cucumber never exhibited this behavior, it's expected to be a major breaking change for all those who have Cucumber-based test suites. In other words: Let's do this in the pickle so more projects can move to the generic parser infrastructure, but lets not incorporate this any further than that.

@ehuelsmann ehuelsmann self-assigned this Aug 8, 2021
@ehuelsmann
Copy link
Contributor

ehuelsmann commented Aug 8, 2021

It's my action to implement this in the Gherkin (and Messages) libraries up to the Pickle (for as many languages as I can get).

ehuelsmann added a commit that referenced this issue Apr 29, 2022
Extracted from #1741 for ease of review on request of @aurelien-reeves,
implements the `messages` side of #768.
ehuelsmann added a commit that referenced this issue May 5, 2022
#1966)

Expand the messages protocol with keyword types

Extracted from #1741 for ease of review on request of @aurelien-reeves,
implements the `messages` side of #768.

Includes minimal Java changes to support the changes in the schema.


Co-authored-by: Aurélien Reeves <aurelien.reeves@smartbear.com>
Co-authored-by: Aslak Hellesøy <aslak.hellesoy@gmail.com>
aslakhellesoy added a commit that referenced this issue May 25, 2022
…word (#1741)

* Expand the messages protocol with keyword types

Extracted from #1741 for ease of review on request of @aurelien-reeves,
implements the `messages` side of #768.

* Incorporate review comments

* Treat all translations associated with multiple keywords as 'General' keywordType

* Incorporate latest reviews

* Change the enum in the Pickle as per your last comment
* Add translation of But/And to the preceeding keyword in the pickle compiler
* Rename the "keywordType" field in the Pickle to `type`

* Adjust Perl implementation over changed naming of keyword type General

* Use Cucumber::Message's constant declarations instead of hard-coded strings

* Align Perl's TokenMatcher implementation with Ruby's

* Port initial Perl implementation to Ruby

* Fix test failures from the latest refactoring

* Add step keyword types to token and AST test data

* Fix error found while fixing test data

* Make sure the 'keywordType' is correctly populated

Since it's legal to start a scenario or even a background with a
conjunction, it's fair to say that the keyword type is unknown until
a keyword with known type is encountered.

* Update testdata with keyword and step types

Sync to the implementations which have been adapted.

* Follow Ruby's token_matcher structure in Perl

And fix a syntax error using constants.

* Eliminate warnings during Perl tests

Based on running tests on the coverted test data,
fix a warning about an undefined value being used.

* Rewrite Perl's Pickle compiler to follow Ruby's structure more

* Expand 'Conjunction's into their implied keyword type

* Convert Perl/Ruby token matcher to pattern available in JS&Java too

* Port Ruby/Perl implementation of keyword retention to Java

* Sync JavaScript testdata

* Progress on JS (works, except Markdown?)

* Implement keywordType in Markdown

Also align the Classic Gherkin implementation

* Use hash lookups to get the keyword's StepKeywordType

At the suggestion of @ciaranmcnulty, simplify the loop in
`match_StepLine` by using a hash lookup.

* Add comment as to why the StepKeywordType is overriden to UNKNOWN

* Fix typo (capitalization error)

* Update and sync Gherkin-In-Markdown test data

* Include background steps when the scenario *has* steps

* Rename 'keywordType' to 'matchedKeywordType'

Change suggested by @ciaranmcnulty to align the field with the names
of the other fields which are only available after the token matched.

* Synchronise testdata

* Adjust PickleCompiler for 'keywordType' type change

Now that 'keywordType' is no longer required, it's type changed to
Optional, which introduces an additional level of indirection.

* Collect all applicable types per step keyword

When more than one mapping exists, return the UNKNOWN
step keyword type to indicate that the keyword's meaning
isn't exact.

* Fix temporarly json-to-messages broken test

We need to upgrade json-to-messages to use ci-environment rather than
the former create-meta.

In the meantime to prevent being stuck, we make the test pass

* Restore original jsontToMessages test

* Report changes from #1972 on gherkin testdata

* Add support for keyword type in go

* Add StepKeywordType support to GherkinDialect

* Add support of step keyword type to php

* revert to c# 9

* Implement PickleStep type in php

* [dotnet] try build keyword type

* [python] Add support of step keyword type

* hopefully fixing .NET finally...

* Clarify order of operations when computing lastKeywordType

* Clarify type of keywordType list

* Add PR to CHANGELOG.md

* Remove useless 'if' condition

* Add again the  check

* Add more changelog entries for Go

Co-authored-by: Ciaran McNulty <mail@ciaranmcnulty.com>
Co-authored-by: aurelien-reeves <aurelien.reeves@smartbear.com>
Co-authored-by: aurelien-reeves <aurelien.reeves@hiptest.net>
Co-authored-by: Aslak Hellesøy <1000+aslakhellesoy@users.noreply.github.com>
Co-authored-by: Aslak Hellesøy <aslak.hellesoy@gmail.com>
Co-authored-by: Gaspar Nagy <gaspar.nagy@gmail.com>
Co-authored-by: Björn Rasmusson <B.Rasmusson@computer.org>
@ehuelsmann
Copy link
Contributor

@aslakhellesoy , @mattwynne , @aurelien-reeves , @mpkorstanje Now that #1741 has been merged, what further work is left in this issue, in your opinions?

@aslakhellesoy
Copy link
Contributor

I can't think of anything left to do other than starting to integrate this in to Cucumbers.

They should all have an option to turn on the strict validation (matching against expression and keyword). I suggest we call this done for now and deal with any problems as we start integrating.

@mattwynne
Copy link
Member

I agree Aslak.

So that we can keep track of the ongoing work to implement this and get it in front of users, I've used the new Github Projects beta to create a cross-org project:

https://github.com/orgs/cucumber/projects/11/views/1

I've created an issue in each of the trackers for the JavaScript, Ruby, Java and Go implementations of Cucumber. If @jenisys @gasparnagy @ciaranmcnulty want to add their own issues to that project too, feel free 😄

@ciaranmcnulty
Copy link
Contributor

PHP will take a while (it requires Behat to actually use the cucumber parser, first)

@ehuelsmann
Copy link
Contributor

ehuelsmann commented May 26, 2022

Perl already behaves like this, however, in order to use this work, we need to start using the cucumber parser.

@gasparnagy
Copy link
Member

SpecFlow already behaves like that, so we are done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
milestone-proposal Candidate for Cucumber cross-platform milestone 🧷 pinned Tells Stalebot not to close this issue
Projects
Development

No branches or pull requests