65
Testing Rich Domain Models Testing Rich Domain Models Ch i ih d Chris Richardson Author of POJOs in Action Ch i Ri h d C lti I Chris Richardson Consulting, Inc http://www.chrisrichardson.net 3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 1

Testing Rich Domain Models

Embed Size (px)

DESCRIPTION

Describes techniques to writing tests for a domain model

Citation preview

Page 1: Testing Rich Domain Models

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

Page 2: Testing Rich Domain Models

Overview

Testing a rich gdomain model: Why? How?

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 2

Page 3: Testing Rich Domain Models

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.

Page 4: Testing Rich Domain Models

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

Page 5: Testing Rich Domain Models

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

Page 6: Testing Rich Domain Models

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

Page 7: Testing Rich Domain Models

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.

Page 8: Testing Rich Domain Models

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.

Page 9: Testing Rich Domain Models

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

Page 10: Testing Rich Domain Models

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

Page 11: Testing Rich Domain Models

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

Page 12: Testing Rich Domain Models

…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

Page 13: Testing Rich Domain Models

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

Page 14: Testing Rich Domain Models

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

Page 15: Testing Rich Domain Models

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.

Page 16: Testing Rich Domain Models

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.

Page 17: Testing Rich Domain Models

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

}

Page 18: Testing Rich Domain Models

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

Page 19: Testing Rich Domain Models

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.

Page 20: Testing Rich Domain Models

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.

Page 21: Testing Rich Domain Models

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.

Page 22: Testing Rich Domain Models

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.

Page 23: Testing Rich Domain Models

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.

Page 24: Testing Rich Domain Models

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

Page 25: Testing Rich Domain Models

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

Page 26: Testing Rich Domain Models

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.

Page 27: Testing Rich Domain Models

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.

Page 28: Testing Rich Domain Models

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.

Page 29: Testing Rich Domain Models

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

Page 30: Testing Rich Domain Models

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.

Page 31: Testing Rich Domain Models

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

Page 32: Testing Rich Domain Models

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

Page 33: Testing Rich Domain Models

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

Page 34: Testing Rich Domain Models

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

Page 35: Testing Rich Domain Models

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

Page 36: Testing Rich Domain Models

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

Page 37: Testing Rich Domain Models

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

Page 38: Testing Rich Domain Models

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

Page 39: Testing Rich Domain Models

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

Page 40: Testing Rich Domain Models

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

Page 41: Testing Rich Domain Models

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

}

Page 42: Testing Rich Domain Models

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

Page 43: Testing Rich Domain Models

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(…);…

Page 44: Testing Rich Domain Models

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

Page 45: Testing Rich Domain Models

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

}

Page 46: Testing Rich Domain Models

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

Page 47: Testing Rich Domain Models

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

Page 48: Testing Rich Domain Models

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

Page 49: Testing Rich Domain Models

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

Page 50: Testing Rich Domain Models

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

}

Page 51: Testing Rich Domain Models

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

Page 52: Testing Rich Domain Models

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))));

Page 53: Testing Rich Domain Models

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 )

Page 54: Testing Rich Domain Models

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

Page 55: Testing Rich Domain Models

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

Page 56: Testing Rich Domain Models

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

Page 57: Testing Rich Domain Models

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

Page 58: Testing Rich Domain Models

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

Page 59: Testing Rich Domain Models

More formal testingg

1000+ pages of 1000+ pages of rigorous testing techniquestechniques

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 59

Page 60: Testing Rich Domain Models

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

Page 61: Testing Rich Domain Models

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

Page 62: Testing Rich Domain Models

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

Page 63: Testing Rich Domain Models

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

Page 64: Testing Rich Domain Models

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

Page 65: Testing Rich Domain Models

For more information

Buy my book ☺Buy my book ☺Go to manning.com

Send email:

[email protected]

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.