A guide for Java developers MANNING LASSE KOSKELA Effective Unit Testing Effective Unit Testing A GUIDE FOR JAVA DEVELOPERS LASSE KOSKELA MANNING SHELTER ISLAND For online information and ordering of this and other Manning books, please visit www.manning.com The publisher offers discounts on this book when ordered in quantity For more information, please contact Special Sales Department Manning Publications Co 20 Baldwin Road PO Box 261 Shelter Island, NY 11964 Email: orders@manning.com ©2013 by Manning Publications Co All rights reserved No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by means electronic, mechanical, photocopying, or otherwise, without prior written permission of the publisher Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks Where those designations appear in the book, and Manning Publications was aware of a trademark claim, the designations have been printed in initial caps or all caps Recognizing the importance of preserving what has been written, it is Manning’s policy to have the books we publish printed on acid-free paper, and we exert our best efforts to that end Recognizing also our responsibility to conserve the resources of our planet, Manning books are printed on paper that is at least 15 percent recycled and processed without the use of elemental chlorine Manning Publications Co 20 Baldwin Road PO Box 261 Shelter Island, NY 11964 Development editor: Copyeditor: Technical proofreader: Proofreader: Typesetter: Cover designer: ISBN 9781935182573 Printed in the United States of America 10 – MAL – 18 17 16 15 14 13 Frank Pohlman Benjamin Berg Phil Hanna Elizabeth Martin Dottie Marsico Marija Tudor Few sights are as captivating as the pure joy of learning new things brief contents PART FOUNDATIONS 1 ■ The promise of good tests ■ In search of good 15 ■ Test doubles 27 PART CATALOG 45 ■ Readability 47 ■ Maintainability ■ Trustworthiness 78 115 PART DIVERSIONS .137 ■ Testable design ■ 139 Writing tests in other JVM languages ■ Speeding up test execution vii 170 156 contents preface xv acknowledgments xvii about this book xix about the cover illustration xxiv PART 1 FOUNDATIONS The promise of good tests 1.1 1.2 State of the union: writing better tests The value of having tests Factors of productivity 1.3 Summary In search of good 2.1 2.2 2.3 2.4 ■ The curve of design potential 10 Tests as a design tool 10 Test-driven development 1.4 11 ■ Behavior-driven development 14 15 Readable code is maintainable code 16 Structure helps make sense of things 18 It’s not good if it’s testing the wrong things 20 Independent tests run easily in solitude 21 ix 13 210 APPENDIX B Extending JUnit Choose runner Runners Runners Runners Test class JUnit Core Delegate test run Chosen runner Figure B.1 Given a test class, JUnit figures out which runner implementation to use One of those runners responds to the @RunWith annotation, if present, and delegates test execution to the user-defined implementation of org.junit.runner.Runner Though writing your custom runner is the single most powerful way to extend JUnit, it can also be more than what you bargained for Luckily, most of the extension needs for JUnit don’t actually warrant an all-new runner If you only need to modify how individual tests are treated rather than dictate how tests are identified in the first place, perhaps you should first look into using or implementing a rule rather than creating a custom runner B.2 Decorating tests with rules Rules are a recent addition to JUnit They provide the ability to manipulate the execution of tests A rule implementation might, for example, skip the whole execution or perform some setup or teardown before and after running the test Rules are applied at class-level and you can apply multiple rules to a single test class In the case of multiple rules, they’re applied one at a time (JUnit doesn’t make any guarantees about the order in which these rules are applied.) When JUnit looks at a test class, it creates an execution plan for running each test it finds Rules, which are implementations of org.junit.rules.MethodRule, essentially wrap or replace the current execution plan To clarify these mysterious do-gooders, let’s take a look at some of the built-in rules STARTING POINT FOR YOUR OWN RULES When you decide to write your own JUnit expressions, you’ll likely end up extending org.junit.rules.TestWatchman so that’s a good place to start reading the JUnit source code B.3 Built-in rules JUnit doesn’t just provide the rules API as an extension point for its users: a number of essential JUnit features have been implemented using the same API, including a global timeout for a whole test class, a more sophisticated way of dealing with expected exceptions, and management of temporary filesystem resources Let’s start the tour by looking at how you can configure a global timeout for all tests in a class 211 Built-in rules B.3.1 Setting a global timeout Remember when we talked about setting a timeout for a test method with @Test(timeout = X)? The rules API gives you the option of defining a timeout just once for all tests in the class This listing shows an example of such a global timeout: Listing B.1 Specifying a global timeout for all test methods in the class import org.junit.Test; import org.junit.Rule; import org.junit.rules.Timeout; B public class GlobalTimeoutTest { @Rule public MethodRule globalTimeout = new Timeout(20); @Test public void infiniteLoop1() { while (true) { } } @Test public void infiniteLoop2() { while (true) { } } Rules are annotated with @Rule C Rules are defined as public fields Rules are defined as public fields } There are three things to note about listing B.1: We define a public field C of type MethodRule B The field is annotated with @Rule The name of the field doesn’t matter to JUnit so you can name your rule how- ever you want Essentially the name should describe what the rule is for In the listing, JUnit applies the Timeout rule to both test methods, interrupting both when they exceed the configured timeout of 20 milliseconds B.3.2 Expected exceptions JUnit’s @Test annotation accepts an expected= attribute for failing the test unless it throws an exception of the specified type There is another, slightly more sophisticated way of checking for the expected exception’s properties such as the exception message, root cause, and so forth (The less sophisticated way is to catch the exception with a try-catch block and interrogate the exception object right then and there.) The sophisticated way is the ExpectedException rule Let’s look at an example to clarify its usage Listing B.2 Example usage of the ExpectedException rule import org.junit.Test; import org.junit.Rule; import org.junit.rules.ExpectedException; 212 ExpectedException rule is initialized to "none" APPENDIX B Extending JUnit public class ExpectedExceptionTest { @Rule public ExpectedException thrown = ExpectedException.none(); B @Test public void thisTestPasses() { } @Test public void throwsExceptionAsExpected() { thrown.expect(NullPointerException.class); throw new NullPointerException(); } @Test public void throwsExceptionWithCorrectMessage() { thrown.expect(RuntimeException.class); thrown.expectMessage("boom"); throw new NullPointerException("Ka-boom!"); } C Expect NullPointerException to be thrown D Expect exception containing "boom." } In the listing, we have one rule, ExpectedException, and three tests In its initial state, the rule expects no exception B to be thrown That’s why the first test passes even though rules apply to all test methods in the class they’re attached to In other words, we need to configure the rule and tell it what kind of an exception we expect And that’s exactly what we in the other two tests In the second test, we tell the rule to expect a NullPointerException C to be thrown from the test “If you don’t see this type of an exception thrown, please fail this test.” What we have here is essentially the same functionality provided by @Test(expected=NullPointerException.class) so this isn’t that exciting yet The real value of the ExpectedException rule begins to show in the third test In the third test, we’re not just telling the rule to expect an exception of a certain type With expectMessage("boom"), we’re also telling it to verify that the exception’s message contains a specific substring—in our case, the word boom D (case-sensitive) Note that this time we told ExpectedException to expect a RuntimeException and the test actually throws a NullPointerException, a subclass of RuntimeException This test passes because the check performed by the rule is that the thrown exception walks like a duck, not that it actually is a duck CHECKING THE ROOT CAUSE Now you know how to test for an exception to be thrown, that it’s of the expected type, and that its message contains the expected bits Sometimes, you want the thrown exception to wrap a specific cause The following listing shows an example of applying custom logic for determining whether the thrown exception matches our expectations Listing B.3 Testing arbitrary details about an expected exception import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; 213 Built-in rules import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; public class ExceptionWithExpectedRootCauseTest { @Rule public ExpectedException thrown = ExpectedException.none(); B Pass custom Hamcrest @Test matcher to public void throwsExceptionWithCorrectCause() { ExpectedException thrown.expect(rootCauseOf(NullPointerException.class)); throw new RuntimeException("Boom!", new NullPointerException()); } Create subclass of BaseMatcher C private Matcher