Test-Driven Development (TDD) and Behaviour-Driven Development (BDD) are powerful techniques, helping developers write better designed, more maintainable and more reliable code, and stay focused on the real user requirements. But how does the rest of the team fit in to the picture?
In this talk, we will look at how BDD techniques, and tools such as easyb, FitNesse, and other BDD-related tools can also act as drivers for the overall development process, and also as communication tools, giving testers and end-users clear and unambiguous feedback on what is being developed and where it is at in terms of delivery and schedule.
Boost Fertility New Invention Ups Success Rates.pdf
Acceptance Test Driven Development
1. Acceptance Test Driven Development
Bringing Testers and Developers Together
John Ferguson Smart
Wakaleo Consulting Ltd.
http://www.wakaleo.com
Email: john.smart@wakaleo.com
Twitter: wakaleo
2. Introduction
So what’s this talk about, anyway
Acceptance tests as a communication tool
Acceptance Test Driven Development
BDD-style Acceptance Tests - easyb
3. Acceptance Tests
Acceptance Tests - a key Agile practice
A communication tool
Owned by the customer
Determine when a feature is ‘done’
Written together (customer, developer, tester)
Focus on What, not How
4. Acceptance Tests
Acceptance Tests - how far do you go?
In depth tests or examples of system usage?
Exhaustive Tests or Sample Stories?
5. Acceptance Tests
Acceptance Tests - a key Agile practice
User Story 1 - Transfer funds
User Story 11- -Calculate my tax rate
User Story Calculate my tax rate
As a bank client, I want to transfer funds from
my current account to my savings account, so
As a tax payer, I want to be able to calculate my
that I a taxearn more interest able to calculate my
As can payer, I want to be
tax online, so that I can put enough money aside.
tax online, so that I can put enough money aside.
So how do we know when
this feature is done?
6. Acceptance Tests
Acceptance Tests - a key Agile practice
User Story 11- ---Acceptance Testsrate
User Story 11Calculate my tax
User Story Transfer funds
User Story Calculate my tax rate
- As a bank client,money between two accounts from
Client can transfer I want to transfer funds
- my current account to my savings account, so
Client can’t transfer negative amount
- Client can’t transfer wantthan currentto calculate my
As a tax payer, I more to be able account balance
that I a taxearn more interest able to calculate my
As can payer, I want to be
- Client can’t transfer from a blocked accountmoney aside.
tax online, so that I can put enough
tax online, so that I can put enough money aside.
So how do we know when
this feature is done?
Let’s write some
Acceptance Criteria
7. Acceptance Tests
Acceptance Criteria
Conditions that must be met before the story is complete
Provided by the customer
Some folks use a more formal notation
How do I get my tests? Just add some examples!
User Story 1 - Acceptance Tests
- Client can transfer money between two accounts
- Client can’t transfer negative amount
- Client can’t transfer more than current account balance
- Client can’t transfer from a blocked account
8. Acceptance Test-Driven Development
Acceptance Tests drive work during the iteration
Pick a story card
Write the acceptance tests
Automate the acceptance tests
Implement the user story
Iteration n-1 Iteration n Iteration n+1
10. Tools for the job
What tools exist? Two main approaches
Narrative
easyb, JBehave, rspec, Cucumber,...
Table-based
Fitnesse,...
11. Introducing easyb
So what is easyb, anyway?
A BDD testing framework for Java
Make testing clearer and easier to write
Make tests self-documenting
Help developers focus on the requirements
Based on Groovy
Java-like syntax
Quick to write
Full access to Java classes and APIs
Well-suited to Acceptance Tests
BDD Acceptance Testing
12. Easyb in Action
Easyb supports Specifications and Stories
Specifications express requirements as simple statements
Stories use the “given-when-then” approach
14. Easyb Specifications
Writing Easyb Specifications
User Story 1 - Acceptance Tests
- Client can transfer money between two accounts
- Client can’t transfer negative amount
- Client can’t transfer more than current account balance
- Client can’t transfer from a blocked account
Start off with our acceptance criteria
AccountTransfer.specifications
description "A client should be able to transfer money between accounts"
it "should let a client transfer money from a current to a savings a/c"
it "should not allow a client to transfer a negative amount"
it "should not allow a client to transfer more than the current balance"
it "should not allow a client to transfer from a blocked account"
Express these in Easyb
15. Easyb Specifications
Writing Easyb Specifications
Executable Requirements
description "A client should be able to transfer money between accounts"
it "should let a client transfer money from a current to a savings a/c"
it "should not allow a client to transfer a negative amount"
it "should not allow a client to transfer more than the current balance"
it "should not allow a client to transfer from a blocked account"
This code will run!
The tests are marked as ‘PENDING’
17. Easyb Specifications
Writing Easyb Specifications
Implementing the tests
package com.wakaleo.accounts.domain
description "A client should be able to transfer money between accounts"
it "should let a client transfer money from a current to a savings a/c", {
current = new Account(200)
savings = new Account(300) A developer implements the test in Groovy
current.transferTo(savings, 50)
savings.balance.shouldBe 350
current.balance.shouldBe 150
}
it "should not allow a client to transfer a negative amount"
it "should not allow a client to transfer more than the current balance"
it "should not allow a client to transfer from a blocked account"
No longer pending
Still pending...
18. Easyb Stories
Writing Easyb Stories
Use a narrative approach
Describe a precise requirement
Can be understood by a stakeholder
Usually made up of a set of scenarios
Use an easy-to-understand structure:
Given [a context]...
When [something happens]...
Then [something else happens]...
19. Easyb Stories
Building an easyb story
A story is made up of scenarios
Scenarios validate specific behaviour
User Story 1 - Acceptance Tests
- Client can transfer money between two accounts
- Client can’t transfer negative amount
- Client can’t transfer more than current account balance
- Client can’t transfer from a blocked account
AccountTransfer.story
scenario "A client can transfer money from a current to a savings a/c"
scenario "A client is not allowed to transfer a negative amount"
scenario "A client is not allowed to transfer more than the current balance"
scenario "A client is not allowed to transfer from a blocked account"
20. Easyb Stories
Anatomy of an easyb story
scenario "A client can transfer money from a current to a savings a/c", {
given 'a current a/c with $200 and a savings a/c with $300'
when 'you transfer $50 from the current a/c to the savings a/c'
then 'the savings a/c should have $350 and the current a/c $150'
}
“Scenario”: corresponds to precise requirement
“Given”: the context in which this requirement applies
“When”: An event or action
“Then”: The expected results of this action
21. Easyb Stories
Implementing the scenario
package com.wakaleo.accounts.domain
scenario "A client can transfer money from a current to a savings a/c", {
given 'a current a/c with $200 and a savings a/c with $300', {
current = new Account(200)
savings = new Account(300)
}
when 'you transfer $50 from the current a/c to the savings a/c', {
current.transferTo(savings, 50)
}
then 'the savings a/c should have $350 and the current a/c $150', {
savings.balance.shouldBe 350
current.balance.shouldBe 150
}
}
scenario "A client is not allowed to transfer a negative amount"
scenario "A client is not allowed to transfer more than the current balance"
scenario "A client is not allowed to transfer from a blocked account"
22. Easyb Stories
Implementing the scenario - an alternative solution
package com.wakaleo.accounts.domain
scenario "A client can transfer money from a current to a savings a/c", {
given 'a current a/c with $200', {
current = new Account(200) Using ‘and’ for more clarity
}
and 'a savings a/c with $300', {
savings = new Account(300)
}
when 'you transfer $50 from the current a/c to the savings a/c', {
current.transferTo(savings, 50)
}
then 'the savings a/c should have $350', {
savings.balance.shouldBe 350
}
and 'the current a/c should have $150', {
current.balance.shouldBe 150
}
}
scenario "A client is not allowed to transfer a negative amount"
scenario "A client is not allowed to transfer more than the current balance"
scenario "A client is not allowed to transfer from a blocked account"
23. Easyb assertions
Ensuring what should be
The shouldBe syntax:
Intuitive, readable and flexible
account.balance.shouldBe initialAmount
Comes in many flavors
account.balance.shouldBeEqualTo initialAmount
account.balance.shouldNotBe 0
account.balance.shouldBeGreaterThan 0
account.shouldHave(balance:initialAmount)
24. Easyb Stories
Implementing another scenario - error conditions
package com.wakaleo.accounts.domain
scenario "A client can transfer money from a current to a savings a/c", {
...
}
scenario "A client is not allowed to transfer a negative amount", {
given 'a current a/c with $200', {
current = new Account(200)
}
and 'a savings a/c with $300', {
savings = new Account(300)
}
when "you try to transfer a negative amount", { Create a closure
transferNegativeAmount = { representing this operation
current.transferTo(savings, -50)
}
}
then "an IllegalTransferException should be thrown", {
ensureThrows(IllegalTransferException.class) {
transferNegativeAmount() Fail if the exception is not
} thrown
}
}
scenario "A client is not allowed to transfer more than the current balance"
scenario "A client is not allowed to transfer from a blocked account"
25. Easyb fixtures
Structuring your tests...fixtures in easyb
Setting up the test environment...
Similar to JUnit fixtures @Before and @BeforeClass
before is run at the start of the whole story
before_each is run before each scenario
Useful for setting up databases, test servers, etc.
26. Easyb fixtures
Refactoring our scenarios - before and before_each
before_each "setup the test accounts", {
given 'a current a/c with $200', {
current = new Account(200) This will be done before
}
and 'a savings a/c with $300', {
each scenario
savings = new Account(300)
}
}
scenario "A client can transfer money from a current to a savings a/c", {
when 'you transfer $50 from the current a/c to the savings a/c', {
current.transferTo(savings, 50)
}
then 'the savings a/c should have $350 and the current a/c $150', {
savings.balance.shouldBe 350
}
and 'the current a/c should have $150', {
current.balance.shouldBe 150
}
}
scenario "A client is not allowed to transfer a negative amount", {
when "you try to transfer a negative amount", {
transferNegativeAmount = {
current.transferTo(savings, -50)
}
}
then "an IllegalTransferException should be thrown", {
ensureThrows(IllegalTransferException.class) {
transferNegativeAmount()
}
}
}
27. Easyb fixtures
Shared behaviour
Refactor common code in easyb scenarios
shared_behavior "shared behaviors", {
given "a string", {
var = "" Common behavior (‘shared_behavior’)
}
when "the string is hello world", {
var = "hello world"
}
}
scenario "first scenario", {
it_behaves_as "shared behaviors"
then "the string should start with hello", { Reused here (‘it_behaves_as’)...
var.shouldStartWith "hello"
}
}
scenario "second scenario", {
it_behaves_as "shared behaviors"
then "the string should end with world", { ...and here
var.shouldEndWith "world"
}
}
28. Web testing with easyb
Easyb is convenient for web testing
BDD/Functional tests
Run against a test server, or use Jetty
Use your choice of web testing frameworks
Selenium
JWebUnit
...
29. Web testing with easyb
Many options - let’s look at two
Selenium
Runs in a browser
High-level API
Runs slower and more work to set up
JWebUnit
Simulates a browser
Runs faster, easy to set up
API slightly lower level
30. Web testing with easyb
An example - writing functional tests with JWebUnit
import net.sourceforge.jwebunit.junit.WebTester
before_each "initialize a web test client", { Set up a JWebUnit client
given "we have a web test client", {
tester = new WebTester()
tester.setBaseUrl("http://localhost:8080/tweeter-web")
}
}
scenario "User signup should add a new user", {
when "I click on the sign up button on the home page", {
tester.beginAt("/home") Click on a link
tester.clickLinkWithText("Sign up now!")
}
and "I enter a new username and password", {
tester.setTextField("username", "jane") Enter some values
tester.setTextField("password", "tiger")
tester.submit()
}
then "the application should log me on as the new user and show a welcome message", {
tester.assertTextPresent("Hi jane!")
} Check the results
}
31. Easyb reports
The easyb HTML report
Test results summary
Unimplemented stories
Failed stories
32. Easyb reports
The easyb HTML report
Test results summary
Test failure details
Unimplemented stories
33. Other Approaches
How does easyb compare with other tools?
Cucumber, RSpec (Ruby) - very similar to easyb
FitNesse - wikis and tables
Concordion - marked-up HTML
34. FitNesse
FitNesse - wiki-based acceptance tests
Test data is written at tables on a Wiki
Java classes implement the tests behind the scenes
36. FitNesse
FitNesse - wiki-based acceptance tests
Testers can write/edit the Wiki pages
You can also import to and from Excel
37. FitNesse
FitNesse - wiki-based acceptance tests
public class TransferMoneyBetweenAccounts {
Tests are implemented by private BigDecimal savingsBalance;
Java classes private BigDecimal currentBalance;
private BigDecimal transfer;
private BigDecimal finalSavingsBalance;
Each column has a private BigDecimal finalCurrentBalance;
private boolean exceptionThrown;
field in the class
public void setSavingsBalance(BigDecimal savingsBalance) {...}
public void setCurrentBalance(BigDecimal currentBalance) {...}
public void setTransfer(BigDecimal transfer) {...}
public BigDecimal finalCurrentBalance() {...}
Expected results have public BigDecimal finalSavingsBalance() {...}
getters public boolean exceptionThrown() {...}
public void execute() {
Account currentAccount = new Account(currentBalance);
Account savingsAccount = new Account(savingsBalance);
exceptionThrown = false;
try {
currentAccount.transferTo(savingsAccount, transfer);
Performing the test finalCurrentBalance = currentAccount.getBalance();
finalSavingsBalance = savingsAccount.getBalance();
} catch (IllegalTransferException e) {
exceptionThrown = true;
}
}
38. Commercial options?
There are also some commercial tools out there...
GreenPepper
Supports tables and BDD-style
Nice tool integration (Confluence, Maven,...)
Twixt
Thoughtworks product, focus on web testing
Others?
39. Thank You
John Ferguson Smart
Wakaleo Consulting Ltd.
http://www.wakaleo.com
Email: john.smart@wakaleo.com
Twitter: wakaleo