Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions testing-cucumber/CHANGES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CHANGES

1.0.0(July 27, 2021)
- First release of Cucumber integration for Split testing module

38 changes: 38 additions & 0 deletions testing-cucumber/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.split.client</groupId>
<artifactId>java-client-parent</artifactId>
<version>4.2.1</version>
</parent>

<artifactId>java-client-testing-cucumber</artifactId>
<version>1.0.0-rc1</version>
<packaging>jar</packaging>
<name>Split Java Client Cucumber testing module</name>
<description>Cucumber integration for the testing module of Split Java Client</description>
<dependencies>
<dependency>
<groupId>io.split.client</groupId>
<artifactId>java-client-testing</artifactId>
<version>${parent.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>6.10.4</version>

Choose a reason for hiding this comment

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

I would add <scope>provided</scope> here so that users can choose a more recent version of Cucumber when it's released. (The API we're using is stable and unlikely to change).

</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>6.10.4</version>

Choose a reason for hiding this comment

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

I would add <scope>test</scope> here since it's not used in src/main, only in src/test.

</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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;

/**
* <p>
* Simple Cucumber plugin for Split.
* </p>
* <p>
* Cucumber scenarios annotated with {@code @split[feature:treatment]} tags can be used to
* configure a {@link SplitClientForTest} instance.
* </p>
* <p>
* 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)}
* method. Example:
* </p>
*
* <pre>
* import io.cucumber.java.Before;
* import io.split.client.testing.SplitClientForTest;
*
* public class StepDefinitions {
* private final SplitClientForTest splitClient = new SplitClientForTest();
*
* &#64;Before
* public void configureSplit(Scenario scenario) {
* CucumberSplit.configureSplit(splitClient, scenario);
* }
* }
* </pre>
*/
public class CucumberSplit {
private static final Pattern SPLIT_TAG_PATTERN = Pattern.compile("^@split\\[(.*):(.*)]");

public static void configureSplit(SplitClientForTest splitClient, Scenario scenario) {
Collection<String> 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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<SKU> getAvailableDrinks() {
if(this.level == 0) return emptyList();

List<SKU> 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.split.client.testing.cucumber;

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 {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.split.client.testing.cucumber;

import java.util.Objects;

/**
* A simple <a href="https://en.wikipedia.org/wiki/Stock_keeping_unit">Stock Keeping Unit</a> (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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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<String, String> 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<SKU> expectedSKUs) {
List<SKU> availableSKUs = coffeeMachine.getAvailableDrinks();
assertEquals(expectedSKUs, availableSKUs);
}

@Then("no drinks should be available")
public void no_drinks_should_be_available() {
List<SKU> availableSKUs = coffeeMachine.getAvailableDrinks();
assertEquals(emptyList(), availableSKUs);
}

@Before
public void configureSplit(Scenario scenario) {
CucumberSplit.configureSplit(splitClient, scenario);
}
}
1 change: 1 addition & 0 deletions testing-cucumber/src/test/resources/cucumber.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cucumber.publish.quiet=true
Original file line number Diff line number Diff line change
@@ -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 file 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 |