Developer Testing Tools Roundup

Preview:

DESCRIPTION

An overview of testing tools and techniques available to Java developers

Citation preview

Java Testing Tools Roundup

What will we cover todayHow to name your testsHamcrest MatchersParameterized testsJUnit RulesMockitoSpockGebWeb testingThucydides and easyb

Agenda

Most importantly...

Practice Test Driven Development

Name your tests well

What’s in a name

"What's in a name? That which we call a roseBy any other name would smell as sweet."

Romeo and Juliet (II, ii, 1-2)

The 10 5 Commandments of Test WritingI. Don’t say “test, say “should” insteadII. Don’t test your classes, test their behaviourIII. Test class names are important tooIV. Structure your tests wellV. Tests are deliverables too

What’s in a name

Don’t use the word ‘test’ in your test names

What’s in a name

testBankTransfer()

testWithdraw()

testDeposit()

testBankTransfer()

testWithdraw()

testDeposit()

Do use the word ‘should’ in your test names

What’s in a name

tranferShouldDeductSumFromSourceAccountBalance()

transferShouldAddSumLessFeesToDestinationAccountBalance()

depositShouldAddAmountToAccountBalance()

Your test class names should represent context

What’s in a name

When is this behaviour applicable?

What behaviour are we testing?

Write your tests consistently‘Given-When-Then’ or ‘Arrange-Act-Assert’ (AAA)

What’s in a name

@Test public void aDeadCellWithOneLiveNeighbourShouldRemainDeadInTheNextGeneration() { String initialGrid = "...\n" + ".*.\n" + "...";

String expectedNextGrid = "...\n" + "...\n" + "...\n";

Universe theUniverse = new Universe(seededWith(initialGrid)); theUniverse.createNextGeneration(); String nextGrid = theUniverse.getGrid(); assertThat(nextGrid, is(expectedNextGrid)); }

Prepare the test data (“arrange”)

Do what you are testing (“act”)

Check the results (“assert”)

Tests are deliverables too - respect them as suchRefactor, refactor, refactor!Clean and readable

What’s in a name

Express Yourself with HamcrestWhy write this...

when you can write this...

import static org.junit.Assert.*;...assertEquals(10000, calculatedTax, 0);

import static org.hamcrest.Matchers.*;...assertThat(calculatedTax, is(10000));

“Assert that are equal 10000 and calculated tax (more or less)” ?!

Don’t I just mean “assert that calculated tax is 10000”?

Express Yourself with HamcrestWith Hamcrest, you can have your cake and eat it!

String color = "red"; assertThat(color, is("blue"));

assertThat(calculatedTax, is(expectedTax));

Readable asserts

String[] colors = new String[] {"red","green","blue"};String color = "yellow";assertThat(color, not(isIn(colors)));

Informative errors

Flexible notation

Express Yourself with HamcrestMore Hamcrest expressiveness

String color = "red";assertThat(color, isOneOf("red",”blue”,”green”));

List<String> colors = new ArrayList<String>();colors.add("red");colors.add("green");colors.add("blue");assertThat(colors, hasItem("blue"));

