Upload
chris-richardson
View
1.824
Download
1
Tags:
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
© 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
© 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
© 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
© 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
© 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.
© 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
© 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
}}
© 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
© 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
© 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
© 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
© 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
© 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
© 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
© 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
© 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
© 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
© 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
© 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
© 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
© 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;}
© 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
© 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
© 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(…);…
© 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
© 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
}
© 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
© 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
© 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???
© 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
© 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
© 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));}
© 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
© 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
© 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));}
© 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))));
© 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 )
© 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
© 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
© 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
© 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
© 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
© 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 ….
© 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
© 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
© 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
© 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
© 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
© 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
© 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
…}
© 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
© 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
© 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
© 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()
© 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
© 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
© 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
© 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
© 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
© 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:
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