Bdd state-of-the-union

Preview:

Citation preview

John  Ferguson  Smart

Behavior-Driven Development on the JVMA State of the Union

John Ferguson Smart

ConsultantTrainerMentorAuthorSpeakerCoder

What is BDD?

Common Language

Business Developer

Business Analyst

Tester

Executable Specifications

OutsideIn

Value Driven

\So why use BDD?

Only build features that add real value

Less wasted effort

Better communication

Higher quality, better tested product

Traceability

BDD - Requirements Analysis and Communication

Examples/Scenarios

Stories

Features

Capabilities

Goals

Acceptance Criteria

11

“We are going to build an online classifieds website”

Successful projects start with a shared vision

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.”

A good goal should add value to the businessIncrease revenueReduce costsAvoid future costsProtect revenue

Determining the value of a goal

“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”

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

So that our sellers can sell their stuff fasterWhy?

Why?

So that they keep selling their stuff on our siteWhy?

So that we keep earning money when they post their ads with us

What does the customer really need?

Good teams push back!Users tend to express requirements as implementationsWe 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 sellersBuyers 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

“Let’s get more sales for our advertisers by making the ads easier to find online.”

Notify potential buyers about new itemsIn order to increase sales of advertised articlesAs a sellerI want previous buyers to know about new items that they might be interested in buying

Search for online adsIn order to increase sales of advertised articlesAs a sellerI want buyers to be able to easily find ads for articles they want to buy

Features and capabilities help deliver these goals

Feature Injection - what features do you do first?

Our goals say what business value we need to deliverWe implement the minimum features required to deliver this business value

Search for online adsIn order to increase sales of advertised articlesAs a sellerI want buyers to be able to easily find ads for articles they want to buy

The goal comes first

The stakeholder is secondary

The feature must be required to achieve the goal

18

We use examples and stories to explore the features

“Searching by category”

Search for online ads

“Searching by keyword and category”

19

We use examples and stories to explore the features

Search for online ads

Searching by keyword and locationGiven  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

Examples and scenarios become acceptance criteria

Acceptance Criteria illustrate and validate the stories

Searching by keyword and locationGiven  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 Keywordspuppy Pets & Animals labradorkitten Pets & Animals burmesekitten Toys fluffy cat

Organize your requirements

Requirements can come from many sources...

CapabilityIn order to increase the number of items I sellAs a sellerI want buyers to be able to view ads for items they might want to purchase

FeatureIn order to increase sales of advertised articlesAs a sellerI want potential buyers to be able to display only the ads for articles that they might be interested in purchasing.

StoryIn order to find the items I am interested in fasterAs a buyerI want to be able to list all the ads with a particular keyword in the description or title.

Goal: In order to increase revenue from commissions on classified ads salesAs the head of the classified ads departmentI want to increase the number of items sold via our classified ads

Keep them organized!

Requirements can come from many sources...

25

BDD - Test Automation and Beyond

26

The original Java BDD framework

27

Narrative:In order to increase sales of advertised articlesAs a sellerI 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.

search_by_keyword_and_location.story

28

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.

search_by_keyword_and_location.story

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

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.

search_by_keyword_and_location.story

1

30

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

search_by_keyword_and_location.story

1

public class SearchAdsSteps {    @Steps    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);    }}

2

31

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

search_by_keyword_and_location.story

1

public class SearchAdsSteps {    @Steps    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);    }}

2public class BuyerStories extends JUnitStories {    public BuyerStories() {        configuredEmbedder().embedderControls().doGenerateViewAfterStories(true).doIgnoreFailureInStories(false)                .doIgnoreFailureInView(true).doVerboseFailures(true).useThreads(2).useStoryTimeoutInSecs(60);    }

    @Override    public Configuration configuration() {        return new MostUsefulConfiguration();    }

    @Override    public InjectableStepsFactory stepsFactory() {        return new InstanceStepsFactory(configuration(), new TraderSteps(new TradingService()), new AndSteps());    }

    @Override    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);    }}

3

32

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

search_by_keyword_and_location.story

1

public class SearchAdsSteps {    @Steps    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);    }}

public class BuyerStories extends ThucydidesJUnitStories {}

3’

2

33

Now available in JVM flavor!

34

Feature:In order to increase sales of advertised articlesAs a sellerI 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

1

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

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

import org.junit.runner.RunWith;import cucumber.junit.Cucumber;

@RunWith(Cucumber.class)@Cucumber.Options(format={"pretty", "html:target/cucumber"})public class RunTests {}

