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

[Core] Add Before and AfterStep hooks #1323

Merged
merged 33 commits into from
Apr 26, 2018

Conversation

coding-yogi
Copy link

@coding-yogi coding-yogi commented Feb 14, 2018

This PR adds a new feature of AfterStep. Methods annotated with AfterStep will be executed after every step. For HTML Reports to work properly after this change , this PR requires to be merged

Motivation and Context

This change was requested by users of cucumber-jvm

Types of changes

  • Bug fix (non-breaking change which fixes an issue).
  • New feature (non-breaking change which adds functionality).
  • Breaking change (fix or feature that would cause existing functionality to not work as expected).

Checklist:

  • I've added tests for my code.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.

@coveralls
Copy link

coveralls commented Feb 14, 2018

Coverage Status

Coverage increased (+0.3%) to 81.939% when pulling 92e4fa6 on coding-yogi:feature_afterstep into f42079a on cucumber:master.

@@ -128,6 +128,9 @@ private void addTestStepsForPickleSteps(List<TestStep> testSteps, PickleEvent pi
match = new FailedStepInstantiationMatch(pickleEvent.uri, step, t);
}
testSteps.add(new PickleTestStep(pickleEvent.uri, step, match));
if (!runtimeOptions.isDryRun()) {
addTestStepsForAfterStepHooks(testSteps, pickleEvent.pickle.getTags());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you provide a test that shows the after hook is executed after each step?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also affects the JSONFormatter. Currently it assumes all hooks are either before or after hooks. Using after step hooks will have undefined results.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brasmusson where do you think the TestStepStarted events for step hooks should go in the json? I am thinking they should go into the same list as the the test steps themselves.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mpkorstanje , I can see afterstep being reported properly in json report. Could you please let me know what exactly is the issue?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can not. It does indeed all seem to work out.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will update the class to HookStep instead of Unskipable step and handle the logic of skipping AfterStep for a failed/skipped step. This will be in sync with ruby.

Let me know if that should be ok

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will have to do some changes for JSONFormatter as there is some incosistency when compared with Ruby Json report. Will add a test for same

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mpkorstanje , @brasmusson , I have implemented logic of skipping AfterStep in case of failed or skipped Step and also tests for JSON formatter showing AfterSteps JSON arry node should belong to it's respective step node.

Would you mind reviewing the changes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into them, but it will probably take half a week or so before I have the time and energy to do it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, Thanks

Copy link
Contributor

@brasmusson brasmusson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have (today) initiated a discussion in the #committers channel of the Cucumber Slack community, with respect to the two behavioral options for step hooks in case of failure, that is, the current Cucumber-Ruby behavior:

before hook - executed
step1 - executed and fails
after step hook - skipped
step2 - skipped
after step hook - skipped
after hook - executed

and the alternative:

before hook - executed
step1 - executed and fails
after step hook - skipped
step2 - skipped
after step hook - skipped
after hook - executed

We'll have to see were that discussion land before finish this PR.

super(definitionMatch);
this.hookType = hookType;
}

