SlideShare une entreprise Scribd logo
1  sur  98
Télécharger pour lire hors ligne
To ATDD And Beyond
Better Automated Acceptance Testing on the JVM




John Ferguson Smart
Consulta
                    nt
            Trainer
           Mentor
           Author
          Speaker
          Coder
John Fer
         guson S
                 mar t
The Plan

 1) What is ATDD?
2) How can I do it
3) Let’s see it work
Acceptance Test Driven Development




      The story of your app
Goals          Make money by selling our stuff online



                   Display catalog
Features/Epics
                      Order products
                           Build a wishlist



  User stories

                  Browse Catalog
                  In order to find stuff I would like to buy
                  As a customer
                  I want to be able to browse through the
                  catalog
User stories
                                       Browse Catalog
                                       In order to find stuff I would like to buy
                                       As a customer
                                       I want to be able to browse through the
                                       catalog
Acceptance criteria
       ☑	
  See	
  all	
  the	
  top-­‐level	
  categories	
  in	
  the	
  catalog
       ☑	
  Browse	
  through	
  the	
  sub-­‐categories
       ☑	
  See	
  all	
  the	
  products	
  in	
  a	
  category
User stories
                                         Browse Catalog
                                         In order to find stuff I would like to buy
                                         As a customer
                                         I want to be able to browse through the
                                         catalog
Acceptance criteria
         ☑	
  See	
  all	
  the	
  top-­‐level	
  categories	
  in	
  the	
  catalog
         ☑	
  Browse	
  through	
  the	
  sub-­‐categories
         ☑	
  See	
  all	
  the	
  products	
  in	
  a	
  category



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


                                                                        Automated acceptance test
Automated acceptance test




Implemented development tests   Implemented acceptance tests
Automated acceptance criteria




                                Define your goals
Automated acceptance criteria




                                keep you on track
Automated acceptance criteria




                                Provide better visibility
Automated acceptance criteria




                          Allow faster release cycles
Automated acceptance criteria




                                Reduce Risk
Automated acceptance criteria



                   31%	
  faster	
  delivery
Delivery	
  Time




                                Using	
  ATDD
                   Tradi4onal




                                                Provide more value
Automated acceptance criteria



                 4	
  4mes	
  less	
  defects
Defect	
  Rate




                              Using	
  ATDD
                 Tradi4onal




                                                Higher Quality
Why only do QA
at the end of the project?
Real quality cannot be injected at the end




                            It must be part of the process
Leave the boring stuff to the automated tests...
...and empower your QA team




          Let testers focus on more intelligent testing
20




Keeping an eye on things
21




(Think “Two-CDs”)
22




1   Discover your acceptance criteria



    2   Automate your acceptance criteria



        3   Implement your acceptance criteria



            4   Execute your acceptance tests
23




        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
24




                               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
25




                       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
26




 2   Automate your acceptance criteria




...but they will be reported as ‘pending’
27




           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
28




3   Implement your acceptance criteria
29




3   Implement your acceptance criteria




                                         JUnit
30




4   Execute your acceptance tests
31
32
33
34
35
36
Functional Test Coverage


  “What was tested”
          vs
  “What’s been done”
What have we tested?


                       Broken




                       Works

In Progress
What have we finished?              Specified but no tests



                        Partially done?




                                            Finished
What have we finished?
What have we finished?
What have we finished?
Better organised requirements
Stay on top of your scenarios
Stay on top of your scenarios
Stay on top of your scenarios




Better visibility on what is done
More targeted reporting
Keep your scenarios organized
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
                                                                     As a buyer
                                                                     I want to be able to list all the ads with a particular keyword in
                                                                     the description or title.
Store your requirements where it suits you
Customize Thucydides to work for you



                                                                                         RequirementsTagProvider
                                                                                         getRequirements()
                                                                                         getParentRequirementOf(testOutcome)
 Capability
 In order to increase the number of items I sell
 As a seller
 I want buyers to be able to view ads for items they might want to
      Feature
      In order to increase sales of advertised articles
      As a seller                                                                           MyRequirementProvider
      I want potential buyers to be able to display only the ads for articles

                MYPROJ-123
                Story
                In order to find the items I am interested in faster
                As a buyer
                I want to be able to list all the ads with a particular keyword in the
                description or title.




        Meta:
        @issue MYPROJ-123

        Scenario: Search by keyword
        Given that I want to find products in the "<range>" range
        When I search for products by keyword "<keywords>"
        Then I should see a product with the title <expectedTitle>
Use tags for orthogonal concerns

• Non-functional requirements
• Iterations
• Current iteration vs regression tests
• Acceptance tests vs more detailed tests
• Related features
• System components
• ...
Use tags for orthogonal concerns
@RunWith(ThucydidesRunner)                  Tagging in JUnit
@Issue("NC-1")
@WithTag(name="Browse Ads")
class BrowseAdsByCategory {
    @Managed
    public WebDriver webdriver

    @ManagedPages(defaultUrl = "http://www.newsclassifieds-uat.appspot.com")
    public Pages pages

    @Steps
    BuyerSteps buyer

    @Test
    void "Browse ads by category"() {
        buyer.opens_home_page()
        buyer.chooses_classification "Furniture & Homewares"
        buyer.should_see_search_results_for "Furniture & Homewares"

        buyer.chooses_entry(1).from().other_results()
        buyer.should_see_details_for_the_selected_ad()

        buyer.returns_to_search_results()
        buyer.should_see_search_results_for "Furniture & Homewares"
    }
}
Use tags for orthogonal concerns
Meta:
@tag component:search
                                            Tagging in JBehave
Scenario: Search by keyword
Given that I want to find products in the "<range>" range
When I search for products by keyword "<keywords>"
Then I should see a product with the title <expectedTitle>

Examples:
| range              |   keywords   |   expectedTitle             |
| Simone             |   Simone     |   3by3 Simone               |
| Grey Steel Classic |   Steel      |   3by3 Grey Steel Classic   |
| KV Bags            |   KV         |   KV Classic Satchel        |
Use tags for orthogonal concerns
Meta:
@tag component:search

Scenario: Search by keyword
Given that I want to find products in the "<range>" range
When I search for products by keyword "<keywords>"
Then I should see a product with the title <expectedTitle>

Examples:
| range              |   keywords   |   expectedTitle             |
| Simone             |   Simone     |   3by3 Simone               |
| Grey Steel Classic |   Steel      |   3by3 Grey Steel Classic   |
| KV Bags            |   KV         |   KV Classic Satchel        |
More structured tests
Have you got web tests that look like this?
Have you got web tests that look like this?
Page Objects To The Rescue!
Page Objects To The Rescue!




 Better encapsulation
 Easier to read
 Easier to maintain
 Still a bit unclear what we are doing
Test Steps To The Rescue!




Step methods focus on what we are doing
Test Steps To The Rescue!




                       Step implementations detail the how
Test Steps To The Rescue!

                                            Steps are ordinary methods
    @Step
    public void proceedToPayment() {
        adPreviewPage.proceedToPayment();
    }
                                                 They can have parameters
     @Step
     public void searches_for(String searchTerms) {
          homePage.searchFor(searchTerms);
     }
                                                            They can be pending
          @Pending @Step
           public void searches_for(String searchTerms) {
               // Not done yet
           }


             @Step("The carousel should display {0} at a time")
             def should_see_a_number_of_visible_ads_in_the_carousel(int adCount) {
                 assert homePage.visibleCarouselAds.size() == adCount
             }

                                             You can customize the title
Test Steps To The Rescue!

    @Step
    def browses_product_categories(String... categories) {
        selects_top_level_category(categories.head())
        categories.tail().each { subcategory ->
            browse_through_subcategory(subcategory)
        }
    }                                            Steps can call other steps
    @Step
    def selects_top_level_category(String category) {
        homePage.select_top_level_category category
    }
    
    @Step
    def browse_through_subcategory(subcategory) {
        homePage.select_subcategory(subcategory)
    }
Test Steps To The Rescue!




                 Test reports document the what and the how
Test Steps To The Rescue!




 Focus on what we are doing, not how we do it
 More reusability
 Better reporting
Even better with JBehave!