assertThat(colors, hasItems("red”,”green”));

assertThat(colors, hasItem(anyOf(is("red"), is("green"), is("blue"))));

Home-made Hamcrest MatchersCustomizing and extending HamcrestCombine existing matchersOr make your own!

Home-made Hamcrest MatchersCustomizing Hamcrest matchersYou can build your own by combining existing Matchers...

List stakeholders = stakeholderManager.findByName("Health");Matcher<Stakeholder> calledHealthCorp = hasProperty("name", is("Health Corp"));assertThat(stakeholders, hasItem(calledHealthCorp));

Create a dedicated Matcher for the Stakeholder class

Use matcher directly with hasItem()

“The stakeholders list has (at least) one item with the name property set to “Health Corp””

Home-made Hamcrest MatchersWriting your own matchers in three easy steps!

public class WhenIUseMyCustomHamcrestMatchers { @Test public void thehasSizeMatcherShouldMatchACollectionWithExpectedSize() { List<String> items = new ArrayList<String>(); items.add("java"); assertThat(items, hasSize(1)); }}

We want something like this...

I want a matcher that checks the size of a collection

Home-made Hamcrest MatchersWriting your own matchers in three easy steps!

public class HasSizeMatcher extends TypeSafeMatcher<Collection<? extends Object>> { private Matcher<Integer> matcher;

public HasSizeMatcher(Matcher<Integer> matcher) { this.matcher = matcher; }

public boolean matchesSafely(Collection<? extends Object> collection) { return matcher.matches(collection.size()); }

public void describeTo(Description description) { description.appendText("a collection with a size that is"); matcher.describeTo(description); }}

Extend the TypeSafeMatcher class

Provide expected values in the constructor

Do the actual matching

Describe our expectations

So let’s write this Matcher!

Home-made Hamcrest MatchersWriting your own matchers in three easy steps!

import java.util.Collection;import org.hamcrest.Factory;import org.hamcrest.Matcher;

public class MyMatchers { @Factory public static Matcher<Collection<? extends Object>> hasSize(Matcher<Integer> matcher){ return new HasSizeMatcher(matcher); }}

Use a factory class to store your matchers

All my custom matchers go in a special Factory class

Home-made Hamcrest MatchersWriting your own matchers in three easy steps!

import static com.wakaleo.gameoflife.hamcrest.MyMatchers.hasSize;import static org.hamcrest.MatcherAssert.assertThat;

public class WhenIUseMyCustomHamcrestMatchers {

@Test public void thehasSizeMatcherShouldMatchACollectionWithExpectedSize() { List<String> items = new ArrayList<String>(); items.add("java"); assertThat(items, hasSize(1)); }}

Hamcrest-style error messages

Home-made Hamcrest MatchersBut wait! There’s more!

@Test public void weCanUseCustomMatchersWithOtherMatchers() { List<String> items = new ArrayList<String>(); items.add("java"); assertThat(items, allOf(hasSize(1), hasItem("java"))); }

Combining matchers

@Test public void weCanUseCustomMatchersWithOtherMatchers() { List<String> items = new ArrayList<String>(); items.add("java"); items.add("groovy"); assertThat(items, hasSize(greaterThan(1))); }

Nested matchers

Using Parameterized Tests

Data-Driven Unit Tests

Using Parameterized TestsParameterized tests - for data-driven testingTake a large set of test data, including an expected resultDefine a test that uses the test dataVerify calculated result against expected result

{2, 0, 0}{2, 1, 2}{2, 2, 4}{2, 3, 6}{2, 4, 8}{2, 5, 10}{2, 6, 12}{2, 7, 14}

...Data

Test

x = a * b

Verify

Using Parameterized TestsParameterized testsExample: Calculating income tax

Using Parameterized Tests

Parameterized tests with JUnit 4.8.1What you need:Some test dataA test class with matching fields And some testsAnd an annotation

Income Expected Tax$0.00 $0.00$10,000.00 $1,250.00$14,000.00 $1,750.00$14,001.00 $1,750.21$45,000.00 $8,260.00$48,000.00 $8,890.00$48,001.00 $8,890.33$65,238.00 $14,578.54$70,000.00 $16,150.00$70,001.00 $16,150.38$80,000.00 $19,950.00$100,000.00 $27,550.00

public class TaxCalculatorDataTest { private double income; private double expectedTax; public TaxCalculatorDataTest(double income, double expectedTax) { this.income = income; this.expectedTax = expectedTax; }}

public class TaxCalculatorDataTest { private double income; private double expectedTax; public TaxCalculatorDataTest(double income, double expectedTax) { super(); this.income = income; this.expectedTax = expectedTax; } @Test public void shouldCalculateCorrectTax() {...}}

@RunWith(Parameterized.class)public class TaxCalculatorDataTest { private double income; private double expectedTax; public TaxCalculatorDataTest(double income, double expectedTax) { super(); this.income = income; this.expectedTax = expectedTax; } @Test public void shouldCalculateCorrectTax() {...}}

Income Expected Tax$0.00 $0.00$10,000.00 $1,250.00$14,000.00 $1,750.00$14,001.00 $1,750.21$45,000.00 $8,260.00$48,000.00 $8,890.00$48,001.00 $8,890.33$65,238.00 $14,578.54$70,000.00 $16,150.00$70,001.00 $16,150.38$80,000.00 $19,950.00$100,000.00 $27,550.00

Using Parameterized TestsHow it works@RunWith(Parameterized.class)public class TaxCalculatorDataTest { private double income; private double expectedTax;

@Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][] { { 0.00, 0.00 }, { 10000.00, 1250.00 }, { 14000.00, 1750.00 }, { 14001.00, 1750.21 }, { 45000.00, 8260.00 }, { 48000.00, 8890.00 }, { 48001.00, 8890.33 }, { 65238.00, 14578.54 }, { 70000.00, 16150.00 }, { 70001.00, 16150.38 }, { 80000.00, 19950.00 }, { 100000.00, 27550.00 }, }); }

