Skip to content

Commit b83e182

Browse files
Add Support for Cucumber using @split[feature:treatment] tags (#247)
* Add CucumberSplit * Add URL in JavaDoc * Improve the Cucumber example to be more realistic * disambiguation
1 parent c87cf45 commit b83e182

File tree

8 files changed

+232
-0
lines changed

8 files changed

+232
-0
lines changed

testing/pom.xml

+10
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,15 @@
2323
<groupId>junit</groupId>
2424
<artifactId>junit</artifactId>
2525
</dependency>
26+
<dependency>
27+
<groupId>io.cucumber</groupId>
28+
<artifactId>cucumber-java</artifactId>
29+
<version>6.10.4</version>
30+
</dependency>
31+
<dependency>
32+
<groupId>io.cucumber</groupId>
33+
<artifactId>cucumber-junit</artifactId>
34+
<version>6.10.4</version>
35+
</dependency>
2636
</dependencies>
2737
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.split.client.testing.cucumber;
2+
3+
import io.cucumber.java.Scenario;
4+
import io.split.client.testing.SplitClientForTest;
5+
6+
import java.util.Collection;
7+
import java.util.regex.Matcher;
8+
import java.util.regex.Pattern;
9+
10+
/**
11+
* <p>
12+
* Simple Cucumber plugin for Split.
13+
* </p>
14+
* <p>
15+
* Cucumber scenarios annotated with {@code @split[feature:treatment]} tags can be used to
16+
* configure a {@link SplitClientForTest} instance.
17+
* </p>
18+
* <p>
19+
* To use it, define a <a href="https://cucumber.io/docs/cucumber/api/#hooks">Before Hook</a> that invokes the {@link CucumberSplit#configureSplit(SplitClientForTest, Scenario)}
20+
* method. Example:
21+
* </p>
22+
*
23+
* <pre>
24+
* import io.cucumber.java.Before;
25+
* import io.split.client.testing.SplitClientForTest;
26+
*
27+
* public class StepDefinitions {
28+
* private final SplitClientForTest splitClient = new SplitClientForTest();
29+
*
30+
* &#64;Before
31+
* public void configureSplit(Scenario scenario) {
32+
* CucumberSplit.configureSplit(splitClient, scenario);
33+
* }
34+
* }
35+
* </pre>
36+
*/
37+
public class CucumberSplit {
38+
private static final Pattern SPLIT_TAG_PATTERN = Pattern.compile("^@split\\[(.*):(.*)]");
39+
40+
public static void configureSplit(SplitClientForTest splitClient, Scenario scenario) {
41+
Collection<String> tags = scenario.getSourceTagNames();
42+
for (String tag : tags) {
43+
Matcher matcher = SPLIT_TAG_PATTERN.matcher(tag);
44+
if (matcher.matches()) {
45+
String feature = matcher.group(1);
46+
String treatment = matcher.group(2);
47+
splitClient.registerTreatment(feature, treatment);
48+
}
49+
}
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.split.client.testing.cucumber;
2+
3+
import io.split.client.SplitClient;
4+
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
8+
import static java.util.Collections.emptyList;
9+
import static java.util.Collections.unmodifiableList;
10+
11+
/**
12+
* A simple coffee machine that displays available drinks. It can offer an experimental cappuccino
13+
* drink that is toggled on/off with Split.
14+
*/
15+
public class CoffeeMachine {
16+
private final SplitClient splitClient;
17+
private final String splitKey;
18+
private double level;
19+
20+
public CoffeeMachine(SplitClient splitClient, String splitKey) {
21+
this.splitClient = splitClient;
22+
this.splitKey = splitKey;
23+
}
24+
25+
/**
26+
* Indicate how full the machine is
27+
*
28+
* @param level a number between 0 and 1
29+
*/
30+
public void setLevel(double level) {
31+
this.level = level;
32+
}
33+
34+
public List<SKU> getAvailableDrinks() {
35+
if(this.level == 0) return emptyList();
36+
37+
List<SKU> availableDrinks = new ArrayList<>();
38+
availableDrinks.add(new SKU("filter coffee", 0.80));
39+
if ("on".equals(this.splitClient.getTreatment(splitKey, "cappuccino"))) {
40+
availableDrinks.add(new SKU("cappuccino", 1.10));
41+
}
42+
return unmodifiableList(availableDrinks);
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.split.client.testing.cucumber;
2+
3+
import io.cucumber.junit.Cucumber;
4+
import org.junit.runner.RunWith;
5+
6+
// This is the entry point for Cucumber, which runs all the .feature files in the same package
7+
@RunWith(Cucumber.class)
8+
public class RunCucumberTest {
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.split.client.testing.cucumber;
2+
3+
import java.util.Objects;
4+
5+
/**
6+
* A simple <a href="https://en.wikipedia.org/wiki/Stock_keeping_unit">Stock Keeping Unit</a> (SKU).
7+
*/
8+
public class SKU {
9+
private final String name;
10+
private final double price;
11+
12+
public SKU(String name, double price) {
13+
this.name = name;
14+
this.price = price;
15+
}
16+
17+
@Override
18+
public String toString() {
19+
return "SKU{" +
20+
"name='" + name + '\'' +
21+
", price=" + price +
22+
'}';
23+
}
24+
25+
@Override
26+
public boolean equals(Object o) {
27+
if (this == o) return true;
28+
if (o == null || getClass() != o.getClass()) return false;
29+
SKU sku = (SKU) o;
30+
return Double.compare(sku.price, price) == 0 && name.equals(sku.name);
31+
}
32+
33+
@Override
34+
public int hashCode() {
35+
return Objects.hash(name, price);
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.split.client.testing.cucumber;
2+
3+
import io.cucumber.java.Before;
4+
import io.cucumber.java.DataTableType;
5+
import io.cucumber.java.Scenario;
6+
import io.cucumber.java.en.Given;
7+
import io.cucumber.java.en.Then;
8+
import io.split.client.testing.SplitClientForTest;
9+
10+
import java.util.List;
11+
import java.util.Map;
12+
13+
import static java.util.Collections.emptyList;
14+
import static org.junit.Assert.assertEquals;
15+
16+
public class StepDefinitions {
17+
private final SplitClientForTest splitClient = new SplitClientForTest();
18+
private final CoffeeMachine coffeeMachine = new CoffeeMachine(splitClient, "arbitraryKey");
19+
20+
// Called by Cucumber to convert each row in the data table in the .feature file to a SKU object
21+
@DataTableType
22+
public SKU sku(Map<String, String> entry) {
23+
return new SKU(
24+
entry.get("name"),
25+
Double.parseDouble(entry.get("price"))
26+
);
27+
}
28+
29+
@Given("the machine is not empty")
30+
public void the_machine_is_not_empty() {
31+
coffeeMachine.setLevel(1.0);
32+
}
33+
34+
@Given("the machine is empty")
35+
public void the_machine_is_empty() {
36+
coffeeMachine.setLevel(0);
37+
}
38+
39+
@Then("the following drinks should be available:")
40+
public void the_following_drinks_should_be_available(List<SKU> expectedSKUs) {
41+
List<SKU> availableSKUs = coffeeMachine.getAvailableDrinks();
42+
assertEquals(expectedSKUs, availableSKUs);
43+
}
44+
45+
@Then("no drinks should be available")
46+
public void no_drinks_should_be_available() {
47+
List<SKU> availableSKUs = coffeeMachine.getAvailableDrinks();
48+
assertEquals(emptyList(), availableSKUs);
49+
}
50+
51+
@Before
52+
public void configureSplit(Scenario scenario) {
53+
CucumberSplit.configureSplit(splitClient, scenario);
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cucumber.publish.quiet=true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# This tag is inherited by all the scenarios, setting the "cappuccino" split feature to "off" by default.
2+
@split[cappuccino:off]
3+
Feature: Make Coffee
4+
The scenarios in this feature file describes how the coffee machine works.
5+
6+
Scenario: Empty machine
7+
Given the machine is empty
8+
Then no drinks should be available
9+
10+
Scenario: Display available drinks
11+
Given the machine is not empty
12+
Then the following drinks should be available:
13+
| name | price |
14+
| filter coffee | 0.80 |
15+
16+
# The tags on this scenario will be ["@split[cappuccino:off]", "@split[cappuccino:on]"]
17+
# The @split tags are processed sequentially, so the cappuccino split feature will be set to "off"
18+
# and then immediately overwritten to "on".
19+
@split[cappuccino:on]
20+
Scenario: Display available drinks (including the new experimental cappuccino)
21+
Given the machine is not empty
22+
Then the following drinks should be available:
23+
| name | price |
24+
| filter coffee | 0.80 |
25+
| cappuccino | 1.10 |

0 commit comments

Comments
 (0)