10. Goals
Capabilities
Features
Stories
Examples/Scenarios
Acceptance Criteria
11. 11
Successful projects start with a shared vision
“We are going to build an online classifieds website”
12. 12
You define goals to achieve your vision
“We can increase
advertising revenue by
letting sellers post their
classified ads online”
“Let’s get more sales for our advertisers by
making the ads easier to find online.”
13. Determining the value of a goal
A good goal should add value to the business
Increase revenue
Reduce costs
Avoid future costs
Protect revenue
“Increase advertising revenue by allowing
sellers to post classified ads online”
“Reduce the costs involved in publishing a classified ad
by allowing sellers to post them online themselves. ”
“Prevent current customers switching to a competing product
by providing support for online credit card payments”
14. What does the customer really need?
I want users to be able to search for products by keyword
Why?
So that potential buyers can find the articles they want
Why?
So that our sellers can sell their stuff faster
Why?
So that they keep selling their stuff on our site
Why?
So that we keep earning money when they post their ads with us
15. What does the customer really need?
Good teams push back!
Users tend to express requirements as implementations
We need to find the business need behind the suggested
implementation
I want users to be able to search by keyword
So in order to make the site more attractive for sellers
Buyers need to be able to find things easily
A search feature might be one way to achieve this
But full-text searches might be more effective than keywords
16. Features and capabilities help deliver these goals
“Let’s get more sales for our advertisers by
making the ads easier to find online.”
Notify potential buyers about new items
In Search for online of advertised articles
order to increase sales ads
As aorder to increase sales of advertised articles
In seller
I want previous buyers to know about new items
As a seller
that theybuyers be interested in buying ads for
I want might to be able to easily find
articles they want to buy
17. Feature Injection - what features do you do first?
Our goals say what business value we need to deliver
We implement the minimum features required to
deliver this business value
Search for online ads The goal comes first
In order to increase sales of advertised articles
The stakeholder is
As a seller secondary
I want buyers to be able to easily find ads for
articles they want to buy
The feature must be
required to achieve the goal
18. 18
We use examples and stories to explore the features
Search for online ads
“Searching by category”
“Searching by keyword and category”
19. 19
We use examples and stories to explore the features
Search for online ads
Searching by keyword and location
Given
Sally
wants
to
buy
a
puppy
for
her
son
When
she
looks
for
‘puppy’
in
the
‘Pets
and
Animals’
category
Then
she
should
obtain
a
list
of
ads
for
puppies
for
sale.
20. 20
Examples and scenarios become acceptance criteria
Searching by keyword and location
Given
Sally
wants
to
buy
a
puppy
for
her
son
When
she
looks
for
‘puppy’
in
the
‘Pets
and
Animals’
category
Then
she
should
obtain
a
list
of
ads
for
puppies
for
sale.
Scenario: Searching by keyword and location
Given Sally wants to buy a present for her son
When she looks for the present in a given category
Then she should obtain a list of matching ads for sale.
Examples:
Present Category Expected Keywords
puppy Pets & Animals labrador
kitten Pets & Animals burmese
kitten Toys fluffy cat
Acceptance Criteria illustrate and validate the stories
24. Goal:
In order to increase revenue from commissions on classified ads sales
As the head of the classified ads department
I want to increase the number of items sold via our classified ads
Capability
In order to increase the number of items I sell Feature
As a seller In order to increase sales of advertised articles
I want buyers to be able to view ads for items As a seller
they might want to purchase I want potential buyers to be able to display only the ads for
articles that they might be interested in purchasing.
Story
In order to find the items I am interested in faster
Keep them organized! As a buyer
I want to be able to list all the ads with a particular keyword in
the description or title.
27. 27
search_by_keyword_and_location.story
Narrative:
In order to increase sales of advertised articles
As a seller
I want buyers to be able to easily find ads for articles they want
to buy
Scenario: Searching by keyword and location
Given Sally wants to buy a puppy for her son
When she looks for 'puppy' in the 'Pets and Animals' category
Then she should obtain a list of ads for puppies for sale.
28. 28
search_by_keyword_and_location.story
Scenario: Searching by keyword and location
Given Sally wants to buy a puppy for her son
When she looks for 'puppy' in the 'Pets and Animals' category
Then she should obtain a list of ads for puppies for sale.
Scenario: Searching by keyword and location
Given Sally wants to buy a <present> for her son
When she looks for '<present>' in the '<category>' category
Then she should obtain a list of ads for <expected> for sale.
Examples:
|present |category |expected|
|puppy |Pets & Animals | puppies|
|kitten |Pets & Animals | kittens|
|seiko |Jewellery & Watches| watch |
29. 29
search_by_keyword_and_location.story
Scenario: Searching by keyword and location 1
Given Sally wants to buy a puppy for her son
When she looks for 'puppy' in the 'Pets and Animals' category
Then she should obtain a list of ads for puppies for sale.
30. 30
search_by_keyword_and_location.story
Scenario: Searching by keyword and location 1
Given Sally wants to buy a puppy for her son
When she looks for 'puppy' in the 'Pets and Animals' category
Then she should obtain a list of puppy ads
public class SearchAdsSteps {
@Steps
2
BuyerSteps buyer;
@Given("Sally wants to buy a $present for her son")
public void buyingAPresent(String present) {
buyer.opens_home_page();
}
@When("she looks for $keyword in the $category category")
public void adSearchByCategoryAndKeyword(String category, String keyword) {
buyer.chooses_category_and_keywords(category, keyword);
buyer.performs_search();
}
@Then("she should obtain a list of $keyword ads")
public void shouldOnlySeeAdsContainingKeyword(String keyword) {
buyer.should_only_see_results_with_titles_containing(keyword);
}
}
31. 31
search_by_keyword_and_location.story
Scenario: Searching by keyword and location 1
Given Sally wants to buy a puppy for her son
When she looks for 'puppy' in the 'Pets and Animals' category
Then she should obtain a list of puppy ads
public class SearchAdsSteps {
@Steps
2
BuyerSteps buyer;
public class BuyerStories extends JUnitStories {
@Given("Sally wants to buy a $present for her son")
public BuyerStories() {
public void buyingAPresent(String present) {
3
configuredEmbedder().embedderControls().doGenerateViewAfterStories(true).doIgnoreFailureInStories(false)
buyer.opens_home_page();
.doIgnoreFailureInView(true).doVerboseFailures(true).useThreads(2).useStoryTimeoutInSecs(60);
}
}
@Override
@When("she looks for $keyword { the $category category")
public Configuration configuration() in
public void adSearchByCategoryAndKeyword(String category, String keyword) {
return new MostUsefulConfiguration();
}
buyer.chooses_category_and_keywords(category, keyword);
buyer.performs_search();
@Override
}
public InjectableStepsFactory stepsFactory() {
return new InstanceStepsFactory(configuration(), new TraderSteps(new TradingService()), new AndSteps());
}
@Then("she should obtain a list of $keyword ads")
public void shouldOnlySeeAdsContainingKeyword(String keyword) {
@Override
buyer.should_only_see_results_with_titles_containing(keyword);
protected List<String> storyPaths() {
String codeLocation = codeLocationFromClass(this.getClass()).getFile();
}
return new StoryFinder().findPaths(codeLocation, asList("**/*.story",
} "**/traders_can_be_subset.story"), asList(""), "file:" + codeLocation);
}
}
32. 32
search_by_keyword_and_location.story
Scenario: Searching by keyword and location 1
Given Sally wants to buy a puppy for her son
When she looks for 'puppy' in the 'Pets and Animals' category
Then she should obtain a list of puppy ads
public class SearchAdsSteps {
@Steps
2
BuyerSteps buyer;
@Given("Sally wants to buy a $present for her son")
public void buyingAPresent(String present) {
buyer.opens_home_page();
}
@When("she looks for $keyword in the $category category")
public void adSearchByCategoryAndKeyword(String category, String keyword) {
buyer.chooses_category_and_keywords(category, keyword);
buyer.performs_search();
}
@Then("she should obtain a list of $keyword ads")
public void shouldOnlySeeAdsContainingKeyword(String keyword) {
buyer.should_only_see_results_with_titles_containing(keyword);
}
3’
}
public class BuyerStories extends ThucydidesJUnitStories {
}
34. 34
Feature: 1
In order to increase sales of advertised articles
As a seller
I want buyers to be able to easily find ads for articles they want
to buy
Scenario: Searching by keyword and location
Given Sally wants to buy a "puppy" for her son
When she looks for "puppy" in the "Pets and Animals" category
Then she should obtain a list of "puppy" ads
35. 35
Scenario: Searching by keyword and location
Given Sally wants to buy a "puppy" for her son
When she looks for "puppy" in the "Pets and Animals" category
Then she should obtain a list of "puppy" ads
Scenario: Searching by keyword and location
Given Sally wants to buy a <present> for her son
When she looks for '<present>' in the '<category>' category
Then she should obtain a list of ads for <expected> for sale.
Examples:
|present |category |expected|
|puppy |Pets & Animals | puppies|
|kitten |Pets & Animals | kittens|
|seiko |Jewellery & Watches| watch |
36. 36
Scenario: Searching by keyword and location 1
Given Sally wants to buy a "puppy" for her son
When she looks for "puppy" in the "Pets and Animals" category
Then she should obtain a list of "puppy" ads
import org.junit.runner.RunWith; 2
import cucumber.junit.Cucumber;
@RunWith(Cucumber.class)
@Cucumber.Options(format={"pretty", "html:target/cucumber"})
public class RunTests {
}
37. 37
Scenario: Searching by keyword and location 1
Given Sally wants to buy a "puppy" for her son
When she looks for "puppy" in the "Pets and Animals" category
Then she should obtain a list of "puppy" ads
import org.junit.runner.RunWith; 2
import cucumber.junit.Cucumber;
public class SearchAdsSteps {
@Steps
@RunWith(Cucumber.class)
3
BuyerSteps buyer;
@Cucumber.Options(format={"pretty", "html:target/cucumber"})
public class RunTests { buy a "([^"]*)" for her son$")
@Given("^Sally wants to
} public void buyingAPresent(String present) {
buyer.opens_home_page();
}
@When("^she looks for "([^"]*)" in the "([^"]*)" category$")
public void adSearchByCategoryAndKeyword(String category, String keyword) {
buyer.chooses_category_and_keywords(category, keyword);
buyer.performs_search();
}
@Then("^she should obtain a list of "([^"]*)" ads$")
public void shouldOnlySeeAdsContainingKeyword(String keyword) {
buyer.should_only_see_results_with_titles_containing(keyword);
}
}
39. 39
search_by_keyword_and_location.story
scenario "Searching by keyword and location", {
given "Sally wants to buy a puppy for her son"
when "she looks for 'puppy' in the 'Pets and Animals' category"
then "she should obtain a list of ads for puppies for sale"
}
scenario "Searching by keyword and location", {
given "Sally wants to buy a #present for her son"
when "she looks for '#present' in the '#category' category"
then "she should obtain a list of ads for #expected for sale"
where "examples should be", {
present = ['puppy', 'kitten', 'seiko']
category = ['Pets & Animals','Pets & Animals', 'Jewellery & Watches']
expected = ['puppies', 'kittens', 'watch']
}
}
40. 40
search_by_keyword_and_location.story
scenario "Searching by keyword and location", { 1
given "Sally wants to buy a puppy for her son"
when "she looks for 'puppy' in the 'Pets and Animals' category"
then "she should obtain a list of ads for puppies for sale"
}
41. 41
search_by_keyword_and_location.story
scenario "Searching by keyword and location", { 1
given "Sally wants to buy a puppy for her son"
when "she looks for 'puppy' in the 'Pets and Animals' category"
then "she should obtain a list of ads for puppies for sale"
}
using "thucydides" 2
thucydides.uses_steps_from BuyerSteps
scenario "Searching by keyword and location", {
given "Sally wants to buy a puppy for her son", {
buyer.opens_home_page()
}
when "she looks for 'puppy' in the 'Pets and Animals' category", {
buyer.chooses_category_and_keywords(category, keyword);
buyer.performs_search();
}
then "she should obtain a list of ads for puppies for sale",{
buyer.should_only_see_results_with_titles_containing keyword
}
}
44. 44
1 Discover your acceptance criteria
2 Automate your acceptance criteria
3 Implement your acceptance criteria
4 Execute your acceptance tests
45. 45
1 Discover your acceptance criteria
Feature: Browse Catalog
In order to find items that I would like to buy
As a customer
I want to be able to browse through the catalog
Story: Browse by category
In order to find items more easily
As a customer
I want to be able to browse through the product categories
Acceptance Criteria
See all the top-level categories
Browse through the category hierarchy
Should display the correct products for each category
Each category should have the correct sub-categories
Define acceptance criteria for each story
46. 46
1 Discover your acceptance criteria
Acceptance Criteria
See all the top-level categories
Browse through the category hierarchy
Should display the correct products for each category
Each category should have the correct sub-categories
Scenario: See all top-level categories
Given I want to browse the catalog
When I am on the home page
Then I should see the following product categories: Clothing, Accessories, Shoes
Clarify the acceptance criteria with examples
48. 48
2 Automate your acceptance criteria
Story: Browse by category
In order to find items more easily
Acceptance Criteria
As a customer top-level categories
See all the
I want Browse through the category the product categories
to be able to browse through hierarchy
Scenario: See all top-level categories
Should display the correct products for each category
Given I want to browse the catalog
Each category should have the correct sub-categories
When I am on the home page
Then I should see the following product categories: Clothing, Accessories, Shoes
Narrative:
In order to find items more easily
As a customer
I want to be able to see what product categories exist
Scenario: See all top-level categories
Given I want to browse the catalog
When I am on the home page
Then I should see the following product categories: Clothing, Accessories, Shoes
We now have an executable requirement
49. 49
2 Automate your acceptance criteria
...but they will be reported as ‘pending’
50. 50
3 Implement your acceptance criteria
Narrative:
In order to find items more easily
As a customer
I want to be able to see what product categories exist
Scenario: See all top-level categories
Given I want to browse the catalog
When I am on the home page
Then I should see the following product categories: Clothing, Accessories, Shoes
63. Make it pass
Write a failing test TDD
Refactor
What test should I write?
64. Acceptance Tests
(high level features)
Spock
Developer Tests
(low level features)
What features should I implement?
etc.
65. Goal: In order to increase revenue from commissions on classified ads sales
As the head of the classified ads department
I want to increase the number of items sold via our classified ads
Story: In order to find the items I am interested in faster
Acceptance
As a buyer
I want to be able to list all the ads with a particular keyword in the description or title. Tests
Scenario: Searching by keyword and location
Scenario: Searching by keyword and location
Scenario: Searching by keyword
Given Sally wants to buy a apuppyfor her son
Given Sally wants to buy apuppy for her son
Given Sally wants to buy puppy for her son
When she looks for ads ininthePets & Animals category containing puppy
When she looks for ads inthe Pets & Animals category containing puppy
When she looks for ads the Pets & Animals category containing puppy
inThen she should obtain a list of ads for puppies for sale
inNew South Wales
New South Wales
class WhenCalculatingGST extends Specification {
class WhenCalculatingGST extends Specification {
class WhenCalculatingGST extends Specification {
def "GST should apply on ordinary articles"() {
given: should apply on ordinary articles"() { {
def "GST "we are selling a shirt"
given: should apply on ordinary articles"()
def "GST "we are selling a shirt"
Developer
def sale = Sale.of(1,"shirt").forANetPriceOf(10.00)
def sale areSale.of(1,"shirt").forANetPriceOf(10.00)
given: "we = selling a shirt"
when: "we calculate the price including GST"
def sale = Sale.of(1,"shirt").forANetPriceOf(10.00)
def "we calculate sale.totalPrice
when: totalPrice = the price including GST"
def "we calculate the price GST of
when: totalPrice including GST"
then: "the totalPrice= =sale.totalPrice 10%"
def price should sale.totalPrice
include
totalPrice == should include GST of 10%"
then: "the price 11.00 include GST of 10%"
then: "the price should
totalPrice == 11.00
Tests
}
totalPrice == 11.00
} }
} }
}
68. Spock
class WhenCalculatingGST extends Specification {
def "GST should apply on ordinary articles"() {
given: "we are selling a shirt"
def sale = Sale.of(1,"shirt").forANetPriceOf(10.00)
when: "we calculate the price including GST"
def totalPrice = sale.totalPrice
then: "the price should include GST of 10%"
totalPrice == 11.00
}
}
Given-When-Then structure
69. Spock
class WhenCalculatingGST extends Specification {
...
def "GST should not apply on GST-exempt articles"() {
given: "we are selling a bottle of milk"
def sale = Sale.of(1,"shirt").forANetPriceOf(5.00)
when: "we calculate the price including GST"
def totalPrice = sale.totalPrice
then: "the price should not include GST%"
totalPrice == 5.00
}
}
Meaningful error messages
70. Spock
Lightweight stubbing
class WhenCalculatingGST extends Specification {
def "GST should apply on ordinary articles"() {
given: "GST is at 12.5%"
def gstRateProvider = Mock(GSTRateProvider)
gstRateProvider.getRate() >> 0.125
Sales sales = new Sales(gstRateProvider)
and: "we are selling a shirt"
def sale = sales.makeSaleOf(1,"shirt").forANetPriceOf(10.00)
when: "we calculate the price including GST"
def totalPrice = sale.totalPrice
then: "the price should include GST of 12.5%"
totalPrice == 11.25
}
}
71. Spock
class WhenDeliveringSoldItems extends Specification {
def gstRateProvider = Mock(GSTRateProvider)
def deliveryService = Mock(DeliveryService)
def "Sold articles should be delivered"() {
given: "we are selling shirts online"
Sales sales = new Sales(gstRateProvider, deliveryService)
when: "we sell a shirt"
sales.makeSaleOf(1,"shirt").forANetPriceOf(10.00)
then: "the shirt should be sent to the delivery service"
1 * deliveryService.dispatch(_)
}
}
Lightweight mocking
72. Spock
class WhenDisplayingTagNamesInAReadableForm extends Specification {
def inflection = Inflector.instance
def "should transform singular nouns into plurals"() {
when: "I find the plural form of a single word"
def pluralForm = inflection.of(singleForm).inPluralForm().toString();
then: "the plural form should be gramatically correct"
pluralForm == expectedPluralForm
where:
singleForm | expectedPluralForm
'epic' | 'epics'
'feature' | 'features'
'story' | 'stories'
'stories' | 'stories'
'octopus' | 'octopi'
'sheep' | 'sheep'
}
}
Data-driven tests
73. Spock with Arquillian
class
AccountServiceSpecification
extends
Specification
{
@Deployment
def
static
JavaArchive
"create
deployment"()
{
return
ShrinkWrap.create(JavaArchive.class)
.addClasses(AccountService.class,
Account.class,
SecureAccountService.class)
.addAsManifestResource(EmptyAsset.INSTANCE,
"beans.xml");
}
@Inject
AccountService
service
def
"transferring
between
accounts
should
result
in
account
withdrawl
and
deposit"()
{
when:
service.transfer(from,
to,
amount)
then: BDD-style integration tests
from.balance
==
fromBalance
to.balance
==
toBalance
where:
from
<<
[new
Account(100),
new
Account(10)]
to
<<
[new
Account(50),
new
Account(90)]
amount
<<
[50,
10]
fromBalance
<<
[50,
0]
toBalance
<<
[100,
100]
}
}
75. class
WhenCalculatingGST
extends
Specification
{
sequential
"GST
should
apply
on
ordinary
articles"
>>
{
"Given
we
are
selling
a
shirt"
>>
{
sale
=
Sale.of(1,
"shirt").forANetPriceOf(10.00)
}
"When
we
calculate
the
price
including
GST"
>>
{
totalPrice
=
sale.totalPrice
}
"Then
the
price
should
include
a
GST
of
10%"
>>
{
totalPrice
===
11.00
}
}
var
sale
=
Sale();
var
totalPrice
=
0.0
}
76. class
WhenCalculatingGST2
extends
Specification
with
Mockito
{
sequential
"GST
should
apply
on
ordinary
articles"
>>
{ Lightweight
"Given
we
are
selling
a
shirt"
>>
{ stubbing DSL
val
sales
=
Sales(mock[GSTProvider])
sales.gstProvider.rate
returns
12.5
sale
=
sales.makeSaleOf(1,
"shirt").forANetPriceOf(10.00)
}
"When
we
calculate
the
price
including
GST"
>>
{
totalPrice
=
sale.totalPrice
}
"Then
the
price
should
include
a
GST
of
12.5%"
>>
{
totalPrice
===
11.25
}
}
var
sale
=
Sale();
var
totalPrice
=
0.0
}
77. class
WhenDeliveringSoldItems
extends
Specification
with
Mockito
{
sequential
"Sold
articles
should
be
delivered"
>>
{
"Given
we
are
selling
shirts
online"
>>
{
sales
=
Sales(mock[GSTProvider],
mock[DeliveryService])
}
"When
we
sell
a
shirt"
>>
{
sale
=
sales.makeSaleOf(1,
"shirt").forANetPriceOf(10.00)
}
"Then
the
shirt
should
be
sent
to
the
delivery
service"
>>
{
there
was
one(sales.deliveryService).dispatch(anyString)
}
}
Lightweight
var
sale
=
Sale();
var
sales
=
Sales() mocking DSL
}
78. class
WhenDisplayingTagNamesInAReadableForm
extends
Specification
with
Tables
{
"The
inflector
should
transform
singular
nouns
into
plurals"
>>
{
"""
when
I
find
the
plural
form
of
a
single
word,
then
the
plural
form
should
be
gramatically
correct:
"""
>>
{
"single
form"
|
"plural
form"
|>
"epic"
!
"epics"
|
"feature"
!
"features"
| Data-driven tests,
"story"
!
"story"
| Scala-style
"stories"
!
"stories"
|
"octopus"
!
"octopi"
|
"sheep"
!
"sheep"
|
{
(singleForm,
pluralForm)
=>
Inflection.of(singleForm).inPluralForm.toString
===
pluralForm
}
}
}
}
80. describe( "temperature converter", function () {
it("converts fahrenheit to celsius", function () {
expect(Convert(50, "F").to("C")).toEqual(10);
});
});
Simple assertion structure
81. describe( "temperature converter", function () {
it("converts fahrenheit to celsius", function () {
expect(Convert(50, "F").to("C")).toEqual(10);
});
it("converts celsius to fahrenheit", function () {
expect(Convert(30, "C").to("F")).toEqual(86);
});
});
More complex behavior
82. describe( "converter library", function () {
describe( "temperature converter", function () {
it("converts fahrenheit to celsius", function () {
expect(Convert(50, "F").to("C")).toEqual(10);
});
it("converts celsius to fahrenheit", function () {
expect(Convert(30, "C").to("F")).toEqual(86);
});
});
describe( "weight converter", function () {
it("converts kilograms to pounds", function () {
expect(Convert(100, "KG").to("LB")).toEqual(220);
});
});
});
Nested behaviors
83. Lots of matchers
it("is
defined",
function
()
{
var
name
=
"Andrew";
property defined
expect(name).toBeDefined();
})
it("is
true",
function
()
{
true or false
expect(Lib.isAWeekDay()).toBeTruthy();
});
it("is
less
than
10",
function
()
{
expect(5).toBeLessThan(10);
greater than or less than
});
it("is
greater
than
10",
function
()
{
expect(20).toBeGreaterThan(10);
});
it("should
contain
oranges",
function
()
{
expect(["apples",
"oranges",
"pears"]).toContain("oranges");
});
contains