COMP5903 project at Carleton University. It is the enhancement of Alexei's original "Cucumberized" JUnit
List of improvement, UML diagram for improved Cucumberized JUnit can be found in doc
directory
TOC:
To use or develop this tool, you only need:
- Java 11 or above
The Maven Package is released in JitPack
Step 1. Add the JitPack repository to your build file:
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
Step 2. Add the dependency
<dependency>
<groupId>com.github.CXwudi</groupId>
<artifactId>comp5903-easy-cucumber</artifactId>
<version>the latest version</version>
</dependency>
Add it in your root build.gradle
at the end of repositories:
allprojects {
repositories {
// your other maven repositories
maven { url 'https://jitpack.io' }
}
}
Step 2. Add the dependency
dependencies {
implementation 'com.github.CXwudi:comp5903-easy-cucumber:<the latest version>'
}
TL;DR: You just call EasyCucumber.build()
to build the test, and call JFeature.executeAll()
to run the test.
You can call these two APIs in anywhere that you can run a piece of Java code. For example, in JUnit:
@Test
void myTest(){
Path myFeatureFile=Paths.get("path/to/my/feature-file.feature");
JFeature jFeature=EasyCucumber.build(myFeatureFile,MyStepDefinition.class);
jFeature.executeAll();
// or JFeature.executeByTag(myTag);
}
As you can see, EasyCucumber.build()
represents the build phase
and JFeature.executeAll()
represents the runtime phase.
If any failure happens, either it is an assertion error or a runtime exception,
will cause the JFeature.executeAll()
stop running and throw exception.
Hence JFeature.executeAll()
finishes execution = your feature file passes.
-
First, write the feature file like following. The feature file can be stored in any directory that you can access using Java's
Path
.# you can add comments Feature: Fruits You can also add multi-line description @tag1 @tag2 Scenario: I eat apple Given I have 5 apple When I eat 4 apple Then I should left 1 apple @tag2 @tag3 Scenario Outline: I eat other fruits Given I have <total> apple When I eat <eatCount> apple Then I should left <leftCount> apple Examples: | total | eatCount | leftCount | | 5 | 4 | 1 | | 10 | 5 | 5 |
-
Create the step definition class(es). These classes must be public and all step definition must be public as well, like following:
// MyStepDefinition.java import scs.comp5903.cucumber.model.annotation.step.JGivenStep; import scs.comp5903.cucumber.model.annotation.step.JThenStep; import scs.comp5903.cucumber.model.annotation.step.JWhenStep; public class MyStepDefinition { private int count; @JGivenStep("I have {int} apple") // @JStep(keyword = JStepKeyword.GIVEN, step = "I have {int} apple") // alternatively, u can use @JStep public void iHave(int count) { this.count = count; } @JWhenStep("I eat {int} apple") public void iEat(int eatCount) { this.count -= eatCount; } @JThenStep("I should left {int} apple") public void iShouldLeft(int leftCount) { assertEquals(leftCount, this.count); } }
Optionally, you can create some hooks, either in the same step definition class(es) or in a separated class:
// MyHooks.java import scs.comp5903.cucumber.model.annotation.hook.*; public class MyHooks { @BeforeAllJScenarios public void beforeAllScenarios() { // do something before all scenarios } @AfterAllJScenarios public void afterAllScenarios() { // do something after all scenarios } @BeforeEachJScenario public void beforeEachScenario(JScenarioStatus status) { // this parameter is optional // do something before each scenario } @AfterEachJScenario public void afterEachScenario(JScenarioStatus status) { // this parameter is optional // do something after each scenario } @BeforeEachJStep public void beforeEachStep(JStepStatus status) { // this parameter is optional // do something before each step } @AfterEachJStep public void afterEachStep(JStepStatus status) { // this parameter is optional // do something after each step } }
-
Call
EasyCucumber.build()
method to parse and create a cucumber test like following:Path myFeatureFile = Paths.get("path/to/my/feature-file.feature"); JFeature jFeature = EasyCucumber.build(myFeatureFile, MyStepDefinition.class, MyHooks.class); // or you can use any one of following alternative // JFeature jFeature = EasyCucumber.build(myFeatureFile, MyStepDefinition1.class, MyStepDefinition2.class, MyHooks.class); // JFeature jFeature = EasyCucumber.build(myFeatureFile, new MyStepDefinition1(), new MyStepDefinition2(), new MyHooks()); // JFeature jFeature = EasyCucumber.build(myFeatureFile, "package.to.stepdefinition"); // JFeature jFeature = EasyCucumber.build(myFeatureFile, List.of(MyStepDefinition1.class, MyStepDefinition2.class), new EasyCachingObjectProvider()); // How BaseObjectProvider works can be found in design document mentioned at the end of this readme file
You can call
EasyCucumber.build()
at anywhere that Java code can execute. For example, in JUnit, you would likely do:@Test void myTest() { Path myFeatureFile = Paths.get("path/to/my/feature-file.feature"); JFeature jFeature = EasyCucumber.build(myFeatureFile, MyStepDefinition.class); // do something with jFeature }
Once you get the executable instance of JFeature
,
you can run the cucumber test through calling JFeature.executeAll()
or JFeature.executeByTag(BaseFilteringTag tag)
.
JFeature.executeAll()
will run all scenarios and scenario outlines in the feature file.
JFeature.executeByTag(BaseFilteringTag tag)
take an instance
of BaseFilteringTag
. Then, each scenario or
scenario outline will be examined by the input tag to check should it be run. The input tag can be created by any public
static method in that class including:
static BaseFilteringTag tag(String tag)
: which create a simple tag instance of the given stringstatic BaseFilteringTag or(BaseFilteringTag... tags)
: which create an or-tag so that any one of the input tags matches will result in the or-tag is matchedstatic BaseFilteringTag and(BaseFilteringTag... tags)
: which create an and-tag so that all input tags need to be matched to result in the and-tag is matchedstatic BaseFilteringTag not(BaseFilteringTag tag)
: which create a not-tag so that negating the match result of the input tag
These 4 methods can be combined to create complex tag expression. For example, you can create an xor-tag so that
scenario or
scenario outline with either @tag1
or @tag2
will be run, but not both or neither, like following:
// import these static methods for convenience
import static scs.comp5903.cucumber.execution.tag.BaseFilteringTag.*;
// then you can create a convenient helper method:
private BaseFilteringTag creatXor(String tag1Str,String tag2Str){
return or(and(tag(tag1Str),not(tag(tag2Str))),and(not(tag(tag1Str)),tag(tag2Str)));
}
Then, then somewhere in your test code:
myJFeature.executeByTag(createXor("tag1","tag2")); // will only run scenarios with either `@tag1` or `@tag2`, but not both or neither
When you run the tests written in easy-cucumber, you might see a warning message:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Easy-cucumber uses SLF4J as the logging facade to provide runtime logs, and easy-cucumber does not bundle a default implementation library of the SLF4J-supported logging framework in order to avoid having a duplicated or conflicting logging framework. (It is likely that your other libraries also come with a logging framework) Hence, by default, you would see this warning message asking for a logging framework.
Easy Cucumber uses info level logging for indicating the start of a feature, a scenario or scenario outline. If a scenario failed, it will use error level logging to log the error message and the exception thrown. When a feature is executed without error, it will use info level logging to indicate that the feature runs successfully.
You can completely disable logging or suppress the warning by adding slf4j-nop. Here is an example in maven:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>any version >= 2.0</version>
</dependency>
However, I would recommend you to use a logging framework (logback, log4j) that is supported by SLF4J, so that you can see the logs in your test output.
Only a subset of keywords and features that official cucumber have are supported.
Feature
: the title of the feature fileScenario
: the scenarioScenario Outline
: the scenario outline, also an alias ofScenario Template
Given
,When
,Then
,And
,But
: the step definitionExamples
: the examples of the scenario outline, also an alias ofExample
@
: the tag, only aboveFeature
,Scenario
orScenario Outline
keywords
- Able to parse
{}
(parsed as string),{int}
,{string}
,{double}
,{biginteger}
and{bigdecimal}
in step definition- see https://github.com/cucumber/cucumber-expressions#parameter-types for more details
- Can ignore comments began with
#
- Can ignore multi-line description placed under
Feature
,Scenario
orScenario Outline
- Accept passing instances of step definition class:
Several
EasyCucumber.build()
methods can take the instance of your step definition class as parameter. In this case, the cucumber will use your instance to run the step, instead of creating a fresh new instance of the step definition class itself using Java Reflection API- For example, you can pass in
new MyStepDefinition()
instead ofMyStepDefinition.class
as the parameter toEasyCucumber.build()
- This is the key feature to support controlled concurrent execution of multiple Cucumber tests, achieved by sharing states between different cucumber tests, See this document for more detail.
- For example, you can pass in
- Enforced Keyword Check:
The step definition keyword in feature file must match the keyword in the annotation of the
step definition method.
- For examples: to match
Given a step
, you have to use@JGivenStep("a step")
instead of@JWhenStep("a step")
or@JThenStep("a step")
. This means if you have bothGiven a step
andWhen a step
in your feature file, you have to write two step definition methods with@JGivenStep("a step")
and@JWhenStep("a step")
respectively. - This is explicitly designed to allow better flexibility
- For examples: to match
Please check the design document for more details.