60
© Copyright 2008, Chris Richardson Colorado Software Summit: October 19 – 24, 2008 I i g t t ith Obj t Improving tests with Object Mothers and Internal DSLs Ch i ih d Chris Richardson Author of POJOs in Action Author of POJOs in Action Founder of Cloud Tools htt // hi ih d t http://www.chrisrichardson.net Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008 1

Improving Tests With Object Mothers And Internal Dsls

Embed Size (px)

DESCRIPTION

Test code needs to be as clean and as simple as production code. However, when writing tests there is the ever present temptation to not be as disciplined as you should be. As a result, test code quality gradually decays over time and becomes difficult to maintain and brittle. For example, a common problem is bloated and duplicated test fixture logic. Another problem is tests that are written at too low-level, which makes them difficult to understand and change. If you are not careful, you run the risk of your test code falling into disrepair and being ignored, which defeats the purpose of having tests.In this talk you will learn how to make tests easier to develop and maintain by using a coding style that abstracts away the details and eliminates code duplication. We describe how to simplify test fixtures by designing domain objects with fluent interfaces, and centralizing test object creation in object mothers. You will also learn how to simplify verification logic with custom assertions. We describe how to improve web tests by writing them in terms of test utility methods, instead of calling Selenium RC directly. These utility methods form an internal domain-specific language that hides low-level details, such as mouse and button clicks.

Citation preview

Page 1: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

I i g t t ith Obj t Improving tests with Object Mothers and Internal DSLs

Ch i i h dChris RichardsonAuthor of POJOs in ActionAuthor of POJOs in ActionFounder of Cloud Tools

htt // h i i h d thttp://www.chrisrichardson.net

Chris Richardson — Improving tests with Object Mothers and DSLs

11/10/2008

1

Page 2: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

OverviewOverview

Writing gmaintainable

tests

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008Copyright (c) 2008 Chris Richardson. All rights

reserved.2

Page 3: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

About ChrisAbout ChrisGrew up in England and live in Oakland, CAp g ,Over twenty years of software development experience

Building object-oriented software since 1986Using Java since 1996Using J2EE since 1999Using J2EE since 1999

Author of POJOs in ActionSpeaker at JavaOne, SpringOne, NFJS, JavaPolis, Spring Experience, etc.Chair of the eBIG Java SIG in Oakland (www ebig org)(www.ebig.org)Run a consulting and training company that helps organizations build better software faster and deploy it on Amazon EC2Founder of Cloud Tools, an open-source project f d l i J li ti A EC2 for deploying Java applications on Amazon EC2: http://code.google.com/p/cloudtools

Chris Richardson — Improving tests with Object Mothers and DSLs Slide 3

Page 4: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

AgendaAgenda

Tests - a double-edged swordTests a double edged swordTaming test fixture logicSimplifying verification codeSimplifying verification codeWriting web testsTesting Ajax applications

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/20084

Page 5: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Why test?Why test?Write new code more easily

A t t h t d i l d Automates what we are doing already -Right!?Run fast unit tests instead of slower web applicationweb applicationUse TDD to incrementally solve a problem

Tests are a safety netTests are a safety netConfidently change existing codeEasier to refactor code to prevent decay

Fewer bugs that impact customers Fewer bugs that impact customers AND development

Chris Richardson — Improving tests with Object Mothers and DSLs 511/10/2008Copyright (c) 2008 Chris Richardson. All rights

reserved.

Page 6: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Why test?Why test?

Increases longevity:g yTestable code is cleaner codeWithout tests your application will decay y pp yand die

Absolutely essential when using a d ldynamic language

Compiler can't catch typos Nothing is too simple to testYou need unit tests

Chris Richardson — Improving tests with Object Mothers and DSLs 611/10/2008

Page 7: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

4 phase tests4 phase testspublic class FooTest