1

2

37

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

import org.junit.runner.RunWith;import cucumber.junit.Cucumber;

@RunWith(Cucumber.class)@Cucumber.Options(format={"pretty", "html:target/cucumber"})public class RunTests {}

1

2public class SearchAdsSteps {    @Steps    BuyerSteps buyer;

    @Given("^Sally wants to buy a \"([^\"]*)\" for her son$")    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);    }}

3

38

100% Groovy

39

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"}

search_by_keyword_and_location.story

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

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"}

search_by_keyword_and_location.story

1

41

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"}

search_by_keyword_and_location.story

1

using "thucydides"

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 }}

2

42

Keeping an eye on things

43

(Think “Two-CDs”)

44

Scenario: Searching by keywordGiven Sally wants to buy a puppy for her sonWhen she looks for ads in the Pets & Animals category containing puppyThen she should obtain a list of ads for puppies for sale

45

Scenario: Searching by keywordGiven Sally wants to buy a puppy for her sonWhen she looks for ads in the Pets & Animals category containing puppyThen she should obtain a list of ads for puppies for salepublic class SearchAdsSteps {

    @Steps    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);    }}

46

public class SearchAdsSteps {    @Steps    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);    }}

public class BuyerSteps extends ScenarioSteps {

    HomePage homePage;    SearchResultsPage searchResultsPage;

    public BuyerSteps(Pages pages) {        super(pages);        homePage = getPages().get(HomePage.class);        searchResultsPage = getPages().get(SearchResultsPage.class);    }

    @Step    public void opens_home_page() {        homePage.open();    }

    @Step    public void chooses_region(String region) {        homePage.chooseRegion(region);    }

    @Step    public void chooses_category_and_keywords(String category, String keywords) {        homePage.chooseCategoryFromDropdown(category);        homePage.enterKeywords(keywords);    }

47

48

49

50

51

52

From Acceptance Tests to Developer Tests

BDD - A Development Tool

TDD or BDD?

Write a failing test

Make it pass

Refactor

TDD

What test should I write?

Developer Tests (low level features)

Acceptance Tests (high level features)

etc.

Spock

What features should I implement?

Story: In order to find the items I am interested in fasterAs a buyerI want to be able to list all the ads with a particular keyword in the description or title.

Goal: In order to increase revenue from commissions on classified ads salesAs the head of the classified ads departmentI want to increase the number of items sold via our classified ads

Scenario: Searching by keyword and locationGiven Sally wants to buy a puppy for her sonWhen she looks for ads in the Pets & Animals category containing puppy in New South Wales

Scenario: Searching by keyword and locationGiven Sally wants to buy a puppy for her sonWhen she looks for ads in the Pets & Animals category containing puppy in New South Wales

Scenario: Searching by keywordGiven Sally wants to buy a puppy for her sonWhen she looks for ads in the Pets & Animals category containing puppyThen she should obtain a list of ads for puppies for sale

Acceptance  Tests

Developer  Tests

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    }}

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    }}

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    }}

Unit Tests Acceptance tests

Spock - BDD for developers

Spockclass 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

Spockclass 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

Spock

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    }}

Lightweight stubbing

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

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'    }}

Spock

Data-driven tests

Spec2 - BDD for Scala

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}

class  WhenCalculatingGST2  extends  Specification  with  Mockito  {  sequential

   "GST  should  apply  on  ordinary  articles"  >>  {        "Given  we  are  selling  a  shirt"  >>  {            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}

Lightweight stubbing DSL

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)        }    }

   var  sale  =  Sale();  var  sales  =  Sales()}

Lightweight mocking DSL

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"          |            "story"                !  "story"                |            "stories"            !  "stories"            |            "octopus"            !  "octopi"              |            "sheep"                !  "sheep"                |  {  (singleForm,  pluralForm)  =>

               Inflection.of(singleForm).inPluralForm.toString  ===  pluralForm

           }        }    }}

Data-driven tests, Scala-style

Jasmine - BDD for Javascript

describe( "temperature converter", function () {     it("converts fahrenheit to celsius", function () {         expect(Convert(50, "F").to("C")).toEqual(10);     }); });

Simple assertion structure

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

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

And it works with Maven!

Evaluate test results in a browser

Evaluate test results in a browser

Generate JUnit-compatible results

hAp://try-­‐jasmine.heroku.com/

It’s behavior all the way down

In conclusion...

Thank You

John  Ferguson  Smart