From fd19e7e7eeffce8b46a8cd738857c5168f64f3c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aslak=20Helles=C3=B8y?=
Date: Tue, 13 Jul 2021 22:51:21 +0100
Subject: [PATCH 1/4] Add CucumberSplit
---
testing/pom.xml | 10 ++++
.../testing/cucumber/CucumberSplit.java | 51 +++++++++++++++++++
.../testing/cucumber/RunCucumberTest.java | 8 +++
.../testing/cucumber/StepDefinitions.java | 22 ++++++++
.../src/test/resources/cucumber.properties | 1 +
.../client/testing/cucumber/Split.feature | 19 +++++++
6 files changed, 111 insertions(+)
create mode 100644 testing/src/main/java/io/split/client/testing/cucumber/CucumberSplit.java
create mode 100644 testing/src/test/java/io/split/client/testing/cucumber/RunCucumberTest.java
create mode 100644 testing/src/test/java/io/split/client/testing/cucumber/StepDefinitions.java
create mode 100644 testing/src/test/resources/cucumber.properties
create mode 100644 testing/src/test/resources/io/split/client/testing/cucumber/Split.feature
diff --git a/testing/pom.xml b/testing/pom.xml
index bbad31d7f..d690ce04f 100644
--- a/testing/pom.xml
+++ b/testing/pom.xml
@@ -23,5 +23,15 @@
junit
junit
+
+ io.cucumber
+ cucumber-java
+ 6.10.4
+
+
+ io.cucumber
+ cucumber-junit
+ 6.10.4
+
diff --git a/testing/src/main/java/io/split/client/testing/cucumber/CucumberSplit.java b/testing/src/main/java/io/split/client/testing/cucumber/CucumberSplit.java
new file mode 100644
index 000000000..bd3b29a2e
--- /dev/null
+++ b/testing/src/main/java/io/split/client/testing/cucumber/CucumberSplit.java
@@ -0,0 +1,51 @@
+package io.split.client.testing.cucumber;
+
+import io.cucumber.java.Scenario;
+import io.split.client.testing.SplitClientForTest;
+
+import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ *
+ * Simple Cucumber plugin for Split.
+ *
+ *
+ * Cucumber scenarios annotated with {@code @split[feature:treatment]} tags can be used to
+ * configure a {@link SplitClientForTest} instance.
+ *
+ *
+ * To use it, define a Before Hook that invokes the {@link CucumberSplit#configureSplit(SplitClientForTest, Scenario)}
+ * method. Example:
+ *
+ *
+ *
+ * import io.cucumber.java.Before;
+ * import io.split.client.testing.SplitClientForTest;
+ *
+ * public class StepDefinitions {
+ * private final SplitClientForTest splitClient = new SplitClientForTest();
+ *
+ * @Before
+ * public void configureSplit(Scenario scenario) {
+ * CucumberSplit.configureSplit(splitClient, scenario);
+ * }
+ * }
+ *
+ */
+public class CucumberSplit {
+ private static final Pattern SPLIT_TAG_PATTERN = Pattern.compile("^@split\\[(.*):(.*)]");
+
+ public static void configureSplit(SplitClientForTest splitClient, Scenario scenario) {
+ Collection tags = scenario.getSourceTagNames();
+ for (String tag : tags) {
+ Matcher matcher = SPLIT_TAG_PATTERN.matcher(tag);
+ if (matcher.matches()) {
+ String feature = matcher.group(1);
+ String treatment = matcher.group(2);
+ splitClient.registerTreatment(feature, treatment);
+ }
+ }
+ }
+}
diff --git a/testing/src/test/java/io/split/client/testing/cucumber/RunCucumberTest.java b/testing/src/test/java/io/split/client/testing/cucumber/RunCucumberTest.java
new file mode 100644
index 000000000..cbb714f82
--- /dev/null
+++ b/testing/src/test/java/io/split/client/testing/cucumber/RunCucumberTest.java
@@ -0,0 +1,8 @@
+package io.split.client.testing.cucumber;
+
+import io.cucumber.junit.Cucumber;
+import org.junit.runner.RunWith;
+
+@RunWith(Cucumber.class)
+public class RunCucumberTest {
+}
diff --git a/testing/src/test/java/io/split/client/testing/cucumber/StepDefinitions.java b/testing/src/test/java/io/split/client/testing/cucumber/StepDefinitions.java
new file mode 100644
index 000000000..559171086
--- /dev/null
+++ b/testing/src/test/java/io/split/client/testing/cucumber/StepDefinitions.java
@@ -0,0 +1,22 @@
+package io.split.client.testing.cucumber;
+
+import io.cucumber.java.Before;
+import io.cucumber.java.Scenario;
+import io.cucumber.java.en.Then;
+import io.split.client.testing.SplitClientForTest;
+
+import static org.junit.Assert.assertEquals;
+
+public class StepDefinitions {
+ private final SplitClientForTest splitClient = new SplitClientForTest();
+
+ @Then("split {string} should be {string}")
+ public void split_should_be(String split, String expectedValue) {
+ assertEquals(expectedValue, splitClient.getTreatment("arbitraryKey", split));
+ }
+
+ @Before
+ public void configureSplit(Scenario scenario) {
+ CucumberSplit.configureSplit(splitClient, scenario);
+ }
+}
diff --git a/testing/src/test/resources/cucumber.properties b/testing/src/test/resources/cucumber.properties
new file mode 100644
index 000000000..b48dd63bf
--- /dev/null
+++ b/testing/src/test/resources/cucumber.properties
@@ -0,0 +1 @@
+cucumber.publish.quiet=true
diff --git a/testing/src/test/resources/io/split/client/testing/cucumber/Split.feature b/testing/src/test/resources/io/split/client/testing/cucumber/Split.feature
new file mode 100644
index 000000000..e41005a02
--- /dev/null
+++ b/testing/src/test/resources/io/split/client/testing/cucumber/Split.feature
@@ -0,0 +1,19 @@
+@split[cappuccino:off]
+@split[dollars:on]
+Feature: Split
+ This is an example of how to use Split with Cucumber
+
+ @split[cappuccino:on]
+ @split[dollars:off]
+ Scenario: with cappuccino, but without dollars
+ Then split "cappuccino" should be "on"
+ And split "dollars" should be "off"
+
+ Scenario: without cappuccino, but with dollars
+ Then split "cappuccino" should be "off"
+ And split "dollars" should be "on"
+
+ @split[dollars:off]
+ Scenario: without cappuccino, nor dollars
+ Then split "cappuccino" should be "off"
+ And split "dollars" should be "off"
From 87d6d4fe734db06882b5af08242a8892ccd8c733 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aslak=20Helles=C3=B8y?=
Date: Tue, 13 Jul 2021 23:05:58 +0100
Subject: [PATCH 2/4] Add URL in JavaDoc
---
.../java/io/split/client/testing/cucumber/CucumberSplit.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/testing/src/main/java/io/split/client/testing/cucumber/CucumberSplit.java b/testing/src/main/java/io/split/client/testing/cucumber/CucumberSplit.java
index bd3b29a2e..716b51088 100644
--- a/testing/src/main/java/io/split/client/testing/cucumber/CucumberSplit.java
+++ b/testing/src/main/java/io/split/client/testing/cucumber/CucumberSplit.java
@@ -16,7 +16,7 @@
* configure a {@link SplitClientForTest} instance.
*
*
- * To use it, define a Before Hook that invokes the {@link CucumberSplit#configureSplit(SplitClientForTest, Scenario)}
+ * To use it, define a Before Hook that invokes the {@link CucumberSplit#configureSplit(SplitClientForTest, Scenario)}
* method. Example:
*
*
From 5024560a7d355c4eef9363a23c6836b2c19c063a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aslak=20Helles=C3=B8y?=
Date: Sun, 18 Jul 2021 15:31:06 +0100
Subject: [PATCH 3/4] Improve the Cucumber example to be more realistic
---
.../testing/cucumber/CoffeeMachine.java | 44 +++++++++++++++++++
.../testing/cucumber/RunCucumberTest.java | 1 +
.../io/split/client/testing/cucumber/SKU.java | 37 ++++++++++++++++
.../testing/cucumber/StepDefinitions.java | 39 ++++++++++++++--
.../testing/cucumber/MakeCoffee.feature | 25 +++++++++++
.../client/testing/cucumber/Split.feature | 19 --------
6 files changed, 143 insertions(+), 22 deletions(-)
create mode 100644 testing/src/test/java/io/split/client/testing/cucumber/CoffeeMachine.java
create mode 100644 testing/src/test/java/io/split/client/testing/cucumber/SKU.java
create mode 100644 testing/src/test/resources/io/split/client/testing/cucumber/MakeCoffee.feature
delete mode 100644 testing/src/test/resources/io/split/client/testing/cucumber/Split.feature
diff --git a/testing/src/test/java/io/split/client/testing/cucumber/CoffeeMachine.java b/testing/src/test/java/io/split/client/testing/cucumber/CoffeeMachine.java
new file mode 100644
index 000000000..2cfa2e913
--- /dev/null
+++ b/testing/src/test/java/io/split/client/testing/cucumber/CoffeeMachine.java
@@ -0,0 +1,44 @@
+package io.split.client.testing.cucumber;
+
+import io.split.client.SplitClient;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.unmodifiableList;
+
+/**
+ * A simple coffee machine that displays available drinks. It can offer an experimental cappuccino
+ * drink that is toggled on/off with Split.
+ */
+public class CoffeeMachine {
+ private final SplitClient splitClient;
+ private final String splitKey;
+ private double level;
+
+ public CoffeeMachine(SplitClient splitClient, String splitKey) {
+ this.splitClient = splitClient;
+ this.splitKey = splitKey;
+ }
+
+ /**
+ * Indicate how full the machine is
+ *
+ * @param level a number between 0 and 1
+ */
+ public void setLevel(double level) {
+ this.level = level;
+ }
+
+ public List getAvailableDrinks() {
+ if(this.level == 0) return emptyList();
+
+ List availableDrinks = new ArrayList<>();
+ availableDrinks.add(new SKU("filter coffee", 0.80));
+ if ("on".equals(this.splitClient.getTreatment(splitKey, "cappuccino"))) {
+ availableDrinks.add(new SKU("cappuccino", 1.10));
+ }
+ return unmodifiableList(availableDrinks);
+ }
+}
diff --git a/testing/src/test/java/io/split/client/testing/cucumber/RunCucumberTest.java b/testing/src/test/java/io/split/client/testing/cucumber/RunCucumberTest.java
index cbb714f82..18a325ab7 100644
--- a/testing/src/test/java/io/split/client/testing/cucumber/RunCucumberTest.java
+++ b/testing/src/test/java/io/split/client/testing/cucumber/RunCucumberTest.java
@@ -3,6 +3,7 @@
import io.cucumber.junit.Cucumber;
import org.junit.runner.RunWith;
+// This is the entry point for Cucumber, which runs all the .feature files in the same package
@RunWith(Cucumber.class)
public class RunCucumberTest {
}
diff --git a/testing/src/test/java/io/split/client/testing/cucumber/SKU.java b/testing/src/test/java/io/split/client/testing/cucumber/SKU.java
new file mode 100644
index 000000000..12e5a8cb1
--- /dev/null
+++ b/testing/src/test/java/io/split/client/testing/cucumber/SKU.java
@@ -0,0 +1,37 @@
+package io.split.client.testing.cucumber;
+
+import java.util.Objects;
+
+/**
+ * A simple Stock Keeping Unit (SKU).
+ */
+public class SKU {
+ private final String name;
+ private final double price;
+
+ public SKU(String name, double price) {
+ this.name = name;
+ this.price = price;
+ }
+
+ @Override
+ public String toString() {
+ return "SKU{" +
+ "name='" + name + '\'' +
+ ", price=" + price +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SKU sku = (SKU) o;
+ return Double.compare(sku.price, price) == 0 && name.equals(sku.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, price);
+ }
+}
diff --git a/testing/src/test/java/io/split/client/testing/cucumber/StepDefinitions.java b/testing/src/test/java/io/split/client/testing/cucumber/StepDefinitions.java
index 559171086..59d3eae29 100644
--- a/testing/src/test/java/io/split/client/testing/cucumber/StepDefinitions.java
+++ b/testing/src/test/java/io/split/client/testing/cucumber/StepDefinitions.java
@@ -1,18 +1,51 @@
package io.split.client.testing.cucumber;
import io.cucumber.java.Before;
+import io.cucumber.java.DataTableType;
import io.cucumber.java.Scenario;
+import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.split.client.testing.SplitClientForTest;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Collections.emptyList;
import static org.junit.Assert.assertEquals;
public class StepDefinitions {
private final SplitClientForTest splitClient = new SplitClientForTest();
+ private final CoffeeMachine coffeeMachine = new CoffeeMachine(splitClient, "arbitraryKey");
+
+ // Called by Cucumber to convert each row in the data table in the .feature file to a SKU object
+ @DataTableType
+ public SKU sku(Map entry) {
+ return new SKU(
+ entry.get("name"),
+ Double.parseDouble(entry.get("price"))
+ );
+ }
+
+ @Given("the machine is not empty")
+ public void the_machine_is_not_empty() {
+ coffeeMachine.setLevel(1.0);
+ }
+
+ @Given("the machine is empty")
+ public void the_machine_is_empty() {
+ coffeeMachine.setLevel(0);
+ }
+
+ @Then("the following drinks should be available:")
+ public void the_following_drinks_should_be_available(List expectedSKUs) {
+ List availableSKUs = coffeeMachine.getAvailableDrinks();
+ assertEquals(expectedSKUs, availableSKUs);
+ }
- @Then("split {string} should be {string}")
- public void split_should_be(String split, String expectedValue) {
- assertEquals(expectedValue, splitClient.getTreatment("arbitraryKey", split));
+ @Then("no drinks should be available")
+ public void no_drinks_should_be_available() {
+ List availableSKUs = coffeeMachine.getAvailableDrinks();
+ assertEquals(emptyList(), availableSKUs);
}
@Before
diff --git a/testing/src/test/resources/io/split/client/testing/cucumber/MakeCoffee.feature b/testing/src/test/resources/io/split/client/testing/cucumber/MakeCoffee.feature
new file mode 100644
index 000000000..5ca4f3351
--- /dev/null
+++ b/testing/src/test/resources/io/split/client/testing/cucumber/MakeCoffee.feature
@@ -0,0 +1,25 @@
+# This tag is inherited by all the scenarios, setting the "cappuccino" split feature to "off" by default.
+@split[cappuccino:off]
+Feature: Make Coffee
+ The scenarios in this feature describes how the coffee machine works.
+
+ Scenario: Empty machine
+ Given the machine is empty
+ Then no drinks should be available
+
+ Scenario: Display available drinks
+ Given the machine is not empty
+ Then the following drinks should be available:
+ | name | price |
+ | filter coffee | 0.80 |
+
+ # The tags on this scenario will be ["@split[cappuccino:off]", "@split[cappuccino:on]"]
+ # The @split tags are processed sequentially, so the cappuccino split feature will be set to "off"
+ # and then immediately overwritten to "on".
+ @split[cappuccino:on]
+ Scenario: Display available drinks (including the new experimental cappuccino)
+ Given the machine is not empty
+ Then the following drinks should be available:
+ | name | price |
+ | filter coffee | 0.80 |
+ | cappuccino | 1.10 |
diff --git a/testing/src/test/resources/io/split/client/testing/cucumber/Split.feature b/testing/src/test/resources/io/split/client/testing/cucumber/Split.feature
deleted file mode 100644
index e41005a02..000000000
--- a/testing/src/test/resources/io/split/client/testing/cucumber/Split.feature
+++ /dev/null
@@ -1,19 +0,0 @@
-@split[cappuccino:off]
-@split[dollars:on]
-Feature: Split
- This is an example of how to use Split with Cucumber
-
- @split[cappuccino:on]
- @split[dollars:off]
- Scenario: with cappuccino, but without dollars
- Then split "cappuccino" should be "on"
- And split "dollars" should be "off"
-
- Scenario: without cappuccino, but with dollars
- Then split "cappuccino" should be "off"
- And split "dollars" should be "on"
-
- @split[dollars:off]
- Scenario: without cappuccino, nor dollars
- Then split "cappuccino" should be "off"
- And split "dollars" should be "off"
From 77b4753709dd08c1b4051387b184f3647b6f6525 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aslak=20Helles=C3=B8y?=
Date: Sun, 18 Jul 2021 15:33:45 +0100
Subject: [PATCH 4/4] disambiguation
---
.../io/split/client/testing/cucumber/MakeCoffee.feature | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/testing/src/test/resources/io/split/client/testing/cucumber/MakeCoffee.feature b/testing/src/test/resources/io/split/client/testing/cucumber/MakeCoffee.feature
index 5ca4f3351..bb2c98a15 100644
--- a/testing/src/test/resources/io/split/client/testing/cucumber/MakeCoffee.feature
+++ b/testing/src/test/resources/io/split/client/testing/cucumber/MakeCoffee.feature
@@ -1,7 +1,7 @@
# This tag is inherited by all the scenarios, setting the "cappuccino" split feature to "off" by default.
@split[cappuccino:off]
Feature: Make Coffee
- The scenarios in this feature describes how the coffee machine works.
+ The scenarios in this feature file describes how the coffee machine works.
Scenario: Empty machine
Given the machine is empty