public TaxCalculatorDataTest(double income, double expectedTax) { super(); this.income = income; this.expectedTax = expectedTax; }

@Test public void shouldCalculateCorrectTax() { TaxCalculator calculator = new TaxCalculator(); double calculatedTax = calculator.calculateTax(income); assertThat(calculatedTax, is(expectedTax)); }}

This is a parameterized test

The @Parameters annotation indicates the test data

The constructor takes the fields from the test data

The unit tests use data from these fields.

Parameterized Tests in EclipseRun the test only onceEclipse displays a result for each data set

Income Expected Tax$0.00 $0.00$10,000.00 $1,250.00$14,000.00 $1,750.00$14,001.00 $1,750.21$45,000.00 $8,260.00$48,000.00 $8,890.00$48,001.00 $8,890.33$65,238.00 $14,578.54$70,000.00 $16,150.00$70,001.00 $16,150.38$80,000.00 $19,950.00$100,000.00 $27,550.00

Using Parameterized Tests

Using Parameterized TestsExample: using an Excel Spreadsheet

@Parameterspublic static Collection spreadsheetData() throws IOException { InputStream spreadsheet = new FileInputStream("src/test/resources/aTimesB.xls"); return new SpreadsheetData(spreadsheet).getData();}

Using Existing and Custom JUnit RulesCustomize and control how JUnit behaves

JUnit Rules

The Temporary Folder Rule

JUnit Rules

