Improving Tests With Object Mothers And Internal Dsls
Upcoming SlideShare
Loading in...5
×
 

Improving Tests With Object Mothers And Internal Dsls

on

  • 3,508 views

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

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.

Statistics

Views

Total Views
3,508
Views on SlideShare
3,496
Embed Views
12

Actions

Likes
1
Downloads
58
Comments
0

4 Embeds 12

http://www.slideshare.net 8
https://www.linkedin.com 2
http://192.168.10.100 1
http://www.google.co.uk 1

Accessibility

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Improving Tests With Object Mothers And Internal Dsls Improving Tests With Object Mothers And Internal Dsls Presentation Transcript

    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Improving tests with Obj t I i g t t ith Object Mothers and Internal DSLs Chris i h d Ch i Richardson Author of POJOs in Action Founder of Cloud Tools http://www.chrisrichardson.net htt // h i i h d t 11/10/2008 Chris Richardson — Improving tests with Object Mothers and DSLs 1
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Overview Writingg maintainable tests Copyright (c) 2008 Chris Richardson. All rights 2 Chris Richardson — Improving tests with Object Mothers and DSLs reserved. 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson About Chris Grew up in England and live in Oakland, CA p g , Over twenty years of software development experience Building object-oriented software since 1986 Using Java since 1996 Using J2EE since 1999 Author of POJOs in Action Speaker 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 EC2 Founder of Cloud Tools, an open-source project for d l i f deploying J Java applications on Amazon EC2: li ti A EC2 http://code.google.com/p/cloudtools Chris Richardson — Improving tests with Object Mothers and DSLs Slide 3
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Agenda Tests - a double-edged sword double edged Taming test fixture logic Simplifying verification code Writing web tests Testing Ajax applications 4 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Why test? Write new code more easily A t t h t Automates what we are doing already - d i l d Right!? Run fast unit tests instead of slower web application Use TDD to incrementally solve a problem Tests are a safety net Confidently change existing code Easier to refactor code to prevent decay Fewer bugs that impact customers AND development Copyright (c) 2008 Chris Richardson. All rights reserved. Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008 5
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Why test? Increases longevity: g y Testable code is cleaner code Without tests your application will decay y pp y and die Absolutely essential when using a dynamic l d language Compiler can't catch typos Nothing is too simple to test You need unit tests Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008 6
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson 4 phase tests public class FooTest extends TestCase { public void setUp() { Setupp } Exercise public void testSomething() { // test-specific setup Verify // Exercise the SUT Teardown // Verification // test-specific tear down test specific } public void tearDown() { } } 7 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Types of tests Domain model tests Easy to write Fast to run Test your domain objects and services In-memory tests Persistence tests Test manipulating persistent objects Service integration tests Test services using database Web tests Automatic tests using Selenium Click and type in the GUI Difficult to write Slow to run 8 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Example tests Walk through some example ptrack tests 9 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson The trouble with tests They make software more difficult to y change That's a good thing since they detect bugs But change is inevitable: new features, refactoring If you can't easily change the test code: Slows down the development Tests are removed or abandoned 10 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Poor quality test code Common test code smells Obscure T t – you can't tell what a test does Ob Tests 't t ll h t t t d Test code duplication – copy and paste tests Badly structured test setup logic Complicated logic to create test fixtures E.g. the test objects (in-memory or in DB) Sprawling web tests p a g b Web test framework APIs are very low-level. Easily end up with large amounts of difficult to maintain code: click(),type(),… (), yp (), Lots of duplication Lots of obscure code 11 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Excellent testing book Test smells and how to fix them Obscure test Fragile test Test code duplication … Comprehensive pattern language: Four phase test Minimal fixture Test utility method Test helper Humble Objectj … 12 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Agenda Tests - a double-edged sword double edged Taming test fixture logic Simplifying verification code Writing web tests Testing Ajax applications 13 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson What s What's a fixture Fixture = everything that needs to be y g in place to test an object/system Object/system we want to test Its collaborators, required test data etc Fixture is created by test fixture y logic: Code in the test methods themselves Junit 3.x setUp() JUnit 4/TestNG @Before* annotations 14 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson The challenge of test fixtures Creating an object isn't always easy g j y y Objects can have lots of attributes Objects are often aggregate roots new() is often insufficient Test fixtures often create multiple objects E.g. money transfer test needs two accounts f d Multiple tests need the same set of objects • AccountTests, MoneyTransferServiceTests need similar Account objects 11/10/2008 Chris Richardson — Improving tests with Object Mothers and DSLs 15
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Object graphs can be complicated We need all of these objects to test a Project! 16 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Constructing individual objects can be tricky Best way to construct an object is to use y j a non-default constructor: Supports objects without setters Supports immutable objects Forces you to supply all required objects Constructor can verify object state Limitations of constructors: Lots of constructor arguments ⇒ code is difficult to read Optional arguments ⇒ multiple constructors 17 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson The alternative to constructors Project project = new Project(initialStatus); project.setName(quot;Test Project #1quot;); project.setDescription(quot;descriptionquot;); project.setRequirementsContactName(quot;Rick Jonesquot;); project.setRequirementsContactEmail(quot;jones@nowhere.comquot;); 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 Breaks •Object is mutable •Cannot validate state 18 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Constructing objects fluently Project project = new Project(initialStatus) .name(quot;Test P j t #1quot;) (quot;T t Project .description(quot;Excellent projectquot;) .initiatedBy(user) .requirementsContactName(quot;Rick Jonesquot;) .requirementsContactEmail(quot;jones@nowhere.comquot;) i t C t tE il(quot;j @ h quot;) .type(ProjectType.EXTERNAL_DB) .artifact(ArtifactType.ARCHITECTURE) .artifact(ArtifactType.DEPLOYMENT) .artifact(ArtifactType.MAINTENANCE); tif t(A tif tT MAINTENANCE) Chained method calls Benefits: •Less noise •Meaning of each value is clear Meaning Drawbacks: •Breaks encapsulation – objects must have mutators/setters •Doesn't work with immutable objects •No opportunity to validate state No 11/10/2008 Chris Richardson — Improving tests with Object Mothers and DSLs 19
    • Fluently creating immutable Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson objects See http://developers.sun.com/learning/javaoneonline http://developers sun com/learning/javaoneonline /2007/pdf/TS-2689.pdf Project project = new Project.ProjectBuilder(initialStatus) .name(quot;Test Project #1quot;) (quot; j .description(quot;descriptionquot;) .initiatedBy(user) .requirementsContactName(quot;Rick Jonesquot;) i C il(quot;j .requirementsContactEmail(quot;jones@nowhere.comquot;) @ h quot;) .type(ProjectType.EXTERNAL_DB) .artifact(ArtifactType.ARCHITECTURE) .artifact(ArtifactType.DEPLOYMENT) .artifact(ArtifactType.MAINTENANCE) tif t(A tif tT MAINTENANCE) .make(); • Initialize the mutable builder • make() instantiates the domain object via a constructor • Allows entity to be immutable • Builder can validate object state This is an internal DSL 11/10/2008 Chris Richardson — Improving tests with Object Mothers and DSLs 20
    • Nested Entity Builder Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson example 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; } public Project make() { // Verify that we have everything j ( ); return new Project(this); } } • Pass builder as the sole constructor argument public Project(ProjectBuilder builder) { this.name = builder.name; this.initiatedBy = builder.initiatedBy; } 11/10/2008 Chris Richardson — Improving tests with Object Mothers and DSLs 21
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Testing makes your objects smarter Production code often has simple p requirements: Create using default constructor Accesses Java bean properties But tests need smarter objects j E.g. fluent interfaces Counter to the concept of not having test code in production code But this is ok: tests are important 22 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Centralizing test object Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson creation with Object Mothers Fluent interfaces can help but public class ProjectMother { Fixture logic can still be public static Project complex makeProjectInProposalState ( Same object created in Status initialStatus, User user) { ) multiple t t ⇒ d li ti lti l tests duplication … Eliminate duplication: } Centralize object creation in a public static Project test utility class called an makeProjectInRequirementsState( Object Mother Status initialStatus, User user) { Defines factory methods for … creating fully formed } aggregate Different methods for different aggregate states } See: http://www xpuniverse com/2001/pdfs/Testing03 pdf http://www.xpuniverse.com/2001/pdfs/Testing03.pdf 23 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Creating multiple connected Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson aggregates Each Object Mother method creates a th d t single aggregate But some tests need to create multiple aggregates with shared sub- aggregates Must avoid itDepartment = DepartmentMother.makeItDepartment(); duplicating that itProjectManager = UserMother.makeProjectManager(itDepartment); code in multiple itBusinessAnalyst = UserMother.makeBusinessAnalyst(itDepartment); tests projectInCompleteState = ProjectMother.makeProjectInCompleteState(…); projectInRequirementsState = ProjectMother.makeProjectInRequirementsState(…); P j tM th k P j tI R i t St t ( ) … 24 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Use stateful object mothers Test instantiates object mother Object mother's constructor Creates aggregates (by calling their Object Mothers) Stores them in (public) fields Test gets the data it needs from the object mother 25 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Example of a stateful mother public class ProjectTests extends TestCase { public class PTrackWorld { private Project project; private User projectManager; private final Department itDepartment; private User businessAnalyst; private final User itProjectManager; protected void setUp() throws Exception { private final User itBusinessAnalyst; PTrackWorld world = new PTrackWorld(); private final Project projectInCompleteState; projectManager = world.getItProjectManager(); businessAnalyst = world.getItBusinessAnalyst(); … project = world.getProjectInProposalState(); } public PTrackWorld() { itDepartment = DepartmentMother.makeItDepartment()' DepartmentMother.makeItDepartment() itProjectManager = UserMother.makeProjectManager(itDepartment); itBusinessAnalyst = UserMother.makeBusinessAnalyst(itDepartment … stateMachine = DefaultStateMachineFactory.makeStateMachine(quot;defaultquot;); initialStatus = stateMachine.getInitialStatus(); projectInCompleteState = ProjectMother.makeProjectInCompleteState(initialStatus, itProjectManager, getAllITDepartmentEmployees()); … } } 26 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Object Mothers and databases Initialize database public class PtrackDatabaseInitializer using objects i bj t implements InitializingBean, DatabaseInitializer { created by private HibernateTemplate template; private PTrackWorld world; mothers public PtrackDatabaseInitializer(HibernateTemplate Create objects this.template = template; template) { using mothers } Persist them public void afterPropertiesSet() { initializeDatabase(); } Very easy when public void initializeDatabase() { using ORM world = new PTrackWorld(); StateMachine stateMachine = world.getStateMachine(); Avoids difficult to template.save(stateMachine); D t Department itD t t t itDepartment = world.getITDepartment(); ld tITD t t() maintain flat files: template.save(itDepartment); … CSV, SQL, XML } } Chris Richardson — Improving tests with Object Mothers and DSLs Slide 27
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Object Mother design Choices Same data each time vs. random data Referenced aggregates as parameters vs vs. create those aggregates too Tip: use descriptive names Bad: makeProject1(), makeProject2(), ... Better: makeProjectIn<State>() … makeProjectIn<State>(), Tip: use Object Mothers from day 1 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008 28
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Finding balance public void testOptionA() { ShoppingCart cart Sh i C t t Intention revealing = ShoppingCartMother.makeEmptyCart(); But risks duplication cart.add(ProductMother.makeInstockPart(), 1); cart.add(ProductMother.makeBackOrderdPart(), 1); cart.add(ProductMother.makeDiscontinuedPart(), 1); t dd(P d tM th k Di ti dP t() 1) … } OR public void testOptionB() { ShoppingCart cart = ShoppingCartMother.makeWithInstockPartBackorderedPartandDiscontinuedPart(); … } Ridiculously long names??? Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008 29
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Agenda Tests - a double-edged sword double edged Taming test fixture logic Simplifying verification code Writing web tests Testing Ajax applications 30 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Writing readable verification Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson logic Verification phase verifies that expected outcome h been obtained t has b bt i d State verification makes assertions about: Returned value State of system under test State of collaborators Test frameworks provide the basic assert methods but we must: Ensure readability E d bilit Avoid code duplication 11/10/2008 Chris Richardson — Improving tests with Object Mothers and DSLs 31
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Smelly verification code protected void setUp() throws Exception { PTrackWorld world = new PTrackWorld(); p j projectManager = world.getItProjectManager(); 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, quot;Excellentquot;); 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(quot;Excellentquot;, operation.getComments()); Lots of assertions = obscure code assertEquals(projectManager, operation.getUser()); assertEquals(state0, operation.getFromStatus()); Likely code duplication assertEquals(state1, operation.getToStatus()); assertFalse(operation.getTimestamp().before(startTime)); assertFalse(operation.getTimestamp().after(endTime)); assertFalse(operation getTimestamp() after(endTime)); } 32 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Using Custom Assertions Verification code calls a Test Utility Method that makes assertions Has an Intention Revealing Name Benefits: Makes th M k the code more readable d d bl Eliminates duplication Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008 33
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Simplified test protected void setUp() throws Exception { … expectedOperation0 = new Operation(null, projectManager, state0, state1, quot;Excellentquot;); } public void testChangeStatus() { boolean result = project.changeStatus(true, projectManager, quot;E j tM quot;Excellentquot;); ll tquot;) Date endTime = new Date(); assertTrue(result); assertEquals(state1, project.getStatus()); q ( ,p j g ()); assertHistoryContains(project, startTime, endTime, expectedOperation0); } 34 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Custom Assertion Methods private 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) { 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.getUser(), operation.getUser()); assertEquals(expectedOperation.getFromStatus(), operation.getFromStatus()); assertEquals(expectedOperation.getToStatus(), operation.getToStatus()); assertFalse(operation.getTimestamp().before(startTime)); tF l ( ti tTi assertFalse(operation.getTimestamp().after(endTime)); t () ft ( dTi )) } 35 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Literate assertions with Hamcrest Hamcrest is an open-source framework http://code.google.com/p/hamcrest/ Readable quot;literatequot; assertions literate Rich set of composable matchers Literate error messages g Used by Jmock expectations import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; org hamcrest Matchers is; import static org.hamcrest.Matchers.isOneOf; assertThat(project.getStatus(), is(state1)); assertThat(project.getStatus(), isOneOf(state1, state2)); assertThat(project.getStatus(), allOf(is(state), not(is(state2)))); tTh t( j t tSt t () llOf(i ( t t ) t(i ( t t 2)))) 36 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Hamcrest custom matchers public class ProjectMatchers { p public static Matcher<Date> withinInclusivePeriod(final Date startTime, ( , 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(quot;expected to be between <%s> and <%s>quot;, startTime, endTime)); } import static org.jia.ptrack.domain.ProjectMatchers.withinInclusivePeriod; p gj 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 g ( j ) org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:21) 37 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Agenda Tests - a double-edged sword double edged Taming test fixture logic Simplifying verification code Writing web tests Testing Ajax applications 38 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Writing web tests Web tests simulate the user Fill-in forms Click buttons and links Assertions: Correct page displayed Correct data is displayed Page elements exist/visible P l t i t/ i ibl 39 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Using Selenium Popular web testing p g framework Launches a browser ⇒ full Javascript support Selenium IDE for recording and running tests ⇒ i quickly create tests Chris Richardson — Improving tests with Object Mothers and DSLs Slide 40
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Selenium RC API for launching and controlling the browser Supports multiple programming language API: click(), type() waitForPageToLoad() iF P T L d() isVisible (), isPresent() Chris Richardson — Improving tests with Object Mothers and DSLs Slide 41
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Selenium RC API + Selenium IDE = trouble API is very level: y Obscure tests Lots of duplication You can quickly generate tests with Selenium IDE The Th result: l Large amounts of difficult to maintain test code Very fragile tests ⇒ small change to UI, many broken tests 42 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson An example of bad test code public class ExampleOfBadWebTests extends AbstractSeleniumTest { @Test public void testCreateProject() { bli id t tC t P j t() open(quot;/ptrack/quot;); type(quot;j_usernamequot;, quot;proj_mgrquot;); type(quot;j_passwordquot;, quot;facesquot;); clickAndWait(quot;Loginquot;); assertTextPresent(quot;(quot; + quot;proj_mgrquot; + quot;)quot;); clickAndWait(quot;link=Create Newquot;); String projectName = quot;XXX Projectquot; + System.currentTimeMillis(); type(quot;projectDetails:nameInputquot;, projectName); select(quot;projectDetails:typeSelectOnequot;, quot;label=External Desktop Applicationquot;); … clickAndWait(quot;projectDetails:savequot;); assertTextPresent(quot;Inbox - approve or reject projectsquot;); assertTextEquals(projectName, quot;inboxPage:inboxTable:2:projectNamequot;); clickAndWait(quot;inboxPage:inboxTable:2:detailsquot;); assertTextPresent(projectName); assertTextPresent(quot;External Desktop Applicationquot;); ( p pp ); assertTextEquals(quot;Sean Sullivanquot;, quot;detailsPage:initiatedByquot;); …. assertTitle(quot;ProjectTrack - Project detailsquot;); clickAndWait(quot;detailsPage:okquot;); clickAndWait(quot;link=Logoutquot;); Easy to write – record with Selenium IDE assertTextPresent( Welcome assertTextPresent(quot;Welcome to Project Trackquot;); Track ); But imagine coming back to this three } } months later …. 43 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Improving tests with utility methods Write Test Utility methods = A Domain-Specific Language Have i t ti H intention revealing names li Call Selenium APIs Examples: login() goto…() assertOn…Page() logout() Write tests in terms of those methods Move those methods into a common superclass when appropriate 44 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Improved example public class ImprovedExampleOfTests extends AbstractSeleniumTest { @Test private void createProject() { public void testCreateProject() { clickAndWait(quot;link=Create Newquot;); login(); projectName = quot;XXX Projectquot; + System.currentTimeMillis(); type( projectDetails:nameInput type(quot;projectDetails:nameInputquot;, projectName); createProject(); select(quot;projectDetails:typeSelectOnequot;, assertProjectDisplayedInInbox(); quot;label=External Desktop Applicationquot;); type(quot;projectDetails:requirementsInputquot;, viewProjectDetails(); quot;Chris Richardsonquot;); assertProjectDetailsDisplayed(); type(quot;projectDetails:requirementsEmailInputquot;, yp ( p j q p , quot;chris@chrisrichardson.netquot;); … returnToInbox(); clickAndWait(quot;projectDetails:savequot;); logout(); } } } Test is a lot more readable Intention is clear Less duplication Less fragile 45 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Intelligently evolve the language Write/record tests using low-level APIs low level Use Extract Method refactoring to create the utility methods Move methods into A common superclass l Test Helper classes Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008 46
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Better ways to handle test data Web tests need data too Filling in forms Asserting the contents of the page Data embedded in code Test data is sprinkled through application Difficult to manage Duplication D li ti … 47 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Example helper method p public class ExampleOfWebTests extends AbstractSeleniumTest { p private String projectName; public void createProject() { clickAndWait(quot;link=Create Newquot;); projectName = quot;XXX Projectquot; + System.currentTimeMillis(); type(quot;projectDetails:nameInputquot;, projectName); select(quot;projectDetails:typeSelectOnequot;, quot;label=External Desktop Applicationquot;); type(quot;projectDetails:requirementsInputquot;, quot;Chris Richardsonquot;); … clickAndWait(quot;projectDetails:savequot;); } } Chris Richardson — Improving tests with Object Mothers and DSLs Slide 48
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Using domain objects in web tests Test utility methods: Use domain objects created by mothers Populate forms Verify the contents of a page Benefits: Improves readability Improves management of test data I t ft td t Parameterized methods are reusable 49 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson A much better example public class ExampleOfGoodWebTests extends AbstractSeleniumTest { @Test public void testCreateProject() { login(); Project P j t projectToCreate = ProjectMother.makeNewProject(); j tT C t P j tM th k N P j t() createProject(projectToCreate); assertProjectDisplayedInInbox(projectToCreate); viewProjectDetails(projectToCreate); assertProjectDetailsDisplayed(projectToCreate); returnToInbox(); logout(); i t id tP j tD t il Di l private void assertProjectDetailsDisplayed(Project projectToCreate) { d(P j t j tT C t ) } assertTextPresent(projectToCreate.getName()); assertTextPresent(projectToCreate.getType().getDescription()); } assertTextEquals(projectToCreate.getInitiatedBy().getFullName(), quot;detailsPage:initiatedByquot;); … } 50 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Agenda Tests - a double-edged sword double edged Taming test fixture logic Simplifying verification code Writing web tests Testing Ajax applications 51 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson The challenge of Ajax Ajax applications behave differently j pp y JavaScript executes after the page loads ⇒ less deterministic, predictable behavior Clicks don't result in page loads Triggers an Ajax request that updates the same page DOM nodes are often hidden rather than non-existent assertElementPresent() ⇒ true even when the element is not visible 52 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Testing Ajax applications Bad approach: Put lots of long sleeps in your code Slows down the tests unnecessarily Improved approach: Loop testing for element visibility with a short sleep Use Selenium RC quot;waitquot; feature Selenium-RC wait 53 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Messy Example protected void waitForVisibility(String selector) { ( ); WaitForElementVisible waiter = new WaitForElementVisible(selector); try { waiter.wait(String.format(quot;Cannot find element <%s>quot;, selector), 3000); } catch (Wait.WaitTimedOutException e) { return; } } public void testSomething() { … class WaitForElementVisible extends Wait { waitForVisibility(quot;someFormquot;); private final String selector; assertTrue(selenium.isVisible(quot;someFormquot;)); public WaitForElementVisible(String selector) { … this.selector this selector = selector; } } @Override public boolean until() { return selenium.isElementPresent(selector) && selenium.isVisible(selector); } } This works but the code quickly becomes cluttered with calls to waitForXXX() Chris Richardson — Improving tests with Object Mothers and DSLs Slide 54
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Better: Implementation #2 Encapsulate the waiting/loop within Test Utility methods, e.g.: assertElementVisible( ) assertElementVisible(…) Benefits: Simplifies the test code 55 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson A simple example protected void waitForVisibility(String selector) { WaitForElementVisible waiter = new WaitForElementVisible(selector); try { waiter.wait(String.format(quot;Cannot find element <%s>quot;, selector), 3000); } catch (Wait.WaitTimedOutException e) { return; } } public void testSomething() { … class WaitForElementVisible extends Wait { assertElementVisible(quot;someFormquot;); … … } } 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
    • Summary of web testing Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson architecture Tests T t Application-specific Utility methods Application specific e.g. login(), logout(), … Wrapped selenium APIs e.g. hides Ajax related timing issues etc Selenium RC 57 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Some useful frameworks Umangite – Selenium web tests code.google.com/p/umangite/ ORMUnit – Persistence tests code.google.com/p/ormunit Arid POJOs Generic DAO A id POJO – G i DAOs code.google.com/p/aridpojos/ And, others: code.google.com/u/chris.e.richardson/ Chris Richardson — Improving tests with Object Mothers and DSLs Slide 58
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson Summary Messy tests will kill your application y y pp Aggressively refactor tests to keep them simple Define classes with fluent interfaces Use Object Mothers to avoid duplication of test fixture logic Aggressively use Test Utility Methods: Simplify web tests Hide Ajax-related issues 59 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008
    • Colorado Software Summit: October 19 – 24, 2008 © Copyright 2008, Chris Richardson For more information Buy my book ☺ Go to G t manning.com i Send email: chris@chrisrichardson.net Visit my website: http://www.chrisrichardson.net Talk to me about consulting and training 60 Chris Richardson — Improving tests with Object Mothers and DSLs 11/10/2008