Your SlideShare is downloading. ×
0
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Testing Rich Domain Models
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Testing Rich Domain Models

1,217

Published on

Describes techniques to writing tests for a domain model

Describes techniques to writing tests for a domain model

Published in: Technology
1 Comment
3 Likes
Statistics
Notes
No Downloads
Views
Total Views
1,217
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
68
Comments
1
Likes
3
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Testing Rich Domain Models Chris i h d Ch i Richardson Author of POJOs in Action Chris Richardson C Ch i Ri h d Consulting, I lti Inc http://www.chrisrichardson.net 3/1/2009 1 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 2. Overview Testing a rich g domain model: Why? How? 3/1/2009 2 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 3. About Chris Grew up in England Live in Oakland, CA Over twenty years of software development experience Building object-oriented g j software since 1986 Using Java since 1996 Using J2EE since 1999 Author of POJOs in Action Speaker at JavaOne, JavaPolis, SpringOne, NFJS, JUGs, …. Chair of the eBIG Java SIG in Oakland (www.ebig.org) Run a consulting and training company that helps organizations build better software faster 3/1/2009 3 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 4. Agenda g Introduction Testing services Testing entities and value objects g j Taming test fixture logic Simplifying verification code Writing more meaningful tests 3/1/2009 4 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 5. Using the Domain Model Pattern g Traditional enterprise Java applications: Business l i i f t service Bi logic in fat i Services manipulate dumb Java beans Doesn t Doesn't handle complexity Domain model pattern: Business logic spread amongst a collection of classes fl Many classes are true objects having both state (fields) and behavior ( ( ) (methods) the ) state Many classes correspond to real world concepts: Order Customer, … Order, Customer 3/1/2009 5 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 6. Procedural versus OO Presentation Tier Presentation Tier Business Tier Business Tier Transaction Scripts Facade (Session Beans) Domain Model Data Objects Obj Data Access Tier Data Access Tier 3/1/2009 6 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 7. An example domain model p Web Tier Behavior Business Tier MoneyTransferService BankingTransaction transfer(fromId, toId, amount) from Account Banking Account BankingTransaction Transaction Repository Repository balance to amount findAccount(id) addTransaction(…) debit(amount) date credit(amount) Explicit <<interface>> OverdraftPolicy Representation State + of key Behavior Limited concepts p NoOverdraft Overdraft Policy y limit 3/1/2009 7 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 8. Why test? y Write new code more easily Automates what we are doing already - Right!? Run fast unit tests instead of slower web application pp Use TDD to incrementally solve a problem Tests are a safety net y Confidently change existing code Easier to refactor code to prevent decay The application has longer, healthier life Fewer bugs that impact customers g p AND development 3/1/2009 8 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 9. Why test a domain model? y Benefits of pushing tests down p g the hierarchy: Easier and cheaper Faster Better error localization 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 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 9 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 10. Domain models make testing easier g More modular: Smaller classes/methods that are easier to test Business l i i i logic implemented b l d by immutable value objects Functions are easier to test that code h d with side-effects Back to basics - POJOs with no dependencies on infrastructure Quickly test in isolation 3/1/2009 10 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 11. Domain models make testing more difficult… Domain models are often used when the business logic is complex Objects are structurally more complex Not relational – i.e. not flat, coarse-grained Finer grained objects, i.e. many classes map to the same table) Richer relationships between objects Deeper object graphs Object behavior is more complex Behavior determined by collaborators + internal state (reflects previous method invocations) Classes are often modal: imposes ordering on method invocations 3/1/2009 11 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 12. …Domain models make testing more difficult Richer collaborations Service does more than invoke getters and setters on domain objects Domain objects invoke methods on one another Encapsulation p Domain Objects != JavaBeans Perhaps getters but preferably not setters Initializing objects is more complex Verifying object state might be more difficult 3/1/2009 12 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 13. Excellent testing book g Test smells and how to fix them Obscure test Fragile test Test code duplication … Comprehensive pattern C h i tt language: Four phase test Minimal fixture Test utility method Test helper Humble Object … 3/1/2009 13 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 14. Agenda g Introduction Testing services Testing entities and value objects g j Taming test fixture logic Simplifying verification code Writing more meaningful tests 3/1/2009 14 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 15. What's a Service? A role that some domain public interface MoneyTransferService { model classes play BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) Implements logic that throws MoneyTransferException; cannot be put in a single } entity Not N t persistent it t public class MoneyTransferServiceImpl implements MoneyTransferService { Consists of an interface and an implementation private final AccountRepository accountRepository; class private final BankingTransactionRepository bankingTransactionRepository; Service method usually: Invoked (indirectly) by public MoneyTransferServiceImpl(AccountRepository accountRepository, BankingTransactionRepository bankingTransactionRepository) { presentation tier this.accountRepository = accountRepository; Invokes one or more this.bankingTransactionRepository = bankingTransactionRepository; repositories } Invokes one or more entities public B ki T bli BankingTransaction t ti transfer(String f f (St i fromAccountId, A tId Well-designed services String toAccountId, double amount) { … are thin fromAccount.debit(amount); toAccount.credit(amount); Improves code quality … Makes them easier to test } } 3/1/2009 15 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 16. How to test a service? p public class MoneyTransferServiceImpl implements MoneyTransferService { y p p y private final AccountRepository accountRepository; private final BankingTransactionRepository bankingTransactionRepository; public MoneyTransferServiceImpl(AccountRepository accountRepository, BankingTransactionRepository b ki ki i i bankingTransactionRepository) { i i ) this.accountRepository = accountRepository; this.bankingTransactionRepository = bankingTransactionRepository; } public BankingTransaction transfer(String fromAccountId 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 16 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 17. Writing service integration tests Fixture logic: public class SpringMoneyTransferServiceTests extends AbstractDependencyInjectionSpringContextTests { … Creates service @Override protected String[] getConfigLocations() { return new String[] { quot;classpath:/appCtx/*.xmlquot; }; Initializes database } Exercise phase: E i h public void setService(MoneyTransferService service) { bli id tS i (M T fS i i) this.service = service; } Calls service @Override Verification: protected void onSetUp() throws Exception { super.onSetUp(); fromAccount = AccountMother Check state of .makeAccountWithNoOverdraft(FROM_ACCOUNT_INITIAL_BALANCE); toAccount = AccountMother database and .makeAccountWithNoOverdraft(TO_ACCOUNT_INITIAL_BALANCE); fromAccountId = fromAccount.getAccountId(); return values toAccountId = toAccount.getAccountId(); repository.addAccount(fromAccount); repository.addAccount(toAccount); repository addAccount(toAccount); Teardown: } public void testTransfer() { clean up service.transfer(fromAccountId, toAccountId, 5); assertBalance(FROM_ACCOUNT_INITIAL_BALANCE - 5, fromAccountId); (somehow) assertBalance(TO_ACCOUNT_INITIAL_BALANCE + 5, toAccountId); } 3/1/2009 17 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 18. The trouble with integration tests g Require lots of setup Setting up the database gp Initializing the database … 10x-100x slower than in-memory tests: y Use an in-memory database, e.g. HSQLDB Don’t commit transactions Database = persistence ⇒ test interference p Execute each test in a transaction that is rolled back ⇒ can interfere with session flushing/object loading Recreate the database schema ⇒ works well with HSQLDB Somehow write tests that only see data that they insert – can be tricky We still need integration tests but we can often g do better… 3/1/2009 18 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 19. Faster testing with mock objects g j A mock object simulates the real object: Returns values or throws exceptions th ti Verifies that the expected methods are called Using mocks: Simplifies tests Speeds up tests Enables an obje t to object be tested in isolation Enables top-down development 3/1/2009 19 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 20. Creating mocks g Write your own mocks Simple for interfaces but it becomes tedious. How t mock concrete classes? H to k tl ? Use a mock object framework jMOCK, EasyMock Create and configure mock object Specify expected method and arguments Define method behavior: return value or throw exception th ti 3/1/2009 20 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 21. Mock objects example: p j p part 1 import org.jmock…; public class MoneyTransferServiceTests extends MockObjectTestCase { protected void setUp() { super.setUp(); accountRepository = mock(AccountRepository class); mock(AccountRepository.class); bankingTransactionRepository = mock(BankingTransactionRepository.class); service = new MoneyTransferServiceImpl(accountRepository, bankingTransactionRepository); Create service with fromAccount = AccountMother.makeAccount(100); mock toAccount = AccountMother.makeAccount(200); fromAccountId = fromAccount getAccountId(); fromAccount.getAccountId(); repositories ii toAccountId = toAccount.getAccountId(); } 3/1/2009 21 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 22. Mock objects example: p j p part 2 p public class MoneyTransferServiceTests extends TestCase { y public void testTransfer_normal() throws Exception { checking(new Expectations() { { one(accountRepository).findAccount(fromAccountId); will(returnValue(fromAccount)); ill( l (f )) 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 22 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 23. Downsides of mocks Testing the collaboration of objects = white box testing Tests can be brittle Change design without changing what it does ⇒ failing tests Discourages ddevelopers f l from writing tests Fortunately, Fortunately many collaborations are stable e.g. e g between services and repositories 3/1/2009 23 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 24. Mock selectively! y Collaborators that slow tests down or introduce undesirable coupling: Repositories Remote Services Collaborators that require elaborate initialization, Stateful entities and value objects Collaborators that don't exist (yet) Entities when developing top down top-down Applications where message ordering is important 3/1/2009 24 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 25. Agenda g Introduction Testing services Testing entities and value objects g j Taming test fixture logic Simplifying verification code Writing more meaningful tests 3/1/2009 25 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 26. What's an Entity? y Objects with a public class Account { distinct identity private int id; Typically correspond private double balance; to real world private OverdraftPolicy overdraftPolicy; private String accountId; concepts private CalendarDate dateOpened; Almost always Account() { persistent } public void debit(double amount) throws MoneyTransferException { Encapsulate state and assert amount > 0; double originalBalance = balance; behavior double newBalance = balance - amount; overdraftPolicy.beforeDebitCheck(this, originalBalance, newBalance); balance = newBalance; Often modal ⇒ overdraftPolicy.afterDebitAction(this, originalBalance, newBalance); } testing is more public void credit(double amount) { difficult assert amount > 0; balance += amount; } 3/1/2009 26 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 27. What's a Value Object? j public class Account Objects that are defined private CalendarDate dateOpened; public class CalendarDate { by the values of their … p ate ate private Date date; } attributes CalendarDate() { Two instances with } identical values can be public CalendarDate(Date date) { this.date = date; used interchangeably } Two flavors public Date getDate() { return date; Persistent – parts of } entities public double getYearsOpen() { Calendar then = Calendar.getInstance(); Transient – intermediate then.setTime(date); values Calendar now = Calendar.getInstance(); Ideally immutable int yearsOpened = now.get(Calendar.YEAR) – then.get(Calendar.YEAR); Often missing from int monthsOpened = now.get(Calendar.MONTH) - then.get(Calendar.MONTH); procedural code d ld if (monthsOpened < 0) { ( th O d yearsOpened--; Have encapsulated state monthsOpened += 12; } and behavior return yearsOpened + (monthsOpened/12.0); } } 3/1/2009 27 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 28. Aggregates gg g A cluster of related entities and values Behaves as a unit Has a root Has b H a boundaryd Objects outside the aggregate can only reference the root Deleting the root removes everything Tests often create and test aggregates rather than individual objects 3/1/2009 28 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 29. Tests for entities and value objects j Fixture logic Create object under test (OUT) (often an aggregate) Create collaborators (also aggregates) Execution E ec tion Invoke method on OUT Verification Return value State of OUT State of collaborators Cleanup Usually do nothing – just use the g y gj garbage g collector 3/1/2009 29 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 30. Testing a POJO domain object g j p public class AccountTests extends TestCase { p public class Account { private Account account; private String accountId; private double balance; public void setUp() { private OverdraftPolicy account = new Account(quot;ABCquot;, 10.0, new overdraftPolicy; d ftP li NoOverdraftAllowed()); d f ll d()) } public double getBalance() { public void testInitialBalance() { return balance; MoneyUtil.assertMoneyEquals(10.0, } account.getBalance()); account getBalance()); } public void debit(double amount) public void testDebit() { {…} account.debit(6); MoneyUtil.assertMoneyEquals(4.0, public void credit(double account.getBalance()); amount) { … } } … Relatively easy to write tests } that run blindingly fast 3/1/2009 30 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 31. Mocking collaborators g Used less often - collaborators are usually fast to create But it can sometimes simplify tests Account and OverdraftPolicy Separate tests Account and OverdraftPolicies ⇒ Fewer, simpler tests But perhaps causes a feeling that you are not testing completely t t ti ltl Useful when creating a collaborator requires writing a l t of code i iti lot f d 3/1/2009 31 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 32. Agenda g Introduction Testing services Testing entities and value objects g j Taming test fixture logic Simplifying verification code Writing more meaningful tests 3/1/2009 32 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 33. What's a fixture Fixture = everything that needs to be in place to test an object Object we want to test Its collaborators Fixture is created by test fixture logic: Code in the test methods themselves Junit 3.x setUp() JUnit 4/TestNG @Before* annotations 3/1/2009 33 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 34. The challenge of test fixtures g Creating objects isn't always easy Individual objects have lots of parameters Objects are often belong to aggregates Test fixtures often create multiple T t fi t ft t lti l objects MoneyTransferServiceTests needs two accounts Multiple tests need the same set of objects AccountTests, MoneyTransferServiceTests need similar A i il Account objects t bj t 3/1/2009 34 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 35. Object g p j graphs can be complicated p We need all of these objects to test a Project! 3/1/2009 35 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 36. Constructing individual objects can be tricky y Best way to construct an object is to use a constructor: Supports objects without setters Supports immutable objects Forces you to support everything that's required Limitations of constructors: Lots of constructor arguments ⇒ code is difficult diffi l to read d Optional arguments ⇒ multiple constructors 3/1/2009 36 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 37. 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); •Handles optional parameters •Is more readable •But lots of noise: 'project set' project.set 3/1/2009 37 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 38. Constructing objects fluently g j y Project project = new Project(initialStatus) .name(quot;Test Project #1quot;) .description(quot;Excellent projectquot;) .initiatedBy(user) .requirementsContactName(quot;Rick Jonesquot;) .requirementsContactEmail(quot;jones@nowhere.comquot;) .type(ProjectType.EXTERNAL_DB) .artifact(ArtifactType.ARCHITECTURE) .artifact(ArtifactType.DEPLOYMENT) .artifact(ArtifactType.MAINTENANCE); • Chained method calls 3/1/2009 38 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 39. Pros and cons of this approach pp Benefits: Less noise Meaning of each value is clear Drawbacks: Breaks encapsulation – objects must have mutators/setters Doesn't work with immutable objects 3/1/2009 39 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 40. Fluently creating immutable objects y g j See http://developers.sun.com/learning/javaoneonl http://developers sun com/learning/javaoneonl ine/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;) .requirementsContactEmail(quot;jones@nowhere.comquot;) i C il(quot;j @ 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 i obje t via a constructor on t to • Allows entity to be immutable 3/1/2009 40 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 41. Nested Entity Builder example y p class 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 everything return new Project(this); } } • Pass builder as the sole public Project(ProjectBuilder builder) { constructor argument this.name = builder.name; this.initiatedBy = builder.initiatedBy; } 3/1/2009 41 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 42. Centralizing object creation with Object Mothers j Fluent interfaces can help but public class ProjectMother { Fixture logic can still be public static Project complex makeProjectInProposalState ( Status initialStatus, User user) { Same object created in … multiple t t ⇒ d li ti lti l tests duplication } Eliminate duplication: public static Project Centralize object creation in makeProjectInRequirementsState( Status initialStatus, User user) { a test utility class called an Object Mother … 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 3/1/2009 42 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 43. Creating multiple connected aggregates gg g Each Object Mother method creates a single aggregate But B t some tests need tt d to create multiple aggregates with shared sub- h d b aggregates itDepartment = DepartmentMother.makeItDepartment(); Must avoid itProjectManager = duplicating that code UserMother.makeProjectManager(itDepartment); itBusinessAnalyst = UserMother.makeBusinessAnalyst(itDepartment); in multiple fixtures projectInCompleteState = ProjectMother.makeProjectInCompleteState(…); projectInRequirementsState = ProjectMother.makeProjectInRequirementsState(…); P j tM th k P j tI R i t St t ( ) … 3/1/2009 43 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 44. Use stateful object mothers j 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 3/1/2009 44 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 45. Example of a stateful mother public class ProjectTests extends TestCase { private Project project; private User projectManager; private User businessAnalyst; public class PTrackWorld { protected void setUp() throws Exception { private final Department itDepartment; PTrackWorld world = new PTrackWorld(); private fi l User itP j tM i t final U itProjectManager; projectManager = world.getItProjectManager(); private final Project projectInCompleteState; businessAnalyst = world.getItBusinessAnalyst(); project = world.getProjectInProposalState(); } … public PTrackWorld() { itDepartment = DepartmentMother.makeItDepartment()' itProjectManager = UserMother.makeProjectManager(itDepartment); stateMachine = DefaultStateMachineFactory.makeStateMachine(quot;defaultquot;); initialStatus = stateMachine.getInitialStatus(); projectInCompleteState = ProjectMother.makeProjectInCompleteState(initialStatus, itProjectManager, getAllITDepartmentEmployees()); … } } 3/1/2009 45 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 46. Object Mother design j g Choices Same data each time vs. random data Referenced aggregates as parameters vs. create those aggregates too Tip: use descriptive names Bad: makeProject1(), makeProject2(), ... Better: makeProjectIn<State>(), … Tip: use Object Mothers from day 1 But 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 46 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 47. Agenda g Introduction Testing services Testing entities and value objects g j Taming test fixture logic Simplifying verification code Writing more meaningful tests 3/1/2009 47 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 48. Writing readable verification logic Verification phase verifies that expected outcome has been obtained State verification makes assertions about: Returned value State of object under test State of collaborators Test frameworks provide the basic assert methods but we must: Ensure readability Avoid code duplication 3/1/2009 48 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 49. Using Custom Assertions Verifying the state of object multiple assert…() calls = obscure code In multiple tests = code duplication Verification code calls a Test Utility Method that makes assertions Has an Intention Revealing Name Benefits: Makes the code more readable Eliminates duplication p 3/1/2009 49 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 50. Example – before and after protected void setUp() throws Exception { PTrackWorld world = new PTrackWorld(); projectManager = world.getItProjectManager(); project = world.getProjectInProposalState(); protected void setUp() throws Exception { startTime = new Date(); … expectedOperation0 = state0 = world.getInitialState(); new Operation(null, projectManager, state1 = state0.getApprovalStatus(); state0, state1, state2 = state1.getApprovalStatus(); g pp (); quot;Excellentquot;); quot; ll quot;) } } public void testChangeStatus() throws InterruptedException { public void testChangeStatus() boolean result = project.changeStatus(true, { projectManager, quot;Excellentquot;); boolean result = project.changeStatus(true, Date endTime = new Date(); projectManager, quot;E j tM quot;Excellentquot;); ll tquot;) Date endTime = new Date(); assertTrue(result); assertEquals(state1, project.getStatus()); assertTrue(result); assertEquals(1, project.getHistory().size()); assertEquals(state1, project.getStatus()); Operation operation = project.getHistory().get(0); assertHistoryContains(project, assertHistoryContains(project assertEquals(quot;Excellentquot;, operation.getComments()); l (quot; ll quot; i C ()) startTime, endTime, assertEquals(projectManager, operation.getUser()); expectedOperation0); assertEquals(state0, operation.getFromStatus()); } assertEquals(state1, operation.getToStatus()); assertFalse(operation.getTimestamp().before(startTime)); assertFalse(operation.getTimestamp().after(endTime)); } 3/1/2009 50 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 51. Example - After 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, 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 51 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 52. Literate assertions with Hamcrest Hamcrest is an open-source framework http://code.google.com/p/hamcrest/ Readable quot;literatequot; assertions Rich set of composable matchers Literate error messages 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( jt tSt t () llOf(i ( t t ) t(i ( t t 2)))) 3/1/2009 52 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 53. 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 org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:21) g ( j ) 3/1/2009 53 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 54. Agenda g Introduction Testing services Testing entities and value objects g j Taming test fixture logic Simplifying verification code Writing more meaningful tests 3/1/2009 54 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 55. TDD says begin with a test but… Daddy, where do tests come from? 3/1/2009 55 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 56. Best to derive tests from requirements q As a customer of the bank I want to transfer money between two bank accounts So that I don't have to write a check class AccountTransferServiceTests … { public void testTransfer_ok() { Account savingsAccount = AccountMother.makeAccountWithNoOverdraft(150); Account checkingAccount = Given that my savings account balance is $150 AccountMother.makeAccountWithNoOverdraft(10); Given that my checking account balance is $10 String savingsAccountId = savingsAccount.getAccountId(); When I transfer $50 from my savings account String checkingAccountId = checkingAccount.getAccountId(); to my checking account repository.addAccount(savingsAccount); Then my savings account balance is $100 repository.addAccount(checkingAccount); p y ( g ); Then my checking account balance is $60 service.transfer(savingsAccountId, checkingAccountId, 50); assertBalance(100, savingsAccountId); assertBalance(60, checkingAccountId); } Given that my savings account balance is $150 Given that my checking account balance is $10 public void testTransfer_overdraftAttempted() { Given that my bank does not allow me …. to have an overdraft } When I transfer $200 from my savings account to my checking account y g Then my savings account balance is unchanged Then my checking account balance is unchanged 3/1/2009 56 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 57. Testing at multiple levels g p Where in the hierarchy to write a test? Web tier Service integration test Mock service test Entity/Value object test Sometimes at every level Few high-level tests, e g verify that the UI tests e.g. handles errors Many lower-level, more detailed tests, e.g. tests for each error scenario Sometimes only lower-level tests E.g. verifying that email is sent after transferring money ⇒ mock object service test fi k bj i 3/1/2009 57 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 58. We sometimes invent requirements q User requirements focus on the external behavior: Behavior of boundary classes, i.e. UI + services Key domain model classes, e.g. account But those classes often have collaborators: E.g. Account has CalendarDate When designing those classes: Informally specify the behavior of those classes Write capture that behavior in tests 3/1/2009 58 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 59. More formal testing g 1000+ pages of rigorous testing techniques 3/1/2009 59 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 60. Deriving tests from a state machine model • State machine is a useful way to model some classes (and ( r UIs) • N+ test strategy • Turn state machine into a p q A B C tree containing all edges • Test cases = paths to all leaves s •Test = sequence of events public testPQR() { } public testPS() { } 3/1/2009 60 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 61. Deriving tests from boolean expressions p Class invariant/function/conditional: (x >= 1 & y < 10) Variable Condition Test #1 #2 #3 #4 x 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 61 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 62. Derive tests from code Write tests to improve code coverageg Identifies behavior/edge cases that you forgot to test Characterization tests: Capture current behavior Useful for refactoring (sometimes) But they can be brittle Tip: Use a code coverage Code might not be tool: Cobertura (f t l Cb t (free), ) correct! Clover (cheap), … 3/1/2009 62 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 63. Use code metrics to identify what to test Cyclomatic complexity Bugs lurk here Simplify if you can Large methods L th d Bugs lurk in here also Simplify if you can Crap4J (www.crap4j.org) Computes code quality by combining code coverage and complexity 3/1/2009 63 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 64. Summary y Testing is essential Use mock objects to accelerate tests Aggressively refactor tests to keep gg y p them simple Use Object Mothers to avoid j duplication of test fixture logic Simplify assertions Derive tests from requirements 3/1/2009 64 Copyright (c) 2007 Chris Richardson. All rights reserved.
  • 65. For more information Buy my book ☺ Go to manning.com Send email: chris@chrisrichardson.net Visit my website: http://www.chrisrichardson. net Talk to me about consulting and training 3/1/2009 65 Copyright (c) 2007 Chris Richardson. All rights reserved.

×