public class LoadDynamicPropertiesTest {

@Rule public TemporaryFolder folder = new TemporaryFolder();

private File properties;

@Before public void createTestData() throws IOException { properties = folder.newFile("messages.properties"); BufferedWriter out = new BufferedWriter(new FileWriter(properties)); // Set up the temporary file out.close(); }

@Test public void shouldLoadFromPropertiesFile() throws IOException { DynamicMessagesBundle bundle = new DynamicMessagesBundle(); bundle.load(properties); // Do stuff with the temporary file }}

Create a temporary folder

Use this folder in the tests

The folder will be deleted afterwards

Prepare some test data

JUnit RulesThe ErrorCollector RuleReport on multiple error conditions in a single test

public class ErrorCollectorTest {

@Rule public ErrorCollector collector = new ErrorCollector(); @Test public void testSomething() { collector.addError(new Throwable("first thing went wrong")); collector.addError(new Throwable("second thing went wrong")); String result = doStuff(); collector.checkThat(result, not(containsString("Oh no, not again"))); }

private String doStuff() { return "Oh no, not again"; }}

Two things went wrong here

Check using Hamcrest matchers

public class ErrorCollectorTest {

@Rule public ErrorCollector collector = new ErrorCollector(); @Test public void testSomething() { collector.addError(new Throwable("first thing went wrong")); collector.addError(new Throwable("second thing went wrong")); String result = doStuff(); collector.checkThat(result, not(containsString("Oh no, not again"))); }

private String doStuff() { return "Oh no, not again"; }}

JUnit RulesThe ErrorCollector RuleReport on multiple error conditions in a single test

All three error messages are reported

JUnit RulesThe Timeout RuleDefine a timeout for all tests

public class GlobalTimeoutTest {

@Rule public MethodRule globalTimeout = new Timeout(1000); @Test public void testSomething() { for(;;); }

@Test public void testSomethingElse() { }}

No test should take longer than 1 second

Oops

Parallel testsSetting up parallel tests with JUnit and Maven

<project...> <plugins> ... <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.5</version> <configuration> <parallel>methods</parallel> </configuration> </plugin> </plugins> ... <build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId>

<version>4.8.1</version> <scope>test</scope> </dependency> </dependencies> </build> ...</project>

Needs JUnit 4.8.1 or better

Needs Surefire 2.5

‘methods’, ‘classes’, or ‘both’

Continuous TestingContinuous Tests with InfinitestInfinitest is a continuous test tool for Eclipse and IntelliJRuns your tests in the background when you save your code

Continuous TestingUsing InfinitestWhenever you save your file changes, unit tests will be rerun

Error message about the failed test

Project containing an error

Failing test

Mocking with styleMockito - lightweight Java mocking

Account

balancenumbergetFees(feeType)

import static org.mockito.Mockito.*;....Account accountStub = mock(Account.class);when(accountStub.getFees(FeeType.ACCOUNT_MAINTENANCE)).thenReturn(4.00);when(accountStub.getFees(FeeType.TRANSACTION_FEE)).thenReturn(0.50);

assertThat(accountStub.getFees(FeeType.TRANSACTION_FEE), is(0.50));

Low-formality mocking

Mocking with style

Mockito - lightweight Java mocking

AccountDao accountDao = mock(AccountDao.class);Account newAccount = mock(Account.class);when(accountDao.createNewAccount(123456)).thenReturn(newAccount) .thenThrow(new AccountExistsException() );

Manage successive calls

AccountDao

createNewAccount(String id)

Mocking with style

Mockito - lightweight Java mocking

@Mock private AccountDao accountDao...

Mockito annotations

AccountDao

createNewAccount(String id)

Mocking with style

Mockito - lightweight Java mocking

when(accountStub.getEarnedInterest(intThat(greaterThan(2000)))).thenReturn(10.00);

Account

balancenumbergetEarnedInterest(year)

Use matchers

Mocking with style

Mockito - lightweight Java mocking

@Mock private AccountDao accountDao...// Test stuff...verify(accountDao).createNewAccount( (String) isNotNull());

Verify interactions

AccountDao

createNewAccount(String id)

Spock - Unit BDD in GroovySpecifications in Groovyimport spock.lang.Specification;

class RomanCalculatorSpec extends Specification { def "I plus I should equal II"() { given: def calculator = new RomanCalculator() when: def result = calculator.add("I", "I") then: result == "II" }}

Specifications, not tests

Spock - Unit BDD in GroovySpecifications in Groovy def "I plus I should equal II"() { when: "I add two roman numbers together" def result = calculator.add("I", "I") then: "the result should be the roman number equivalent of their sum" result == "II" }

BDD-style

Spock - Unit BDD in GroovySpecifications in Groovy def "I plus I should equal II"() { when: "I add two roman numbers together" def result = calculator.add("I", "I") then: "the result should be the roman number equivalent of their sum" result == "II" }

BDD-style

This is the assert

I plus I should equal II(com.wakaleo.training.spocktutorial.RomanCalculatorSpec) Time elapsed: 0.33 sec <<< FAILURE!Condition not satisfied:

result == "II"| |I false 1 difference (50% similarity) I(-) I(I)

at com.wakaleo.training.spocktutorial .RomanCalculatorSpec.I plus I should equal II(RomanCalculatorSpec.groovy:17)

Spock - Unit BDD in GroovySpecifications in Groovydef "The lowest number should go at the end"() { when: def result = calculator.add(a, b)

then: result == sum where: a | b | sum "X" | "I" | "XI" "I" | "X" | "XI" "XX" | "I" | "XXI" "XX" | "II" | "XXII" "II" | "XX" | "XXII" }

Data-driven testing

Spock - Unit BDD in GroovySpecifications in Groovydef "Messages published by the publisher should only be received by active subscribers"() {

given: "a publisher" def publisher = new Publisher()

and: "some active subscribers" Subscriber activeSubscriber1 = Mock() Subscriber activeSubscriber2 = Mock()

activeSubscriber1.isActive() >> true activeSubscriber2.isActive() >> true

publisher.add activeSubscriber1 publisher.add activeSubscriber2

and: "a deactivated subscriber" Subscriber deactivatedSubscriber = Mock() deactivatedSubscriber.isActive() >> false publisher.add deactivatedSubscriber

when: "a message is published" publisher.publishMessage("Hi there")

then: "the active subscribers should get the message" 1 * activeSubscriber1.receive("Hi there") 1 * activeSubscriber2.receive({ it.contains "Hi" })

and: "the deactivated subscriber didn't receive anything" 0 * deactivatedSubscriber.receive(_)}

Setting up mocks

Asserts on mocks

Geb - Groovy Page Objects

DSL for WebDriver web testingimport geb.*   Browser.drive("http://google.com/ncr") {  assert title == "Google"   // enter wikipedia into the search field  $("input", name: "q").value("wikipedia")   // wait for the change to results page to happen  // (google updates the page without a new request)  waitFor { title.endsWith("Google Search") }   // is the first link to wikipedia?  def firstLink = $("li.g", 0).find("a.l")  assert firstLink.text() == "Wikipedia"   // click the link  firstLink.click()   // wait for Google's javascript to redirect  // us to Wikipedia  waitFor { title == "Wikipedia" }}

Concise expression language

Higher level than WebDriver

Power asserts

The story of your app

ATDDor Specification by example

As a job seekerI want to find jobs in relevant categoriesSo that I can find a suitable job

User stories

Features/Epics

As a job seekerI want to find jobs in relevant categoriesSo that I can find a suitable job

User stories

☑  The  job  seeker  can  see  available  categories  on  the  home  page☑  The  job  seeker  can  look  for  jobs  in  a  given  category☑  The  job  seeker  can  see  what  category  a  job  belongs  to

Acceptance criteria

As a job seekerI want to find jobs in relevant categoriesSo that I can find a suitable job

User stories

☑  The  job  seeker  can  see  available  categories  on  the  home  page☑  The  job  seeker  can  look  for  jobs  in  a  given  category☑  The  job  seeker  can  see  what  category  a  job  belongs  to

scenario "A job seeker can see the available job categories on the home page",{ when "the job seeker is looking for a job", then "the job seeker can see all the available job categories"}

Automated acceptance test

Acceptance criteria

scenario "A job seeker can see the available job categories on the home page",{ when "the job seeker is looking for a job", then "the job seeker can see all the available job categories"}

Implemented development tests Implemented acceptance tests

Automated acceptance test

or how not to have web tests like this

The art of sustainable web tests

The Three Ways of Automated Web Testing

Record/Replay

Scripting

Page Objects

Record-replay automated tests

Promise Reality

Record-replay automated tests

Script-based automated tests

Selenium RC

HTMLUnit

Canoe Webtest

JWebUnit

Watir

Script-based automated tests

Selenium RC

HTMLUnit

Canoe Webtest

JWebUnit

Watir

How about Page Objects?

Hide unnecessary detail

Reusable

Low maintenance

2

A sample Page ObjectA web page

A sample Page Object

lookForJobsWithKeywords(values : String)getJobTitles() : List<String>

FindAJobPage

A Page Object

A sample Page Object

public class FindAJobPage extends PageObject {

WebElement keywords; WebElement searchButton;

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

public void lookForJobsWithKeywords(String values) { typeInto(keywords, values); searchButton.click(); }

public List<String> getJobTitles() { List<WebElement> tabs = getDriver() .findElements(By.xpath("//div[@id='jobs']//a")); return extract(tabs, on(WebElement.class).getText()); }}

An implemented Page Object

A sample Page Object

public class WhenSearchingForAJob {

@Test public void searching_for_a_job_should_display_matching_jobs() { FindAJobPage page = new FindAJobPage(); page.open("http://localhost:9000"); page.lookForJobsWithKeywords("Java"); assertThat(page.getJobTitles(), hasItem("Java Developer")); }}

A test using this Page Object

Sustainable web tests

Are we there yet?

The high-level view

Acceptance Tests

So  where  are  we  at?

Page Objects

Implementation focus

Page  Objects  rock!

How do we bridge the gap?

How do we bridge the gap?

Test steps

scenario "A job seeker can see the available job categories on the home page",{ when "the job seeker is looking for a job", then "the job seeker can see all the available job categories"}

Automated

scenario "The user can see the available job categories on the home page",{ when "the job seeker is looking for a job", { job_seeker.open_jobs_page() } then "the job seeker can see all the available job categories", { job_seeker.should_see_job_categories "Java Developers", "Groovy Developers" }}

Implemented

JobSeekerSteps

open_jobs_page()should_see_job_categories(String...  categories)...

JobSeekerSteps

open_jobs_page()should_see_job_categories(String...  categories)...

JobSeekerSteps

open_jobs_page()should_see_job_categories(String...  categories)...

Step libraries

scenario "The user can see the available job categories on the home page",{ when "the job seeker is looking for a job", { job_seeker.open_jobs_page() } then "the job seeker can see all the available job categories", { job_seeker.should_see_job_categories "Java Developers", "Groovy Developers" }}

JobSeekerSteps

open_jobs_page()should_see_job_categories(String...  categories)...

JobSeekerSteps

open_jobs_page()should_see_job_categories(String...  categories)...

JobSeekerSteps

open_jobs_page()should_see_job_categories(String...  categories)...

Step libraries

Page Objects

Implemented Tests

help organize your tests

Test steps

are a communication tool

Test steps

are reusable building blocks

Test steps

Test steps

help estimate progress

And so we built a tool...

Webdriver/Selenium 2 extension

Organize tests, stories and features

Measure functional coverage

Record/report test execution

Thucydides in action A simple demo app

Defining your acceptance tests

scenario "The administrator deletes a category from the system",{ given "a category needs to be deleted", when "the administrator deletes a category", then "the system will confirm that the category has been deleted", and "the deleted category should no longer be visible to job seekers",}

focus on business value

scenario "The administrator adds a new category to the system",{ given "a new category needs to be added to the system", when "the administrator adds a new category", then "the system should confirm that the category has been created", and "the new category should be visible to job seekers",}

...defined in business terms

scenario "A job seeker can see the available job categories on the home page",

{ when "the job seeker is looking for a job", then "the job seeker can see all the available job categories"}

High level requirements...

Organizing your requirements

public class Application {

@Feature public class ManageCompanies { public class AddNewCompany {} public class DeleteCompany {} public class ListCompanies {} }

@Feature public class ManageCategories { public class AddNewCategory {} public class ListCategories {} public class DeleteCategory {} }

@Feature public class BrowseJobs { public class UserLookForJobs {} public class UserBrowsesJobTabs {} }}

Features

Stories

Implementing your acceptance testsusing "thucydides"thucydides.uses_steps_from AdministratorStepsthucydides.uses_steps_from JobSeekerStepsthucydides.tests_story AddNewCategory

scenario "The administrator adds a new category to the system",{ given "a new category needs to be added to the system", { administrator.logs_in_to_admin_page_if_first_time() administrator.opens_categories_list() } when "the administrator adds a new category", { administrator.selects_add_category() administrator.adds_new_category("Scala Developers","SCALA") } then "the system should confirm that the category has been created", { administrator.should_see_confirmation_message "The Category has been created" } and "the new category should be visible to job seekers", { job_seeker.opens_jobs_page() job_seeker.should_see_job_category "Scala Developers" }}

We are testing this story

Narrative style

Step through an example

An acceptance criteria

Still high-level

Some folks prefer JUnit...@RunWith(ThucydidesRunner.class)@Story(AddNewCategory.class)public class AddCategoryStory {

@Managed public WebDriver webdriver;

@ManagedPages(defaultUrl = "http://localhost:9000") public Pages pages;

@Steps public AdministratorSteps administrator;

@Steps public JobSeekerSteps job_seeker;

@Test public void administrator_adds_a_new_category_to_the_system() { administrator.logs_in_to_admin_page_if_first_time(); administrator.opens_categories_list(); administrator.selects_add_category(); administrator.adds_new_category("Java Developers","JAVA"); administrator.should_see_confirmation_message("The Category has been created");

job_seeker.opens_job_page(); job_seeker.should_see_job_category("Java Developers"); }

@Pending @Test public void administrator_adds_an_existing_category_to_the_system() {}}

Thucydides handles the web driver instances

Using the same steps

Tests can be pending

Defining your test stepspublic class AdministratorSteps extends ScenarioSteps {

@Step public void opens_categories_list() { AdminHomePage page = getPages().get(AdminHomePage.class); page.open(); page.selectObjectType("Categories"); }

@Step public void selects_add_category() { CategoriesPage categoriesPage = getPages().get(CategoriesPage.class); categoriesPage.selectAddCategory(); }

@Step public void adds_new_category(String label, String code) { EditCategoryPage newCategoryPage = getPages().get(EditCategoryPage.class); newCategoryPage.saveNewCategory(label, code); }

@Step public void should_see_confirmation_message(String message) { AdminPage page = getPages().get(AdminPage.class); page.shouldContainConfirmationMessage(message); }

@StepGroup public void deletes_category(String name) { opens_categories_list(); displays_category_details_for(name); deletes_category(); }}

A step library

High level steps...

...implemented with Page Objects

...or with other steps

Defining your page objectspublic class EditCategoryPage extends PageObject {

@FindBy(id="object_label") WebElement label;

@FindBy(id="object_code") WebElement code;

@FindBy(name="_save") WebElement saveButton;

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

public void saveNewCategory(String labelValue, String codeValue) { typeInto(label, labelValue); typeInto(code, codeValue); saveButton.click(); }}

Provides some useful utility methods...

but otherwise a normal WebDriver Page Object

Data-driven testing

categories.csv

public class DataDrivenCategorySteps extends ScenarioSteps { public DataDrivenCategorySteps(Pages pages) { super(pages); }

private String name; private String code;

@Steps public AdminSteps adminSteps;

public void setCode(String code) {...} public void setName(String name) {...}

@Step public void add_a_category() { adminSteps.add_category(name, code); }}

Test data

Test steps

Data-driven testing

categories.csv

public class DataDrivenCategorySteps extends ScenarioSteps { public DataDrivenCategorySteps(Pages pages) { super(pages); }

private String name; private String code;

@Steps public AdminSteps adminSteps;

public void setCode(String code) {...} public void setName(String name) {...}

@Step public void add_a_category() { adminSteps.add_category(name, code); }}

@Steps public DataDrivenCategorySteps categorySteps;

@Test public void adding_multiple_categories() throws IOException { steps.login_to_admin_page_if_first_time(); steps.open_categories_list();

withTestDataFrom("categories.csv").run(categorySteps).add_a_category(); }

Test data

Test steps

Call this step for each row

Now run your tests

Displaying the results in easyb

Displaying the results in easyb

Thucydides reports

Browse the features

Browse the stories

Browse the stories

Browse the stories

Browse the test scenarios

Illustrating the test paths

Test scenarios

A test scenario

Steps

Illustrating the test paths

Test scenarios

Test scenario

Steps

Screenshots

Functional coverage

Features

Functional coverage

User stories

Functional coverage

Passing tests

Failing tests

Pending tests

Functional coverage

Test scenarios

Functional coverage

Thank you!

John Ferguson SmartEmail: john.smart@wakaleo.com

Web: http://www.wakaleo.comTwitter: wakaleo

Recommended