Narrative:
In order to find items more easily
As a customer
I want to be able to browse through the product categories

Scenario: Browse through product categories
Given I am on the home page
When I browse through the product categories Clothing, Mens, Shirts
Then I should see a product with the title 3by3 Milano Stretch Cotton Shirt

                                       JBehave describes what we are doing




                       Use normal Thucydides steps in the implementation
Even better with JBehave!

Narrative:
In order to find items more easily
As a customer
I want to be able to browse through the product categories

Scenario: Browse through product categories
Given I am on the home page
When I browse through the product categories Clothing, Mens, Shirts
Then I should see a product with the title 3by3 Milano Stretch Cotton Shirt
    class BrowseByCategorySteps extends BigCommerceJBehaveSteps{

       @Steps
       CustomerSteps customer;                  Use normal Thucydides steps
       @Given('I am on the home page')
       public void givenIAmOnTheHomePage() {
           customer.opens_home_page()
       }
       
       @When('I browse through the product categories $categories')
       public void whenIBrowseThroughTheProductCategories(List<String> categories) {
           customer.browses_product_categories(*categories)
       }

       @Then('I should see a product with the title "$expectedTitle"')
       public void thenIShouldSeeAProductWithTheTitle(String expectedTitle) {
           customer.should_see_product withTitle(expectedTitle)
       }
   }
Even better with JBehave!

Narrative:
In order to find items more easily
As a customer
I want to be able to browse through the product categories
                                                                      Context
Scenario: Browse through product categories
Given I am on the home page
When I browse through the product categories Clothing, Mens, Shirts
Then I should see a product with the title 3by3 Milano Stretch Cotton Shirt



 What


    How




                                                                      Illustrations
Even better with JBehave!

Narrative:
In order to find items more easily
As a customer
I want to be able to browse through the product categories

Scenario: Browse through product categories
Given I am on the home page
When I browse through the product categories Clothing, Mens, Shirts
Then I should see a product with the title 3by3 Milano Stretch Cotton Shirt



   Living documentation
   Easier to read
   Easier to maintain
   More work maintaining the .story files
Better Page Objects
Thucydides Page Object support
 Fluent selectors
 Fluent matchers
 HTML tables
 Fluent waits
Thucydides Fluent Selectors
public class HomePage extends PageObject {

    @FindBy(name="adFilter.searchTerm")
    WebElement searchTerm;

    @FindBy(css=".keywords button")
    WebElement search;

    public HomePage2(WebDriver driver) {
        super(driver);
    }
                                                      Fluent selectors
    public void chooseCategory(String category) {
        findBy("#category-select").then(".arrow").then().click();
        findBy("#category-select").then(By.linkText(category)).then().click();
    }

    public void enterKeywords(String keywords) {
        $(searchTerm).type(keywords);
    }

    public void performSearch() {
        $(search).click();
                                                   Thucydides helper methods
    }
}
Thucydides Fluent Selectors
class ShoppingCartPage extends PageObject {

    ShoppingCartPage(WebDriver driver) {
        super(driver)
    }                             Page Object   returns domain classes
    @FindBy(css = ".CartContents tbody tr")
    List<WebElement> shoppingCartItems

    List<CartItem> getCartItems() {
        shoppingCartItems.collect { cartItemfromElement(it) }
    }

    CartItem cartItemfromElement(WebElement element) {
        Integer quantity = Integer.parseInt(itemQuantity(element))
        String product = productName(element)
        BigDecimal itemPrice = priceOf(itemPrice(element))
        BigDecimal totalPrice = priceOf(totalPrice(element))

        return CartItem.containing(quantity).productsCalled(product).
                                             withAnItemPriceOf(itemPrice).
                                             andATotalOf(totalPrice)
    }
    ...
                             Building a CartItem from the WebElement
Thucydides Fluent Selectors
  class ShoppingCartPage extends PageObject {

