Test Driven Lasse Koskela Chapter 4: Concepts and Patterns for TDD Paul Ammann http://cs.gmu.edu/~pammann/ Overview • • • • • • How to Write Tests and Make Them Pass Essential Testing Concepts Closer Look into Test Doubles Guidelines for Testable Designs Unit-Testing Patterns Working With Legacy Code Lots of Stuff in This Chapter 7/1/2016 2 How to Write Tests And Make Them Pass • Test Selection Strategies – Details-First vs. Big-Picture • Details-First Offers Concrete Progress, but • Big-Picture Fleshes Out Overall Design • Judgment Call In Any Given Situation – Uncertain vs. Familiar • Attacking Uncertainty Can Reduce Risk • Familiar Code May Have Large Payoff – High Value vs. Low Hanging Fruit • Usually, High Value is Better – Happy Path vs. Error Situations • Happy Path First – Mostly for Value • Sometimes Error Situations Help Define Remainder as Happy Path Keep Your Options in Mind; Avoid a Rut 7/1/2016 3 How to Write Tests And Make Them Pass(2) • Implementation Strategies – Faking It • Useful Strategy For Handling Big Steps • Incurs Later Obligations To Squeeze Out Fakes – Triangulation • Strategy For Evolving Towards More General Implementation • Use To Drive Out Hard Coded Solutions – Obvious Implementation • Sometimes The Correct Solution Really Is Simple • Try It And See – If Tests Fail, Back It Out Goal Is To Get To Green Fast 7/1/2016 4 How to Write Tests And Make Them Pass(3) • Prime Guidelines for Test Driving – Do. Not. Skip. Refactoring. • The Design Cycle for TDD is Crucial • Skipping This Step Leads to Code/Test Bloat • Result Is Unmaintainable Software – Get To Green Fast • Write Code That Passes The Tests First • Then Worry About Refactoring – Slow Down After a Mistake • Mistakes Indicate That Your Reach Exceeds Your Grasp • Back Off and Try Smaller Steps • The Tests Are The Oracle That Keeps You On Track The First Rule Is The Most Important 7/1/2016 5 Essential Testing Concepts • Fixtures are the Context for Tests – Holistic View of State – Fixtures Remove Duplication • Bloated Tests Are Hard to Read and Hard to Maintain – Fixtures Allow For Focused Tests • Test Doubles Stand In for Dependencies – Example: java.sql.ResultSet – Details Depend on Database Used – More on Doubles in Later Slides Real-World JUnit Tests Can’t Avoid Some Complexities 7/1/2016 6 Essential Testing Concepts(2) • State and Interaction-Based Testing – State-Based Testing • Idea Is To Look At Variable State To Verifying Result @Test public void notEmpty() { Collection<String> c = new ArrayList<String>(); assertTrue (c.isEmpty()); c.add(“Bob”); assertFalse(c.isEmpty()); } – Testing For Interactions • Goal: Did The Expected Methods Calls Happen In The Right Order? • Usually Requires Some Sort of Double We lean on interaction-based testing to verify how an object talks to its collaborators; we lean on state-based testing to verify how well the object listens. 7/1/2016 7 Closer Look Into Test Doubles • Replace Object with Double If Real Object is – – – – Too Slow It’s Not Available It Depends on Something That’s Not Available Its Too Difficult To Instantiate Or Configure For a Test • Examples – How to test exceptions, such as » “Dead”code? » An unplugged network cable? – How to interact with something that’s nondeterministic? Doubles Are Key To Unit Testing 7/1/2016 8 Closer Look Into Test Doubles(2) • Example of a Test Double – Next Slide • Stubs, Fakes, and Mocks – Stub: Simplest Possible Implementation – Fake: Still Hand Coded, But A Degree More Sophisticated – Mock: Usually Generated By Tools • Mock Objects in Action – Next slide Checking Correct Behavior is Complicated! 7/1/2016 9 Test Double Example: State Testing 7/1/2016 10 Test Double Example: Interaction Testing 7/1/2016 11 Guidelines for Testable Designs • Choose Composition Over Inheritance – General Advice For Normal Coding And Testing – More Verbose, But Worth It • Avoid Static and Singleton – These Are Hard To Double • Static class names are hardcoded. How to replace at test time? – This Can Conflict With Normal Coding Advice • Consider Factories! Test Requirements Impact Code! 7/1/2016 12 4.4. Code smell: methods obtaining dependencies through static method calls Dependency Example: Static Method public class OrderProcessor { public void process(Order order) { PricingService service = PricingService.getInstance(); // use the PricingService object for processing the order } } 7/1/2016 13 Exploiting a Seam 7/1/2016 14 Injecting Dependencies: Easy Test! public class OrderProcessorTest { @Test public void testOrderProcessorWithDependencyInjection() throws Exception { OrderProcessor p = new OrderProcessor(); p.setPricingService(new FakePricingService()); ... }} 7/1/2016 15 Unit Testing Patterns • Assertion Patterns – Resulting State Assertion • Most Common Usage – Guard Assertion • Test Both Before and After The Action (precondition testing) – Delta Assertion • Verify Part of the State – Eg, List is One Bigger Than Before – Custom Assertion • Encodes Complex Verification Rules – Interaction Assertion • Verification For Interaction Tests Choose and Use Standard Patterns 7/1/2016 16 Example Interaction Assertion 7/1/2016 17 Fowler: Conventional JUnit Example public class OrderStateTester extends TestCase { private static String TALISKER = "Talisker"; private static String HIGHLAND_PARK = "Highland Park"; private Warehouse warehouse = new WarehouseImpl(); protected void setUp() throws Exception { warehouse.add(TALISKER, 50); warehouse.add(HIGHLAND_PARK, 25); } public void testOrderIsFilledIfEnoughInWarehouse() { Order order = new Order(TALISKER, 50); order.fill(warehouse); assertTrue(order.isFilled()); assertEquals(0, warehouse.getInventory(TALISKER)); } public void testOrderDoesNotRemoveIfNotEnough() { Order order = new Order(TALISKER, 51); order.fill(warehouse); assertFalse(order.isFilled()); assertEquals(50, warehouse.getInventory(TALISKER)); } } 7/1/2016 18 4.4. Code smell: methods obtaining dependencies through static method calls Fowler: jMock Version 7/1/2016 public class OrderInteractionTester extends MockObjectTestCase { private static String TALISKER = “Talisker”; public void testFillingRemovesInventoryIfInStock() { //setup - data Order order = new Order(TALISKER, 50); Mock warehouseMock = new Mock(Warehouse.class); // constructor //setup - expectations warehouseMock.expects(once()).method("hasInventory") .with(eq(TALISKER),eq(50)) .will(returnValue(true)); warehouseMock.expects(once()).method("remove") .with(eq(TALISKER), eq(50)) .after("hasInventory"); //exercise order.fill((Warehouse) warehouseMock.proxy()); //verify warehouseMock.verify(); assertTrue(order.isFilled()); } public void testFillingDoesNotRemoveIfNotEnoughInStock() { Order order = new Order(TALISKER, 51); Mock warehouse = mock(Warehouse.class); // vs. mock call warehouse.expects(once()).method("hasInventory") .withAnyArguments() .will(returnValue(false)); order.fill((Warehouse) warehouse.proxy()); assertFalse(order.isFilled()); }} 19 4.4. Code smell: methods obtaining dependencies through static method calls Fowler: EasyMock Example public class OrderEasyTester extends TestCase { private static String TALISKER = "Talisker"; private MockControl warehouseControl; private Warehouse warehouseMock; public void setUp() { warehouseControl = MockControl.createControl(Warehouse.class); warehouseMock = (Warehouse) warehouseControl.getMock(); } public void testFillingRemovesInventoryIfInStock() { //setup - data Order order = new Order(TALISKER, 50); //setup - expectations warehouseMock.hasInventory(TALISKER, 50); warehouseControl.setReturnValue(true); warehouseMock.remove(TALISKER, 50); warehouseControl.replay(); //exercise order.fill(warehouseMock); //verify warehouseControl.verify(); assertTrue(order.isFilled()); } 7/1/2016 20 Fowler/Meszaros “Double” Definitions • A Test Double is anything that stands in for a real object – Dummy • Used to fill parameter lists – Fake • Actual working implementations, but take shortcuts • Example: In Memory Database – Stub • Canned answers to calls made during tests, but useless elsewhere – Mock • Objects preprogrammed with expectations that form a specification of the calls they expect to receive Various Levels of Complexity 7/1/2016 21 4.4. Code smell: methods obtaining dependencies through static method calls Fowler: Stub Example public interface MailService { public void send (Message msg); } public class MailServiceStub implements MailService { private List<Message> messages = new ArrayList<Message>(); public void send (Message msg) { messages.add(msg); } public int numberSent() { return messages.size(); }} // Usage class OrderStateTester... public void testOrderSendsMailIfUnfilled() { Order order = new Order(TALISKER, 51); MailServiceStub mailer = new MailServiceStub(); order.setMailer(mailer); order.fill(warehouse); assertEquals(1, mailer.numberSent()); } 7/1/2016 22 4.4. Code smell: methods obtaining dependencies through static method calls Fowler: Mock Version class OrderInteractionTester... public void testOrderSendsMailIfUnfilled() { Order order = new Order(TALISKER, 51); Mock warehouse = mock(Warehouse.class); Mock mailer = mock(MailService.class); order.setMailer((MailService) mailer.proxy()); mailer.expects(once()).method("send"); warehouse.expects(once()).method("hasInventory") .withAnyArguments() .will(returnValue(false)); order.fill((Warehouse) warehouse.proxy()); } } 7/1/2016 23 Unit Testing Patterns (2) • Fixture Patterns – Parameterized Creation Method • Populating Complex Set Of Objects – Object Mother • Aggregate of Creation Methods – Automated TearDown • More Important For Integration Testing Than Unit Testing Fixtures Need Attention Too 7/1/2016 24 Unit Testing Patterns (3) • Test Patterns – Parameterized Test (see next slide) – Self Shunt • The Double Is The Test Class – Intimate Inner Class • Sharing Between Test Class and Test Double Classes – Privileged Access • Reflection-Based Injection Approaches For Legacy Code (Careful) – Extra Constructor • Compensates For Classes Not Designed For Testing Tests Need To Accommodate A Variety of Real Code 7/1/2016 25 Parameterized JUnit Example 7/1/2016 26 Working With Legacy Code • Test-Driven Legacy Development • Analyzing the Change – Change Points and Inflection Points • Change Code at Change Points • See Effects At Inflection Points • Hopefully, The Points Are Close Together… • Preparing for the Change – Install Tests To Capture Current Behavior of Inflection Point • Test-Driving the Change – Add Tests To Capture New Behavior, Also At Inflection Point Turning Legacy Code Into TDD Code 7/1/2016 27