Contenu connexe Similaire à Improving Tests With Object Mothers And Internal Dsls (20) Plus de Chris Richardson (20) Improving Tests With Object Mothers And Internal Dsls1. 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
2. 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
3. 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
4. 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
5. 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
6. 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
7. 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
8. 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
9. 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
10. 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
11. 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
12. 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
13. 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
14. 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
15. 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
16. 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
17. 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
18. 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
19. 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
20. 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
21. 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
22. 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
23. 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
24. 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
25. 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
26. 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
27. 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
28. 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
29. 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
30. 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
31. 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
32. 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
33. 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
34. 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
35. 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
36. 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
37. 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
38. 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
39. 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
40. 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
41. 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
42. 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
43. 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
44. 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
45. 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
46. 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
47. 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
48. 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
49. 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
50. 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
51. 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
52. 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
53. 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
54. 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
55. 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
56. 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
57. 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
58. 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
59. 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
60. 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