      ShoppingCartPage(WebDriver driver) {
          super(driver)
      }
                                                           Classic WebDriver
    private String =productName(WebElement tr")
      @FindBy(css    ".CartContents tbody cartItem) {
        cartItem.findElement(By.className("ProductName")).text
      List<WebElement> shoppingCartItems
    }
      List<CartItem> getCartItems() {
    private String totalPrice(WebElement cartItem) {
          shoppingCartItems.collect { cartItemfromElement(it) }
        cartItem.findElement(By.className("CartItemTotalPrice")).text
      }
    }
      CartItem cartItemfromElement(WebElement element) {
    private String quantity = Integer.parseInt(itemQuantity(element))
          Integer itemPrice(WebElement cartItem) {
        cartItem.findElement(By.className("CartItemIndividualPrice")).text
          String product = productName(element)
    }
          BigDecimal itemPrice = priceOf(itemPrice(element))
          BigDecimal totalPrice = priceOf(totalPrice(element))
    private String itemQuantity(WebElement cartItem) {
        def itemQuantityDropdown
          return CartItem.containing(quantity).productsCalled(product).
          = cartItem.findElement(
                                               withAnItemPriceOf(itemPrice).
                     By.xpath(".//td[contains(@class,'CartItemQuantity')]/select"))
                                               andATotalOf(totalPrice)
        element(itemQuantityDropdown).selectedValue
      }
    } ...
Thucydides Fluent Selectors
 class ShoppingCartPage extends PageObject {

     ShoppingCartPage(WebDriver driver) {
         super(driver)
     }                                      Using Thucydides Fluent Selectors
      @FindBy(css = ".CartContents tbody tr")
    private String productName(WebElement cartItem) {
      List<WebElement> shoppingCartItems
        element(cartItem).findBy(".ProductName").text
    }
      List<CartItem> getCartItems() {
          shoppingCartItems.collect { cartItemfromElement(it) }
    private String totalPrice(WebElement cartItem) {
      }
        element(cartItem).findBy(".CartItemTotalPrice").text
    }
      CartItem cartItemfromElement(WebElement element) {
          Integer quantity = Integer.parseInt(itemQuantity(element))
    private String itemPrice(WebElement cartItem) {
          String product = productName(element)
        element(cartItem).findBy(".CartItemIndividualPrice").text
          BigDecimal itemPrice = priceOf(itemPrice(element))
    }
          BigDecimal totalPrice = priceOf(totalPrice(element))
    private String itemQuantity(WebElement cartItem) {
          return CartItem.containing(quantity).productsCalled(product).
        element(cartItem).findBy(".CartItemQuantity").then("select").selectedValue
                                               withAnItemPriceOf(itemPrice).
    }
                                               andATotalOf(totalPrice)
      }
      ...
Thucydides Fluent Selectors
 class ShoppingCartPage extends PageObject {

     ShoppingCartPage(WebDriver driver) {
         super(driver)
     }                      Using short-hand    Thucydides Fluent Selectors
      @FindBy(css = ".CartContents tbody tr")
    private String productName(WebElement cartItem) {
      List<WebElement> shoppingCartItems
        $(cartItem).findBy(".ProductName").text
    }
      List<CartItem> getCartItems() {
          shoppingCartItems.collect { cartItemfromElement(it) }
    private String totalPrice(WebElement cartItem) {
      }
        $(cartItem).findBy(".CartItemTotalPrice").text
    }
      CartItem cartItemfromElement(WebElement element) {
          Integer quantity = Integer.parseInt(itemQuantity(element))
    private String itemPrice(WebElement cartItem) {
          String product = productName(element)
        $(cartItem).findBy(".CartItemIndividualPrice").text
          BigDecimal itemPrice = priceOf(itemPrice(element))
    }
          BigDecimal totalPrice = priceOf(totalPrice(element))
    private String itemQuantity(WebElement cartItem) {
          return CartItem.containing(quantity).productsCalled(product).
        $(cartItem).findBy(".CartItemQuantity").then("select").selectedValue
                                               withAnItemPriceOf(itemPrice).
    }
                                               andATotalOf(totalPrice)
      }
      ...
Thucydides Fluent Selectors
 class ShoppingCartPage extends PageObject {

     ShoppingCartPage(WebDriver driver) {
         super(driver)
     }
                                                                 (Pure Java version)
      @FindBy(css = ".CartContents tbody tr")
    private String productName(WebElement cartItem) {
      List<WebElement> shoppingCartItems
        return $(cartItem).findBy(".ProductName").getText();
    }
      List<CartItem> getCartItems() {
    private String totalPrice(WebElement cartItem) {
          shoppingCartItems.collect { cartItemfromElement(it) }
        return $(cartItem).findBy(".CartItemTotalPrice").getText();
      }
    }
     CartItem cartItemfromElement(WebElement element) {
    private String itemPrice(WebElement cartItem) {
          Integer quantity = Integer.parseInt(itemQuantity(element))
        return $(cartItem).findBy(".CartItemIndividualPrice").getText();
          String product = productName(element)
    }
          BigDecimal itemPrice = priceOf(itemPrice(element))
          BigDecimal totalPrice = priceOf(totalPrice(element))
    private String itemQuantity(WebElement cartItem) {
        return $(cartItem).findBy(".CartItemQuantity").then("select").getSelectedValue();
    }
          return CartItem.containing(quantity).productsCalled(product).
                                              withAnItemPriceOf(itemPrice).
                                              andATotalOf(totalPrice)
     }
     ...
Thucydides Fluent Selectors
    List<String> getYAxes() {
        def axesElements = findAll(".dygraph-axis-label-y");
        axesElements.collect { WebElement axis -> axis.text }
    }
                                          Finding multiple elements
Thucydides Fluent Matchers
Thucydides Fluent Matchers
import static org.hamcrest.Matchers.is;
import static net.thucydides.core.matchers.BeanMatchers.the;
...
    @Steps
    public DeveloperSteps developer;

    @Test
    public void should_search_for_artifacts_by_name() {
        developer.opens_the_search_page();
        developer.searches_for("Thucydides");
        developer.should_see_artifacts_where(the("ArtifactId", is("thucydides")),
                                             the("GroupId", is("net.thucydides")));
    }




              Using Thucydides matchers

                                              And Hamcrest matchers
Thucydides Fluent Matchers
import static org.hamcrest.Matchers.is;
           import static net.thucydides.core.matchers.BeanMatcherAsserts.shouldMatch;
import static net.thucydides.core.matchers.BeanMatchers.the;
...
           public class DeveloperSteps extends ScenarioSteps {
    @Steps
    public DeveloperSteps developer;
               SearchPage searchPage;
               SearchResultsPage searchResultsPage;
    @Test
    public void should_search_for_artifacts_by_name() {
               public DeveloperSteps(Pages pages) {
        developer.opens_the_search_page();
                   super(pages);
        developer.searches_for("Thucydides");
                   searchResultsPage = pages.getPage(SearchResultsPage.class);
        developer.should_see_artifacts_where(the("ArtifactId", is("thucydides")),
                   searchPage = pages.getPage(SearchPage.class);
                                             the("GroupId", is("net.thucydides")));
               }
    }
                                                      The Page Object returns a
              @Step
              public void opens_the_search_page() {         list of POJOs
                  searchPage.open();
              }
                            Applying the matchers
              @Step
              public void searches_for(String search_terms) {
                  searchPage.enter_search_terms(search_terms);
                  searchPage.starts_search();
              }

              @Step
              public void should_see_artifacts_where(BeanMatcher... matchers) {
                  shouldMatch(searchResultsPage.getSearchResults(), matchers);
Thucydides Fluent Matchers
import static org.hamcrest.Matchers.is;
import static net.thucydides.core.matchers.BeanMatchers.the;
...
    @Steps
    public DeveloperSteps developer;  The page object should return POJOs
    @Test
             public List<Artifact> getResults() {
    public void should_search_for_artifacts_by_name() {
                 List<WebElement> rows = resultTable.findElements(By.xpath(".//tr[td]"));
        developer.opens_the_search_page();
                 return convert(rows, toArtifacts());
        developer.searches_for("Thucydides");
             }
        developer.should_see_artifacts_where(the("ArtifactId", is("thucydides")),
                                             the("GroupId", is("net.thucydides")));
    }        private Converter<WebElement, Artifact> toArtifacts() {
                 return new Converter<WebElement, Artifact>() {
                     public Artifact convert(WebElement row) {
                         List<WebElement> cells = row.findElements(By.tagName("td"));
                         String groupId = cells.get(0).getText();
                         String artifactId = cells.get(1).getText();
                         String latestVersion = cells.get(2).getText();
                         return new Artifact(groupId, artifactId, latestVersion);
                     }
                 };
             }
Thucydides Fluent Matchers
List<Person> persons = Arrays.asList(new Person("Bill", "Oddie"),
                                     new Person("Tim", "Brooke-Taylor"));

shouldMatch(persons, the("firstName", is(not("Tim"))));
shouldMatch(persons, the("firstName", startsWith("B")));




                                            Matcher work with simple POJOs
Thucydides and Tables
import static net.thucydides.core.pages.components.HtmlTable.rowsFrom;

public class SearchResultsPage extends PageObject {

    WebElement resultTable;

    public SearchResultsPage(WebDriver driver) {
        super(driver);
    }

    public List<Map<String, String>> getSearchResults() {
        return rowsFrom(resultTable);
    }
}



                                             Convenience methods: convert
                                               a table to a map of Strings
Thucydides and Tables
    @Test
    public void clicking_on_artifact_should_display_details_page() {
        developer.opens_the_search_page();
        developer.searches_for("Thucydides");
        developer.open_artifact_where(the("ArtifactId", is("thucydides")),
                                      the("GroupId", is("net.thucydides")));

        developer.should_see_artifact_details_where(the("artifactId", is("thucydides")),
                                                    the("groupId", is("net.thucydides")));
    }



                                Using matchers to click on a
                                       row in a table
Thucydides and Tables
    @Test
    public void clicking_on_artifact_should_display_details_page() {
        developer.opens_the_search_page();
        developer.searches_for("Thucydides");
        developer.open_artifact_where(the("ArtifactId", is("thucydides")),
                                      the("GroupId", is("net.thucydides")));

        developer.should_see_artifact_details_where(the("artifactId", is("thucydides")),
                                                    the("groupId", is("net.thucydides")));
    }                                                      Pass through matchers
     @Step
     public void open_artifact_where(BeanMatcher... matchers) {
         searchResultsPage.clickOnFirstRowMatching(matchers);
     }
   import static net.thucydides.core.pages.components.HtmlTable.filterRows;
   ...
       @FindBy(css="#resultTable")
       WebElement resultTable;                       Return matching rows
       public void clickOnFirstRowMatching(BeanMatcher... matchers) {
           List<WebElement> matchingRows = filterRows(resultTable, matchers);
           WebElement targetRow = matchingRows.get(0);
           WebElement detailsLink
               = $(targetRow).findBy(".//a[contains(@href,'artifactdetails')]");
           detailsLink.click();
       }
Thucydides Fluent Waits

 Convenience methods
 Extends the WebDriver Wait API
Thucydides Fluent Waits
    void addToCart(Integer quantity) {
        $(quantitySelection).selectByVisibleText(quantity.toString())
        $(addToCart).click()
        waitForTextToAppear("added to your cart")
    }
                                                              A simple wait

    void uploadExistingImage(String imageUrl) {
        // upload an image
        ...
        waitForPresenceOf(".photo")
    }
                                        Wait for an element to be rendered
    def waitForPageToLoad() {
        waitFor(500).milliseconds()
        waitForAbsenceOf("#loading")
    }


                                        Wait for an element to disappear
Thucydides Fluent Waits
    def searchByLocation(postcode) {
        element(location).typeAndTab(postcode)
        element(locationGo).click()
        waitFor("//div[@class='f-item'][contains(.,'${postcode}')]")
    }
                                                 Waiting for an XPath expression

    def filterByLocation(String state) {
        element(locationList).click()
        waitFor("#location-select li a")
        findBy("//span[@id='location-select']//a[contains(.,'$state')]").then().click()
        waitForCondition().until(stateIsChosen(state))

    }                                           Wait for a non-trivial condition
    Function<? super WebDriver, Boolean> stateIsChosen(final String state) {
        return new Function<? super WebDriver, Boolean>() {

            @Override
            Boolean apply(driver) {
                return state == getCurrentSelectedState()
            };
        }
    }                                    What are we waiting for
Thucydides Asserts
def shouldDisplaySubcategory(subCategory) {
    findBy(".SubCategoryList").then(By.linkText(subCategory)).shouldBeCurrentlyVisible()
}



              Check that this element is visible

@FindBy(id=”search”)
WebElement searchButton

def searchShouldBeEnabled() {
    searchButton.shouldBeCurrentlyEnabled()
}




            Check that this element is enabled
Smarter Webdriver coding
Smarter Webdriver coding



   Prefer CSS to XPath
Smarter Webdriver coding



    Use Nested Finds
Nested Finds
public void chooseCategory(String category) {
    findBy("//div[@id='category-select']//a[contains(@class,'arrow')]").click();
    findBy("//div[@id='category-select']//a[.,contains($category)]").click();
}


                                             Hard to read, hard to maintain


    public void chooseCategory(String category) {
        findBy("#category-select").then(".arrow").then().click();
        findBy("#category-select").then(By.linkText(category)).then().click();
    }


                                                  A more readable approach
Smarter Webdriver coding



  Confine Webdriver to
   your Page Objects
Encapsulate Page Objects
class ShoppingCartPage extends PageObject {

    public ShoppingCartPage(WebDriver driver) {
        super(driver)                               Public API
    }

    public List<CartItem> getCartItems() {
        shoppingCartItems.collect { cartItemfromElement(it) }
    }
                                                        Webdriver stays
    @FindBy(css = ".CartContents tbody tr")
    private List<WebElement> shoppingCartItems             private
    private CartItem cartItemfromElement(WebElement element) {
        Integer quantity = Integer.parseInt(itemQuantity(element))
        String product = productName(element)
        BigDecimal itemPrice = priceOf(itemPrice(element))
        BigDecimal totalPrice = priceOf(totalPrice(element))

        return CartItem.containing(quantity).productsCalled(product).
                                             withAnItemPriceOf(itemPrice).
                                             andATotalOf(totalPrice)
    }
    ...
Smarter Webdriver coding


 Webdriver Query ~= JDBC Query
Learning More




                                                                         sts
http://thucydi                                             cydides-webte
                 des.info                   github.com/thu




                     http://thucydides-webtests.com
To ATDD And Beyond
       Better Automated Acceptance Testing on the JVM




               Thank You


John Ferguson Smart

Contenu connexe

Similaire à To atdd-and-beyond

SearchLove San Diego - Nancy-Lee McLaughlin - Advertising on Amazon: How to T...
SearchLove San Diego - Nancy-Lee McLaughlin - Advertising on Amazon: How to T...SearchLove San Diego - Nancy-Lee McLaughlin - Advertising on Amazon: How to T...
SearchLove San Diego - Nancy-Lee McLaughlin - Advertising on Amazon: How to T...Distilled
 
Wantlet Sales Presentation
Wantlet Sales PresentationWantlet Sales Presentation
Wantlet Sales PresentationWantlet
 
Audelyaustria research presentation
Audelyaustria research presentationAudelyaustria research presentation
Audelyaustria research presentationaudelyaustria1
 
Stop Delivering Product Training and Start Delivering Product Sales Training
Stop Delivering Product Training and Start Delivering Product Sales TrainingStop Delivering Product Training and Start Delivering Product Sales Training
Stop Delivering Product Training and Start Delivering Product Sales TrainingISA Marketing & Sales Summit
 
My journey in amazon seller VA master class
My journey in amazon seller VA master classMy journey in amazon seller VA master class
My journey in amazon seller VA master classLaennec Abello
 
Selling You & Your Services Drupalcon 2009
Selling You & Your Services   Drupalcon 2009Selling You & Your Services   Drupalcon 2009
Selling You & Your Services Drupalcon 2009Neil Giarratana
 
Advanced Keyword Modeling
Advanced Keyword ModelingAdvanced Keyword Modeling
Advanced Keyword ModelingBill Hunt
 
How To Sell On Amazon Successfully
How To Sell On Amazon SuccessfullyHow To Sell On Amazon Successfully
How To Sell On Amazon SuccessfullyBobsled Marketing
 
Specification-By-Example with Gherkin
Specification-By-Example with GherkinSpecification-By-Example with Gherkin
Specification-By-Example with GherkinChristian Hassa
 
Agile Business Analysis
Agile Business AnalysisAgile Business Analysis
Agile Business Analysisdavid_draper
 
Catalogue overview
Catalogue overviewCatalogue overview
Catalogue overviewPaytm
 
How to Write Amazon Product Reviews
How to Write Amazon Product ReviewsHow to Write Amazon Product Reviews
How to Write Amazon Product ReviewsNate Rivers
 
CrossView's "Working Together to Drive Optimization: Google's Universal Analy...
CrossView's "Working Together to Drive Optimization: Google's Universal Analy...CrossView's "Working Together to Drive Optimization: Google's Universal Analy...
CrossView's "Working Together to Drive Optimization: Google's Universal Analy...CrossView
 
Beat your fba competition
Beat your fba competitionBeat your fba competition
Beat your fba competitionDJeffreyHonore
 
Navigating Google Shopping Changes by SingleFeed - Sept. 13, 2012
Navigating Google Shopping Changes by SingleFeed -  Sept. 13, 2012Navigating Google Shopping Changes by SingleFeed -  Sept. 13, 2012
Navigating Google Shopping Changes by SingleFeed - Sept. 13, 2012ryandouglas
 
Analyzing Queries to Find Revenue Opportunities
Analyzing Queries to Find Revenue OpportunitiesAnalyzing Queries to Find Revenue Opportunities
Analyzing Queries to Find Revenue OpportunitiesBill Hunt
 
AMAZON SELLER VIRTUAL ASSISTANT PRODUCT RESEARCH .pdf
AMAZON SELLER VIRTUAL ASSISTANT PRODUCT RESEARCH .pdfAMAZON SELLER VIRTUAL ASSISTANT PRODUCT RESEARCH .pdf
AMAZON SELLER VIRTUAL ASSISTANT PRODUCT RESEARCH .pdfJohnCarloValencia4
 
Product Research Presentation
Product Research PresentationProduct Research Presentation
Product Research PresentationRenMarkCueva
 
E Commerce Guide For Apparel
E Commerce Guide For ApparelE Commerce Guide For Apparel
E Commerce Guide For ApparelJun Loayza
 

Similaire à To atdd-and-beyond (20)

SearchLove San Diego - Nancy-Lee McLaughlin - Advertising on Amazon: How to T...
SearchLove San Diego - Nancy-Lee McLaughlin - Advertising on Amazon: How to T...SearchLove San Diego - Nancy-Lee McLaughlin - Advertising on Amazon: How to T...
SearchLove San Diego - Nancy-Lee McLaughlin - Advertising on Amazon: How to T...
 
Wantlet Sales Presentation
Wantlet Sales PresentationWantlet Sales Presentation
Wantlet Sales Presentation
 
Audelyaustria research presentation
Audelyaustria research presentationAudelyaustria research presentation
Audelyaustria research presentation
 
Stop Delivering Product Training and Start Delivering Product Sales Training
Stop Delivering Product Training and Start Delivering Product Sales TrainingStop Delivering Product Training and Start Delivering Product Sales Training
Stop Delivering Product Training and Start Delivering Product Sales Training
 
My journey in amazon seller VA master class
My journey in amazon seller VA master classMy journey in amazon seller VA master class
My journey in amazon seller VA master class
 
Selling You & Your Services Drupalcon 2009
Selling You & Your Services   Drupalcon 2009Selling You & Your Services   Drupalcon 2009
Selling You & Your Services Drupalcon 2009
 
Advanced Keyword Modeling
Advanced Keyword ModelingAdvanced Keyword Modeling
Advanced Keyword Modeling
 
How To Sell On Amazon Successfully
How To Sell On Amazon SuccessfullyHow To Sell On Amazon Successfully
How To Sell On Amazon Successfully
 
Specification-By-Example with Gherkin
Specification-By-Example with GherkinSpecification-By-Example with Gherkin
Specification-By-Example with Gherkin
 
Agile Business Analysis
Agile Business AnalysisAgile Business Analysis
Agile Business Analysis
 
Catalogue overview
Catalogue overviewCatalogue overview
Catalogue overview
 
How to Write Amazon Product Reviews
How to Write Amazon Product ReviewsHow to Write Amazon Product Reviews
How to Write Amazon Product Reviews
 
CrossView's "Working Together to Drive Optimization: Google's Universal Analy...
CrossView's "Working Together to Drive Optimization: Google's Universal Analy...CrossView's "Working Together to Drive Optimization: Google's Universal Analy...
CrossView's "Working Together to Drive Optimization: Google's Universal Analy...
 
Beat your fba competition
Beat your fba competitionBeat your fba competition
Beat your fba competition
 
Navigating Google Shopping Changes by SingleFeed - Sept. 13, 2012
Navigating Google Shopping Changes by SingleFeed -  Sept. 13, 2012Navigating Google Shopping Changes by SingleFeed -  Sept. 13, 2012
Navigating Google Shopping Changes by SingleFeed - Sept. 13, 2012
 
Analyzing Queries to Find Revenue Opportunities
Analyzing Queries to Find Revenue OpportunitiesAnalyzing Queries to Find Revenue Opportunities
Analyzing Queries to Find Revenue Opportunities
 
AMAZON SELLER VIRTUAL ASSISTANT PRODUCT RESEARCH .pdf
AMAZON SELLER VIRTUAL ASSISTANT PRODUCT RESEARCH .pdfAMAZON SELLER VIRTUAL ASSISTANT PRODUCT RESEARCH .pdf
AMAZON SELLER VIRTUAL ASSISTANT PRODUCT RESEARCH .pdf
 
Product Research Presentation
Product Research PresentationProduct Research Presentation
Product Research Presentation
 
Product Research Presentation
Product Research PresentationProduct Research Presentation
Product Research Presentation
 
E Commerce Guide For Apparel
E Commerce Guide For ApparelE Commerce Guide For Apparel
E Commerce Guide For Apparel
 

Plus de John Ferguson Smart Limited

My Reading Specs - Refactoring Patterns for Gherkin Scenarios
My Reading Specs - Refactoring Patterns for Gherkin ScenariosMy Reading Specs - Refactoring Patterns for Gherkin Scenarios
My Reading Specs - Refactoring Patterns for Gherkin ScenariosJohn Ferguson Smart Limited
 
Artisti e Condotierri - How can your team become artists of the 21st century ...
Artisti e Condotierri - How can your team become artists of the 21st century ...Artisti e Condotierri - How can your team become artists of the 21st century ...
Artisti e Condotierri - How can your team become artists of the 21st century ...John Ferguson Smart Limited
 
Engage! Bringing teams together to deliver software that makes a difference
Engage! Bringing teams together to deliver software that makes a differenceEngage! Bringing teams together to deliver software that makes a difference
Engage! Bringing teams together to deliver software that makes a differenceJohn Ferguson Smart Limited
 
Sustainable Test Automation with Serenity BDD and Screenplay
Sustainable Test Automation with Serenity BDD and ScreenplaySustainable Test Automation with Serenity BDD and Screenplay
Sustainable Test Automation with Serenity BDD and ScreenplayJohn Ferguson Smart Limited
 
Engage! Bringing teams together to deliver software that makes a difference
Engage! Bringing teams together to deliver software that makes a differenceEngage! Bringing teams together to deliver software that makes a difference
Engage! Bringing teams together to deliver software that makes a differenceJohn Ferguson Smart Limited
 
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...John Ferguson Smart Limited
 
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...John Ferguson Smart Limited
 
Screenplay - Next generation automated acceptance testing
Screenplay - Next generation automated acceptance testingScreenplay - Next generation automated acceptance testing
Screenplay - Next generation automated acceptance testingJohn Ferguson Smart Limited
 
All the world's a stage – the next step in automated testing practices
All the world's a stage – the next step in automated testing practicesAll the world's a stage – the next step in automated testing practices
All the world's a stage – the next step in automated testing practicesJohn Ferguson Smart Limited
 
It's Testing, Jim, but not as we know it - BDD for Testers
It's Testing, Jim, but not as we know it - BDD for TestersIt's Testing, Jim, but not as we know it - BDD for Testers
It's Testing, Jim, but not as we know it - BDD for TestersJohn Ferguson Smart Limited
 

Plus de John Ferguson Smart Limited (20)

My Reading Specs - Refactoring Patterns for Gherkin Scenarios
My Reading Specs - Refactoring Patterns for Gherkin ScenariosMy Reading Specs - Refactoring Patterns for Gherkin Scenarios
My Reading Specs - Refactoring Patterns for Gherkin Scenarios
 
Artisti e Condotierri - How can your team become artists of the 21st century ...
Artisti e Condotierri - How can your team become artists of the 21st century ...Artisti e Condotierri - How can your team become artists of the 21st century ...
Artisti e Condotierri - How can your team become artists of the 21st century ...
 
Engage! Bringing teams together to deliver software that makes a difference
Engage! Bringing teams together to deliver software that makes a differenceEngage! Bringing teams together to deliver software that makes a difference
Engage! Bringing teams together to deliver software that makes a difference
 
BE A POD OF DOLPHINS, NOT A DANCING ELEPHANT
BE A POD OF DOLPHINS, NOT A DANCING ELEPHANTBE A POD OF DOLPHINS, NOT A DANCING ELEPHANT
BE A POD OF DOLPHINS, NOT A DANCING ELEPHANT
 
Sustainable Test Automation with Serenity BDD and Screenplay
Sustainable Test Automation with Serenity BDD and ScreenplaySustainable Test Automation with Serenity BDD and Screenplay
Sustainable Test Automation with Serenity BDD and Screenplay
 
Feature Mapping Workshop
Feature Mapping WorkshopFeature Mapping Workshop
Feature Mapping Workshop
 
Engage! Bringing teams together to deliver software that makes a difference
Engage! Bringing teams together to deliver software that makes a differenceEngage! Bringing teams together to deliver software that makes a difference
Engage! Bringing teams together to deliver software that makes a difference
 
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
 
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
 
Shift left-devoxx-pl
Shift left-devoxx-plShift left-devoxx-pl
Shift left-devoxx-pl
 
Screenplay - Next generation automated acceptance testing
Screenplay - Next generation automated acceptance testingScreenplay - Next generation automated acceptance testing
Screenplay - Next generation automated acceptance testing
 
Cucumber and Spock Primer
Cucumber and Spock PrimerCucumber and Spock Primer
Cucumber and Spock Primer
 
All the world's a stage – the next step in automated testing practices
All the world's a stage – the next step in automated testing practicesAll the world's a stage – the next step in automated testing practices
All the world's a stage – the next step in automated testing practices
 
CukeUp 2016 Agile Product Planning Workshop
CukeUp 2016 Agile Product Planning WorkshopCukeUp 2016 Agile Product Planning Workshop
CukeUp 2016 Agile Product Planning Workshop
 
BDD Anti-patterns
BDD Anti-patternsBDD Anti-patterns
BDD Anti-patterns
 
Serenity and the Journey Pattern
Serenity and the Journey PatternSerenity and the Journey Pattern
Serenity and the Journey Pattern
 
BDD - Collaborate like you mean it!
BDD - Collaborate like you mean it!BDD - Collaborate like you mean it!
BDD - Collaborate like you mean it!
 
BDD-Driven Microservices
BDD-Driven MicroservicesBDD-Driven Microservices
BDD-Driven Microservices
 
BDD Anti-patterns
BDD Anti-patternsBDD Anti-patterns
BDD Anti-patterns
 
It's Testing, Jim, but not as we know it - BDD for Testers
It's Testing, Jim, but not as we know it - BDD for TestersIt's Testing, Jim, but not as we know it - BDD for Testers
It's Testing, Jim, but not as we know it - BDD for Testers
 

To atdd-and-beyond

  • 1. To ATDD And Beyond Better Automated Acceptance Testing on the JVM John Ferguson Smart
  • 2. Consulta nt Trainer Mentor Author Speaker Coder John Fer guson S mar t
  • 3. The Plan 1) What is ATDD? 2) How can I do it 3) Let’s see it work
  • 4. Acceptance Test Driven Development The story of your app
  • 5. Goals Make money by selling our stuff online Display catalog Features/Epics Order products Build a wishlist User stories Browse Catalog In order to find stuff I would like to buy As a customer I want to be able to browse through the catalog
  • 6. User stories Browse Catalog In order to find stuff I would like to buy As a customer I want to be able to browse through the catalog Acceptance criteria ☑  See  all  the  top-­‐level  categories  in  the  catalog ☑  Browse  through  the  sub-­‐categories ☑  See  all  the  products  in  a  category
  • 7. User stories Browse Catalog In order to find stuff I would like to buy As a customer I want to be able to browse through the catalog Acceptance criteria ☑  See  all  the  top-­‐level  categories  in  the  catalog ☑  Browse  through  the  sub-­‐categories ☑  See  all  the  products  in  a  category 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 Automated acceptance test
  • 8. Automated acceptance test Implemented development tests Implemented acceptance tests
  • 9. Automated acceptance criteria Define your goals
  • 10. Automated acceptance criteria keep you on track
  • 11. Automated acceptance criteria Provide better visibility
  • 12. Automated acceptance criteria Allow faster release cycles
  • 14. Automated acceptance criteria 31%  faster  delivery Delivery  Time Using  ATDD Tradi4onal Provide more value
  • 15. Automated acceptance criteria 4  4mes  less  defects Defect  Rate Using  ATDD Tradi4onal Higher Quality
  • 16. Why only do QA at the end of the project?
  • 17. Real quality cannot be injected at the end It must be part of the process
  • 18. Leave the boring stuff to the automated tests...
  • 19. ...and empower your QA team Let testers focus on more intelligent testing
  • 20. 20 Keeping an eye on things
  • 22. 22 1 Discover your acceptance criteria 2 Automate your acceptance criteria 3 Implement your acceptance criteria 4 Execute your acceptance tests
  • 23. 23 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
  • 24. 24 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
  • 25. 25 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
  • 26. 26 2 Automate your acceptance criteria ...but they will be reported as ‘pending’
  • 27. 27 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
  • 28. 28 3 Implement your acceptance criteria
  • 29. 29 3 Implement your acceptance criteria JUnit
  • 30. 30 4 Execute your acceptance tests
  • 31. 31
  • 32. 32
  • 33. 33
  • 34. 34
  • 35. 35
  • 36. 36
  • 37. Functional Test Coverage “What was tested” vs “What’s been done”
  • 38. What have we tested? Broken Works In Progress
  • 39. What have we finished? Specified but no tests Partially done? Finished
  • 40. What have we finished?
  • 41. What have we finished?
  • 42. What have we finished?
  • 44. Stay on top of your scenarios
  • 45. Stay on top of your scenarios
  • 46. Stay on top of your scenarios Better visibility on what is done More targeted reporting
  • 47. Keep your scenarios organized 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 As a buyer I want to be able to list all the ads with a particular keyword in the description or title.
  • 48. Store your requirements where it suits you
  • 49. Customize Thucydides to work for you RequirementsTagProvider getRequirements() getParentRequirementOf(testOutcome) Capability In order to increase the number of items I sell As a seller I want buyers to be able to view ads for items they might want to Feature In order to increase sales of advertised articles As a seller MyRequirementProvider I want potential buyers to be able to display only the ads for articles MYPROJ-123 Story In order to find the items I am interested in faster As a buyer I want to be able to list all the ads with a particular keyword in the description or title. Meta: @issue MYPROJ-123 Scenario: Search by keyword Given that I want to find products in the "<range>" range When I search for products by keyword "<keywords>" Then I should see a product with the title <expectedTitle>
  • 50. Use tags for orthogonal concerns • Non-functional requirements • Iterations • Current iteration vs regression tests • Acceptance tests vs more detailed tests • Related features • System components • ...
  • 51. Use tags for orthogonal concerns @RunWith(ThucydidesRunner) Tagging in JUnit @Issue("NC-1") @WithTag(name="Browse Ads") class BrowseAdsByCategory {     @Managed     public WebDriver webdriver     @ManagedPages(defaultUrl = "http://www.newsclassifieds-uat.appspot.com")     public Pages pages     @Steps     BuyerSteps buyer     @Test     void "Browse ads by category"() {         buyer.opens_home_page()         buyer.chooses_classification "Furniture & Homewares"         buyer.should_see_search_results_for "Furniture & Homewares"         buyer.chooses_entry(1).from().other_results()         buyer.should_see_details_for_the_selected_ad()         buyer.returns_to_search_results()         buyer.should_see_search_results_for "Furniture & Homewares"     } }
  • 52. Use tags for orthogonal concerns Meta: @tag component:search Tagging in JBehave Scenario: Search by keyword Given that I want to find products in the "<range>" range When I search for products by keyword "<keywords>" Then I should see a product with the title <expectedTitle> Examples: | range | keywords | expectedTitle | | Simone | Simone | 3by3 Simone | | Grey Steel Classic | Steel | 3by3 Grey Steel Classic | | KV Bags | KV | KV Classic Satchel |
  • 53. Use tags for orthogonal concerns Meta: @tag component:search Scenario: Search by keyword Given that I want to find products in the "<range>" range When I search for products by keyword "<keywords>" Then I should see a product with the title <expectedTitle> Examples: | range | keywords | expectedTitle | | Simone | Simone | 3by3 Simone | | Grey Steel Classic | Steel | 3by3 Grey Steel Classic | | KV Bags | KV | KV Classic Satchel |
  • 55. Have you got web tests that look like this?
  • 56. Have you got web tests that look like this?
  • 57. Page Objects To The Rescue!
  • 58. Page Objects To The Rescue! Better encapsulation Easier to read Easier to maintain Still a bit unclear what we are doing
  • 59. Test Steps To The Rescue! Step methods focus on what we are doing
  • 60. Test Steps To The Rescue! Step implementations detail the how
  • 61. Test Steps To The Rescue! Steps are ordinary methods     @Step     public void proceedToPayment() {         adPreviewPage.proceedToPayment();     } They can have parameters    @Step    public void searches_for(String searchTerms) {         homePage.searchFor(searchTerms);    } They can be pending    @Pending @Step     public void searches_for(String searchTerms) {         // Not done yet     }     @Step("The carousel should display {0} at a time")     def should_see_a_number_of_visible_ads_in_the_carousel(int adCount) {         assert homePage.visibleCarouselAds.size() == adCount     } You can customize the title
  • 62. Test Steps To The Rescue!     @Step     def browses_product_categories(String... categories) {         selects_top_level_category(categories.head())         categories.tail().each { subcategory ->             browse_through_subcategory(subcategory)         }     } Steps can call other steps     @Step     def selects_top_level_category(String category) {         homePage.select_top_level_category category     }          @Step     def browse_through_subcategory(subcategory) {         homePage.select_subcategory(subcategory)     }
  • 63. Test Steps To The Rescue! Test reports document the what and the how
  • 64. Test Steps To The Rescue! Focus on what we are doing, not how we do it More reusability Better reporting
  • 65. Even better with JBehave! Narrative: In order to find items more easily As a customer I want to be able to browse through the product categories Scenario: Browse through product categories Given I am on the home page When I browse through the product categories Clothing, Mens, Shirts Then I should see a product with the title 3by3 Milano Stretch Cotton Shirt JBehave describes what we are doing Use normal Thucydides steps in the implementation
  • 66. Even better with JBehave! Narrative: In order to find items more easily As a customer I want to be able to browse through the product categories Scenario: Browse through product categories Given I am on the home page When I browse through the product categories Clothing, Mens, Shirts Then I should see a product with the title 3by3 Milano Stretch Cotton Shirt class BrowseByCategorySteps extends BigCommerceJBehaveSteps{     @Steps     CustomerSteps customer; Use normal Thucydides steps     @Given('I am on the home page')     public void givenIAmOnTheHomePage() {         customer.opens_home_page()     }          @When('I browse through the product categories $categories')     public void whenIBrowseThroughTheProductCategories(List<String> categories) {         customer.browses_product_categories(*categories)     }     @Then('I should see a product with the title "$expectedTitle"')     public void thenIShouldSeeAProductWithTheTitle(String expectedTitle) {         customer.should_see_product withTitle(expectedTitle)     } }
  • 67. Even better with JBehave! Narrative: In order to find items more easily As a customer I want to be able to browse through the product categories Context Scenario: Browse through product categories Given I am on the home page When I browse through the product categories Clothing, Mens, Shirts Then I should see a product with the title 3by3 Milano Stretch Cotton Shirt What How Illustrations
  • 68. Even better with JBehave! Narrative: In order to find items more easily As a customer I want to be able to browse through the product categories Scenario: Browse through product categories Given I am on the home page When I browse through the product categories Clothing, Mens, Shirts Then I should see a product with the title 3by3 Milano Stretch Cotton Shirt Living documentation Easier to read Easier to maintain More work maintaining the .story files
  • 70. Thucydides Page Object support Fluent selectors Fluent matchers HTML tables Fluent waits
  • 71. Thucydides Fluent Selectors public class HomePage extends PageObject {     @FindBy(name="adFilter.searchTerm")     WebElement searchTerm;     @FindBy(css=".keywords button")     WebElement search;     public HomePage2(WebDriver driver) {         super(driver);     } Fluent selectors     public void chooseCategory(String category) {         findBy("#category-select").then(".arrow").then().click();         findBy("#category-select").then(By.linkText(category)).then().click();     }     public void enterKeywords(String keywords) {         $(searchTerm).type(keywords);     }     public void performSearch() {         $(search).click(); Thucydides helper methods     } }
  • 72. Thucydides Fluent Selectors class ShoppingCartPage extends PageObject {     ShoppingCartPage(WebDriver driver) {         super(driver)     } Page Object returns domain classes     @FindBy(css = ".CartContents tbody tr")     List<WebElement> shoppingCartItems     List<CartItem> getCartItems() {         shoppingCartItems.collect { cartItemfromElement(it) }     }     CartItem cartItemfromElement(WebElement element) {         Integer quantity = Integer.parseInt(itemQuantity(element))         String product = productName(element)         BigDecimal itemPrice = priceOf(itemPrice(element))         BigDecimal totalPrice = priceOf(totalPrice(element))         return CartItem.containing(quantity).productsCalled(product).                                              withAnItemPriceOf(itemPrice).                                              andATotalOf(totalPrice)     } ... Building a CartItem from the WebElement
  • 73. Thucydides Fluent Selectors class ShoppingCartPage extends PageObject {     ShoppingCartPage(WebDriver driver) {         super(driver)     } Classic WebDriver     private String =productName(WebElement tr")     @FindBy(css ".CartContents tbody cartItem) {         cartItem.findElement(By.className("ProductName")).text     List<WebElement> shoppingCartItems     }     List<CartItem> getCartItems() {     private String totalPrice(WebElement cartItem) {         shoppingCartItems.collect { cartItemfromElement(it) }         cartItem.findElement(By.className("CartItemTotalPrice")).text     }     }     CartItem cartItemfromElement(WebElement element) {     private String quantity = Integer.parseInt(itemQuantity(element))         Integer itemPrice(WebElement cartItem) {         cartItem.findElement(By.className("CartItemIndividualPrice")).text         String product = productName(element)     }         BigDecimal itemPrice = priceOf(itemPrice(element))         BigDecimal totalPrice = priceOf(totalPrice(element))     private String itemQuantity(WebElement cartItem) {         def itemQuantityDropdown         return CartItem.containing(quantity).productsCalled(product).           = cartItem.findElement(                                              withAnItemPriceOf(itemPrice).                      By.xpath(".//td[contains(@class,'CartItemQuantity')]/select"))                                              andATotalOf(totalPrice)         element(itemQuantityDropdown).selectedValue     }     } ...
  • 74. Thucydides Fluent Selectors class ShoppingCartPage extends PageObject {     ShoppingCartPage(WebDriver driver) {         super(driver)     } Using Thucydides Fluent Selectors     @FindBy(css = ".CartContents tbody tr")     private String productName(WebElement cartItem) {     List<WebElement> shoppingCartItems         element(cartItem).findBy(".ProductName").text     }     List<CartItem> getCartItems() {         shoppingCartItems.collect { cartItemfromElement(it) }     private String totalPrice(WebElement cartItem) {     }         element(cartItem).findBy(".CartItemTotalPrice").text     }     CartItem cartItemfromElement(WebElement element) {         Integer quantity = Integer.parseInt(itemQuantity(element))     private String itemPrice(WebElement cartItem) {         String product = productName(element)         element(cartItem).findBy(".CartItemIndividualPrice").text         BigDecimal itemPrice = priceOf(itemPrice(element))     }         BigDecimal totalPrice = priceOf(totalPrice(element))     private String itemQuantity(WebElement cartItem) {         return CartItem.containing(quantity).productsCalled(product).         element(cartItem).findBy(".CartItemQuantity").then("select").selectedValue                                              withAnItemPriceOf(itemPrice).     }                                              andATotalOf(totalPrice)     } ...
  • 75. Thucydides Fluent Selectors class ShoppingCartPage extends PageObject {     ShoppingCartPage(WebDriver driver) {         super(driver)     } Using short-hand Thucydides Fluent Selectors     @FindBy(css = ".CartContents tbody tr")     private String productName(WebElement cartItem) {     List<WebElement> shoppingCartItems         $(cartItem).findBy(".ProductName").text     }     List<CartItem> getCartItems() {         shoppingCartItems.collect { cartItemfromElement(it) }     private String totalPrice(WebElement cartItem) {     }         $(cartItem).findBy(".CartItemTotalPrice").text     }     CartItem cartItemfromElement(WebElement element) {         Integer quantity = Integer.parseInt(itemQuantity(element))     private String itemPrice(WebElement cartItem) {         String product = productName(element)         $(cartItem).findBy(".CartItemIndividualPrice").text         BigDecimal itemPrice = priceOf(itemPrice(element))     }         BigDecimal totalPrice = priceOf(totalPrice(element))     private String itemQuantity(WebElement cartItem) {         return CartItem.containing(quantity).productsCalled(product).         $(cartItem).findBy(".CartItemQuantity").then("select").selectedValue                                              withAnItemPriceOf(itemPrice).     }                                              andATotalOf(totalPrice)     } ...
  • 76. Thucydides Fluent Selectors class ShoppingCartPage extends PageObject {     ShoppingCartPage(WebDriver driver) {         super(driver)     } (Pure Java version)     @FindBy(css = ".CartContents tbody tr")     private String productName(WebElement cartItem) {     List<WebElement> shoppingCartItems         return $(cartItem).findBy(".ProductName").getText();     }     List<CartItem> getCartItems() {     private String totalPrice(WebElement cartItem) {         shoppingCartItems.collect { cartItemfromElement(it) }         return $(cartItem).findBy(".CartItemTotalPrice").getText();     }     }     CartItem cartItemfromElement(WebElement element) {     private String itemPrice(WebElement cartItem) {         Integer quantity = Integer.parseInt(itemQuantity(element))         return $(cartItem).findBy(".CartItemIndividualPrice").getText();         String product = productName(element)     }         BigDecimal itemPrice = priceOf(itemPrice(element))         BigDecimal totalPrice = priceOf(totalPrice(element))     private String itemQuantity(WebElement cartItem) {         return $(cartItem).findBy(".CartItemQuantity").then("select").getSelectedValue();     }         return CartItem.containing(quantity).productsCalled(product).                                              withAnItemPriceOf(itemPrice).                                              andATotalOf(totalPrice)     } ...
  • 77. Thucydides Fluent Selectors     List<String> getYAxes() {         def axesElements = findAll(".dygraph-axis-label-y");         axesElements.collect { WebElement axis -> axis.text }     } Finding multiple elements
  • 79. Thucydides Fluent Matchers import static org.hamcrest.Matchers.is; import static net.thucydides.core.matchers.BeanMatchers.the; ...     @Steps     public DeveloperSteps developer;     @Test     public void should_search_for_artifacts_by_name() {         developer.opens_the_search_page();         developer.searches_for("Thucydides");         developer.should_see_artifacts_where(the("ArtifactId", is("thucydides")),                                              the("GroupId", is("net.thucydides")));     } Using Thucydides matchers And Hamcrest matchers
  • 80. Thucydides Fluent Matchers import static org.hamcrest.Matchers.is; import static net.thucydides.core.matchers.BeanMatcherAsserts.shouldMatch; import static net.thucydides.core.matchers.BeanMatchers.the; ... public class DeveloperSteps extends ScenarioSteps {     @Steps     public DeveloperSteps developer;     SearchPage searchPage;     SearchResultsPage searchResultsPage;     @Test     public void should_search_for_artifacts_by_name() {     public DeveloperSteps(Pages pages) {         developer.opens_the_search_page();         super(pages);         developer.searches_for("Thucydides");         searchResultsPage = pages.getPage(SearchResultsPage.class);         developer.should_see_artifacts_where(the("ArtifactId", is("thucydides")), searchPage = pages.getPage(SearchPage.class);                                              the("GroupId", is("net.thucydides")));     }     } The Page Object returns a     @Step     public void opens_the_search_page() { list of POJOs         searchPage.open();     } Applying the matchers     @Step     public void searches_for(String search_terms) {         searchPage.enter_search_terms(search_terms);         searchPage.starts_search();     }     @Step     public void should_see_artifacts_where(BeanMatcher... matchers) {         shouldMatch(searchResultsPage.getSearchResults(), matchers);
  • 81. Thucydides Fluent Matchers import static org.hamcrest.Matchers.is; import static net.thucydides.core.matchers.BeanMatchers.the; ...     @Steps     public DeveloperSteps developer; The page object should return POJOs     @Test     public List<Artifact> getResults() {     public void should_search_for_artifacts_by_name() {         List<WebElement> rows = resultTable.findElements(By.xpath(".//tr[td]"));         developer.opens_the_search_page();         return convert(rows, toArtifacts());         developer.searches_for("Thucydides");     }         developer.should_see_artifacts_where(the("ArtifactId", is("thucydides")),                                              the("GroupId", is("net.thucydides")));     }     private Converter<WebElement, Artifact> toArtifacts() {         return new Converter<WebElement, Artifact>() {             public Artifact convert(WebElement row) {                 List<WebElement> cells = row.findElements(By.tagName("td"));                 String groupId = cells.get(0).getText();                 String artifactId = cells.get(1).getText();                 String latestVersion = cells.get(2).getText();                 return new Artifact(groupId, artifactId, latestVersion);             }         };     }
  • 82. Thucydides Fluent Matchers List<Person> persons = Arrays.asList(new Person("Bill", "Oddie"), new Person("Tim", "Brooke-Taylor")); shouldMatch(persons, the("firstName", is(not("Tim")))); shouldMatch(persons, the("firstName", startsWith("B"))); Matcher work with simple POJOs
  • 83. Thucydides and Tables import static net.thucydides.core.pages.components.HtmlTable.rowsFrom; public class SearchResultsPage extends PageObject {     WebElement resultTable;     public SearchResultsPage(WebDriver driver) {         super(driver);     }     public List<Map<String, String>> getSearchResults() {         return rowsFrom(resultTable);     } } Convenience methods: convert a table to a map of Strings
  • 84. Thucydides and Tables     @Test     public void clicking_on_artifact_should_display_details_page() {         developer.opens_the_search_page();         developer.searches_for("Thucydides");         developer.open_artifact_where(the("ArtifactId", is("thucydides")),                                       the("GroupId", is("net.thucydides")));         developer.should_see_artifact_details_where(the("artifactId", is("thucydides")),                                                     the("groupId", is("net.thucydides")));     } Using matchers to click on a row in a table
  • 85. Thucydides and Tables     @Test     public void clicking_on_artifact_should_display_details_page() {         developer.opens_the_search_page();         developer.searches_for("Thucydides");         developer.open_artifact_where(the("ArtifactId", is("thucydides")),                                       the("GroupId", is("net.thucydides")));         developer.should_see_artifact_details_where(the("artifactId", is("thucydides")),                                                     the("groupId", is("net.thucydides")));     } Pass through matchers @Step     public void open_artifact_where(BeanMatcher... matchers) {         searchResultsPage.clickOnFirstRowMatching(matchers);     } import static net.thucydides.core.pages.components.HtmlTable.filterRows; ...     @FindBy(css="#resultTable")     WebElement resultTable; Return matching rows     public void clickOnFirstRowMatching(BeanMatcher... matchers) {         List<WebElement> matchingRows = filterRows(resultTable, matchers);         WebElement targetRow = matchingRows.get(0);         WebElement detailsLink = $(targetRow).findBy(".//a[contains(@href,'artifactdetails')]");         detailsLink.click();     }
  • 86. Thucydides Fluent Waits Convenience methods Extends the WebDriver Wait API
  • 87. Thucydides Fluent Waits     void addToCart(Integer quantity) {         $(quantitySelection).selectByVisibleText(quantity.toString())         $(addToCart).click()         waitForTextToAppear("added to your cart")     } A simple wait     void uploadExistingImage(String imageUrl) {         // upload an image         ...         waitForPresenceOf(".photo")     } Wait for an element to be rendered     def waitForPageToLoad() {         waitFor(500).milliseconds()         waitForAbsenceOf("#loading")     } Wait for an element to disappear
  • 88. Thucydides Fluent Waits     def searchByLocation(postcode) {         element(location).typeAndTab(postcode)         element(locationGo).click()         waitFor("//div[@class='f-item'][contains(.,'${postcode}')]")     } Waiting for an XPath expression     def filterByLocation(String state) {         element(locationList).click()         waitFor("#location-select li a")         findBy("//span[@id='location-select']//a[contains(.,'$state')]").then().click()         waitForCondition().until(stateIsChosen(state))     } Wait for a non-trivial condition     Function<? super WebDriver, Boolean> stateIsChosen(final String state) {         return new Function<? super WebDriver, Boolean>() {             @Override             Boolean apply(driver) {                 return state == getCurrentSelectedState()             };         }     } What are we waiting for
  • 89. Thucydides Asserts def shouldDisplaySubcategory(subCategory) {     findBy(".SubCategoryList").then(By.linkText(subCategory)).shouldBeCurrentlyVisible() } Check that this element is visible @FindBy(id=”search”) WebElement searchButton def searchShouldBeEnabled() {     searchButton.shouldBeCurrentlyEnabled() } Check that this element is enabled
  • 91. Smarter Webdriver coding Prefer CSS to XPath
  • 92. Smarter Webdriver coding Use Nested Finds
  • 93. Nested Finds public void chooseCategory(String category) {     findBy("//div[@id='category-select']//a[contains(@class,'arrow')]").click();     findBy("//div[@id='category-select']//a[.,contains($category)]").click(); } Hard to read, hard to maintain public void chooseCategory(String category) {     findBy("#category-select").then(".arrow").then().click();     findBy("#category-select").then(By.linkText(category)).then().click(); } A more readable approach
  • 94. Smarter Webdriver coding Confine Webdriver to your Page Objects
  • 95. Encapsulate Page Objects class ShoppingCartPage extends PageObject {     public ShoppingCartPage(WebDriver driver) {         super(driver) Public API     }     public List<CartItem> getCartItems() {         shoppingCartItems.collect { cartItemfromElement(it) }     } Webdriver stays     @FindBy(css = ".CartContents tbody tr")     private List<WebElement> shoppingCartItems private     private CartItem cartItemfromElement(WebElement element) {         Integer quantity = Integer.parseInt(itemQuantity(element))         String product = productName(element)         BigDecimal itemPrice = priceOf(itemPrice(element))         BigDecimal totalPrice = priceOf(totalPrice(element))         return CartItem.containing(quantity).productsCalled(product).                                              withAnItemPriceOf(itemPrice).                                              andATotalOf(totalPrice)     } ...
  • 96. Smarter Webdriver coding Webdriver Query ~= JDBC Query
  • 97. Learning More sts http://thucydi cydides-webte des.info github.com/thu http://thucydides-webtests.com
  • 98. To ATDD And Beyond Better Automated Acceptance Testing on the JVM Thank You John Ferguson Smart