Upload
chris-richardson
View
1.207
Download
0
Tags:
Embed Size (px)
DESCRIPTION
Describes techniques to writing tests for a domain model
Citation preview
Testing Rich Domain ModelsTesting Rich Domain Models
Ch i i h dChris Richardson
Author of POJOs in ActionCh i Ri h d C lti IChris Richardson Consulting, Inc
http://www.chrisrichardson.net3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 1
Overview
Testing a rich gdomain model: Why? How?
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 2
About Chris
Grew up in EnglandGrew up in EnglandLive in Oakland, CAOver twenty years of software development experience
Building object-oriented g jsoftware since 1986Using Java since 1996Using J2EE since 1999
Author of POJOs in ActionSpeaker at JavaOne, JavaPolis, SpringOne, NFJS, JUGs, ….Chair of the eBIG Java SIG in Oakland (www.ebig.org)Run a consulting and training Run a consulting and training company that helps organizations build better software faster
3/1/2009 3Copyright (c) 2007 Chris Richardson. All rights reserved.
Agendag
IntroductionTesting servicesTesting entities and value objectsg jTaming test fixture logicSimplifying verification codeSimplifying verification codeWriting more meaningful tests
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 4
Using the Domain Model Patterng
Traditional enterprise Java applications:B i l i i f t iBusiness logic in fat serviceServices manipulate dumb Java beansDoesn't handle complexityDoesn t handle complexity
Domain model pattern:Business logic spread amongst a collection f lof classes
Many classes are true objects having both state (fields) and behavior (methods) the ( ) ( )state
Many classes correspond to real world concepts: Order Customer concepts: Order, Customer, …
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 5
Procedural versus OOPresentation Tier
Presentation Tier
Business Tier
TransactionScripts
(Session Beans)
Business Tier
Domain Model
Facade
Data Obj
Domain Model
Objects
Data Access Tier Data Access Tier
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 6
An example domain modelp
Web Tier
MoneyTransferService
BehaviorBusiness Tier
BankingTransaction transfer(fromId, toId, amount)
debit(amount)credit(amount)
balance
Account
amountdate
BankingTransaction
findAccount(id)
AccountRepository
from
toaddTransaction(…)
BankingTransactionRepository
<<interface>>OverdraftPolicy
NoOverdraftPolicy
LimitedOverdraft
State + Behavior
ExplicitRepresentation
of key conceptsy
limitp
3/1/2009 7Copyright (c) 2007 Chris Richardson. All rights reserved.
Why test?y
Write new code more easilyAutomates what we are doing already Automates what we are doing already - Right!?Run fast unit tests instead of slower web applicationppUse TDD to incrementally solve a problem
Tests are a safety netyConfidently change existing codeEasier to refactor code to prevent decayThe application has longer, healthier life
Fewer bugs that impact customers g pAND development
3/1/2009 8Copyright (c) 2007 Chris Richardson. All rights reserved.
Why test a domain model?y
Benefits of pushing tests down p gthe hierarchy:
Easier and cheaperFasterBetter error localization
You change a complex interest You change a complex interest calculation:How do you want to test it:
Through a changing GUI?With simple unit tests
When do you want to find out When do you want to find out whether it works?
15 minutes later after running web tests?10 seconds later after running hundreds of unit tests?
Determining why a test fails?Because of a problem with the database or UI?Because the algorithm was wrong?
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 9
Domain models make testing easierg
More modular:Smaller classes/methods that are easier to test
i l i i l d bBusiness logic implemented by immutable value objects
h dFunctions are easier to test that code with side-effects
Back to basics POJOs with no Back to basics - POJOs with no dependencies on infrastructure
Quickly test in isolationQuickly test in isolation
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 10
Domain models make testing more difficult…
Domain models are often used when the business logic is complexbusiness logic is complexObjects are structurally more complex
Not relational – i.e. not flat, coarse-grainedFiner grained objects, i.e. many classes map to the same table)Richer relationships between objectsDeeper object graphs
Object behavior is more complexBehavior determined by collaborators + internalBehavior determined by collaborators + internal state (reflects previous method invocations)Classes are often modal: imposes ordering on method invocationsmethod invocations
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 11
…Domain models make testing more difficult
Richer collaborationsService does more than invoke getters and setters on domain objectsDomain objects invoke methods on one Domain objects invoke methods on one another
EncapsulationpDomain Objects != JavaBeansPerhaps getters but preferably not settersInitializing objects is more complexVerifying object state might be more difficult
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 12
Excellent testing bookgTest smells and how to fix them
Obscure testFragile testTest code duplication…
C h i tt Comprehensive pattern language:
Four phase testMinimal fixtureMinimal fixtureTest utility methodTest helperHumble Object…
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 13
Agendag
IntroductionTesting servicesTesting entities and value objectsg jTaming test fixture logicSimplifying verification codeSimplifying verification codeWriting more meaningful tests
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 14
What's a Service?
A role that some domain public interface MoneyTransferService {
model classes playImplements logic that cannot be put in a single entityN t i t t
BankingTransaction transfer(String fromAccountId,String toAccountId, double amount)throws MoneyTransferException;
}
Not persistentConsists of an interface and an implementation classService method usually:
public class MoneyTransferServiceImpl implements MoneyTransferService{
private final AccountRepository accountRepository;
private final BankingTransactionRepositorybankingTransactionRepository;Service method usually:
Invoked (indirectly) by presentation tierInvokes one or more repositoriesInvokes one or more entities
public MoneyTransferServiceImpl(AccountRepository accountRepository,BankingTransactionRepository bankingTransactionRepository) {
this.accountRepository = accountRepository;this.bankingTransactionRepository = bankingTransactionRepository;
}
bli B ki T ti t f (St i f A tIdInvokes one or more entitiesWell-designed services are thin
Improves code qualityMakes them easier to test
public BankingTransaction transfer(String fromAccountId,String toAccountId, double amount) {
…fromAccount.debit(amount);toAccount.credit(amount); …
}
}
3/1/2009 15Copyright (c) 2007 Chris Richardson. All rights reserved.
How to test a service?
public class MoneyTransferServiceImpl implements MoneyTransferService {p y p p y
private final AccountRepository accountRepository;private final BankingTransactionRepository bankingTransactionRepository;
public MoneyTransferServiceImpl(AccountRepository accountRepository,ki i i b ki i i ) {BankingTransactionRepository bankingTransactionRepository) {
this.accountRepository = accountRepository;this.bankingTransactionRepository = bankingTransactionRepository;
}
public BankingTransaction transfer(String fromAccountIdpublic BankingTransaction transfer(String fromAccountId,String toAccountId, double amount) throws MoneyTransferException {
Account fromAccount = accountRepository.findAccount(fromAccountId);Account toAccount = accountRepository.findAccount(toAccountId);fromAccount.debit(amount);toAccount.credit(amount);TransferTransaction txn = new TransferTransaction(fromAccount,
toAccount, amount, new Date());bankingTransactionRepository.addTransaction(txn);return txn;
} Database access
…
3/1/2009 16Copyright (c) 2007 Chris Richardson. All rights reserved.
Writing service integration tests
Fixture logic: public class SpringMoneyTransferServiceTests extendsFixture logic:Creates serviceInitializes database
E i h
AbstractDependencyInjectionSpringContextTests {…@Overrideprotected String[] getConfigLocations() {return new String[] { "classpath:/appCtx/*.xml" };
}
bli id tS i (M T f S i i ) {Exercise phase:Calls service
Verification:
public void setService(MoneyTransferService service) {this.service = service;
}
@Overrideprotected void onSetUp() throws Exception {super.onSetUp();fromAccount AccountMother
Check state of database and return values
fromAccount = AccountMother.makeAccountWithNoOverdraft(FROM_ACCOUNT_INITIAL_BALANCE);
toAccount = AccountMother.makeAccountWithNoOverdraft(TO_ACCOUNT_INITIAL_BALANCE);
fromAccountId = fromAccount.getAccountId();toAccountId = toAccount.getAccountId();repository.addAccount(fromAccount);repository addAccount(toAccount);
Teardown:clean up (somehow)
repository.addAccount(toAccount);}
public void testTransfer() {service.transfer(fromAccountId, toAccountId, 5);assertBalance(FROM_ACCOUNT_INITIAL_BALANCE - 5, fromAccountId);assertBalance(TO_ACCOUNT_INITIAL_BALANCE + 5, toAccountId);
}
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 17
}
The trouble with integration testsg
Require lots of setupSetting up the databaseg pInitializing the database…
10x-100x slower than in-memory tests:yUse an in-memory database, e.g. HSQLDBDon’t commit transactions
Database = persistence ⇒ test interferencepExecute each test in a transaction that is rolled back ⇒ can interfere with session flushing/object loadingRecreate the database schema ⇒ works well with HSQLDBHSQLDBSomehow write tests that only see data that they insert – can be tricky
We still need integration tests but we can often gdo better…
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 18
Faster testing with mock objectsg j
A mock object A mock object simulates the real object:
Returns values or th tithrows exceptionsVerifies that the expected methods are called
Using mocks:Simplifies tests Speeds up testsEnables an obje t to Enables an object to be tested in isolationEnables top-down development
3/1/2009 19Copyright (c) 2007 Chris Richardson. All rights reserved.
Creating mocksg
Write your own mocksSimple for interfaces but it becomes tedious.H t k t l ?How to mock concrete classes?
Use a mock object frameworkjMOCK, EasyMock
Create and configure mock objectSpecify expected method and argumentsDefine method behavior: return value or th tithrow exception
3/1/2009 20Copyright (c) 2007 Chris Richardson. All rights reserved.
Mock objects example: part 1j p pimport org.jmock…;
public class MoneyTransferServiceTests extends MockObjectTestCase {public class MoneyTransferServiceTests extends MockObjectTestCase {
protected void setUp() {super.setUp();accountRepository = mock(AccountRepository class);accountRepository = mock(AccountRepository.class);bankingTransactionRepository =
mock(BankingTransactionRepository.class);service = new MoneyTransferServiceImpl(accountRepository,
bankingTransactionRepository);bankingTransactionRepository);
fromAccount = AccountMother.makeAccount(100);toAccount = AccountMother.makeAccount(200);fromAccountId = fromAccount getAccountId();
Create service with mock
i ifromAccountId fromAccount.getAccountId();toAccountId = toAccount.getAccountId();
}
repositories
3/1/2009 21Copyright (c) 2007 Chris Richardson. All rights reserved.
Mock objects example: part 2j p p
public class MoneyTransferServiceTests extends TestCase {p y
public void testTransfer_normal() throws Exception {checking(new Expectations() {
{one(accountRepository).findAccount(fromAccountId);
ill( l (f ))will(returnValue(fromAccount));one(accountRepository).findAccount(toAccountId);
will(returnValue(toAccount));one(bankingTransactionRepository).addTransaction(
with(is(BankingTransaction.class)));}}
});
BankingTransaction result = service.transfer(fromAccountId, toAccountId, 30);
assertNotNull(result);
MoneyUtil.assertMoneyEquals(70.0, fromAccount.getBalance());MoneyUtil.assertMoneyEquals(230.0, toAccount.getBalance());
}}
3/1/2009 22Copyright (c) 2007 Chris Richardson. All rights reserved.
Downsides of mocks
Testing the collaboration of objects = white box testingTests can be brittle
Change design without changing what it does ⇒ failing tests
d l fDiscourages developers from writing tests
Fortunately many collaborations are Fortunately, many collaborations are stable
e g between services and repositoriese.g. between services and repositories
3/1/2009 23Copyright (c) 2007 Chris Richardson. All rights reserved.
Mock selectively!y
Collaborators that slow tests down or introduce undesirable coupling:introduce undesirable coupling:
RepositoriesRemote Services
Collaborators that require elaborate initialization,
Stateful entities and value objectsCollaborators that don't exist (yet)
Entities when developing top downEntities when developing top-downApplications where message ordering is importantimportant
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 24
Agendag
IntroductionTesting servicesTesting entities and value objectsg jTaming test fixture logicSimplifying verification codeSimplifying verification codeWriting more meaningful tests
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 25
What's an Entity?y
Objects with a public class Account {Objects with a distinct identityTypically correspond to real world
public class Account {
private int id;
private double balance;
private OverdraftPolicy overdraftPolicy;
to real world conceptsAlmost always persistent
private String accountId;
private CalendarDate dateOpened;
Account() {}persistent
Encapsulate state and behaviorOften modal ⇒
public void debit(double amount) throws MoneyTransferException {assert amount > 0;double originalBalance = balance;double newBalance = balance - amount;overdraftPolicy.beforeDebitCheck(this, originalBalance, newBalance);balance = newBalance;overdraftPolicy.afterDebitAction(this, originalBalance, newBalance);Often modal ⇒
testing is more difficult
}
public void credit(double amount) {assert amount > 0;balance += amount;
}
3/1/2009 26Copyright (c) 2007 Chris Richardson. All rights reserved.
What's a Value Object?j
Objects that are defined by the values of their
public class CalendarDate {
private Date date;
public class Accountprivate CalendarDate dateOpened;
…}by the values of their
attributesTwo instances with identical values can be used interchangeably
p ate ate date;
CalendarDate() {}
public CalendarDate(Date date) {this.date = date;
}
}
used interchangeablyTwo flavors
Persistent – parts of entitiesTransient intermediate
}
public Date getDate() {return date;
}
public double getYearsOpen() {Calendar then = Calendar.getInstance();Transient – intermediate
valuesIdeally immutableOften missing from
d l d
then.setTime(date);Calendar now = Calendar.getInstance();
int yearsOpened = now.get(Calendar.YEAR) –then.get(Calendar.YEAR);
int monthsOpened = now.get(Calendar.MONTH) -then.get(Calendar.MONTH);
if ( th O d 0) {procedural codeHave encapsulated state and behavior
if (monthsOpened < 0) {yearsOpened--;monthsOpened += 12;
}return yearsOpened + (monthsOpened/12.0);
}
}
3/1/2009 27Copyright (c) 2007 Chris Richardson. All rights reserved.
Aggregatesgg g
A cluster of related A cluster of related entities and valuesBehaves as a unitHas a rootH b dHas a boundaryObjects outside the aggregate can only reference the rootDeleting the root removes everything
Tests often create Tests often create and test aggregates rather than individual objects
3/1/2009 28Copyright (c) 2007 Chris Richardson. All rights reserved.
Tests for entities and value objectsj
Fixture logicCreate object under test (OUT) (often an Create object under test (OUT) (often an aggregate)Create collaborators (also aggregates)
E ec tionExecutionInvoke method on OUT
VerificationVerificationReturn value State of OUTState of collaboratorsState of collaborators
CleanupUsually do nothing – just use the garbage y g j g gcollector
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 29
Testing a POJO domain objectg j
public class Account { public class AccountTests extends TestCase {p {
private String accountId;private double balance;private OverdraftPolicy
d ftP li
p
private Account account;
public void setUp() {account = new Account("ABC", 10.0, new
d f ll d())overdraftPolicy;
public double getBalance() {return balance;
}
NoOverdraftAllowed());}
public void testInitialBalance() {MoneyUtil.assertMoneyEquals(10.0,
account getBalance());
public void debit(double amount) { … }
public void credit(double
account.getBalance());}
public void testDebit() {account.debit(6);MoneyUtil.assertMoneyEquals(4.0, public void credit(double
amount) { … }account.getBalance());
}…}Relatively easy to write tests
that run blindingly fastthat run blindingly fast
3/1/2009 30Copyright (c) 2007 Chris Richardson. All rights reserved.
Mocking collaboratorsg
Used less often - collaborators are usually fast to createBut it can sometimes simplify testsAccount and OverdraftPolicy
Separate tests Account and OverdraftPolicies ⇒ Fewer, simpler testsBut perhaps causes a feeling that you
t t ti l t lare not testing completely
Useful when creating a collaborator i iti l t f drequires writing a lot of code
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 31
Agendag
IntroductionTesting servicesTesting entities and value objectsg jTaming test fixture logicSimplifying verification codeSimplifying verification codeWriting more meaningful tests
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 32
What's a fixture
Fixture = everything that needs to be in place to test an object
Object we want to testIts collaborators
Fixture is created by test fixture logic:
Code in the test methods themselvesJunit 3.x setUp()JUnit 4/TestNG @Before* annotations
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 33
The challenge of test fixturesg
Creating objects isn't always easyIndividual objects have lots of parametersObjects are often belong to aggregates
T t fi t ft t lti l Test fixtures often create multiple objects
MoneyTransferServiceTests needs two MoneyTransferServiceTests needs two accounts
Multiple tests need the same set of Multiple tests need the same set of objects
AccountTests, MoneyTransferServiceTests need i il A t bj tsimilar Account objects
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 34
Object graphs can be complicatedj g p p
We need all of these of these objects to test a Project!
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 35
Constructing individual objects can be trickyy
Best way to construct an object is to use a constructor:use a constructor:
Supports objects without settersSupports immutable objectsSupports immutable objectsForces you to support everything that's required
Limitations of constructors:Lots of constructor arguments ⇒ code is diffi l ddifficult to readOptional arguments ⇒ multiple constructorsconstructors
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 36
The 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);
•Handles optional parameters•Is more readable•But lots of noise: 'project set'
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 37
•But lots of noise: project.set
Constructing objects fluentlyg j y
Project project = new Project(initialStatus).name("Test Project #1").description("Excellent project").initiatedBy(user).requirementsContactName("Rick Jones").requirementsContactEmail("[email protected]").type(ProjectType.EXTERNAL_DB).artifact(ArtifactType.ARCHITECTURE).artifact(ArtifactType.DEPLOYMENT).artifact(ArtifactType.MAINTENANCE);
• Chained method calls
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 38
Pros and cons of this approach pp
Benefits:Less noiseMeaning of each value is clear
Drawbacks:Breaks encapsulation – objects must have mutators/settersDoesn't work with immutable objects
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 39
Fluently creating immutable objectsy g j
See http://developers sun com/learning/javaoneonlhttp://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 obje t i on t to
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 40
object via a constructor • Allows entity to be immutable
Nested Entity Builder exampley pclass Project {
public 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 everythingreturn new Project(this);
}}
public Project(ProjectBuilder builder) {this.name = builder.name;this.initiatedBy = builder.initiatedBy;
}
• Pass builder as the sole constructor argument
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 41
}
Centralizing object creation with Object Mothersj
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 ⇒ duplicationEliminate duplication:
Centralize object creation in a test utility class called an
…}
public static Project makeProjectInRequirementsState(
Status initialStatus, User user) {a test utility class called an Object MotherDefines factory methods for creating fully formed aggregate
…}
}aggregateDifferent methods for different aggregate states
}
See: http://www xpuniverse com/2001/pdfs/Testing03 pdf
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 42
See: http://www.xpuniverse.com/2001/pdfs/Testing03.pdf
Creating multiple connected aggregatesgg g
Each Object Mother Each Object Mother method creates a single aggregateB t t t d But some tests need to create multiple aggregates with h d bshared sub-
aggregatesMust avoid
itDepartment = DepartmentMother.makeItDepartment();
itProjectManager =
duplicating that code in multiple fixtures
UserMother.makeProjectManager(itDepartment);itBusinessAnalyst =
UserMother.makeBusinessAnalyst(itDepartment);projectInCompleteState =
ProjectMother.makeProjectInCompleteState(…);projectInRequirementsState =
P j tM th k P j tI R i t St t ( )
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 43
ProjectMother.makeProjectInRequirementsState(…);…
Use stateful object mothersj
Test instantiates object motherObject 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
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 44
Example of a stateful motherpublic class ProjectTests extends TestCase {
public class PTrackWorld {
private final Department itDepartment;i t fi l U itP j tM
private Project project;private User projectManager;private User businessAnalyst;
protected void setUp() throws Exception {PTrackWorld world = new PTrackWorld();
private final User itProjectManager;private final Project projectInCompleteState;
…
public PTrackWorld() {
projectManager = world.getItProjectManager();businessAnalyst = world.getItBusinessAnalyst();project = world.getProjectInProposalState();
}
public PTrackWorld() {itDepartment = DepartmentMother.makeItDepartment()'
itProjectManager = UserMother.makeProjectManager(itDepartment);stateMachine = DefaultStateMachineFactory.makeStateMachine("default");initialStatus = stateMachine.getInitialStatus();projectInCompleteState = ProjectMother.makeProjectInCompleteState(initialStatus,
itProjectManager, getAllITDepartmentEmployees());…
}
}
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 45
}
Object Mother designj gChoices
Same data each time vs. random dataReferenced aggregates as parameters vs. create those aggregates too
Tip: use descriptive namesBad: makeProject1(), makeProject2(), ...Better: makeProjectIn<State>(), …
Tip: use Object Mothers from day 1But don’t create obscure tests
Hiding details in mothers can make tests difficult to understand
?!?ShoppingCartMother.makeWithInstockPartBackorderedPartandDiscontinuedPart() ?!?Need to find the balance between too much and too little
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 46
Agendag
IntroductionTesting servicesTesting entities and value objectsg jTaming test fixture logicSimplifying verification codeSimplifying verification codeWriting more meaningful tests
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 47
Writing readable verification logic
Verification phase verifies that expected outcome has been obtainedoutcome has been obtainedState verification makes assertions about:
Returned valueReturned valueState of object under testState of collaboratorsState of collaborators
Test frameworks provide the basic assert methods but we must:
Ensure readabilityAvoid code duplication
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 48
Using Custom Assertions
Verifying the state of object multiple assert…() calls = obscure codeIn multiple tests = code duplication
Verification code calls a Test Utility Method that makes assertionsHas an Intention Revealing NameBenefits:
Makes the code more readableEliminates duplicationp
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 49
Example – before and afterprotected void setUp() throws Exception {
PTrackWorld world = new PTrackWorld();projectManager = world.getItProjectManager();
project = world.getProjectInProposalState();startTime = new Date();
state0 = world.getInitialState();state1 = state0.getApprovalStatus();state2 = state1.getApprovalStatus();
protected void setUp() throws Exception {…
expectedOperation0 = new Operation(null, projectManager,
state0, state1," ll ")
g pp ();}
public void testChangeStatus() throws InterruptedException {boolean result = project.changeStatus(true,
projectManager, "Excellent");Date endTime = new Date();
"Excellent");}
public void testChangeStatus() {
boolean result = project.changeStatus(true, j tM "E ll t")
assertTrue(result);assertEquals(state1, project.getStatus());assertEquals(1, project.getHistory().size());
Operation operation = project.getHistory().get(0);l (" ll " i C ())
projectManager, "Excellent");Date endTime = new Date();
assertTrue(result);assertEquals(state1, project.getStatus());
assertHistoryContains(project 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));
}
assertHistoryContains(project, startTime, endTime,expectedOperation0);
}
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 50
}
Example - After
private void assertHistoryContains(Project project, Date startTime, Date endTime,Operation... expectedOperations) {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 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.getFromStatus(), operation.getFromStatus());assertEquals(expectedOperation.getToStatus(), operation.getToStatus());assertFalse(operation.getTimestamp().before(startTime));assertFalse(operation.getTimestamp().after(endTime));
}
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 51
Literate assertions with Hamcrest
Hamcrest is an open-source frameworkhttp://code.google.com/p/hamcrest/Readable "literate" assertionsRich set of composable matchersLiterate error messagesUsed 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))))
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 52
assertThat(project.getStatus(), allOf(is(state), not(is(state2))));
Hamcrest 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)
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 53
g ( j )
Agendag
IntroductionTesting servicesTesting entities and value objectsg jTaming test fixture logicSimplifying verification codeSimplifying verification codeWriting more meaningful tests
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 54
TDD says begin with a test but…
Daddy, where do tests come from?
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 55
Best to derive tests from requirementsq
As a customer of the bankI want to transfer money between two bank accountsSo that I don't have to write a check
class AccountTransferServiceTests … {
public void testTransfer_ok() {Account savingsAccount =
AccountMother.makeAccountWithNoOverdraft(150);
Given that my savings account balance is $150Given that my checking account balance is $10When I transfer $50 from my savings account
to my checking accountThen my savings account balance is $100
AccountMother.makeAccountWithNoOverdraft(150);Account checkingAccount =
AccountMother.makeAccountWithNoOverdraft(10);String savingsAccountId = savingsAccount.getAccountId();String checkingAccountId = checkingAccount.getAccountId();repository.addAccount(savingsAccount);repository.addAccount(checkingAccount);
Then my checking account balance is $60p y ( g );
service.transfer(savingsAccountId, checkingAccountId, 50);
assertBalance(100, savingsAccountId);assertBalance(60, checkingAccountId);
}
public void testTransfer_overdraftAttempted() {….
}
Given that my savings account balance is $150Given that my checking account balance is $10Given that my bank does not allow me
to have an overdraftWhen I transfer $200 from my savings account
to my checking account
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 56
y gThen my savings account balance is unchangedThen my checking account balance is unchanged
Testing at multiple levelsg p
Where in the hierarchy to write a test?Web tierWeb tierService integration testMock service testEntity/Value object test
Sometimes at every levelFew high-level tests e g verify that the UI Few high-level tests, e.g. verify that the UI handles errorsMany lower-level, more detailed tests, e.g. tests for each error scenariofor each error scenario
Sometimes only lower-level testsE.g. verifying that email is sent after
f i k bj i transferring money ⇒ mock object service test
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 57
We sometimes invent requirementsq
User requirements focus on the external behavior:behavior:
Behavior of boundary classes, i.e. UI + servicesKey domain model classes, e.g. account
But those classes often have collaborators:collaborators:
E.g. Account has CalendarDateWhen designing those classes:When designing those classes:
Informally specify the behavior of those classesWrite capture that behavior in tests
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 58
More formal testingg
1000+ pages of 1000+ pages of rigorous testing techniquestechniques
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 59
Deriving tests from a state machine model
r
• State machine is a useful way to model some classes (and
A B Cp q
r (UIs)• N+ test strategy
• Turn state machine into a tree containing all edges
s
tree containing all edges• Test cases = paths to all leaves•Test = sequence of events
public testPQR() {}
public testPS() {}
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 60
Deriving tests from booleanexpressions p
Class invariant/function/conditional: (x >= 1 & y < 10)
Variable Condition Test #1 #2 #3 #4
x X >= 1 On 1x X > 1 On 1
Off 0
Typical 10 10
Y Y < 10 On 10
Off 9
Typical 2 2
Result True False False True
1x1 selection criteria
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 61
1x1 selection criteria
Derive tests from codeWrite tests to improve code coveragegIdentifies behavior/edge cases that you forgot to testCharacterization tests:
Capture current behaviorUseful for refactoring Useful for refactoring (sometimes)But they can be brittle
Code might not be Tip: Use a code coveraget l C b t (f ) Code might not be
correct! tool: Cobertura (free), Clover (cheap), …
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 62
Use code metrics to identify what to test
Cyclomaticcomplexitycomplexity
Bugs lurk hereSimplify if you can
L th dLarge methodsBugs lurk in here alsoSimplify if you canSimplify if you can
Crap4J (www.crap4j.org)
Computes code Computes code quality by combining code coverage and complexitycomplexity
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 63
Summaryy
Testing is essentialUse mock objects to accelerate testsAggressively refactor tests to keep gg y pthem simpleUse Object Mothers to avoid jduplication of test fixture logicSimplify assertionsSimplify assertionsDerive tests from requirements
3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 64
For more information
Buy my book ☺Buy my book ☺Go to manning.com
Send email:
Visit my website:Visit my website:
http://www.chrisrichardson.net
Talk to me about consulting and training
3/1/2009 65Copyright (c) 2007 Chris Richardson. All rights reserved.