Amazon deploys to production every second
"... automated acceptance tests can be costly to maintain. Done badly, they can inflict a significant cost on your delivery team."
@Test
public void shouldInsertPetIntoDatabaseAndGenerateId() {
Owner owner6 = this.clinicService.findOwnerById(6);
int found = owner6.getPets().size();
Pet pet = new Pet();
pet.setName("bowser");
Collection<PetType> types = this.clinicService.findPetTypes();
pet.setType(EntityUtils.getById(types, PetType.class, 2));
pet.setBirthDate(new DateTime());
owner6.addPet(pet);
assertThat(owner6.getPets().size()).isEqualTo(found + 1);
this.clinicService.savePet(pet);
this.clinicService.saveOwner(owner6);
owner6 = this.clinicService.findOwnerById(6);
assertThat(owner6.getPets().size()).isEqualTo(found + 1);
// checks that id has been generated
assertThat(pet.getId()).isNotNull();
}
Example from github.com/spring-projects/spring-petclinic
Feature: Finding Owners
Scenario: Owners can be found by last name
Given an owner with last name "John"
When searching for "John"
Then exactly the given owner is found
public class CustomerStepdefs {
@Given("an owner with last name (.*)")
public void anOwnerWithLastName(String lastName) { ... }
@When("searching for (.*)")
public void searchingFor(String lastName) { ... }
@Then("exactly the given owner is found")
public void exactlyTheGivenOwnerIsFound() { ... }
}
"... while modern tools [like Cucumber] reduce the overhead of writing executable acceptance criteria and keeping them synchronized with the acceptance test implementation, there is inevitably some overhead."
import org.junit.Test;
import com.tngtech.jgiven.junit.ScenarioTest;
public class FindOwnerTest
extends ScenarioTest<GivenOwner, WhenSearching, ThenOwner> {
@Test
public void owners_can_be_found_by_last_name() {
given().an_owner_with_last_name("John");
when().searching_for("John");
then().exactly_the_given_owner_is_found();
}
}
@JGivenStage // only needed when using Spring
public class WhenSearching extends Stage<WhenSearching> {
@Autowired
ClinicService clinicService;
@ScenarioState
Owner owner;
public WhenSearching searching_for(String name) {
owner = this.clinicService.findOwnerByName(name);
return this;
}
}
[...] organize the code into two layers: an implementation layer [...] and, a declarative layer [...] to describe the purpose of each fragment. The declarative layer describes what the code will do, while the implementation layer describes how the code does it. The declarative layer is, in effect, a small domain-specific language embedded (in this case) in Java.
Owners can be found by last name
Given an owner with last name "John"
When searching for "John"
Then exactly the given owner is found
given().a_customer_with_name( "John" );
Given a customer with name John
Given there are 5 coffees left
given().there_are_$_coffees_left( 5 );
@Test
@DataProvider({
"1, 0",
"3, 2",
"5, 4"})
public void the_stock_is_reduced_when_a_book_is_ordered( int initial,
int left ) {
given().a_customer()
.and().a_book()
.with().$_items_on_stock( initial );
when().the_customer_orders_the_book();
then().there_are_$_items_left_on_stock( left );
}
Uses the DataProviderRunner (github.com/TNG/junit-dataprovider). The stock is reduced when a book is ordered
Given a customer
And a book
With <initial> items on stock
When the customer orders the book
Then there are <left> items left on stock
Cases:
| # | initial | left | Status |
+---+---------+------+---------+
| 1 | 1 | 0 | Success |
| 2 | 3 | 2 | Success |
| 3 | 5 | 4 | Success |
@Format( value = BooleanFormatter.class, args = { "on", "off" } )
@Retention( RetentionPolicy.RUNTIME )
@interface OnOff {}
public SELF the_machine_is_$( @OnOff boolean onOrOff ) { ... }
given().the_machine_is_$( false );
Given the machine is off
SELF the_following_books_are_on_stock( @Table String[][] stockTable ) {
...
}
@Test
public void ordering_a_book_reduces_the_stock() {
given().the_following_books_on_stock(new String[][]{
{"id", "name", "author", "stock"},
{"1", "The Hitchhiker's Guide to the Galaxy", "Douglas Adams", "5"},
{"2", "Lord of the Rings", "John Tolkien", "3"},
});
when().a_customer_orders_book("1");
then().the_stock_looks_as_follows(new String[][]{
{"id", "name", "author", "stock"},
{"1", "The Hitchhiker's Guide to the Galaxy", "Douglas Adams", "4"},
{"2", "Lord of the Rings", "John Tolkien", "3"},
});
}
Ordering a book reduces the stock
Given the following books on stock
| id | name | author | stock |
+----+--------------------------------------+---------------+-------+
| 1 | The Hitchhiker's Guide to the Galaxy | Douglas Adams | 5 |
| 2 | Lord of the Rings | John Tolkien | 3 |
When a customer orders book 1
Then the stock looks as follows
| id | name | author | stock |
+----+--------------------------------------+---------------+-------+
| 1 | The Hitchhiker's Guide to the Galaxy | Douglas Adams | 4 |
| 2 | Lord of the Rings | John Tolkien | 3 |
public class GivenSteps extends Stage<GivenSteps> {
@ProvidedScenarioState
File temporaryFolder;
@BeforeScenario
void setupTemporaryFolder() {
temporaryFolder = ...
}
@AfterScenario
void deleteTemporaryFolder() {
temporaryFolder.delete();
}
}
public class TemporaryFolderRule {
File temporaryFolder;
public void before() {
temporaryFolder = ...
}
public void after() {
temporaryFolder.delete();
}
}
public class GivenSteps extends Stage<GivenSteps> {
@ScenarioRule
TemporaryFolderRule rule = new TemporaryFolderRule();
}
public class GivenCustomer extends Stage<GivenSteps> {
CustomerBuilder builder;
@ProvidedScenarioState
Customer customer;
public void a_customer() {
builder = new CustomerBuilder();
}
public void with_age(int age) {
builder.withAge(age);
}
@AfterStage
void buildCustomer() {
customer = builder.build();
}
}
@Test @FeatureEmail
void the_customer_gets_an_email_when_ordering_a_book() {
...
}
@Test @Story( "ABC-123" )
void the_customer_gets_an_email_when_ordering_a_book() { ... }
@Hidden
public SELF doSomethingTechnical() { ... }
@ExtendedDescription("The Hitchhiker's Guide to the Galaxy, "
+ "by default the book is not on stock" )
public SELF a_book() { ... }
public class Html5ReportStage {
@ExpectedScenarioState
protected CurrentStep currentStep; // provided by JGiven
protected void takeScreenshot() {
String base64 = ( (TakesScreenshot) webDriver )
.getScreenshotAs( OutputType.BASE64 );
currentStep.addAttachment(
Attachment.fromBase64( base64, MediaType.PNG )
.withTitle( "Screenshot" ) );
}
}