extends TestCase {

Setup

extends TestCase {

public void setUp() {}p

ExerciseVerify

public void testSomething() {// test-specific setup

// Exercise the SUTVerifyTeardown

// Exercise the SUT

// Verification

// test-specific tear down// test specific tear down}

public void tearDown() {}

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/20087

}}

Page 8: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Types of testsTypes of testsDomain model tests

Test your domain objects and

Easy to writeFast to run

Test your domain objects and servicesIn-memory tests

Persistence testsTest manipulating persistent objects

Service integration testsService integration testsTest services using database

Web testsWeb testsAutomatic tests using SeleniumClick and type in the GUI

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/20088

Difficult to writeSlow to run

Page 9: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Example testsExample tests

Walk through some example ptrack Walk through some example ptrack tests

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/20089

Page 10: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

The trouble with testsThe trouble with tests

They make software more difficult to ychange

That's a good thing since they detect bugsBut change is inevitable: new features, refactoringIf you can't easily change the test code:

Slows down the developmentTests are removed or abandoned

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200810

Page 11: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Poor quality test codePoor quality test codeCommon test code smells

Ob T t 't t ll h t t t dObscure Tests – you can't tell what a test doesTest code duplication – copy and paste tests

Badly structured test setup logicComplicated logic to create test fixturesE.g. the test objects (in-memory or in DB)

Sprawling web testsp a g bWeb test framework APIs are very low-level.Easily end up with large amounts of difficult to maintain code: click(),type(),…(), yp (),Lots of duplicationLots of obscure code

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200811

Page 12: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Excellent testing bookExcellent testing bookTest smells and how to fix them

Obscure testFragile testTest code duplication…

Comprehensive pattern language:Four phase testMinimal fixtureMinimal fixtureTest utility methodTest helperHumble Objectj…

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200812

Page 13: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

AgendaAgenda

Tests - a double-edged swordTests a double edged swordTaming test fixture logicSimplifying verification codeSimplifying verification codeWriting web testsTesting Ajax applications

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200813

Page 14: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

What's a fixtureWhat s a fixture

Fixture = everything that needs to be y gin place to test an object/system

Object/system we want to testIts collaborators, required test data etc

Fixture is created by test fixture ylogic:

Code in the test methods themselvesJunit 3.x setUp()JUnit 4/TestNG @Before* annotations

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200814

Page 15: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

The challenge of test fixturesThe challenge of test fixturesCreating an object isn't always easyg j y y

Objects can have lots of attributesObjects are often aggregate rootsnew() is often insufficient

Test fixtures often create multiple objectsf dE.g. money transfer test needs two accounts

Multiple tests need the same set of objectsobjects

• AccountTests, MoneyTransferServiceTests need similar Account objects

Chris Richardson — Improving tests with Object Mothers and DSLs11/10/2008

15

Page 16: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Object graphs can be complicatedcomplicated

We need all We need all of these objects to test a Project!

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200816

Page 17: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Constructing individual objects can be trickycan be tricky

Best way to construct an object is to use y ja non-default constructor:

Supports objects without settersSupports immutable objectsForces you to supply all required objectsConstructor can verify object stateConstructor can verify object state

Limitations of constructors:Lots of constructor arguments ⇒ code is Lots of constructor arguments ⇒ code is difficult to readOptional arguments ⇒ multiple constructors

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200817

Page 18: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

The alternative to constructorsThe alternative to constructors

Project project = new Project(initialStatus);project.setName("Test Project #1");project.setDescription("description");project.setRequirementsContactName("Rick Jones");project.setRequirementsContactEmail("[email protected]");project.setType(ProjectType.EXTERNAL_DB);project.setArtifacts(new ArtifactType[] {

ArtifactType.ARCHITECTURE,ArtifactType.DEPLOYMENT, ArtifactType.MAINTENANCE });

project.setInitiatedBy(user);

Benefits:•Handles optional parameters•Is more readable

But •Lots of noise: 'project.set‘•Breaks encapsulation

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200818

Breaks encapsulation•Object is mutable•Cannot validate state

Page 19: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Constructing objects fluentlyConstructing objects fluentlyProject project = new Project(initialStatus)

("T t P j t #1").name("Test Project #1").description("Excellent project").initiatedBy(user).requirementsContactName("Rick Jones")

i t C t tE il("j @ h ").requirementsContactEmail("[email protected]").type(ProjectType.EXTERNAL_DB).artifact(ArtifactType.ARCHITECTURE).artifact(ArtifactType.DEPLOYMENT)

tif t(A tif tT MAINTENANCE).artifact(ArtifactType.MAINTENANCE);Chained method callsBenefits:

•Less noise•Meaning of each value is clearMeaning of each value is clear

Drawbacks:•Breaks encapsulation – objects must have mutators/setters•Doesn't work with immutable objectsNo opportunity to validate state

Chris Richardson — Improving tests with Object Mothers and DSLs11/10/2008

19

•No opportunity to validate state

Page 20: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Fluently creating immutable objectsobjects

See http://developers sun com/learning/javaoneonlinehttp://developers.sun.com/learning/javaoneonline/2007/pdf/TS-2689.pdf

Project project = new Project.ProjectBuilder(initialStatus)(" j #1").name("Test Project #1")

.description("description")

.initiatedBy(user)

.requirementsContactName("Rick Jones")i C il("j @ h ").requirementsContactEmail("[email protected]")

.type(ProjectType.EXTERNAL_DB)

.artifact(ArtifactType.ARCHITECTURE)

.artifact(ArtifactType.DEPLOYMENT)tif t(A tif tT MAINTENANCE).artifact(ArtifactType.MAINTENANCE)

.make();• Initialize the mutable builder• make() instantiates the domain object via a constructor • Allows entity to be immutable

Chris Richardson — Improving tests with Object Mothers and DSLs11/10/2008

20

• Builder can validate object state

This is an internal DSL

Page 21: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Nested Entity Builder exampleexample

class Project {

public static class ProjectBuilder {private String name;…

public ProjectBuilder(Status initialStatus) {this.status = status;

}

ProjectBuilder name(String name) {this.name = name;return this;return this;

}

public Project make() {// Verify that we have everythingreturn new Project(this);j ( );

}}

public Project(ProjectBuilder builder) {this.name = builder.name;

• Pass builder as the sole constructor argument

Chris Richardson — Improving tests with Object Mothers and DSLs11/10/2008

21

this.initiatedBy = builder.initiatedBy;}

Page 22: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Testing makes your objects smartersmarter

Production code often has simple prequirements:

Create using default constructorAccesses Java bean properties

But tests need smarter objectsjE.g. fluent interfacesCounter to the concept of not having test code in production code

But this is ok: tests are important

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200822

Page 23: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Centralizing test object creation with Object Motherscreation with Object Mothers

Fluent interfaces can help but public class ProjectMother {but

Fixture logic can still be complexSame object created in

lti l t t d li ti

public class ProjectMother {

public static Project makeProjectInProposalState (

Status initialStatus, User user) {multiple tests ⇒ duplication

Eliminate duplication:Centralize object creation in a test utility class called an

) {…

}

public static Project makeProjectInRequirementsState(test utility class called an

Object MotherDefines factory methods for creating fully formed aggregate

makeProjectInRequirementsState(Status initialStatus, User user) {

…}aggregate

Different methods for different aggregate states }

See: http://www xpuniverse com/2001/pdfs/Testing03 pdf

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200823

See: http://www.xpuniverse.com/2001/pdfs/Testing03.pdf

Page 24: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Creating multiple connected aggregatesaggregates

Each Object Mother th d t method creates a

single aggregateBut some tests need to create multiple aggregates with shared sub-aggregatesMust avoid duplicating that

itDepartment = DepartmentMother.makeItDepartment();

itProjectManager = duplicating that code in multiple tests

UserMother.makeProjectManager(itDepartment);itBusinessAnalyst =

UserMother.makeBusinessAnalyst(itDepartment);projectInCompleteState =

ProjectMother.makeProjectInCompleteState(…);projectInRequirementsState =

P j tM th k P j tI R i t St t ( )

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200824

ProjectMother.makeProjectInRequirementsState(…);…

Page 25: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Use stateful object mothersUse stateful object mothers

Test instantiates object motherTest instantiates object motherObject mother's constructor

Creates aggregates (by calling their Object Creates aggregates (by calling their Object Mothers)Stores them in (public) fieldsStores them in (public) fields

Test gets the data it needs from the object motherobject mother

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200825

Page 26: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Example of a stateful motherExample of a stateful motherpublic class ProjectTests extends TestCase {

public class PTrackWorld {

private final Department itDepartment;private final User itProjectManager;private final User itBusinessAnalyst;private final Project projectInCompleteState;

private Project project;private User projectManager;private User businessAnalyst;

protected void setUp() throws Exception {PTrackWorld world = new PTrackWorld();

private final Project projectInCompleteState;

public PTrackWorld() {itDepartment = DepartmentMother.makeItDepartment()'

projectManager = world.getItProjectManager();businessAnalyst = world.getItBusinessAnalyst();project = world.getProjectInProposalState();

}

itDepartment DepartmentMother.makeItDepartment()

itProjectManager = UserMother.makeProjectManager(itDepartment);itBusinessAnalyst = UserMother.makeBusinessAnalyst(itDepartment…

stateMachine = DefaultStateMachineFactory.makeStateMachine("default");initialStatus = stateMachine.getInitialStatus();projectInCompleteState = ProjectMother.makeProjectInCompleteState(initialStatus,

itProjectManager, getAllITDepartmentEmployees());…

}

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200826

}

Page 27: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Object Mothers and databasesObject Mothers and databasesInitialize database

i bj t public class PtrackDatabaseInitializer

using objects created by mothers

implements InitializingBean, DatabaseInitializer {

private HibernateTemplate template;private PTrackWorld world;

public PtrackDatabaseInitializer(HibernateTemplate

Create objects using mothersPersist them

template) {this.template = template;

}

public void afterPropertiesSet() {initializeDatabase();

}

Very easy when using ORMAvoids difficult to

}

public void initializeDatabase() {world = new PTrackWorld();StateMachine stateMachine = world.getStateMachine();template.save(stateMachine);D t t itD t t ld tITD t t()Avoids difficult to

maintain flat files: CSV, SQL, XML

Department itDepartment = world.getITDepartment();template.save(itDepartment);…

}}

Chris Richardson — Improving tests with Object Mothers and DSLs Slide 27

Page 28: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Object Mother designObject Mother design

ChoicesChoicesSame data each time vs. random dataReferenced aggregates as parameters vs Referenced aggregates as parameters vs. create those aggregates too

Tip: use descriptive namesTip: use descriptive namesBad: makeProject1(), makeProject2(), ...Better: makeProjectIn<State>() Better: makeProjectIn<State>(), …

Tip: use Object Mothers from day 1

Chris Richardson — Improving tests with Object Mothers and DSLs 2811/10/2008

Page 29: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Finding balanceFinding balancepublic void testOptionA() {

Sh i C t t ShoppingCart cart = ShoppingCartMother.makeEmptyCart();

cart.add(ProductMother.makeInstockPart(), 1);cart.add(ProductMother.makeBackOrderdPart(), 1);

t dd(P d tM th k Di ti dP t() 1)

Intention revealingBut risks duplication

cart.add(ProductMother.makeDiscontinuedPart(), 1);

…}

public void testOptionB() {ShoppingCart cart =

OR

ShoppingCart cart ShoppingCartMother.makeWithInstockPartBackorderedPartandDiscontinuedPart();

…} Ridiculously long

Chris Richardson — Improving tests with Object Mothers and DSLs 2911/10/2008

} Ridiculously long names???

Page 30: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

AgendaAgenda

Tests - a double-edged swordTests a double edged swordTaming test fixture logicSimplifying verification codeSimplifying verification codeWriting web testsTesting Ajax applications

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200830

Page 31: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Writing readable verification logiclogic

Verification phase verifies that expected t h b bt i doutcome has been obtained

State verification makes assertions about:Returned valueReturned valueState of system under testState of collaborators

Test frameworks provide the basic assert methods but we must:

E d bilitEnsure readabilityAvoid code duplication

Chris Richardson — Improving tests with Object Mothers and DSLs11/10/2008

31

Page 32: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Smelly verification codeSmelly verification codeprotected void setUp() throws Exception {

PTrackWorld world = new PTrackWorld();projectManager = world.getItProjectManager();p j g g j g ();

project = world.getProjectInProposalState();startTime = new Date();

state0 = world.getInitialState();state1 = state0.getApprovalStatus();state2 = state1.getApprovalStatus();

}

public void testChangeStatus() throws InterruptedException {boolean result = project.changeStatus(true,

projectManager, "Excellent");Date endTime = new Date();

assertTrue(result);assertEquals(state1, project.getStatus());assertEquals(1, project.getHistory().size());

O ti ti j t tHi t () t(0)Operation operation = project.getHistory().get(0);assertEquals("Excellent", operation.getComments());assertEquals(projectManager, operation.getUser());assertEquals(state0, operation.getFromStatus());assertEquals(state1, operation.getToStatus());assertFalse(operation.getTimestamp().before(startTime));assertFalse(operation getTimestamp() after(endTime));

Lots of assertions = obscure codeLikely code duplication

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200832

assertFalse(operation.getTimestamp().after(endTime));}

Page 33: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Using Custom AssertionsUsing Custom Assertions

Verification code calls a Test Utility Verification code calls a Test Utility Method that makes assertionsHas an Intention Revealing NameHas an Intention Revealing NameBenefits:

M k th d d blMakes the code more readableEliminates duplication

Chris Richardson — Improving tests with Object Mothers and DSLs 3311/10/2008

Page 34: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Simplified testSimplified testprotected void setUp() throws Exception {…

expectedOperation0 = new Operation(null, projectManager,

state0, state1,"Excellent");

}}

public void testChangeStatus() {

boolean result = project.changeStatus(true, j tM "E ll t")projectManager, "Excellent");

Date endTime = new Date();

assertTrue(result);assertEquals(state1, project.getStatus());q ( , p j g ());

assertHistoryContains(project, startTime, endTime,expectedOperation0);

}

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200834

Page 35: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Custom Assertion MethodsCustom Assertion Methodsprivate void assertHistoryContains(Project project, Date startTime,

Date endTime,Operation... expectedOperations) {

int i = 0;List<Operation> history = project.getHistory();assertEquals(expectedOperations.length, history.size());for (Operation expectedOperation : expectedOperations) {for (Operation expectedOperation : expectedOperations) {Operation operation = history.get(i++);assertOperationEqual(expectedOperation, startTime, endTime, operation);startTime = operation.getTimestamp();

}}

private void assertOperationEqual(Operation expectedOperation, Date startTime, Date endTime, Operation operation) {

assertEquals(expectedOperation.getComments(), operation.getComments());

}

assertEquals(expectedOperation.getComments(), operation.getComments());assertEquals(expectedOperation.getUser(), operation.getUser());assertEquals(expectedOperation.getFromStatus(), operation.getFromStatus());assertEquals(expectedOperation.getToStatus(), operation.getToStatus());assertFalse(operation.getTimestamp().before(startTime));

tF l ( ti tTi t () ft ( dTi ))

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200835

assertFalse(operation.getTimestamp().after(endTime));}

Page 36: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Literate assertions with HamcrestLiterate assertions with HamcrestHamcrest is an open-source frameworkframeworkhttp://code.google.com/p/hamcrest/Readable "literate" assertionsReadable literate assertionsRich set of composable matchersLiterate error messagesgUsed by Jmock expectations

import static org.hamcrest.MatcherAssert.assertThat;import static org hamcrest Matchers is;import static org.hamcrest.Matchers.is;import static org.hamcrest.Matchers.isOneOf;

assertThat(project.getStatus(), is(state1));assertThat(project.getStatus(), isOneOf(state1, state2));

tTh t( j t tSt t () llOf(i ( t t ) t(i ( t t 2))))

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200836

assertThat(project.getStatus(), allOf(is(state), not(is(state2))));

Page 37: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Hamcrest custom matchersHamcrest custom matcherspublic class ProjectMatchers {

public static Matcher<Date> withinInclusivePeriod(final Date startTime,p ( ,final Date endTime) {

return new TypeSafeMatcher<Date>() {

public boolean matchesSafely(Date date) {return !date.before(startTime) && !date.after(endTime);

}

public void describeTo(Description description) {description.appendText(String.format("expected to be between <%s> and <%s>",

startTime, endTime));}

import static org.jia.ptrack.domain.ProjectMatchers.withinInclusivePeriod;};

}

p g j p j ;

public void testChangeStatus() {

assertThat(operation.getTimestamp(), is(withinInclusivePeriod(startTime, endTime)));

}}java.lang.AssertionError: Expected: is expected to be between <Wed Nov 14 19:39:23 PST 2007> and <Wed Nov 14 19:39:23 PST 2007>

got: <Wed Dec 31 16:00:00 PST 1969>at

org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:21)

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200837

g ( j )

Page 38: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

AgendaAgenda

Tests - a double-edged swordTests a double edged swordTaming test fixture logicSimplifying verification codeSimplifying verification codeWriting web testsTesting Ajax applications

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200838

Page 39: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Writing web testsWriting web tests

Web tests simulate the userWeb tests simulate the userFill-in formsClick buttons and linksClick buttons and links

Assertions:Correct page displayedCorrect page displayedCorrect data is displayedP l t i t/ i iblPage elements exist/visible

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200839

Page 40: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Using SeleniumUsing Selenium

Popular web testing p gframeworkLaunches a browser ⇒ full JavascriptsupportSelenium IDE for recording and

i running tests ⇒quickly create tests

Chris Richardson — Improving tests with Object Mothers and DSLs Slide 40

Page 41: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Selenium RCSelenium RC

API for launching and controlling the API for launching and controlling the browserSupports multiple programming Supports multiple programming languageAPI: API:

click(), type()i F P T L d()waitForPageToLoad()

isVisible (), isPresent()

Chris Richardson — Improving tests with Object Mothers and DSLs Slide 41

Page 42: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Selenium RC API + Selenium IDE = troubleIDE = trouble

API is very level:yObscure testsLots of duplication

You can quickly generate tests with Selenium IDETh lThe result:

Large amounts of difficult to maintain test codecodeVery fragile tests ⇒ small change to UI, many broken tests

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200842

Page 43: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

An example of bad test codeAn example of bad test codepublic class ExampleOfBadWebTests extends AbstractSeleniumTest {

@Testbli id t tC t P j t() {public void testCreateProject() {

open("/ptrack/");type("j_username", "proj_mgr");type("j_password", "faces");clickAndWait("Login");assertTextPresent("(" + "proj_mgr" + ")");clickAndWait("link=Create New");String projectName = "XXX Project" + System.currentTimeMillis();type("projectDetails:nameInput", projectName);select("projectDetails:typeSelectOne", "label=External Desktop Application");…

clickAndWait("projectDetails:save");assertTextPresent("Inbox - approve or reject projects");assertTextEquals(projectName, "inboxPage:inboxTable:2:projectName");

clickAndWait("inboxPage:inboxTable:2:details");assertTextPresent(projectName);assertTextPresent("External Desktop Application");( p pp );assertTextEquals("Sean Sullivan", "detailsPage:initiatedBy");….assertTitle("ProjectTrack - Project details");clickAndWait("detailsPage:ok");clickAndWait("link=Logout");assertTextPresent("Welcome to Project Track");

Easy to write – record with Selenium IDEBut imagine coming back to this three

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200843

assertTextPresent( Welcome to Project Track );}

}

But imagine coming back to this three months later ….

Page 44: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Improving tests with utility methodsmethods

Write Test Utility methods = A Domain-Specific Language H i t ti li Have intention revealing namesCall Selenium APIs

Examples:login()goto…()assertOn…Page()logout()

Write tests in terms of those methodsMove those methods into a common superclassMove those methods into a common superclasswhen appropriate

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200844

Page 45: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Improved exampleImproved examplepublic class ImprovedExampleOfTests extends AbstractSeleniumTest {

@Testpublic void testCreateProject() {

login();

private void createProject() {clickAndWait("link=Create New");projectName = "XXX Project" +

System.currentTimeMillis();type("projectDetails:nameInput" projectName);

createProject();assertProjectDisplayedInInbox();

viewProjectDetails();assertProjectDetailsDisplayed();

type( projectDetails:nameInput , projectName);select("projectDetails:typeSelectOne",

"label=External Desktop Application");type("projectDetails:requirementsInput",

"Chris Richardson");type("projectDetails:requirementsEmailInput",assertProjectDetailsDisplayed();

returnToInbox();logout();

}

yp ( p j q p ,"[email protected]");

…clickAndWait("projectDetails:save");

}

} Test is a lot more readableIntention is clear

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200845

Less duplicationLess fragile

Page 46: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Intelligently evolve the languageIntelligently evolve the language

Write/record tests using low-level APIsWrite/record tests using low level APIsUse Extract Method refactoring to create the utility methodscreate the utility methodsMove methods into

A lA common superclassTest Helper classes

Chris Richardson — Improving tests with Object Mothers and DSLs 4611/10/2008

Page 47: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Better ways to handle test dataBetter ways to handle test data

Web tests need data tooWeb tests need data tooFilling in formsAsserting the contents of the pageAsserting the contents of the page

Data embedded in codeTest data is sprinkled through applicationTest data is sprinkled through applicationDifficult to manageD li tiDuplication…

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200847

Page 48: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Example helper methodExample helper methodpublic class ExampleOfWebTests extends AbstractSeleniumTest {p p {

private String projectName;

public void createProject() {public void createProject() {clickAndWait("link=Create New");projectName = "XXX Project" + System.currentTimeMillis();type("projectDetails:nameInput", projectName);select("projectDetails:typeSelectOne",

"label=External Desktop Application");type("projectDetails:requirementsInput", "Chris Richardson");…clickAndWait("projectDetails:save");

}}

Chris Richardson — Improving tests with Object Mothers and DSLs Slide 48

Page 49: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Using domain objects in web teststests

Test utility methods:Test utility methods:Use domain objects created by mothersPopulate formsPopulate formsVerify the contents of a page

Benefits:Benefits:Improves readabilityI t f t t d tImproves management of test dataParameterized methods are reusable

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200849

Page 50: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

A much better example

public class ExampleOfGoodWebTests extends AbstractSeleniumTest {public class ExampleOfGoodWebTests extends AbstractSeleniumTest {

@Testpublic void testCreateProject() {

login();P j t j tT C t P j tM th k N P j t()Project projectToCreate = ProjectMother.makeNewProject();

createProject(projectToCreate);assertProjectDisplayedInInbox(projectToCreate);

viewProjectDetails(projectToCreate);assertProjectDetailsDisplayed(projectToCreate);

returnToInbox();logout(); i t id tP j tD t il Di l d(P j t j tT C t ) {logout();

}

}

private void assertProjectDetailsDisplayed(Project projectToCreate) {assertTextPresent(projectToCreate.getName());assertTextPresent(projectToCreate.getType().getDescription());assertTextEquals(projectToCreate.getInitiatedBy().getFullName(),

"detailsPage:initiatedBy");

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200850

…}

Page 51: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

AgendaAgenda

Tests - a double-edged swordTests a double edged swordTaming test fixture logicSimplifying verification codeSimplifying verification codeWriting web testsTesting Ajax applications

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200851

Page 52: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

The challenge of AjaxThe challenge of AjaxAjax applications behave differentlyj pp yJavaScript executes after the page loads ⇒ less deterministic, predictable behaviorClicks don't result in page loads

Triggers an Ajax request that updates the same page

DOM nodes are often hidden rather than non-existentnon-existent

assertElementPresent() ⇒ true even when the element is not visible

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200852

Page 53: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Testing Ajax applicationsTesting Ajax applications

Bad approach:Bad approach:Put lots of long sleeps in your codeSlows down the tests unnecessarilySlows down the tests unnecessarily

Improved approach:Loop testing for element visibility with a Loop testing for element visibility with a short sleepUse Selenium RC "wait" featureUse Selenium-RC wait feature

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200853

Page 54: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Messy ExampleMessy Exampleprotected void waitForVisibility(String selector) {

WaitForElementVisible waiter = new WaitForElementVisible(selector);( );try {

waiter.wait(String.format("Cannot find element <%s>", selector), 3000);} catch (Wait.WaitTimedOutException e) {

return;}

} public void testSomething() {}

class WaitForElementVisible extends Wait {private final String selector;

public WaitForElementVisible(String selector) {this selector = selector;

…waitForVisibility("someForm");assertTrue(selenium.isVisible("someForm"));…

this.selector = selector;}

@Overridepublic boolean until() {

return selenium.isElementPresent(selector) && selenium.isVisible(selector);}

}

}}

This works but the code quickly becomes

Chris Richardson — Improving tests with Object Mothers and DSLs Slide 54

This works but the code quickly becomes cluttered with calls to waitForXXX()

Page 55: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Better: Implementation #2Better: Implementation #2

Encapsulate the waiting/loop within Encapsulate the waiting/loop within Test Utility methods, e.g.:

assertElementVisible( )assertElementVisible(…)

Benefits:Simplifies the test codeSimplifies the test code

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200855

Page 56: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

A simple exampleA simple exampleprotected void waitForVisibility(String selector) {WaitForElementVisible waiter = new WaitForElementVisible(selector);WaitForElementVisible waiter = new WaitForElementVisible(selector);try {waiter.wait(String.format("Cannot find element <%s>", selector), 3000);

} catch (Wait.WaitTimedOutException e) {return;

}}

class WaitForElementVisible extends Wait {

public void testSomething() {…assertElementVisible("someForm");

……}

public void assertElementVisible(String selector) {i F Vi ibili ( l )

}

waitForVisibility(selector);assertTrue(selenium.isVisible(selector));

}

Simplifies the test code

Chris Richardson — Improving tests with Object Mothers and DSLs Slide 56

Simplifies the test code

Page 57: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Summary of web testing architecturearchitecture

T tTestsApplication-specific Utility methodsApplication specific Utility methods

e.g. login(), logout(), …

Wrapped selenium APIsWrapped selenium APIs

e.g. hides Ajax related timing issues etc

Selenium RC

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200857

Page 58: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

Some useful frameworksSome useful frameworks

Umangite – Selenium web testsUmangite Selenium web testscode.google.com/p/umangite/

ORMUnit – Persistence testsORMUnit – Persistence testscode.google.com/p/ormunit

A id POJO G i DAOArid POJOs – Generic DAOscode.google.com/p/aridpojos/

And, others:code.google.com/u/chris.e.richardson/

Chris Richardson — Improving tests with Object Mothers and DSLs Slide 58

Page 59: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

SummarySummaryMessy tests will kill your applicationy y ppAggressively refactor tests to keep them simpleDefine classes with fluent interfacesUse Object Mothers to avoid duplication of test fixture logicAggressively use Test Utility Methods:

Simplify web testsHide Ajax-related issues

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200859

Page 60: Improving Tests With Object Mothers And Internal Dsls

© Copyright 2008, Chris RichardsonColorado Software Summit: October 19 – 24, 2008

For more informationFor more informationBuy my book ☺

G t iGo to manning.com

Send email:

[email protected]

Visit my website:Visit my website:

http://www.chrisrichardson.net

Talk to me about consulting and training

Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/200860