Reactive Acceptance Test Driven Development (ATDD) workshop with Spring WebFlux and Cucumber.
ATDD stands for Acceptance Test Driven Development. "ATDD involves team members with different perspectives (customer, development, testing) collaborating to write acceptance tests in advance of implementing the corresponding functionality".
We used Behaviour Driven Development (BDD) to link the business language to technical testing through Gherkin DSL. "BDD offers more precise guidance on organizing the conversation between developers, testers and domain experts"
Cucumber is a tool that supports BDD. Cucumber allows to specify different scenarios using Gherkin syntax written using Given/When/Then steps.
Reactive Programming provides the following benefits when designing your architecture:
- Asynchronous and non-blocking event-driven architecture
- Responsiveness, quick and consistent service
- Elastic under unpredictable workloads
- Resiliency, through replication and isolation in case of system failures
Reactive systems are more flexible, loosely-coupled and scalable.
Let's explain how we applied the ATDD cycle in a real example of Reactive Programming:
- ATDD helps to discuss in advance the features to be implemented. Firstly, we discuss and agree focus on the User Story agreed taking into account different perspectives (business, development and testing). Then, we write the User Story using Cucumber features and scenarios (as a user, I want, To):
Feature: Retrieve the amount of my assets
Scenario: As a crypto wallet user, I want to retrieve my past assets increments to know my overall amount of crypto assets
Given that we have stored data on the amount of crypto assets we have acquired in the past
When I request the amount of my assets
Then I get the list of the increments of amount of my assets
- Secondly, we write the failing acceptance tests using Given/When/Then approach:
@Given("that we have stored data on the amount of crypto assets we have acquired in the past")
public void setup() {
log.info("Before any test execution");
}
@When("I request the amount of my assets")
public void whenIRequestTheAmountOfMyAssets() {
log.info("Running: I request the amount of my assets at " + new Date());
assets = getAssets().collectList().block();
}
@Then("I get the list of the increments of amount of my assets")
public void thenIGetTheListOfTheIncrementsOfAmountOfMyAssets() {
log.info("Running: I get the list of the increments of amount of my assets at " + new Date());
assertEquals(3, assets.size());
assertAll("asset",
() -> assertTrue(assets.contains(new Asset("1", "BTC", 0.1))),
() -> assertTrue(assets.contains(new Asset("2", "BTC", 0.2))),
() -> assertTrue(assets.contains(new Asset("3", "BTC", 0.05)))
);
}
- Thirdly, we create the Acceptance code for the criteria defined. Remember that at this point the tests continue failing. We generate acceptance reports for the user to check it is ok:
- Next, we write the Unit tests and the code for the User Story to pass them
- Finally, we deploy a version with the new functionality for the users to test
The PoC provides a very simple wallet management of crypto assets using Reactive Programming. It exposes an API with these operations:
- [GET]
/status, get the status of the system - [GET]
/price/btc, get the current BTC spot price - [GET]
/assets, get all the assets stored - [POST]
/assets, create a new asset value - [GET]
/assets/{assetId}, get asset by id - [DEL]
/assets/{assetId}, remove asset by id - [GET]
/name/{name}, get asset by name - [GET]
/value, get the value of all the assets stored retrieving the current spot price from Coinbase
For further information check the Swagger configuration
- Java 11+
- Gradle for Java Dependency Management
- Spring WebFlux for Reactive Programming
- Spring Cucumber (and Gherkin) for the Acceptance Tests
- Lombok for the models
- Terraform for provisioning the GKE cluster
- Google Cloud Platform
- CI pipeline with GitHub actions, SonarCloud and CodeCov
Spring WebFlux provides Reactive Programming support. It internally uses Project Reactor and its two reactive and composable APIs Flux [N] and Mono [0|1].
We use Spring Cucumber to write the acceptance tests:
- The
*.featurefiles, where we define the scenarios are in./test/resources/folder - We configure gradle to run them as integration tests
- The source code for the tests are in
./test/acceptancefolder written asGiven/When/Thenclauses. Remember that the text MUST be the same used in the*.featurefile to associate the description with the actual test - When execute
gradlew integrationTest, it generatescucumber-report.htmllocated at./build/reports/folder.
We use Hashicorp scripts to create a GKE cluster in GCP with some modifications. Requirements:
- Terraform
- GCP account
- Configure gcloud SDK
Request permissions to interact with GCP
gcloud init
gcloud auth application-default login
Enable GKE services and Container Registry APIs
gcloud services enable \
containerregistry.googleapis.com \
container.googleapis.com
Modify terraform.tfvars including the project_id and region variables and execute:
terraform init
terraform apply
We included a GitHub action to deploy the container to GKE. Before we need to:
- Create a service account with admin roles on container and storage
- Generate the JSON key for the service account
Create a service account
$ gcloud iam service-accounts create $SA_NAME
Retrieve the email of the service account created
$ gcloud iam service-accounts list
Assign these permissions for the deployment to happen:
- Kubernetes Engine Admin
- Kubernetes Engine Developer
- Storage Admin
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
--member=serviceAccount:$SA_EMAIL \
--role=roles/container.admin \
--role=roles/storage.admin
Download the JSON keyfile for the service account
$ gcloud iam service-accounts keys create key.json --iam-account=$SA_EMAIL
Copy the content of the JSON keyfile and store it in a GitHub secret (GKE_SA_KEY):
$ cat key.json | base64
And configure two secrets in GitHub:
GKE_PROJECTwith the GCPproject_idGKE_SA_KEYwith the JSON key value
Run the unit tests:
./gradlew test
Run the acceptance tests. For the moment, to execute properly, they need the application deployed:
./gradlew bootRun
./gradlew integrationTest
Compile and package the project with
./gradlew clean bootJar
and execute
java -jar ./build/libs/reactive-atdd-workshop*.jar
It can also be run as:
./gradlew bootRun
Go to your browser and type http://localhost:8080/swagger-ui/
The project comes with a docker-compose which could handle the deployment of specific databases to use in the project:
- Check the
docker-compose.ymlscript - Execute
docker-compose up -d - Wait until the stack is deployed. Check the logs with
docker logs --details reactive-atdd-workshop - Check the Swagger configuration created at
http://localhost:8080/swagger-ui/
Generate the jar:
./gradlew clean bootJar
Build the image:
docker build -t thoughtworks/reactive-atdd-workshop .
Run the image:
docker run --name reactive -d thoughtworks/reactive-atdd-workshop
- ATDD from the trenches
- Driving Development with Tests: ATDD and TDD
- What is Cucumber?
- Cucumber 10 Minute Tutorial
- Cucumber Gherkin Reference
- Acceptance TDD with Cucumber
- Reactive Systems in Java
- Spring WebFlux
- Spring Cucumber
- How to Configure Integration Testing with Gradle
- Deploying to Google Kubernetes Engine
- Provision a GKE Cluster with Terraform
Apache 2.0
By downloading this software, the downloader agrees with the specified terms and conditions and the particularities of the license provided.