protected Result.Type executeStep(String language, Scenario scenario, boolean skipSteps) throws Throwable {
definitionMatch.runStep(language, scenario);
return Result.Type.PASSED;
if(hookType == HookType.After || hookType == HookType.Before) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current convention in the code base is to use "if (" rather than "if(" (and "switch (" rather than "switch("), so for the sake of consistency this convention should be followed in this PR too.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will fix these throughout PR

import cucumber.api.Scenario;
import cucumber.api.StepDefinitionReporter;
import cucumber.api.TestCase;
import cucumber.api.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, will fix this one. I think it happened due to Intellij formatting it to .* in case all the classes are being used and imported from package

"formatter.result({\n" +
" \"status\": \"passed\"\n" +
"});\n", "" +
"formatter.afterstep({\n" +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the intention is to move away from the Cucumber-HTML-project (see cucumber-attic/cucumber-html#44), I'm thinking about exploring the use of "formatter.after" also for after step hooks, instead of coupling this PR to a required change in the Cucumber-HTML (I'm not sure we want to open that Box of Pandora at this time).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't exactly get what needs to be done for this, should I leave it as it is for now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to take a look at what can best be done with respect to the HTML formatter.

" \"status\": \"passed\"\n" +
" },\n" +
" \"line\": 4,\n" +
" \"afterstep\": [\n" +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Json key should be "after" also for after step hooks, so that "before" and "after" are the Json key used for hooks both on the scenario level and the step level (see the Cucumber-Ruby spec https://github.com/cucumber/cucumber-ruby/blob/master/spec/cucumber/formatter/json_spec.rb#L677).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I saw that in ruby but there is a small catch here. In the code every where I have used string representation of HookType enum , for eg mapToAddTo.put(hookType.toString(), new ArrayList<Map<String, Object>>());
So in this case I will have to explicitly handle the AfterStep case to report it as After

InOrder inOrder = inOrder(step1, step2, hook, backend);
inOrder.verify(step1).getText();
inOrder.verify(step2).getText();
inOrder.verify(hook, times(2)).execute(Matchers.<Scenario>any());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is confusing, it looks like it verifies that the after step hook is executed twice after step2 has been executed, but the expected behavior is that the hook is executed once in between step1 and step2 and once after step2. Can this test be change to better specify the expected behavior (and not look like it verifies the wrong behavior)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test was commented and was supposed to be removed as this behaviour is verified in other test. I will check once again and remove this test

@coding-yogi
Copy link
Author

@brasmusson , I have made the requested changes except the HTML formatter one for which I didn't exactly get what needs to be done

@brasmusson
Copy link
Contributor

Even thought there has not as wide participation as I would have liked in the discussion in the #committers channel, the conclusion seems to be that it is reasonable to run the after step hook for a (gherkin) step that fails (but not the after step hooks for subsequent steps). Therefore I took a stab at implementing this (f3c908c). The TestCase abstraction is still a (flat) sequence of TestStep, but the skipStep state now has three states (RUN_ALL, RUN_HOOKS, SKIP_ALL_SKIPABLE) instead of two (true, false).

Result stepResult = step.run(bus, pickleEvent.pickle.getLanguage(), scenarioResult, skipNextStep);
if (!stepResult.is(Result.Type.PASSED)) {
skipNextStep = true;
if (!stepResult.is(Result.Type.PASSED) && skipNextStep == SkipStatus.RUN_ALL) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it a bit hard to qualify why but the concept underlying this solution feels wrong. TestStep is leaking information into TestCase as to how the test should proceed. It shouldn't have to.

I think this is caused by trying to represent the test execution as a list of steps rather then the tree that it is (it may help to imagine the existence of a before step hook).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have one suggestion, why can't we have skipStep as binary bit flags, or bit array or a map for eg {"skipBeforeStep": false, "skipGherkingStep": false, "skipAfterStep": false}

when step fails set skipGherkingStep to true but skipAfterStep still remains false and executes the afterStep. After execution of afterStep, if skipGherkingStep is true , set skipAfterStep to true so that none of the next afterStep executes

@mpkorstanje
Copy link
Contributor

mpkorstanje commented Mar 7, 2018

I've moved the after step hooks into the PickleTestSteps. This reduces the complexity of the skip logic in TestCase significantly.

One thing we should still look at though are the UnsupportedOperationException in HookStep and PickleTestStep though. It smells like a refused bequest.

And perhaps also make sure we don't expose too much of the internal cucumber apis here.

@mpkorstanje
Copy link
Contributor

I've extracted a Step and HookStep class from TestStep. Now it appears that Step doesn't contain any methods that are relevant to the event consumers of the TestStepFinishedEvent. They always have to cast to either TestStep or HookStep.

This raises the question, shouldn't hook events be in their own events?

AMBIGUOUS,
UNDEFINED,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ambiguous and Failed both unconditionally make the exit code non-zero, but Undefined does not, so Undefined must be less severe than Ambiguous.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right.

@brasmusson
Copy link
Contributor

I've moved the after step hooks into the PickleTestSteps.

I'm not sure that is the right move. I have always envisioned that it is only the result of the test step from the PickleStep itself that are reported in the summary, even if an after step hook fails the summary should report "passed" of the test step from the PickleStep itself passed.

@mpkorstanje
Copy link
Contributor

I am not sure I understand. The summary is created from events emitted through the bus not from the StepResult returned by the PickleStep. It should work as you've described.

The StepResult is only used to created the Result of the TestCase which is done by finding the most severe result and its error.

@brasmusson
Copy link
Contributor

This raises the question, shouldn't hook events be in their own events?

The drive to unify the event model between the Cucumber-implementations (so that for instance common formatters processing the Json representation of the event stream), mean that we cannot do that in Cucumber-JVM without commitment that the other Cucumber-implementations also do it.

I think we in the community is very much into the view that everything that is executed during the execution of a "Test Case" is a "Test Step". A "Test Step" can be related to 0..1 "Pickle Step" (0 meaning that it is a "Test Step" created from a hook), and it can be related to 0..* code methods/functions (0 meaning that it is a "Test Step" created from a "Pickle Step" that could not be matched to any step definition - undefined, and * meaning that it is a "Test Step" created from a "Pickle Step" that could be matched to several step definitions - ambiguous). The "started" and "finished" event should therefore be of the same type for all "Test Step"s.

@brasmusson
Copy link
Contributor

It should work as you've described.

You are right.

I think we can use a hierarchical model for steps in test cases internally in the runner. However, I think that if it is possible to get the steps of a test case through classes in the api packages, then it should be a flat list of test steps, so that the abstraction of a test case as a flat sequence of test steps is maintained there.

@mpkorstanje
Copy link
Contributor

mpkorstanje commented Mar 8, 2018

However, I think that if it is possible to get the steps of a test case through classes in the api packages, then it should be a flat list of test steps, so that the abstraction of a test case as a flat sequence of test steps is maintained there.

That would be the depth first iteration order over the step tree. Should be easy enough to change TestCase.getTestSTeps() to do that

@mpkorstanje
Copy link
Contributor

I think we in the community is very much into the view that everything that is executed during the execution of a "Test Case" is a "Test Step".

We should conform to that then. How should we name set of proper TestSteps and HookSteps? I've currently named them Step. I reckon TestStep would be a better name but that leaves the proper TestSteps without a name.

@mpkorstanje
Copy link
Contributor

mpkorstanje commented Mar 8, 2018

What is the expected behavior of the TestStepStarted and TestStepFinished events for hooks on a dry run? Currently the AfterStepHook emits and event while the other hooks don't. They're not added to TestCase on a dryrun. Presumably because they're unskipable but hat seems to be rather inconsistent with the idea that they're regular test steps. I would expect them to emit a skipped event.

edit: And also quite easy to fix by making them skipable but only skipping them on a dry run.

To stay consistent with the TestStep, PickleStepTestStep and HookStep
from `cucumber.api` we should use the same naming convention in
`cucumber.runtime`.
@coding-yogi
Copy link
Author

I have fixed the HTML reports including issue #1303, let me know if I should include those changes in this PR

@brasmusson
Copy link
Contributor

@coding-yogi I have looked into the behavior of the HTML formatter of Cucumber-Ruby. Since the intention is to replace the HTML formatters for all Cucumber implementation with one binary that can process the event stream and produce a HTML report, we should keep the different HTML formatters as aligned as reasonable possible until then.

When using afterStep hooks and the HTML formatter in Cucumber-Ruby and all the step hooks pass, then nothing is added to the HTML DOM regarding the afterStep hooks. Therefore I do not think that the js functions for step hooks here should unconditional add anything to the HTML DOM either, that is not doing something like:

  this.afterstep = function(afterStep) {
    currentStep = $('.afterstep', $templates).clone();
    currentStep.appendTo(currentSteps);

The HTML formatter of Cucumber-Ruby will display output (in Cucumber-JVM Scenario.write) from:

  • the step definition (this is not possible in Cucumber-JVM)
  • the after step hook
  • the after hook (in case of the last step)

in exactly the same way (they will all appear below the text of the Gherkin step), therefore I do not think that there is any need to do anything new to handle output and embeddings with the introduction of step hooks (like making a change where handleTestStepStarted would just register what type of StepHook has started), the current behavior is sufficient.

In the end I think that the changes needed in Cucumber-HTML is cucumber-attic/cucumber-html@d757b90 (now when cucumber-attic/cucumber-html#49 has been fixed).

@mpkorstanje
Copy link
Contributor

Since the intention is to replace the HTML formatters for all Cucumber implementation with one binary that can process the event stream and produce a HTML report, we should keep the different HTML formatters as aligned as reasonable possible until then.

Is that ideal still alive? From what I can see all the other implementations moved away from it.

@brasmusson
Copy link
Contributor

The new initiative https://github.com/cucumber/cucumber-pickle-runner, why not shared binaries for formatters too?

@aslakhellesoy
Copy link
Contributor

I think the single binary is a very exciting development, and it makes perfect sense to me to put formatters in there too.

@mpkorstanje
Copy link
Contributor

I must be misunderstanding something somewhere. I am I right in assuming the go project is a second attempt at unifying the different formatters? In that case nevermind.

@aslakhellesoy
Copy link
Contributor

@coding-yogi
Copy link
Author

@brasmusson , thanks for the clarification, so I won't push any further changes for HTML formatters

@naelabdeljawad
Copy link

Hi,
Did you release new version which includes the new hooks (AfterStep/BeforeStep) for java?

@aslakhellesoy
Copy link
Contributor

@naelabdeljawad if you're ever in doubt about what's been released, check out the CHANGELOG

@aslakhellesoy
Copy link
Contributor

An open pull request (such as this one) means the code has not been merged, and therefore, not released.

@mpkorstanje
Copy link
Contributor

@brasmusson brasmusson dismissed their stale review April 21, 2018 12:48

Now I think my issues have been resolved

@mpkorstanje mpkorstanje mentioned this pull request Apr 24, 2018
6 tasks
@mpkorstanje mpkorstanje merged commit fc6dabc into cucumber:master Apr 26, 2018
@aslakhellesoy
Copy link
Contributor

Hi @coding-yogi,

Thanks for your making your first contribution to Cucumber, and welcome to the Cucumber committers team! You can now push directly to this repo and all other repos under the cucumber organization! 🍾

In return for this generous offer we hope you will:

  • ✅ Continue to use branches and pull requests. When someone on the core team approves a pull request (yours or someone else's), you're welcome to merge it yourself.
  • 💚 Commit to setting a good example by following and upholding our code of conduct in your interactions with other collaborators and users.
  • 💬 Join the community Slack channel to meet the rest of the team and make yourself at home.
  • ℹ️ Don't feel obliged to help, just do what you can if you have the time and the energy.
  • 🙋 Ask if you need anything. We're looking for feedback about how to make the project more welcoming, so please tell us!

On behalf of the Cucumber core team,
Aslak Hellesøy
Creator of Cucumber

@lock
Copy link

lock bot commented Apr 26, 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 Apr 26, 2019
@aslakhellesoy
Copy link
Contributor

Hi @coding-yogi,

Thanks for your making your first contribution to Cucumber, and welcome to the Cucumber committers team! You can now push directly to this repo and all other repos under the cucumber organization! 🍾

In return for this generous offer we hope you will:

  • ✅ Continue to use branches and pull requests. When someone on the core team approves a pull request (yours or someone else's), you're welcome to merge it yourself.
  • 💚 Commit to setting a good example by following and upholding our code of conduct in your interactions with other collaborators and users.
  • 💬 Join the community Slack channel to meet the rest of the team and make yourself at home.
  • ℹ️ Don't feel obliged to help, just do what you can if you have the time and the energy.
  • 🙋 Ask if you need anything. We're looking for feedback about how to make the project more welcoming, so please tell us!

On behalf of the Cucumber core team,
Aslak Hellesøy
Creator of Cucumber

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants