Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 MEAP Edition Manning Early Access Program Copyright 2009 Manning Publications For more information on this and other Manning titles go to www.manning.com Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Part I JUnit 1. JUnit jumpstart 2. Exploring JUnit 3. Software testing principles 4. Software tests at their best Part II Testing strategies 5. Course-grained testing with stubs 6. Mock objects 7. In-container testing Part III JUnit and the build process 8. Running JUnit tests from Ant 9. Running JUnit tests from Maven2 10. Continuous integration tools Part IV JUnit extensions 11. Presentation layer testing 12. Ajax testing 13. Server-side testing with Cactus 14. Testing JSF applications with JSFUnit 15. Testing OSGi components 16. Database testing with DBUnit 17. Testing JPA-based applications 18. JUnit on steroids Appendices A. Differences between JUnit 3.x and JUnit 4.x B. Extending JUnit API with custom runners and matchers C. The source code for the book D. JUnit integration with different IDEs E. Installing software Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Part I JUnit Welcome to JUnit in Action. JUnit is a framework that was started by Kent Beck and Erich Gamma in the late 1995. Ever since then the popularity of the framework has been growing and is now the de-fact standard for unit-testing Java applications. This book is actually a second edition. The first edition was a best-seller, written by Vincent Massol and Ted Husted in 2003, and was dedicated in version 3.x of JUnit We will try to cover the newest version of JUnit - 4.5, and will talk about lots of features that were included after the first edition of the book. At the same time we will try to focus on some other interesting techniques in testing your code - mock objects, JUnit extensions, testing different layers of your application and so forth. This part will start by exploring JUnit itself. We will focus on the other tools and techniques further in the book. The first chapter will give you a very quick introduction to the concepts of testing. You need this knowledge to get you jumpstarted. You will get straight to the code and see how to write a very simple test and how to execute it and see the results from it. The second chapter introduces a JUnit at its most. We build a bigger project and walking over the code we will let you know not only of the JUnit concepts, widgets and guts, but also we will show you the best-practices in writing a test-case and will demonstrate them with the project we have. The third chapter is dedicated on tests as a whole. We describe different kinds of tests, and the different scenarios to which they apply. We will also get to know the different platforms (development, production, etc.) and will show you which tests and which scenarios are best to execute there. The last chapter in this part of the book is dedicated on improving your testing skills. We will show you how to measure your test-coverage and how to improve it. How to produce testable code before you write your tests and how to write the tests before you write a single line of code. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 01 JUnit jump-start This chapter covers ƒ What JUnit is ƒ Installing JUnit ƒ Writing your first test ƒ Running tests Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Never in the field of software development was so much owed by so many to so few lines of code. —Martin Fowler All code is tested. During development, the first thing we do is run our own programmer’s “acceptance test.” We code, compile, and run. When we run, we test. The “test” may just be clicking a button to see if it brings up the expected menu. Nevertheless, every day, we code, we compile, we run…, and we test. When we test, we often find issues—especially on the first run. Therefore, we code, compile, run, and test again. Most of us will quickly develop a pattern for our informal tests: We add a record, view a record, edit a record, and delete a record. Running a little test suite like this by hand is easy enough to do; so we do it. Over and over again. Some programmers like doing this type of repetitive testing. It can be a pleasant break from deep thought and hard coding. When our little click-through tests finally succeed, there is a real feeling of accomplishment: Eureka! I found it! Other programmers dislike this type of repetitive work. Rather than run the test by hand, they prefer to create a small program that runs the test automatically. Play-testing code is one thing; running automated tests is another. If you are a “play-test” developer, this book is for you. We will show you how creating automated tests can be easy, effective, and even fun. If you are already “test-infected,” this book is also for you. We cover the basics in part 1, and then move on to the tough, real-life problems in parts 2, 3, and 4. 1.1 Proving it works Some developers feel that automated tests are an essential part of the development process: You cannot prove a component works until it passes a comprehensive series of tests. In fact, two developers felt that this type of “unit testing” was so important that it deserved its own framework. In 1997, Erich Gamma and Kent Beck created a simple but effective unit testing framework for Java, called JUnit. The work followed the design of an earlier framework Kent Beck created for Smalltalk, called SUnit. DEFINITION: framework — A framework is a semi-complete application1. A framework provides a reusable, common structure to share between applications. Developers incorporate the 1 Ralph Johnson and Brian Foote, “Designing Reusable Classes,” Journal of Object-Oriented Programming 1.5 (June/July 1988): 22–35; http://www.laputan.org/drc/drc.html. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 framework into their own application and extend it to meet their specific needs. Frameworks differ from toolkits by providing a coherent structure, rather than a simple set of utility classes. If you recognize those names, it is for good reason. Erich Gamma is one of the “Gang of Four” who gave us the now classic Design Patterns book2. We know Kent Beck equally well for his groundbreaking work in the software discipline known as Extreme Programming (http://www.extremeprogramming.org). JUnit (http://www.junit.org) is open source software, released under IBM’s Common Public License Version 1.0 and hosted on SourceForge. The Common Public License is business-friendly: People can distribute JUnit with commercial products without a lot of red tape or restrictions. JUnit quickly became the de facto standard framework for developing unit tests in Java. In fact, the underlying testing model, known as xUnit, is on its way to becoming the standard framework for any language. There are xUnit frameworks available for ASP, C++, C#, Eiffel, Delphi, Perl, PHP, Python, REBOL, Smalltalk, and Visual Basic—just to name a few! Of course, the JUnit team did not invent software testing or even the unit test. Originally, the term unit test described a test that examined the behavior of a single unit of work. Over time, usage of the term unit test broadened. For example, IEEE has defined unit testing as “Testing of individual hardware or software units or groups of related units” (emphasis added)3. In this book, we use the term unit test in the narrower sense of a test that examines a single unit in isolation from other units. We focus on the type of small, incremental test that programmers apply to their own code. Sometimes we call these programmer tests to differentiate them from quality assurance tests or customer tests (http://c2.com/cgi/wiki?ProgrammerTest). Here is a generic description of a typical unit test from our perspective: “Confirm that the method accepts the expected range of input, and that the method returns the expected value for each input.” This description asks us to test the behavior of a method through its interface. If we give it value x, will it return value y? If we give it value z instead, will it throw the proper exception? 2 Erich Gamma et al., Design Patterns (Reading, MA: Addison-Wesley, 1995). 3 IEEE Standard Computer Dictionary: A Compilation of IEEE Standard Computer Glossaries (New York, IEEE, 1990). Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 DEFINITION: unit test—A unit test examines the behavior of a distinct unit of work. Within a Java application, the “distinct unit of work” is often (but not always) a single method. By contrast, integration tests and acceptance tests examine how various components interact. A unit of work is a task that is not directly dependent on the completion of any other task. Unit tests often focus on testing whether a method is following the terms of its API contract. Like a written contract by people who agree to exchange certain goods or services under specific conditions, an API contract is a formal agreement made by the signature of a method. A method requires its callers to provide specific object references or primitive values and returns an object reference or primitive value.. If the method cannot fulfill the contract, the test throws an exception and we say that the method has broken its contract. DEFINITION: API contract—A view of an Application Programming Interface (API) as a formal agreement between the caller and the callee. Often the unit tests help define the API contract by demonstrating the expected behavior. The notion of an API contract stems from the practice of Design by Contract, popularized by the Eiffel programming language (http://archive.eiffel.com/doc/manuals/technology/contract). In this chapter, we will walk through creating a unit test for a simple class from scratch. We will start by writing a test and its minimal runtime framework, so you can see how we used to do things. Then, we will roll out JUnit to show you how the right tools can make life much simpler. 1.2 Starting from scratch Let us say you have just written the Calculator class shown in listing 1.1. Listing 1.1 The test calculator class public class Calculator { public double add(double number1, double number2) { return number1 + number2; } } Although the documentation is not shown, the intended purpose of the Calculator’s add(double, double) method is to take two doubles and return the sum as a double. The compiler can tell you that it compiles, but you should also make sure it works at runtime. A core tenet of unit testing is “Any program feature without an automated test simply doesn’t Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 exist.”4 The add method represents a core feature of the calculator. You have some code that allegedly implements the feature. What is missing is an automated test that proves your implementation works. Isn’t the add method just “too simple to break”? The current implementation of the add method is too simple to break. If add were a minor utility method, then you might not test it directly. In that case, if add did fail, then tests of the methods that used add would fail. The add method would be tested indirectly, but tested nonetheless. In the context of the calculator program, add is not just a method, it is a program feature. In order to have confidence in the program, most developers would expect there to be an automated test for the add feature, no matter how simple the implementation appears. In some cases, you can prove program features through automatic functional tests or automatic acceptance tests. For more about software tests in general, see chapter 3. Yet testing anything at this point seems problematic. You do not even have a user interface with which to enter a pair of doubles. You could write a small command line program that waited for you to type in two double values and then displayed the result. Of course, then you would also be testing your own ability to type a number and add the result ourselves. This is much more than what you want to do. You just want to know if this “unit of work” will actually add two doubles and return the correct sum. You do not want to test whether programmers can type numbers! Meanwhile, if you are going to go to the effort of testing your work, you should also try to preserve that effort. It is good to know that the add(double,double) method worked when you wrote it. However, what you really want to know is whether the method works when you ship the rest of the application or whenever you make a subsequent modification. If we put these requirements together, we come up with the idea of writing a simple test program for the add method. The test program could pass known values to the method and see if the result matches our expectations. You could also run the program again later to be sure the method continues to work as the application grows. So what is the simplest possible test program you could write? What about the CalculatorTest program shown in listing 1.2? Listing 1.2 A simple test calculator program public class CalculatorTest { public static void main(String[] args) { Calculator calculator = new Calculator(); double result = calculator.add(10,50); 4 Kent Beck, Extreme Programming Explained: Embrace Change (Reading, MA: Addison- Wesley, 1999). Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 if (result != 60) { System.out.println("Bad result: " + result); } } } The first CalculatorTest is simple indeed. It creates an instance of Calculator, passes it two numbers, and checks the result. If the result does not meet your expectations, you print a message on standard output. If you compile and run this program now, the test will quietly pass, and all will seem well. However, what happens if you change the code so that it fails? You will have to watch carefully the screen for the error message. You may not have to supply the input, but you are still testing your own ability to monitor the program’s output. You want to test the code, not yourself! The conventional way to signal an error conditions in Java is to throw an exception. Let us throw an exception instead to indicate a test failure. Meanwhile, you may also want to run tests for other Calculator methods that you have not written yet, like subtract or multiply. Moving to a modular design would make it easier to catch and handle exceptions and make it easier to extend the test program later. Listing 1.3 shows a slightly better CalculatorTest program. Listing 1.3 A (slightly) better test calculator program public class CalculatorTest { private int nbErrors = 0; public void testAdd() { (1) Calculator calculator = new Calculator(); | double result = calculator.add(10, 50); | if (result != 60) { | throw new IllegalStateException("Bad result: " + result); | } | } (1) public static void main(String[] args) { Cal try { (2) culatorTest test = new CalculatorTest(); test.testAdd(); | } catch (Throwable e) { | | test.nbErrors++; | e.printStackTrace(); | } | if (test.nbErrors > 0) { (2) throw new IllegalStateException("There were " + test.nbErrors + " error(s)"); } } } Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Working from listing 1.3, at (1) you move the test into its own testAdd method. It is now easier to focus on what the test does. You can also add more methods with more unit tests later, without making the main method harder to maintain. At (2), you change the main method to print a stack trace when an error occurs and then, if there are any errors, end by throwing a summary exception. 1.3 Understanding unit-testing frameworks There are several best practices that unit testing frameworks should follow. These seemingly minor improvements in the CalculatorTest program highlight three rules that (in our experience) all unit testing frameworks should follow: ƒ Each unit test must run independently of all other unit tests. ƒ The framework must detect and report errors test by test. ƒ It must be easy to define which unit tests will run. The “slightly better” test program comes close to following these rules but still falls short. For example, in order for each unit test to be truly independent, each should run in a different class instance and ideally in a different class loader instance. You can now add new unit tests by adding a new method and then adding a corresponding try/catch block to main. This is a step up, but still short of what you would want in a real unit test suite. Our experience tells us that large try-catch blocks cause maintenance problems. You could easily leave a unit test out and never know it! It would be nice if you could just add new test methods and continue working. However, how would the program know which methods to run? Well, you could have a simple registration procedure. A registration method would at least inventory which tests are running. Another approach would be to use Java’s reflection and introspection capabilities. A program could look at itself and decide to run whatever methods follow a certain naming convention —like those that begin with the letters test, for example. Making it easy to add tests (the third rule in our earlier list) sounds like another good rule for a unit testing framework. The support code to realize this rule (via registration or introspection) would not be trivial, but it would be worthwhile. There would be a lot of work up front, but that effort would pay off each time you added a new test. Happily, the JUnit team has saved you the trouble. The JUnit framework already supports introspecting methods. It also supports using a different class instance and class loader instance for each test, and reports all errors on a test-by-test basis. Now that you have a better idea of why you need a unit testing framework, let us set up JUnit and see it in action. Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 1.4 Setting up JUnit In order to use JUnit to write your application tests, you will simply need to add the JUnit jar file to your project’s compilation classpath and to your execution classpath. Follow these steps: Download the JUnit distribution (junit-4.6 or newer) from http://www.junit.org. JUnit contains several test samples that you will run to get familiar with executing JUnit tests. Unzip the distribution Zip file to a directory on your computer system (for example, C:\ on Windows or /opt/ on UNIX). In this directory, unzipping will create a subdirectory for the JUnit distribution you downloaded (for example, C:\junit4.6 on Windows or /opt/junit4.6 on UNIX). You are now ready to run the tests provided with the JUnit distribution. JUnit comes complete with Java programs that you can use to view the result of a test, including a text- based test runner with console output (figure 1.2). To run the text test runner, open a shell in C:\junit4.6 on Windows or in /opt/junit4.6 UNIX, and type the appropriate command for your operating system: Windows: java -cp junit-4.6.jar;. junit.samples.AllTests UNIX: java -cp junit-4.6.jar:. junit.samples.AllTests The AllTests class contains a main method to execute the sample tests: public static void main (String[] args) { junit.textui.TestRunner.run(suite()); } Figure 1.1 shows the result of the test executing. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Fig 1.1 Execution of the JUnit distribution sample tests using the text test runner Notice that the JUnit text test runner displays passing tests with a dot. Had there been errors, they would have displayed with an E instead of a dot. In part III of the book, we will look at running tests using the Ant build tool and also the Maven build tool. 1.5 Testing with JUnit JUnit has many features that make it easy to write and run tests. You will see these features at work throughout this book: ƒ Separate test class instances and class loaders for each unit test to avoid side effects. ƒ JUnit annotations to provide resource initialization and reclamation methods: @Before, @BeforeClass, @After, and @AfterClass. ƒ A variety of assert methods to make it easy to check the results of your tests. ƒ Integration with popular tools like Ant, Maven, and popular IDEs like Eclipse, NetBeans, IntelliJ, and JBuilder. Without further ado, let us turn to listing 1.4 and see what the simple Calculator test looks like when written with JUnit Listing 1.4 The JUnit CalculatorTest program import static org.junit.Assert.*; import org.junit.Test; public class CalculatorTest { (1) @Test (2) public void testAdd() { Calculator calculator = new Calculator(); (3) double result = calculator.add(10, 50); (4) assertEquals(60, result, 0); (5) Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } } This is a much simpler test, let us walk through it. At (1), you start by defining a test class. The only restriction is that the class must be public, you can name it whatever you like though. It is standard practice to end the class name with "Test". Notice also that in contrast to JUnit 3 where you needed to extend the TestCase class, this requirement has been removed in JUnit 4. At (2), you mark the method as a unit test method by adding the @Test annotation5. A best-practice is to name your test methods following the testXXX pattern. JUnit does not have method name restrictions, you can name your methods as you like, as long as they have the @Test annotation they will be executed by JUnit. At (3), you start the test by creating an instance of the Calculator class (the “object under test”), and at (4), as before, you execute the test by calling the method to test, passing it two known values. At (5), the JUnit framework begins to shine! To check the result of the test, you call an assertEquals method, which you imported with a static import on the first line of the class. The Javadoc for the assertEquals method is: /** * Asserts that two doubles or floats are equal to within a positive delta. * If the expected value is infinity then the delta value is ignored. */ static public void assertEquals( double expected, double actual, double delta) In listing 1.4, you passed assertEquals these parameters: expected = 60 actual = result delta = 0 Since you passed the calculator the values 10 and 50, you tell assertEquals to expect the sum to be 60. (You pass 0 as the delta since you are adding integers.) When you called the calculator object, you tucked the return value into a local double named result. Therefore, you pass that variable to assertEquals to compare against the expected value of 60. If the actual value is not equal to the expected value, JUnit throws an unchecked exception, which causes the test to fail. Most often, the delta parameter can be zero, and you can safely ignore it. It comes into play with calculations that are not always precise, which includes many floating-point 5 Annotations were first introduced in JDK 1.5. so in order to use them you need to have the 1.5 or later version of the JDK. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 calculations. The delta provides a range factor. If the actual value is within the range expected - delta and expected + delta the test will pass. You may find it useful when doing mathematical computations with rounding or truncating errors or when asserting a condition about the modification date of a file, as the precision of these dates depend on the operating system. JUnit Design Goals The JUnit team has defined three discrete goals for the framework: The framework must help us write useful tests. The framework must help us create tests that retain their value over time. The framework must help us lower the cost of writing tests by reusing code. In listing 1.4, we showed how easy it is to write tests with JUnit. We will return to the other goals in chapter 2. Let us assume you have entered the code from listings 1.1 and 1.4 in the C:\junitbook\jumpstart directory (/opt/junitbook/jumpstart on UNIX). Let us first compile the code by opening a command shell in that directory and typing the following (we will assume you have the javac executable on your PATH): Windows: javac -cp \junit4.6\junit-4.6.jar *.java UNIX: javac -cp /junit4.6/junit-4.6.jar *.java You are now ready to start the console test runner, by typing the following: Windows: java -cp .;\junit4.6\junit-4.6.jar ➔ org.junit.runner.JUnitCore CalculatorTest UNIX: java -cp .:/junit4.6/junit-4.6.jar ➔org.junit.runner.JUnitCore CalculatorTest Figure 1.2 shows the test result . The remarkable thing about the JUnit CalculatorTest class in listing 1.4 is that the code is easier to write than the first CalculatorTest program in listing 1.2. In addition, you can run the test automatically through the JUnit framework. Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 1.2 Execution of the first JUnit test CalculatorTest using the text test runner NOTE If you are maintaining tests written prior to JUnit version 3.8.1, your class needs a String constructor, for example: public CalculatorTest(String name) { super(name); } This is no longer required with JUnit 3.8.1 and later. 1.6 Summary Every developer should perform some type of test to see if code actually works. Developers who use automatic unit tests can repeat these tests on demand to ensure that new code works and does not break existing tests. Simple unit tests are not difficult to create without JUnit, but as tests are added and become more complex, writing and maintaining tests becomes more difficult. JUnit is a unit testing framework that makes it easier to create, run, and revise unit tests. In this chapter, we scratched the surface of JUnit by stepping through a simple test. Of course, JUnit has much more to offer. In chapter 2, we take a closer look at the JUnit framework classes (different annotations and assertion mechanisms) and how they work together to make unit testing efficient and effective. We will also walk through the differences between the old-style JUnit 3 and the new features in JUnit 4. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 02 Exploring JUnit This chapter covers ƒ Using the core JUnit classes ƒ Understanding JUnit mechanisms ƒ Understanding the JUnit lifecycle Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Mistakes are the portals of discovery. —James Joyce In chapter 1, we decided that we need a reliable and repeatable way to test our program. Our solution is to write or reuse a framework to drive test code that exercises our program's API. As our program grows with new classes and new methods to existing classes, we need to grow our test code as well. As experience has taught us that sometimes classes interact in unexpected ways, we need to make sure that we can run all of our tests at any time, no matter what code changes took place. The question becomes, how do we run multiple test classes? How do we find out which tests passed and which ones failed? In this chapter, we will look at how JUnit provides the functionality to answer those questions. We will begin with an overview of the core JUnit concepts – the test class, test suite and test runner. We will take a close look at the core test runners and the test suite, before we revisit our old friend the test class. We will also examine how the core classes work together. In the second part of this chapter, we will use an example application to show you how to use these core JUnit concepts. We will demonstrate best practices for writing and organizing test code. 2.1 Exploring core JUnit The CalculatorTest program from chapter 1, shown in listing 2.1, defines a test class with a single test method testAdd. The requirements to define a test class are that the class must be public and contain a zero-argument constructor. In our example, since we do not define any other constructors, we do not need to define the zero-argument constructor, Java creates it for us implicitly. The requirement to create a test method is that it must be annotated with @Test, be public, take no arguments, and return void. Listing 2.1 The CalculatorTest test case import static org.junit.Assert.assertEquals; import org.junit.Test; public class CalculatorTest { @Test public void testAdd() { Calculator calculator = new Calculator(); double result = calculator.add(10, 50); assertEquals(60, result, 0); } } JUnit creates a new instance of the test class before invoking each @Test method. This helps provide independence between test methods and avoids unintentional side effects in Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 the test code. Since each test methods runs on a new test class instance, you cannot reuse instance variables values across test methods. To perform test validation, you use the assert methods provided by the JUnit Assert class. As you can see from the previous example, you statically import these methods in your test class. Alternatively, you can import JUnit Assert class itself, depending on your taste for static imports. The following table lists some of the most popular assert methods. Table 2.1 Some of the assertXXX methods in JUnit. assertXXX method What is it used for assertArrayEquals(“message”, A, B) Asserts the equality of the A and B arrays. assertEquals(“message”, A, B) Assert equality of objects A and B. This assert actually invokes the equals() method on the first object against the second. assertSame(“message”, A, B) Asserts that the A and B objects have the same value. While the previous assert method checks to see that both the A and B are the same objects (using equals method), the assertSame method actually checks to see if the A and B objects have the same value (using == operator). assertTrue(“message”, A) Assert the A condition is true. assertNotNull(“message”, A) Assert the A object is not null. Assert methods with two value parameters follow a pattern worth memorizing: the first parameter (A above) is the expected value and the second parameter (B above) is the actual value. JUnit provides many other methods – like assertArrayNotEquals, assertNotSame, assertNotTrue, etc. It also provides the same methods with a different signature – without the message parameter. It is a best practice to provide an error message for all your assert method calls. Recall Murphy's Law and apply it here, when an assertion fails, describe what went wrong in a human readable message. When you need to run several test classes at once, you create another object called a test suite (or Suite.) Your test suite is actually a special test runner (or Runner), so you can run it as you would a test class. Once you understand how a test class, Suite, and Runner work, you will be able to write whatever tests you need. These three objects form the backbone of the JUnit framework. On a daily basis, you only need to write test classes and test suites. The other classes work behind the scenes to bring your tests to life. Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 DEFINITION: Test class (or TestCase or test case) — A class that contains one or more tests represented by methods annotated with @Test. Use a test case to group together tests that exercise common behaviors. In the remainder of this book, when we mention a test, we mean a method annotated with @Test; and when we mention a test case (or test class), we mean a class that holds these test methods—that is, a set of tests. Suite (or test suite) — A group of tests. A test suite is a convenient way to group together tests that are related. For example, if you do not define a test suite for a test class, JUnit automatically provides a test suite that includes all tests found in the test class (more on that later). Runner (or test runner) — A runner of test suites. JUnit provides different runners to execute your tests. We cover these runners later in this chapter and show you how to write your own test runners. Let us take a closer look at the responsibilities of each of the core objects that make up JUnit. Table 2.2 Core objects that make up JUnit. JUnit concept Responsibilities Introduced in … Assert Lets you define the conditions that you want to test. An assert method is silent when its proposition succeeds but throws an exception if the proposition fails. Section 2.1 Test A method with an @Test annotation defines a test. To run this method JUnit constructs a new instance of the containing class and then invokes the annotated method. Section 2.1 Test class A test class is the container for @Test methods. Section 2.1 Suite The Suite allows you to group test classes together. Section 2.3 Runner The Runner class runs tests. JUnit 4 is backward compatible and will run JUnit 3 tests. Section 2.2 We can move on now on explaining in details those objects from the table above that we still haven’t talked about – that are the Runner and Suite objects. We will start by introducing the Runner object in the next section and later on in section 2.3 we will talk about Suites. 2.2 Running tests with the JUnit test runner Writing tests can be fun, but what about the grunt work of running them? When you are first writing tests, you want them to run as quickly and easily as possible. You should be able to Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 make testing part of the development cycle— code : run : test : code (or test : code : run : test if you are test-first inclined). There are IDEs and compilers for quickly building and running applications, but what can you use to run the tests? 2.2.1 JUnit’s test runners JUnit4 is built around the notion of backward compatibility. Because of the fact that the 4.x version of JUnit is completely different from the 3.x ones, it should be possible to execute not only JUnit4 test, but also to execute “older” 3.x tests. That is the reason why in its latest versions JUnit provides a couple of runners – for running JUnit3x tests, JUnit4 tests and for running different sets of tests. Table 2.3 Different test runners that come with JUnit4 Runner Purpose org.junit.internal.runners.JUnit38ClassRunner This runner is included in the current release of JUnit only for backward compatibility. It will start the test case as a JUnit38 test case. org.junit.runners.JUnit4 This runner is inclined to force JUnit to start the test case as a JUnit4 test case. org.junit.runners.Parameterized Parameterized test-runner is supposed to run same sets of tests with different parameters. org.junit.runners.Suite The Suite is actually a container that can hold different tests together. The Suite is also a runner that executes all the @Test annotated methods in your test-class. Normally JUnit will detect the right test runner to use without asking you, based on the test-case you provide. If you want to force it to use any particular runner you need to use the @RunWith annotation as shown in the following listing. Listing 2.2 @RunWith annotation of JUnit @RunWith(value=org.junit.internal.runners.JUnit38ClassRunner.class) public class TestWithJUnit38 extends junit.framework.TestCase { […] } So far we saw an overview of the different JUnit test-runners and how to instruct JUnit to use any of them. But in order to choose the right test-runner we need to look in more details into those objects Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 2.2.2 Selecting a test runner The two first runners listed in table 2.3 force JUnit to run the test respectfully as JUnit3.x or JUnit4.x test. JUnit does not need to be instructed which test-runner to use and these two test-runners are only for special occasions. The third listed runner (Parameterized) is an interesting creature that allows you to run your single tests many times with different sets of parameters. As we believe an example is worth several pages of explanation, the following listing demonstrates the Parameterized runner in action. Listing 2.3 Running tests with the Parameterized test-runner. […] @RunWith(value=Parameterized.class) (1) public class ParameterizedTest { private double expected; (2) private double valueOne; (2) private double valueTwo; (2) @Parameters (3) public static Collection dataParameters() { (3) return Arrays.asList(new Object[][] { {2,1,1}, //expected, valueOne, valueTwo {3,2,1}, {4,3,1}, }); } public ParameterizedTest(double expected, double valueOne, double valueTwo) { (4) this.expected = expected; this.valueOne = valueOne; this.valueTwo = valueTwo; } @Test public void sum() { (5) Calculator calc = new Calculator(); (6) assertEquals(expected, calc.add(valueOne, valueTwo), 0); (7) } } So to have a test-class run with the Parameterized test-runner you need to have several obligatory elements. You need to denote the @RunWith annotation to use the Parameterized test-runner (1). Then we declare some local instance variables we are going to use in our tests (at (2)) and we provide the obligatory @Parameters-annotated method (3). This method is used to provide the input and output data for the parameterized tests. This method needs to be public, static and return a Collection instance by signature. As we want to test the add method of our Calculator program we provide three parameters – expected value, and two values that we will add together. At (4) we specify Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 the obligatory constructor for the test. Note that this time our test-case does not have a no- argument constructor, but instead has a constructor that accepts parameters for the test. At (5) we finally implement the sum @Test method, that instantiates the Calculator program (6), and asserts against the parameters we have provided (7). If you run this test you will see that it is executed exactly as many times as the size of the Collection returned by the @Parameters method – that is the number of the parameter entries. The execution of this single test-case has actually the same result as the execution of the following many test-cases with different parameters: sum: assertEquals( 2, calculator.add( 1,1 ), 0 ); sum: assertEquals( 3, calculator.add( 2,1 ), 0 ); sum: assertEquals( 4, calculator.add( 3,1 ), 0 ); For the last test-runner (Suite) we dedicate a separate section (2.3) so we will take a closer look on it later on. 2.2.3 The JUnitCore façade To make running tests as quick and easy as possible, JUnit provides a façade (org.junit.runner.JUnitCore), which operates with any of the test runners. This facade is designed to execute your tests and provide you with statistics regarding the outcome. Because it is specifically designed for this purpose, the facade can be very easy to use. You can see the JUnitCore class in action in figure 1.3 in the previous chapter. Design patterns in action: Facade A1 façade is a design pattern that provides a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use. This can be used to simplify a number of complicated object interactions into a single interface. Facade exposes the interface of contained objects to the world, and the point of it is that this way clients don't have to be concerned with exactly which object in a subsystem they're dealing with. They just call their methods on the facade in blissful ignorance. The JUnit façade works the exact same way as described – it determines which runner to use for running your tests. It supports running JUnit38 tests, JUnit4 tests or a mixture of both. There used to be a whole set of test runners in the previous 3.x version of JUnit, including Swing and AWT test runners, but they are not included in the current 4.x version of the framework. Those graphical test runners usually had a progress indicator running across the screen, which was known as the famous JUnit green bar. JUnit testers tend to refer to 1 The definition is taken from the Portland Pattern Repository here: http://c2.com/cgi/wiki?FacadePattern Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 passing tests as green-bar and failing tests as red-bar. “Keep the bar green to keep the code clean” is the JUnit motto. So don’t get curious what its origin is, the next time you read it. As I already mentioned, in the current version of JUnit there is no graphical test runners included, so the only way to use a graphical way to run your tests is to use your favorite IDE. All of the IDEs out there support integration with JUnit. You can see the Eclipse’s JUnit plugin executing a test in figure 2.1. 2.2.4 Defining your own custom test runner Unlike other elements of the JUnit framework, there is no Runner interface. Instead, the various test runners bundled with JUnit all extend Runner. If you needed to write your own test runner for any reason, you could also extend this class yourself. Please refer to Appendix B where we cover this topic in details. 2.3 Composing tests with TestSuite Simple things should be simple … and complex things should be possible. Suppose you compile the simple calculator test program from listing 2.1 and hand it to the console façade runner, like this: >java org.junit.runner.JUnitCore CalculatorTest It should run just fine, assuming you have the correct classpath. Altogether, this seems simple - at least as far as running a single test case is concerned. But what happens when you want to run multiple test cases? Or just some of your test cases? How can you group test cases? Between the notions of test-case and test-runner, it would seem that you need some type of container that can collect several tests together and run them as a set. But, by making it easier to run multiple cases, you don’t want to make it harder to run a single test case. JUnit’s answer to this puzzle is the Suite. The Suite is designed to run one or more test cases. The test runner launches the Suite; which test-cases to run is up to the Suite. 2.3.1 Running the suite You might wonder how you managed to run the example at the end of chapter 1, when you didn’t define a Suite. To keep simple things simple, the test runner automatically creates a Suite if you don’t provide one of your own. (Sweet!) Fig. 2.1 JUnit’s green bar, shown in Eclipse. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The default Suite scans your test class for any methods that are @Test annotated. Internally, the default Suite creates an instance of your TestClass for each @Test method. This way every @Test method is executed separately from the others – so your test- methods do not interfere. If you added another test in the CalculatorTest class, like testSubtract, and you annotate it with the @Test annotation, the default Suite would automatically include it too. So you see the Suite object is actually a Runner that executes all of the @Test annotated methods in the test-class. But is it possible to combine test-methods from different test-classes? Of course it is - that’s the real purpose of the Suite object! The next code-listing shows exactly how to do this. Listing 2.4 Composing test-methods from different test-classes. […] @RunWith(value=org.junit.runners.Suite.class) (1) @SuiteClasses(value={TestFolderConfiguration.class, (2) TestFileConfiguration.class}) (2) public public class RunConfigurationTests { } As you can see the only thing you need to do is Specify the appropriate runner with the @RunWith annotation. List the tests we want to include in this test by specifying the test-classes in the @SuiteClasses annotation. All the test-methods from those classes will be included. Now you see for the CalculatorTest in listing 2.1, the default Suite could be represented in code like this: @RunWith(value=Suite.class) @SuiteClasses(value={CalculatorTest.class}) public class AllTests { } What we did so far was to introduce the different objects that build the backbone of the JUnit framework. We looked over TestClass, Runner, and Suite to explore them in details. What we want next is to see how to use those classes in a real-world example. And that is exactly what we are going to do! In the next section we introduce the Controller design pattern and build a sample Controller component application that we will test with JUnit. This way we will not only show you how to use the JUnit components we have been dealing so far, but we will also introduce a lot of JUnit best-practices with the means of the example application we have. Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 2.4 Introducing the controller component Core J2EE Patterns describes a controller as a component that “interacts with a client, controlling and managing the handling of each request”, and tells us that it is used in both presentation-tier and business-tier patterns.2 In general, a controller does the following: ƒ Accepts requests ƒ Performs any common computations on the request ƒ Selects an appropriate request handler ƒ Routes the request so that the handler can execute the relevant business logic ƒ May provide a top-level handler for errors and exceptions A controller is a handy class and can be found in a variety of applications. For example, in a presentation-tier pattern, a web controller accepts HTTP requests and extracts HTTP parameters, cookies, and HTTP headers, perhaps making the HTTP elements easily accessible to the rest of the application. A web controller determines the appropriate business logic component to call based on elements in the request, perhaps with the help of persistent data in the HTTP session, a database, or some other resource. The Apache Struts framework is an example of a web controller. Another common use for a controller is to handle applications in a business tier pattern. Many business applications support several presentation layers. Web applications may be handled through HTTP clients. Desktop applications may be handled through Swing clients. Behind these presentation tiers there is often an application controller, or state machine. Many Enterprise Java Bean (EJB) applications are implemented this way. The EJB tier has its own controller, which connects to different presentation tiers through a business façade or delegate. Given the many uses for a controller, it’s no surprise that controllers crop up in a number of enterprise architecture patterns, including Page Controller, Front Controller, and Application Controller3. The controller you will design here could be the first step in implementing any of these classic patterns. Let’s work through the code for the simple controller, to see how it works, and then try a few tests. If you would like to follow along and run the tests as you go, all the source code for this chapter is available at SourceForge (http://junitbook.sf.net). See appendix A for more about setting up the source code. 2.4.1 Designing the interfaces Looking over the description of a controller, four objects pop out: the Request, the Response, the RequestHandler, and the Controller. The Controller accepts a 2 Deepak Alur, John Crupi, and Dan Malks, Core J2EE Patterns: Best Practices and Design Strategies(Upper Saddle River, NJ: Prentice Hall, 2001). 3 Martin Fowler, Patterns of Enterprise Application Architecture (Boston: Addison-Wesley, 2003). Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Request, dispatches a RequestHandler, and returns a Response object. With a description in hand, you can code some simple starter interfaces, like those shown in listing 2.5. Listing 2.5 Request, Response, RequestHandler and Controller interfaces public interface Request { String getName(); (1) } public interface Response (2) { } public interface RequestHandler { Response process(Request request) throws Exception; (3) } public interface Controller { Response processRequest(Request request); (4) void addHandler(Request request, RequestHandler requestHandler); (5) } (1) Define a Request interface with a single getName method that returns the request’s unique name, just so you can differentiate one request from another. As you develop the component you will need other methods, but you can add those as you go along. (2) Here you specify an empty interface. To begin coding, you only need to return a Response object. What the Response encloses is something you can deal with later. For now, you just need a Response type you can plug into a signature. (3) Define a RequestHandler that can process a Request and return your Response. RequestHandler is a helper component designed to do most of the dirty work. It may call upon classes that throw any type of exception. So, Exception is what you have the process method throw. (4) Define a top-level method for processing an incoming request. After accepting the request, the controller dispatches it to the appropriate RequestHandler. Notice that processRequest does not declare any exceptions. This method is at the top of the control stack and should catch and cope with any and all errors internally. If it did throw an exception, the error would usually go up to the Java Virtual Machine (JVM) or servlet container. The JVM or container would then present the user with one of those nasty white screens. Better you handle it yourself. (5) This is a very important design element. The addHandler method allows you to extend the Controller without modifying the Java source. Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Design patterns in action: Inversion of Control Registering a handler with the controller is an example of Inversion of Control. This pattern is also known as the Hollywood Principle, or “Don’t call us, we’ll call you.” Objects register as handlers for an event. When the event occurs, a hook method on the registered object is invoked. Inversion of Control lets frameworks manage the event life cycle while allowing developers to plug in custom handlers for framework events.4 2.4.2 Implementing the base class Following up on the interfaces in listing 2.5, listing 2.6 shows a first draft of the simple controller class. Listing 2.6 The generic controller package junitbook.sampling; import java.util.HashMap; import java.util.Map; public class DefaultController implements Controller { private Map requestHandlers = new HashMap(); (1) protected RequestHandler getHandler(Request request) (2) { if (!this.requestHandlers.containsKey(request.getName())) { String message = "Cannot find handler for request name " throw new RuntimeException(message); (3) + "[" + request.getName() + "]"; } return (RequestHandler) this.requestHandlers.get(request.getName()); (4) } public Response processRequest(Request request) (5) { Response response; try { response = getHandler(request).process(request); } catch (Exception exception) { response = new ErrorResponse(request, exception); } return response; } public void addHandler(Request request, RequestHandler requestHandler) { if (this.requestHandlers.containsKey(request.getName())) 4 http://c2.com/cgi/wiki?HollywoodPrinciple Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 { throw new RuntimeException("A request handler has " (6) + "already been registered for request name " (6) + "[" + request.getName() + "]"); (6) } else { this.requestHandlers.put(request.getName(), requestHandler); } } } (1) Declare a HashMap (java.util.HashMap) to act as the registry for your request handlers. (2) Add a protected method, getHandler, to fetch the RequestHandler for a given request. (3) If a RequestHandler has not been registered, you throw a RuntimeException (java.lang.RuntimeException), because this happenstance represents a programming mistake rather than an issue raised by a user or external system. Java does not require you to declare the RuntimeException in the method’s signature, but you can still catch it as an exception. An improvement would be to add a specific exception to the controller framework (NoSuitableRequestHandlerException, for example). (4) Your utility method returns the appropriate handler to its caller. (5) This is the core of the Controller class: the processRequest method. This method dispatches the appropriate handler for the request and passes back the handler’s Response. If an exception bubbles up, it is caught in the ErrorResponse class, shown in listing 2.7. (6) Check to see whether the name for the handler has been registered, and throw an exception if it has. Looking at the implementation, note that the signature passes the request object, but you only use its name. This sort of thing often occurs when an interface is defined before the code is written. One way to avoid over-designing an interface is to practice Test-Driven Development (see chapter 4). Listing 2.7 Special response class signaling an error. […] public class ErrorResponse implements Response { private Request originalRequest; private Exception originalException; public ErrorResponse(Request request, Exception exception) { this.originalRequest = request; this.originalException = exception; } public Request getOriginalRequest() { return this.originalRequest; } Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 public Exception getOriginalException() { return this.originalException; } } At this point, you have a crude but effective skeleton for the controller. Table 2.4 shows how the requirements at the top of this section relate to the source code. Table 2.4 Resolving the base requirements for the component Requirement Resolution Accept requests public Response processRequest(Request request) Select handler this.requestHandlers.get(request.getName()) Route requests response = getRequestHandler(request).process(request); Error-handling Subclass ErrorResponse The next step for many developers would be to cobble up a stub application to go with the skeleton controller. But not us! As “test-infected” developers, we can write a test suite for the controller without fussing with a stub application. That’s the beauty of unit testing! You can write a package and verify that it works, all outside of a conventional Java application. 2.5 Let’s test it! A fit of inspiration has led us to code the four interfaces shown in listing 2.5 and the two starter classes shown in listings 2.6 and 2.7. If we don’t write an automatic test now, the Bureau of Extreme Programming will be asking for our membership cards back! Listings 2.6 and 2.7 began with the simplest implementations possible. So, let’s do the same with the new set of unit tests. What’s the simplest possible test case we can explore? 2.5.1 Testing the DefaultController How about a test-case that instantiates the DefaultController class? The first step in doing anything useful with the controller is to construct it, so let’s start there. Listing 2.8 shows the bootstrap test code. It constructs the DefaultController object and sets up a framework for writing tests. Listing 2.8 TestDefaultController – a bootstrap iteration package junitbook.sampling; import org.junit.core.Test; import static org.junit.Assert.*; public class TestDefaultController (1) { private DefaultController controller; Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 @Before public void instantiate() throws Exception (2) { controller = new DefaultController(); } @Test public void testMethod() (3) { throw new RuntimeException("implement me"); (4) } } (1) Start the name of the test case class with the prefix Test. The naming convention is not required, but doing so we mark the class as a test case so that you can easily recognize test classes and possibly filter them in build scripts. (2) Use the @Before-annotated method to instantiate DefaultController. This is a built- in extension point that the JUnit framework calls between test methods. (3) Here you insert a dummy test method, just so you have something to run. As soon as you are sure the test infrastructure is working, you can begin adding real test methods. Of course, although this test runs, it also fails. The next step will be to fix the test! (4) Use a “best practice” by throwing an exception for test code that has not yet been implemented. This prevents the test from passing and reminds you that you must implement this code. JUnit’s details The @Before and @After annotated methods are executed right before/after the execution of each one of your @Test methods, and regardless of the fact whether the test failed or not. This helps you to extract all of your common logic, like instantiating your domain objects and setting them up in some known state. You can have as many of these methods, as you want, but beware because in case you have more than one of the @Before/@After methods no one knows what is the order of their execution. JUnit also provides the @BeforeClass and @AfterClass annotations to annotate your methods in that class. The methods that you annotate will get executed, only once before/after all of your @Test methods. Again, as with the @Before and @After annotations you can have as many of these methods as you want, and again nothing is specified about the order of the execution. You need to remember that both the @Before/@After and @BeforeClass/@AfterClass annotated methods must be public by signature. The @BeforeClass/@AfterClass annotated methods must be public and also be static by signature. Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 2.5.2 Adding a handler Now that you have a bootstrap test, the next step is to decide what to test first. We started the test case with the DefaultController object, because that’s the point of this exercise: to create a controller. You wrote some code and made sure it compiled. But how can you test to see if it works? The purpose of the controller is to process a request and return a response. But before you process a request, the design calls for adding a RequestHandler to do the actual processing. So, first things first: you should test whether you can add a RequestHandler. The tests you ran in chapter 1 returned a known result. To see if the test succeeded, you compared the result you expected with whatever result the object you were testing returned. The signature for addHandler is void addHandler(Request request, RequestHandler requestHandler) To add a RequestHandler, you need a Request with a known name. To check to see if adding it worked, you can use the getHandler method from DefaultController, which uses this signature: RequestHandler getHandler(Request request) This is possible because the getHandler method is protected, and the test classes are located in the same package as the classes they are testing. For the first test, it looks like you can do the following: ƒ Add a RequestHandler, referencing a Request. ƒ Get a RequestHandler and pass the same Request. ƒ Check to see if you get the same RequestHandler back. WHERE DO TESTS COME FROM? Now you know what objects you need. The next question is “where do these objects come from?” Should you go ahead and write some of the objects you will use in the application, like a logon request? The point of unit testing is to test one object at a time. In an object-oriented environment like Java, objects are designed to interact with other objects. To create a unit test, it follows that you need two flavors of objects: the domain object you are testing and test objects to interact with the object under test. DEFINITION: domain object—In the context of unit testing, the term domain object is used to contrast and compare the objects you use in your application with the objects that you use to test your application (test objects). Any object under test is considered to be a domain object. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 If you used another domain object, like a logon request, and a test failed, it would be hard to identify the culprit. You might not be able to tell if the problem was with the controller or the request. So, in the first series of tests, the only class you will use in production is DefaultController. Everything else should be a special test class. JUnit best practices: unit-test one object at a time A vital aspect of unit tests is that they are finely grained. A unit test independently examines each object you create, so that you can isolate problems as soon as they occur. If more than one object is put under test, you cannot predict how the objects will interact when changes occur to one or the other. When an object interacts with other complex objects, you can surround the object under test with predictable test objects. Another form of software test, integration testing, examines how working objects interact with each other. See chapter 3 for more about other types of tests. WHERE DO TEST CLASSES LIVE? Where do you put the test classes? Java provides several alternatives. For starters, you could do one of the following: ƒ Make them public classes in your package ƒ Make them inner classes within your test case class If the classes are simple and likely to stay that way, then it is easiest to code them as inner classes. The classes in this example are pretty simple. Listing 2.9 shows the inner classes you can add to the TestDefaultController class. Listing 2.9 Test classes as inner classes public class TestDefaultController { [...] private class SampleRequest implements Request (1) { public String getName() { return "Test"; } } private class SampleHandler implements RequestHandler (2) { public Response process(Request request) throws Exception { return new SampleResponse(); } } private class SampleResponse implements Response (3) { // empty } [...] Licensed to Alison Tyler Download at Boykma.Com 18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 (1) Set up a request object that returns a known name (Test). (2) Implement a SampleHandler. The interface calls for a process method, so you have to code that, too. You’re not testing the process method right now, so you have it return a SampleResponse object to satisfy the signature. (3) Go ahead and define an empty SampleResponse just so you have something to instantiate. With the scaffolding from listing 2.9 in place, let’s look at listing 2.10, which shows the test for adding a RequestHandler. Listing 2.10 TestDefaultController.testAddHandler […] import static org.junit.Assert.*; public class TestDefaultController { [...] @Test public void testAddHandler() (1) { Request request = new SampleRequest(); (2) RequestHandler handler = new SampleHandler(); (2) controller.addHandler(request, handler); (3) RequestHandler handler2 = controller.getHandler(request); (4) assertSame(“Handler we set in controller should be the same handler we get”, handler2, handler);(5) } } (1) Pick an obvious name for the test method and annotate your test method with the @Test annotation. (2) Instantiate your test objects. (3) This code gets to the point of the test: controller (the object under test) adds the test handler. Note that the DefaultController object is instantiated by the @Before- annotated method (see listing 2.8). (4) Read back the handler under a new variable name. (5) Check to see if you get back the same object you put in. JUnit best practices: choose meaningful test method names You can see that a method is a test-method by the @Test annotation. But you also must be able to understand what a method is testing by reading the name. Although JUnit does not imply any special rules for naming your test methods, a good rule is to start with the testXXX naming scheme, where XXX is the name of the method to test. As you add other tests against the same method, move to the testXXXYYY scheme, where YYY describes how the tests differ. Don’t be afraid that the names of your tests are getting Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 long. As you will see by the end of the chapter it is sometimes not so obvious what a method is testing simply by looking at the assert methods in it – so the only chance we have is – naming your test-methods in a descriptive fashion and putting comments where necessary. Although it’s very simple, this unit test confirms the key premise that the mechanism for storing and retrieving RequestHandler is alive and well. If addHandler or getRequest fails in the future, the test will quickly detect the problem. As you create more tests like this, you will notice that you follow a pattern of steps: Set up the test by placing the environment in a known state (create objects, acquire resources). The pre-test state is referred to as the test fixture. Invoke the method under test. Confirm the result, usually by calling one or more assert methods. 2.5.3 Processing a request Let’s look at testing the core purpose of the controller, processing a request. Because you know the routine, we’ll just present the test in listing 2.11 and review it. Listing 2.11 testProcessRequest import static org.junit.Assert.*; public class TestDefaultController { [...] @Test public void testProcessRequest() (1) { Request request = new SampleRequest(); (2) RequestHandler handler = new SampleHandler(); (2) controller.addHandler(request, handler); (2) Response response = controller.processRequest(request); (3) assertNotNull("Must not return a null response", response); (4) assertEquals(“Response should be of type SampleResponse”, SampleResponse.class, response.getClass()); (5) } } (1) First annotate the test with the @Test annotation and give the test a simple, uniform name. (2) Set up the test objects and add the test handler. (3) Here the code diverges from listing 2.10 and calls the processRequest method. (4) You verify that the returned Response object is not null. This is important because you call the getClass method on the Response object. It will fail with a dreaded NullPointerException if the Response object is null. You use the assertNotNull(String, Object) signature so that if the test fails, the error displayed Licensed to Alison Tyler Download at Boykma.Com 20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 is meaningful and easy to understand. If you had used the assertNotNull(Object) signature, the JUnit runner would have displayed a stack trace showing a java.lang.AssertionError exception with no message, which would be more difficult to diagnose. (5) Once again, compare the result of the test against the expected SampleResponse class. JUnit best practices: explain the failure reason in assert calls Whenever you use any of the JUnit assert* methods, make sure you use the signature that takes a String as the first parameter. This parameter lets you provide a meaningful textual description that is displayed in the JUnit test runner if the assert fails. Not using this parameter makes it difficult to understand the reason for a failure when it happens. FACTORIZING SETUP LOGIC Because both tests do the same type of setup, you can try moving that code into a @Before annotated method. At the same time you don’t want to move it into a new @Before method because you are not sure which method will be executed first, and thus you may get an exception. Instead you can move it into the same @Before method. As you add more test methods, you may need to adjust what you do in the @Before methods. For now, eliminating duplicate code as soon as possible helps you write more tests more quickly. Listing 2.12 shows the new and improved TestDefaultController class (changes are shown in bold). Listing 2.12 TestDefaultController after some refactoring […] public class TestDefaultController { private DefaultController private Request request; controller; private RequestHandler handler; @Before public void initialize() throws Exception { controller = new DefaultController(); request = new SampleRequest(); (1) handler = new SampleHandler(); (1) controller.addHandler(request, handler); (1) } private class SampleRequest implements Request { // Same as in listing 2.5 } private class SampleHandler implements RequestHandler { // Same as in listing 2.5 } Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 private class SampleResponse implements Response { // Same as in listing 2.5 } @Test public void testAddHandler() (2) { RequestHandler handler2 = controller.getHandler(request); assertSame(handler2, handler); } @Test public void testProcessRequest() (3) { Response response = controller.processRequest(request); assertNotNull("Must not return a null response", response); assertEquals(“Response should be of type SampleResponse”, SampleResponse.class, response.getClass()); } } The instantiation of the test Request and RequestHandler objects is moved to initialize (1). This saves you repeating the same code in testAddHandler (2) and testProcessRequest (3). Also, we make a new @Before annotated method for adding the handler to the controller. Since @Before methods are executed before every single @Test method, we make sure we have a fully setup DefaultController object. DEFINITION: refactor — To improve the design of existing code. For more about refactoring, see Martin Fowler’s already-classic book5 Note that you do not try to share the setup code by testing more than one operation in a test method, as shown in listing 2.13 (an anti-example). Listing 2.13 Do not combine test methods like this (anti-example) public class TestDefaultController { [...] @Test public void testAddAndProcess() { Request request = new SampleRequest(); RequestHandler handler = new SampleHandler(); controller.addHandler(request, handler); RequestHandler handler2 = controller.getHandler(request); assertEquals(handler2,handler); // DO NOT COMBINE TEST METHODS THIS WAY Response response = controller.processRequest(request); assertNotNull("Must not return a null response", response); 5 Martin Fowler, Refactoring: Improving the Design of Existing Code (Reading, MA: Addison-Wesley, 1999). Licensed to Alison Tyler Download at Boykma.Com 22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 assertEquals(SampleResponse.class, response.getClass()); } } JUnit best practices: one unit test equals one @Test method Do not try to cram several tests into one method. The result will be more complex test methods, which will become increasingly difficult to read and understand. Worse, the more logic you write in your test methods, the more risk there is that it will not work and will need debugging. This is a slippery slope that can end with writing tests to test your tests! Unit tests give you confidence in a program by alerting you when something that had worked now fails. If you put more than one unit test in a method, it makes it more difficult to zoom in on exactly what went wrong. When tests share the same method, a failing test may leave the fixture in an unpredictable state. Other tests embedded in the method may not run, or may not run properly. Your picture of the test results will often be incomplete or even misleading. Because all the test methods in a TestClass share the same fixture, and JUnit can now generate an automatic test suite, it’s really just as easy to place each unit test in its own method. If you need to use the same block of code in more than one test, extract it into a utility method that each test method can call. Better yet, if all methods can share the code, put it into the fixture. Another common pitfall is to make test-methods that do not contain any assert statements. When you execute those tests you see JUnit flags them as successful, but this is an illusion of successful tests. Don’t do this! For best results, your test methods should be as concise and focused as your domain methods. Each test method must be as clear and focused as possible. This is why JUnit provides you with the @Before and @BeforeClass methods: so you can share fixtures between tests without combining test methods. 2.5.4 Improving testProcessRequest When we wrote the testProcessRequest method in listing 2.11, we wanted to confirm that the response returned is the expected response. The implementation confirms that the object returned is the object that we expected. But what we would really like to know is whether the response returned equals the expected response. The response could be a different class. What’s important is whether the class identifies itself as the correct response. The assertSame method confirms that both references are to the same object. The assertEquals method utilizes the equals method, inherited from the base Object class. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 To see if two different objects have the same identity, you need to provide your own definition of identity. For an object like a response, you can assign each response its own command token (or name). The empty implementation of SampleResponse didn’t have a name property you can test. To get the test you want, you have to implement a little more of the Response class first. Listing 2.14 shows the enhanced SampleResponse class. Listing 2.14 A refactored SampleResponse public class TestDefaultController { [...] private class SampleResponse implements Response { private static final String NAME = "Test"; public String getName() { return NAME; } public boolean equals(Object object) { boolean result = false; if (object instanceof SampleResponse) { result = ((SampleResponse) object).getName().equals(getName()); } return result; } public int hashCode() { return NAME.hashCode(); } } [...] Now that SampleResponse has an identity (represented by getName()) and its own equals method, you can amend the test method: @Test public void testProcessRequest() { Response response = controller.processRequest(request); assertNotNull("Must not return a null response", response); assertEquals(new SampleResponse(), response); } We have introduced the concept of identity in the SampleResponse class for the purpose of the test. However, the tests are really telling you that this should have existed in the proper Response class. Thus you need to modify the Response interface as follows: public interface Response Licensed to Alison Tyler Download at Boykma.Com 24 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 { String getName(); } As you see tests can sometimes “talk” and guide you to a better design of your application. But this is not the real purpose of the tests. Don’t forget that the tests are used to protect us from introducing errors in our code. To do this we need to test absolutely every condition under which our application might be executed. We start investigating the exceptional conditions in the next chapter. 2.6 Testing exception handling So far, your tests have followed the main path of execution. If the behavior of one of your objects under test changes in an unexpected way, this type of test points to the root of the problem. In essence, you have been writing diagnostic tests that monitor the application’s health. But sometimes, bad things happen to healthy programs. Say an application needs to connect to a database. Your diagnostics may test whether you are following the database’s API. If you open a connection but don’t close it, a diagnostic can note that you have failed to meet the expectation that all connections are closed after use. But what if a connection is not available? Maybe the connection pool is tapped out. Or, perhaps the database server is down. If the database server is configured properly and you have all the resources you need, this may never happen. But all resources are finite, and someday, instead of a connection, you may be handed an exception. “Anything that can go wrong, will” (http://www.murphys-laws.com). If you are testing an application by hand, one way to test for this sort of thing is to turn off the database while the application is running. Forcing actual error conditions is an excellent way to test your disaster-recovery capability. Creating error conditions is also very time-consuming. Most of us cannot afford to do this several times a day—or even once a day. And many other error conditions are not easy to create by hand. Testing the main path of execution is a good thing, and it needs to be done. But testing exception-handling can be even more important. If the main path does not work, your application will not work either (a condition you are likely to notice). JUnit best practices: test anything that could possibly fail Unit tests help ensure that your methods are keeping their API contracts with other methods. If the contract is based solely on other components’ keeping their contracts, then there may not be any useful behavior for you to test. But if the method changes the parameter’s or field’s value in any way, then you are providing unique behavior that you should test. The method is no longer a simple go-between—it’s a filtering or munging method with its own behavior that future changes could conceivably break. If a method is changed so it is not so simple anymore, then you should add a test when that change Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 25 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 takes place, but not before. As the JUnit FAQ puts it, “The general philosophy is this: if it can’t break on its own, it’s too simple to break.” But what about things like JavaBean getters and setters? Well, that depends. If you are coding them by hand in a text editor, then yes, you might want to test them. It’s surprisingly easy to miscode a setter in a way that the compiler won’t catch. But if you are using an IDE that watches for such things, then your team might decide not to test simple JavaBean properties. We are all too human, and often we tend to be sloppy when it comes to exception cases. Even textbooks scrimp on error-handling so as to simplify the examples. As a result, many otherwise great programs are not error-proofed before they go into production. If properly tested, an application should not expose a screen of death but should trap, log, and explain all errors gracefully. 2.6.1 Simulating exceptional conditions The exceptional test case is where unit tests really shine. Unit tests can simulate exceptional conditions as easily as normal conditions. Other types of tests, like functional and acceptance tests, work at the production level. Whether these tests encounter systemic errors is often a matter of happenstance. A unit test can produce exceptional conditions on demand. During our original fit of inspired coding, we had the foresight to code an error handler into the base classes. As you saw back in listing 2.6, the processRequest method traps all exceptions and passes back a special error response instead: try { response = getHandler(request).process(request); } catch (Exception exception) { response = new ErrorResponse(request, exception); } How do you simulate an exception to test whether your error handler works? To test handling a normal request, you created a SampleRequestHandler that returned a SampleRequest (see listing 2.9). To test the handling of error conditions, you can create a SampleExceptionHandler that throws an exception instead, as shown in listing 2.15. Listing 2.15 Request handler for exception cases public class TestDefaultController { [...] private class SampleExceptionHandler implements RequestHandler { public Response process(Request request) throws Exception { throw new Exception("error processing request"); Licensed to Alison Tyler Download at Boykma.Com 26 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } } } This just leaves creating a test method that registers the handler and tries processing a request—for example, like the one shown in listing 2.16. Listing 2.16 testProcessRequestAnsweresErrorResponse, first iteration public class TestDefaultController { [...] @Test public void testProcessRequestAnswersErrorResponse() { SampleRequest request = new SampleRequest(); (1) SampleExceptionHandler handler = new SampleExceptionHandler(); (1) controller.addHandler(request, handler); (2) Response response = controller.processRequest(request); assertNotNull("Must not return a null response", response); (3) assertEquals(ErrorResponse.class, response.getClass()); (3) } } (1) Create the request and handler objects. (2) You reuse the controller object created by the @Before fixture (see listing 2.12). (3) Test the outcome against your expectations. But if you run this test through JUnit, it would fail! A quick look at the message tells you two things. First, you need to use a different name for the test request, because there is already a request named Test in the fixture. Second, you may need to add more exception-handling to the class so that a RuntimeException is not thrown in production. As to the first item, you can try using the request object in the fixture instead of your own, but that fails with the same error. (Moral: Once you have a test, use it to explore alternative coding strategies.) You consider changing the fixture. If you remove from the fixture the code that registers a default SampleRequest and SampleHandler, you introduce duplication into the other test methods. Not good. Better to fix the SampleRequest so it can be instantiated under different names. Listing 2.17 is the refactored result (changes from listing 2.15 and 2.16 are in bold). Listing 2.17 testProccessRequestExceptionHandler, fixed and refactored public class TestDefaultController { [...] private class SampleRequest implements Request { private static final String DEFAULT_NAME = "Test"; (1) private String name; (1) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 public SampleRequest(String name) (2) { (2) this.name = name; (2) } (2) public SampleRequest() (3) { (3) this(DEFAULT_NAME); (3) } (3) public String getName() { return this.name; } } [...] @Test public void testProcessRequestAnswersErrorResponse() { SampleRequest request = new SampleRequest("testError"); (4) SampleExceptionHandler handler = new SampleExceptionHandler(); controller.addHandler(request, handler); Response response = controller.processRequest(request); assertNotNull("Must not return a null response", response); assertEquals(ErrorResponse.class, response.getClass()); } } (1) Introduce a member field to hold the request’s name and set it to the previous version’s default. (2) Introduce a new constructor that lets you pass a name to the request, to override the default. (3) Here you introduce an empty constructor, so existing calls will continue to work. (4) Call the new constructor instead, so the exceptional request object does not conflict with the fixture. Of course, if you added another test method that also used the exception handler, you might move its instantiation to the @Before fixture, to eliminate duplication. JUnit best practices: let the test improve the code Writing unit tests often helps you write better code. The reason is simple: A test case is a user of your code. And, it is only when using code that you find its shortcomings. Thus, do not hesitate to listen to your tests and refactor your code so that it is easier to use. The practice of Test-Driven Development (TDD) relies on this principle. By writing the tests first, you develop your classes from the point of view of a user of your code. See chapter 4 for more about TDD. Licensed to Alison Tyler Download at Boykma.Com 28 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 But because the duplication hasn’t happened yet, let’s resist the urge to anticipate change, and let it stand. (“Don’t anticipate, navigator!” the captain barked.) 2.6.2 Testing for exceptions During testing, you found that addHandler throws an undocumented RuntimeException if you try to register a request with a duplicate name. (By undocumented, we mean that it doesn’t appear in the signature.) Looking at the code, you see that getHandler throws a RuntimeException if the request hasn’t been registered. Whether you should throw undocumented RuntimeException exceptions is a larger design issue. (You can make that a to-do for later study.) For now, let’s write some tests that prove the methods will behave as designed. Listing 2.18 shows two test methods that prove addHandler and getHandler will throw runtime exceptions when expected. Listing 2.18 Testing methods that throw an exception public class TestDefaultController { [...] @Test(expected=RuntimeException.class) (1) public void testGetHandlerNotDefined() (2) { SampleRequest request = new SampleRequest("testNotDefined"); (3) //The following line is supposed to throw a RuntimeException controller.getHandler(request); (4) } @Test( xpec public void testAddRequestDuplicateName() (5) e ted=RuntimeException.class) (5) { SampleRequest request = new SampleRequest(); SampleHandler handler = new SampleHandler(); // The following line is supposed to throw a RuntimeException controller.addHandler(request, handler); (5) } } (1) Annotate your method with the @Test annotation to denote that it is a test-method. Since we are going to test an exceptional condition and we expect that the test-method will produce an exception of some kind, we need also to specify what kind of an exception we expect to be raised. We do this by specifying the expected parameter of the @Test annotation. (2) Give the test an obvious name. Because this test represents an exceptional case, append NotDefined to the standard testGetHandler prefix. Doing so keeps all the getHandler tests together and documents the purpose of each derivation. (3) You create the request object for the test, also giving it an obvious name. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 29 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 (4) Pass the (unregistered) request to the default getHandler method. Since this request has no handler attached, a RuntimeException should be raised. (5) You follow the same pattern as the first method: 1. Insert a statement that should throw an exception. 2. Add the expected parameter to the @Test annotation to denote what kind of an exception you expect. 3. Proceed normally. JUnit best practices: make exception tests easy to read Normally the expected parameter in the @Test annotation tells the developers very clearly that an exception of that type should be raised. But you can go even further. Besides naming your test-methods in an obvious fashion to understand that this method is testing an exceptional condition, you can also put some comments to highlight the line of the code which produces the expected exception. The controller class is by no means done, but you have a respectable first iteration and a test suite proving that it works. Now you can commit the controller package, along with its tests, to the project’s code repository and move on to the next task on your list. JUnit best practices: let the test improve the code An easy way to identify exceptional paths is to examine the different branches in the code you’re testing. By branches, we mean the outcome of if clauses, switch statements, and try/catch blocks. When you start following these branches, sometimes you may find that testing each alternative is painful. If code is difficult to test, it is usually just as difficult to use. When testing indicates a poor design (called a code smell, http://c2.com/cgi/wiki?CodeSmell), you should stop and refactor the domain code. In the case of too many branches, the solution is usually to split a larger method into several smaller methods6. Or, you may need to modify the class hierarchy to better represent the problem domain7. Other situations would call for different refactorings. A test is your code’s first “customer,” and, as the maxim goes, “the customer is always right.” 6 Fowler, Refactoring, “Extract Method.” 7 More about writing a testable code you may find in chapter 4. Licensed to Alison Tyler Download at Boykma.Com 30 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 2.7 Timeout testing So far we have tested our application for working properly – when supplied with the right data, not only behave in the expected manner, but also produce the expected result. Now we want to take a look at another aspect of testing our application – scalability. How scalable our DefaultController class is? We are going to write some tests and expect that they run below a given time-barrier. To do this JUnit provides us with another parameter to the @Test annotation called timeout. In this parameter you can specify your time-barrier in terms of milliseconds, and in case the test takes more time to execute JUnit will mark the test as failed. For example let’s take a look at the following code-listing: Listing 2.19 Timeout tests […] public class TestDefaultController { [...] @Test( imeo public void testProcessMultipleRequestsTimeout() t ut=130) (1) { Request request; (2) Response response = new SampleResponse(); (2) RequestHandler handler = new SampleHandler(); (3) for(int i=0; i< 99999; i++) (3) { request = new SampleRequest(String.valueOf(i)); controller.addHandler(request, handler); (3) response = controller.processRequest(); assertNotNull(response); (4) assertNotSame(ErrorResponse.class, response.getClass()); (4) } } We start by specifying the timeout parameter in milliseconds, which we expect to be our time-barrier (1). Then, at (2), we declare the Request, Response and RequestHandler objects we are going to use in the test. At (3) we start a for-loop to create a 99999 SampleRequest objects to and to attach them to a handler in the controller. After that invoke the processRequest() method of the controller and assert (at (4)) that we get a non-null Response object and also that the Response we get is not an ErrorResponse. You might consider the 130 milliseconds time-barrier to be pretty optimistic and you are right. This time barrier was the lowest possible on my machine. But of course the execution time depends on the hardware it runs on (processor speed, memory available, etc.), and also on the software it runs on (mainly on the operating system, but also Java version, etc.). So for different developers this test would be failing or passing. Even more – when adding more functionality in the processRequest() method the time-barrier we have chosen will get insufficient for our needs. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 31 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 So we get to the point where a few timeout tests might fail the whole build for some of the developers. Sometimes it is good to skip some of the tests. In the previous version 3.x of JUnit to do this you had to change the name of the test-method (not to start with the test prefix). In version 4.x of JUnit, however, we have a very nice way to skip a test. The only thing we need to do is annotate the @Test method with a @Ignore annotation. Let’s have a look at the following code-listing: Listing 2.21 Ignoring a test method in JUnit 4.x […] @Test(timeout=130) @Ignore(value=”Ignore for now until we decide a decent time-limit”) (1) public void testProcessMultipleRequestTimeout() { […] } As you can see the only thing we have added is the @Ignore annotation to the method. This annotation accepts the value parameter, which gives you the possibility to specify some message why we skip the test. JUnit best-practice: always specify a reason for skipping a test. As you saw in the previous code-listing we specified a reason why we need to skip the execution of the test. It is a good practice to do that – first of all you notify the fellow- developers what is the reason to skip the execution of the test, and second of all you prove to yourself that you know what the test does, and you don’t ignore it just because it fails. As we mentioned in the previous version of JUnit the only way to skip the execution of a test was to rename it, or comment it out. This way you get no information whatsoever, how many tests were failing and how many were skipped. In the current version of JUnit once annotated the method with the @Ignore annotation we get a detailed statistics of how many of the tests passed, how many failed, and how many were skipped. Pretty cool, isn’t it? 2.8 Introducing Hamcrest matchers The statistics show that people get easily “infected” with the unit-testing philosophy. Once you get accustomed to writing tests and see how good the feeling of someone protecting you from possible mistakes is, you will wonder how was possible to live without unit-testing before. But as you write more and more unit tests and assertions you will inevitably get to the problem that some of the assertions are big and hard to read. For example consider the following code-listing: Listing 2.19 Cumbersome JUnit assert method […] Licensed to Alison Tyler Download at Boykma.Com 32 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 public class HamcrestTest { private List values; @Before (1) public void setUpList() { values = new ArrayList(); values.add("x"); values.add("y"); values.add("z"); } @Test (2) public void withoutHamcrest() { assertTrue(values.contains("one") (3) || values.contains("two") (3) || values.contains("three")); (3) } } What we do in this example is construct a simple JUnit test – exactly like the ones we have been constructing so far. We have a @Before fixture at (1), which will initialize some data for our test and then we have a single test-method at (2). In this test-method you can see we make a long and hard-to read assertion (maybe it’s not that hard to read, but it is definitely not obvious what it does on a first glance). Our goal is to simplify the assertion we make in the test-method. To solve this problem we are going to present a library of matchers for building test expressions – Hamcrest. Hamcrest (http://code.google.com/p/hamcrest/) is a library that contains a lot of helpful matcher objects (known also as constraints or predicates), ported in several languages (Java, C++, Objective-C, Python and PHP). Note that Hamcrest is not a testing framework itself, but it rather helps you declaratively specify simple matching rules. These matching rules can be used in a lot of different situations, but they are particularly helpful when it comes to unit-testing. Here is the same test-method, this time written using the Hamcrest library: Listing 2.20 Hamcrest library to simplify our assert declarations. […] import static org.junit.Assert.assertThat; (1) import static org.hamcrest.Matchers.anyOf; (1) import static org.hamcrest.Matchers.equalTo; (1) import static org.hamcrest.Matchers.hasItem; (1) […] @Test public void withHamcrest() { assertThat(values, hasItem(anyOf(equalTo("one"), equalTo("two"), (2) equalTo("three")))); } […] Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 33 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Here we reuse the 2.19 listing and simply add another test-method to it. This time we import the needed matchers and the assertThat method (1), and after that construct a test-method. In the test-method we use one of the most-powerful features of the matchers – they can nest into each-other. The result you can see on your own – a very human readable assert statement. And if that is not enough reason to include the Hamcrest matchers in your testing arsenal, how about this – the Hamcrest matchers provide you with a much more through output when the test fails. If you followed the examples in the two previous listings you have probably noticed that in both the cases we construct a List with the “x”, “y” and “z” as elements in it. After that we assert the presence of either “one”, “two” or “three”, which means that the test, that written will fail. So let’s really execute that test. The result from the execution is shown in the figure 2.2: Figure 2.2 The figure on the left shows the stack-trace from the execution of the test without using the Hamcrest, and the one on the right shows the same using Hamcrest. As you can see from the figures the right one give a lot more details, doesn’t it? The following table tries to list some of the most-common used Hamcrest matchers. Table 2.4 Some of the most-common used Hamcrest matchers. Core Logical Anything Matches to absolutely anything. Useful in some cases where you want to make the assert statement more readable. is This matcher is used only to improve the readability of your statements. allOf A matcher that checks if all contained matchers match (just like the && operator). Licensed to Alison Tyler Download at Boykma.Com 34 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 anyOf hecks if any of the contained matchers match (like the A matcher that c operator ||). not ning of the contained matchers (just A matcher that traverses the mea like the ! operator in Java). instanceOf, isCompatibleType hether objects are of compatible type (are A matchers that matches w instance of one another). sameInstance A matcher to test object identity. notNullValue, nullValue A matcher to test for null values (or non-null values). hasProperty A matcher to test whether a Java Bean has a certain property. hasEntry, hasKey, hasValue This matcher tests whether a given Map has a given entry, key or value. hasItem, hasItems This matchers tests a given collection for presence of item or items. closeTo, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual reater than or equal, less than or less than or equal to a given value. Test given numbers are close to, greater than, g equalToIgnoringCase Tests a given string equals another one, ignoring the case. equalToIgnoringWhiteSpace es. Tests a given string equals another one, by ignoring the white spac containsString, endsWith, startWith Tests whether the given string contains, starts with or ends with a certain string. All of them seem pretty straight-forward to read and use, and remember you can compose them into each other. Last but not least Hamcrest is extremely extensible. It is very easy to write your own matchers that check a certain condition. The only thing you need to do is implement the Matcher interface and an appropriately-named factory method. More on how to write custom matchers you can find in Appendix D of the book – that is where we provide a rs. r project. In chapter 1, you kept all . Accordingly, you’ve set up the source code repository just like you would for a real project. complete overview how to write your own matche 2.9 Setting up a project for testing Because this chapter covers testing a fairly realistic component, let’s finish up by looking at how you set up the controller package as part of a large the Java domain code and test code in the same folder. They were introductory tests on an example class, so this approach seemed simplest for everyone. In this chapter, you’ve begun to build real classes with real tests, as you would for one of your own projects Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 35 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 2.3 A “separate but equal” filing system keeps tests in the same package but in different directories. cla Java package. The solution? One package, two folders. Fig see nder the root directory we created separate sr h02.internals package. The working interfaces and classes benefits. Right now, the only test class has the convenient Test prefix. Later you may need So far, you have only one test case. Mixing this in with the domain classes would not have been a big deal. But, experience tells us that soon you will have at least as many test sses as you have domain classes. Placing all of them in the same directory will begin to create file-management issues. It will become difficult to find the class you want to edit next. Meanwhile, you want the test classes to be able to unit-test protected methods, so you want to keep everything in the same ure 2.3 shows a snapshot of how the directory structure looks in a popular integrated development environment (IDE). This is the code for the second chapter, so we used 'ch2-internals' for the toplevel project directory name ( appendix C). U c/main/java and src/main/test folders. Under each of these, the actual package structure begins. In this case, all of the code falls under the com.manning.junitbook.c go under src/main/java; the classes we write for testing only go under the src/main/test directory. Beyond eliminating clutter, a “separate but equal” directory structure yields several other Licensed to Alison Tyler Download at Boykma.Com 36 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 se classes with Test, and time jar with only the domain classes. And, it simplifies running all the tests automatically. other helper classes to create more sophisticated tests. These might include stubs, mock objects, and other helpers. It may not be convenient to prefix all of the it becomes harder to tell the domain classes from the test classes. Using a separate test folder also makes it easy to deliver a run JUnit best practices: same package, separate directories Put test classes in the same package as the class they test but in a parallel directory structure. You need tests in the same package to allow access to protected methods. You want tests in a separate directory to simplify file management and to clearly delineate test and domain classes. into JUnit are several convenient assert me are part of the same package, we can still test pro our applications. We will also talk about how unit testing ts in the development life cycle. 2.10 Summary As you saw in chapter 1, it’s not hard to jump in and begin writing JUnit tests for your applications. In this chapter, we zoomed in and took a closer look at how JUnit works under the hood. A key feature of JUnit is that it provides a convenient spot to hang the scaffolding (or fixture) that you need for a test. Also built thods that make tests quick and easy to build. In this chapter, we also created a test case for a simple but complete application controller. Rather than test a single component, the test case examined how several components worked together. We started with a bootstrap test case that could be used with any class. Then we added new tests to TestCase one by one until all of the original components were under test. As the assertions were getting more and more complicated we found a way to simplify them by the means of the Hamcrest matchers. We expect this package to grow, so we created a second source code directory for the test classes. Because the test and domain source directories tected and package default members. In the next chapter, we will put unit testing in perspective with other types of tests that you need to perform to fully test y fi Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 03 Software Testing Principles This chapter covers ƒ The need for software tests ƒ Types of software tests ƒ Types of unit tests Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 A crash is when your competitor’s program dies. When your program dies, it is an ‘idiosyncrasy’. Frequently, crashes are followed with a message like ‘ID 02’. ‘ID’ is an abbreviation for idiosyncrasy and the number that follows indicates how many more months of testing the product should have had. —Guy Kawasaki Earlier chapters in this book took a very pragmatic approach to designing and deploying unit tests. This chapter steps back and looks at the various types of software tests and the roles they play in the application’s life cycle. Why would you need to know all this? Because unit testing is not just something you do out of the blue. In order to become a well-rounded developer, you need to understand unit tests compared to functional, integration, and other types of tests. Once you understand why unit tests are necessary, then you need to know how far to take your tests. Testing in and of itself is not the goal. 3.1 The need for unit tests The main goal of unit testing is to verify that your application works as expected and to catch bugs early. While functional testing accomplishes the same goal, unit tests are extremely powerful and versatile and offer much more than simply verifying that the application works. Unit tests: ƒ Allow greater test coverage than functional tests. ƒ Increase team productivity. ƒ Detect regressions and limit the need for debugging. ƒ Give us the confidence to refactor, and in general, make changes. ƒ Improve implementation. ƒ Document expected behavior. ƒ Enable code coverage and other metrics. 3.1.1 Allowing greater test-coverage Unit tests are the first type of tests any application should have. If you had to choose between writing unit tests and functional tests, you should choose the latter. In our experience, functional tests are able to cover about 70% of the application code. If you wish to go further and provide more test coverage, you need to write unit tests. Unit tests can easily simulate error conditions, which is extremely difficult to do with functional tests (it is impossible in some instances). Unit tests provide much more than just testing, as explained in the following sections. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 3.1.2 Increasing team productivity Imagine you are on a team working on a large application. Unit tests allow you to deliver quality code (tested code) without having to wait for all the other components to be ready. On the other hand, functional tests are more coarse-grained and need the full application (or a good part of it) to be ready before you can test it. 3.1.3 Detecting regressions and limiting debugging A passing unit test suite confirms your code works and gives you the confidence to modify your existing code, either for refactoring or to add and modify new features. As a developer, there is no better feeling than knowing that someone is watching your back and will warn you if you break something. A suite of unit tests reduces the need to debug an application to find out why something is failing. Whereas a functional test will tell you that a bug exists somewhere in the implementation of a use case, a unit test will tell you that a specific method is failing for a specific reason. You no longer need to spend hours trying to find the problem. JUnit best practice: Refactor Throughout the history of computer science, many great teachers have advocated iterative development. Niklaus Wirth, for example, who gave us the now-ancient languages Algol and Pascal, championed techniques like stepwise refinement. For a time, these techniques seemed difficult to apply to larger, layered applications. Small changes can reverberate throughout a system. Project managers looked to up-front planning as a way to minimize change, but productivity remained low. The rise of the xUnit framework has fueled the popularity of agile methodologies that once again advocate iterative development. Agile methodologists favor writing code in vertical slices to produce a working use case, as opposed to writing code in horizontal slices to provide services layer by layer. When you design and write code for a single use case or functional chain, your design may be adequate for this feature, but it may not be adequate for the next feature. To retain a design across features, agile methodologies encourage refactoring to adapt the code base as needed. However, how do you ensure that refactoring, or improving the design of existing code, does not break the existing code? This answer is that unit tests that tell you when and where code breaks. In short, unit tests give you the confidence to refactor. The agile methodologies try to lower project risks by providing the ability to cope with change. They allow and embrace change by standardizing on quick iterations and applying principles like YAGNI (You Ain’t Gonna Need It) and The Simplest Thing That Could Possibly Work. However, the foundation upon which all these principles rest is a solid bed of unit tests. Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 3.1.4 Refactoring with confidence Without unit tests, it is difficult to justify refactoring, because there is always a relatively high chance that you may break something. Why would you risk spending hours of debugging time (and putting the delivery at risk) only to improve the implementation or change a method name? Unit tests provide the safety net that gives you the confidence to refactor. 3.1.5 Improving implementation Unit tests are a first-rate client of the code they test. They force the API under test to be flexible and to be unit-testable in isolation. You usually have to refactor your code under test to make it unit-testable (or use the TDD approach, which by definition spawns code that can be unit-tested; see next chapter). It is important to monitor your unit tests as you create and modify them. If a unit test is too long and unwieldy, it usually means the code under test has a design smell and you should refactor it. You may also be testing too many features in one test method. If a test cannot verify a feature in isolation, it usually means the code is not flexible enough and you should refactor it. Modifying code to test it is normal. 3.1.6 Documenting expected behavior Imagine you need to learn a new API. On one side is a 300-page document describing the API, and on the other are some examples showing how to use it. Which would you use? The power of examples is well known. Unit tests are exactly this: examples that show how to use the API. As such, they make excellent developer documentation. Because unit tests match the production code, they must always be up to date, unlike other forms of documentation, Listing 3.1 illustrates how unit tests help provide documentation. The testTransferWithoutEnoughFunds() method shows that an AccountInsufficientFundsException is thrown when an account transfer is performed without enough funds. Listing 3.1 Unit tests as automatic documentation import org.junit.Test; public class TestAccount { [...] @Test(expected=AccountInsufficientFundsException.class) (1) public void tranferWithoutEnoughFunds() { long balance = 1000; (2) long amountToTransfer = 2000; (3) Account credit = new Account(balance); (2) Account debit = new Account(); credit.transfer(debit, amountToTransfer); (4) } } Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 At (1) we declare the method as a test-method by annotating it with @Test, and declare that it must throw the AccountInsufficientFundsException (with the expected parameter). Next, we create a new account with a balance of 1000 (2), and request a transfer of 2000 (4). As expected, the transfer method throws an AccountInsufficientFundsException, if it did not, JUnit would fail the test. 3.1.7 Enabling code coverage and other metrics Unit tests tell you, at the push of a button, if everything still works. Furthemore, unit tests enable you to gather code coverage metrics (see the next chapter) showing, statement by statement, what code the tests caused to execute and what code the tests did not touch. You can also use tools to track the progress of passing vs. failing tests from one build to the next. You can also monitor performance and cause a test to fail if its performance has degraded compared to a previous build. 3.2 Test types Figure 3.1 outlines our five categories of software tests. There are other ways of categorizing software tests, but we find these most useful for the purposes of this book. Please note that this section is discussing software tests in general, not just the automated unit tests covered elsewhere in the book. In figure 3.1, the outermost tests are broadest in scope. The innermost tests are narrowest in scope. As you move from the inner boxes to the outer boxes, the software tests get more and more functional and require that more and more of the application be present. Figure 3.1 The five types of tests Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Next, we will first look at the general test types. Then, we will focus on the types of unit tests. 3.2.1 The four types of software tests We have mentioned that unit tests each focus on a distinct unit of work. What about testing different units of work combined into a workflow. Will the result of the workflow do what you expect? How well will the application work when many people are using it at once? Different kinds of tests answer these questions; we categorize them into four varieties: ƒ Integration tests ƒ Functional tests ƒ Stress and load tests ƒ Acceptance tests Let us look at each of the test types, starting with the innermost after unit testing and working our way out. INTEGRATION SOFTWARE TESTING Individual unit tests are an essential quality control, but what happens when different units of work are combined into a workflow? Once you have the tests for a class up and running, the next step is to hook up the class with other methods and services. Examining the interaction between components, possibly running in their target environment, is the job of integration testing. Table 3.1 describes the various cases under which components interact. Table 3.1 Testing how object, services and subsystems interact Interaction Test Description Objects The test instantiates objects and calls methods on these objects. Services The test runs while a servlet or EJB container hosts the application, which may connect to a database, or attach to any other external resource or device. Subsystems A layered application may have a front-end to handle the presentation and a back-end to execute the business logic. Tests can verify that a request passes through the front end and returns an appropriate response from the back end. Just as more traffic collisions occur at intersections, the points where objects interact are major contributors of bugs. Ideally, you should define integration tests before you write Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 application code. Being able to code to the test dramatically increases a programmer’s ability to write well-behaved objects. FUNCTIONAL SOFTWARE TESTING Functional tests examine the code at the boundary of its public API. In general, this corresponds to testing application use cases. Developers often combine functional tests with integration tests. For example, a web application contains a secure web page that only authorized clients can access. If the client does not log in, then trying to access the page should result in a redirect to the login page. A functional unit test can examine this case by sending an HTTP request to the page to verify that a redirect (HTTP status code 302) response code comes back. Depending on the application, you can use several types of functional tests, as shown in table 3.2. Table 3.2 Testing frameworks, GUIs and subsystems Application Type Functional Test Description The application uses a framework. Functional testing within a framework focuses on testing the framework API that is used by users of the framework (end users or service providers). The application has a GUI. Functional testing of a GUI verifies that all features can be accessed and provide expected results. The tests access the GUI directly, which may in turn call several other components or a back end. The application is made up of subsystems. A layered system tries to separate systems by roles. There may be a presentation subsystem, a business logic subsystem, and a data subsystem. Layering provides flexibility and the ability to access the back end with several different front ends. Each layer defines an API for other layers to use. Functional tests verify that the API contract is enforced. STRESS TESTING How well will the application perform when many people are using it at once? Most stress tests examine whether the application can process a large number of requests within a given period. Usually, you implement this with software like JMeter1, which automatically sends pre-programmed requests and track how quickly the application responds. These tests usually do not verify the validity of responses; which is why we have the other tests. Figure 3.2 shows a JMeter throughput graph 1 http://jakarta.apache.org/jmeter Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 3.2 A JMeter throughput graph You normally perform stress tests in a separate environment, typically more controlled than a development environment. The stress test environment should be as close as possible to the production environment, if not the results will not be very useful. Let us prefix our quick look at performance testing with the often-quoted number one rule of optimization: "don't do it". The point is that before you spend valuable time optimizing code, you must do it to address a specific problem. That said; let us proceed on to performance testing. Aside from stress tests, you can perform other types of performance tests within the development environment. A profiler can look for bottlenecks in an application, which the developer can try to optimize. You must be able to prove that a specific bottleneck exists, and then prove that your changes remove the bottleneck. Unit tests can also help you profile an application as a natural part of development. With JUnit, you can create a performance test to match your unit test.. You might want to assert that a critical method never takes too long to execute. Listing 3.2 shows a timed test. Listing 3.2 Enforcing a timeout on a method with JUnit package com.manning.junitbook2; import org.junit.Test; public class ExampleTimedTest { Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 @Test(timeout=5000) (1) public void someVeryLongTest() { […] } } The example uses the timeout parameter (1) on the @Test annotation to set a timeout in milliseconds on the method. If this method takes more than 5,000 milliseconds to run, the test will fail. An issue with this kind of test is that you may need to update the timeout value when the underlying hardware changes, the OS changes, or when the test environment changes to or from running under virtualization. ACCEPTANCE SOFTWARE TESTING It is important that an application perform well; but the application must also meet the customer's needs. Acceptance tests are our final level of testing. The customer or a proxy usually conducts acceptance tests to ensure that the application has met whatever goals the customer or stakeholder defined. Acceptance tests are a superset of all other tests. Usually they start as functional and performance tests, but they may include subjective criteria like “ease of use” and “look and feel.” Sometimes, the acceptance suite may include a subset of the tests run by the developers, the difference being that this time the customer or QA team runs the tests. For more about using acceptance tests with an agile software methodology, visit the Wiki site regarding Ward Cunningham’s fit framework (http://fit.c2.com/). 3.2.2 The three types of unit tests Writing unit test and production code take place in tandem, ensuring that your application is under test from the very beginning. We encourage this process and urge for programmers to use their knowledge of implementation details to create and maintain unit tests that can be run automatically in builds. Use your knowledge of implementation details to write tests is also known as white box testing. Your application should undergo other forms of testing, starting with unit tests of course, and finishing with acceptance tests as described in the previous section. As a developer, you want to ensure that each of your subsystems works correctly. As you write code, your first tests will probably be logic unit tests. As you write more tests and more code, you will add integration and functional unit tests. At any one time, you may be working on a logic unit test, an integration unit test, or a functional unit test. Table 3.3 summarizes these unit test types. Table 3.3 Three flavors of unit tests: logic, integration, and functional Test type Description Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Logic unit test A test that exercises the code logic by focusing on a single method. You can control the boundaries of a given test method using mock objects or stubs (see part II of the book). Integration unit test A test that focuses on the interaction between components in their real environment (or part of the real environment). For example, code that accesses a database has tests that effectively call the database (see chapters 16 and 17). Functional unit test A test that extend the boundaries of integration unit testing to confirm a stimulus-response. For example, a web application contains a secure web page that only authorized clients can access. If the client does not log in, then trying to access the page should result in a redirect to the login page. A functional unit test can examine this case by sending an HTTP request to the page to verify that a redirect (HTTP status code 302) response code comes back. Figure 3.3 illustrates how these three flavors of unit tests interact. Figure 3.3 Unit test types including functional, integration, and logic The sliders define the boundaries between the types of unit tests. You need all three types of tests to ensure your code works. Using this type of testing will increase your test code coverage, which will increase your confidence in making changes to the existing code base while minimizing the risk of introducing regression bugs. Strictly speaking, functional unit tests are not pure unit tests, but nor are they pure functional tests. They are more dependent on an external environment than pure unit tests Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 are, but they do not test a complete workflow, as expected by pure functional tests. We put functional unit tests in our scope because they are often useful as part of the battery of tests run in development. A typical example is the StrutsTestCase framework (http://strutstestcase.sourceforge.net/), which provides functional unit testing of the runtime Struts configuration. These tests tell a developer that the controller is invoking the appropriate software action and forwarding to the expected presentation page, but they do not confirm that the page is actually present and renders correctly. Having described the various types of unit tests, we now have a complete picture of application testing. We develop with confidence because we are creating tests as we go and we are running existing tests as we go to find regression bugs. When a test fails, we know exactly what failed and where, we can then focus on fixing each problem directly. 3.2.3. Black box vs. white box testing Before we close this chapter let us focus on one other categorization of software tests - black box and white box testing. This categorization is intuitive and easy to grasp but developers often forget about it. We will start by exploring black box testing with a definition: DEFINITION: black box test - A black box test has no knowledge of the internal state or behavior of the system. The test relies solely on the external system interface to verify its correctness. As the name of this methodology suggests, you treat the system as a black box, imagine it with buttons and LEDs. You do not know what is inside, or how the system operates - all you know is that by providing correct input, the system produces the desired ouput. All we need to know in order to test the system properly is the system's functional specification. The early stages of a project typically produces this kind of specification, which means we can start testing early. Anyone can take part of testing the system - a QA engineer, a developer, or even a customer. The simplest form of black box testing would try to mimic manually actions on the user interface. Another, more sophisticated approach would be to use a tool for this task - such as HTTPUnit, HTMLUnit, or Selenium. We will discuss most of these tools in the last part of the book. At the other end of the spectrum is white box testing, sometimes called glass-box testing. In contrast to black box testing, we use detailed knowledge of the implementation to create tests and drive the testing process. Not only is a component's implementation knowledge required but also how it interacts with other components. For these reasons, the implementers are the best candidates to create white box tests. Which one of the two approaches should you use? Unfortunately, there is no correct answer and we suggest that you use both approaches. In some situations, you will need Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 user-centric tests, and in others you will need to test the implementation details of the system. Next, we present pros and cons for the both approaches. USER-CENTRIC APPROACH We know that there is tremendous value in customer feedback and one of our goals from extreme programming is to "release early and release often." However, we are unlikely to get useful feedback if we just tell the customer, “Here it is. Let me know what you think.” It is far better to get customer involved by providing a manual test script to run through. By making the customer think about the application, he can also clarify what the system should really do. TESTING DIFFICULTIES Black-box tests are more difficult to write and run2 because they usually deal with a graphical front-end, whether a web browser or desktop application. Another issue is that a valid result on the screen does not always mean the application is correct. White box tests are usually easier to write and run, but the developers must implement them. TEST COVERAGE White box testing provides better test coverage than black box testing. On the other hand, black box tests can bring more value than white box tests. We will focus on test coverage in the next chapter. While these test distinctions can seem academic, recall that divide and conquer does not have to apply only to writing production software, it can also apply to testing. We encourage you to use these different types of tests to provide the best code coverage possible, therefore giving you the confidence to refactor and evolve your applications. 3.3 Summary The pace of change is increasing. Product release cycles are getting shorter, and we need to react to change quickly. In addition, the development process is shifting—development as the art of writing code is not enough. Development must be the art of writing complete and tested solutions. To accommodate rapid change, we must break with the waterfall model where testing follows development. Late stage testing does not scale when change and swiftness are paramount. When it comes to unit testing an application, you can use several types of unit tests: logic, integration, and functional unit tests. All are useful during development and complement each other. They also complement the other software tests that performed by quality assurance personnel and by the customer. In the next chapter, we will continue to explore the world of testing. We will present best practices, like measuring test coverage, writing testable code, and practicing test-driven development (TDD). 2 Black box testing is getting easier with tools like Selenium and HtmlUnit, which we describe in chapter 11. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 04 Test coverage and development This chapter covers ƒ Measuring test coverage ƒ Writing testable code ƒ Practicing Test-Driven Development Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 I don't think anybody tests enough of anything. —James Gosling In the previous chapters, we introduced testing software and started exploring testing with JUnit, we also presented different test methodologies. Now that we are writing test cases, it is time to measure how good these tests are by using a test coverage tool to report what code is exercised by the tests and what code is not. We will also discuss how to write code that is easy to test. We will finish by looking at test- driven development (TDD). 4.1 Measuring test coverage Writing unit tests gives you the confidence to change and refactor an application. As you make changes, you run tests, which give you immediate feedback on new features under test and whether or not changes break existing tests. The issue is that these changes may still break existing untested functionality. In order to resolve this issue, we need to know precisely what code runs when you or the build invokes tests. Ideally, our tests should cover 100% of our application code. Let us look into more detail at what help test coverage provides. 4.1.1 Introduction to test coverage Using black box testing, we can create tests that cover the public API of an application. Since we are using documentation as our guide and not knowledge of the implementation, we do not create tests, for example, that use special parameter values to exercise special conditions in the code. One metric of test coverage would be to track which methods the tests call. This does not tell you whether the tests are complete, but it does tell you if you have a test for a method. Figure 4.1 shows the partial test coverage typically achieved using only black box testing. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 Figure 4.1 Partial test coverage with black box tests You can write a unit test with intimate knowledge a method's implementation. If a method contains a conditional branch, you can write two unit tests: one for each branch. Because you need to see into the method to create such a test, this is falls under white box testing. Figure 4.2 shows 100% test coverage using white box testing. Figure 4.2 Complete test coverage using white box tests You can achieve higher test coverage using white box unit tests because you have access to more methods and because you can control both the inputs to each method and the behavior of secondary objects (using stubs or mock objects, as you will see in later chapters). Since you can write white box unit tests against package protected, default access and public methods, you get more code coverage. 4.1.2 Introduction to Cobertura Cobertura is a code coverage tool integrated with JUnit and Ant (there is also a Maven plug- in.) Cobertura provides the following features: ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 ƒ Free and open source. ƒ Integrates with JUnit and Ant; also callable from a command line. ƒ Generates reports in HTML or XML. ƒ Sorts the HTML results by various criteria. ƒ Computes the percentage of code lines and code branches covered for each class, package, and in the entire project. In order to measure test coverage Cobertura creates instrumented copies of class files you specify. This process, called byte-code instrumentation adds byte-codes to existing compiled code to enable logging of what byte-codes are executed. Instead of, or in addition to, running the normally compiled unit tests, you run the compiled and instrumented tests. Let us now get started with Cobertura. Download Cobertura from http://cobertura.sourceforge.net/ and extract the archive. Define a COBERTURA_HOME environment variable and add it to the execution PATH environment variable. The COBERTURA_HOME folder contains several command-line scripts we will use in this section.. We first start by compiling our test cases with the following command. >javac -cp junit-4.6.jar -d uninstrumented src\*.java We instrument our classes with the following command: >cobertura-instrument --destination instrumented ➔uninstrumented\Calculator.class The –-destination parameter specifies where to place the instrumented classes. The application argument specifies the path to the pre-compiled classes, in our case, uninstrumented\Calculator.class. Next, we run the unit tests against the instrumented code. Cobertura integrates with JUnit and Ant, but is also tool-agnostic and can work with any other testing framework. To run your tests, you need to place two resources on your CLASSPATH: ƒ the cobertura.jar ƒ the directory containing the instrumented classes before the directory containing the un-instrumented classes. You can run the tests from the command line or Ant, with identical results. For example, the following runs tests from the command line. >java -cp junit-4.6.jar;cobertura.jar;instrumented;uninstrumented; ➔ -Dnet.sourceforge.cobertura.datafile= ➔ cobertura.ser org.junit.runner.JUnitCore Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 TestCalculator The net.sourceforge.cobertura.datafile property points to a file where Cobertura will store the code coverage results. If you do not specify this property, Cobertura will create a file called cobertura.ser in the current directory. 4.1.3 Generating test coverage reports After you run the above scripts, you will get your instrumented classes in the instrumented folder, and a code coverage file for a given test run. To produce an HTML report, use the cobertura-report script. >cobertura-report -–format html --datafile cobertura.ser ➔--destination reports src The destination parameter specifies the output directory for the report. The reports folder contains the HTML report shown in figure 4.3. ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 4.3 A Cobertura code-coverage report. Cobertura shows not only code coverage by package, but also by class. You can click on any of the classes in the report to see to which extent that particular class was tested. Figure 4.4 shows the report for one class. Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 Figure 4.4 Class code coverage with Cobertura. The report shows good test coverage of the squareRoot method in the Calculator class. The number next to the line number shows that the tests called the method ten times, covering all lines in the method (there is only one line in this case.) On the other hand, we have zero executions of the sum method. Overall, we have 67% code coverage of the Calculator class, indicating that developers need to create more tests. Depending on how you application is composed, it might not be possible to reach all code in the test environment. You may consider refactoring your code to allow for better coverage in combination with the use of mock objects or stubs. Whether or not you choose this approach to reach 100% code coverage is a policy decision your team can review through the development cycle. 4.1.4 Combining black box and white box testing If we can achieve higher test coverage with white box unit tests, and we can generate reports to prove it, do we need to bother with black box tests? If you think about the differences between figure 4.1 and figure 4.2, there’s more going on than how many methods the tests execute. The black box tests in figure 4.1 are verifying interactions between objects. The white box unit tests in figure 4.2, by definition, do not test ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 stub or a mock object designed to produce specific test behavior (see chapters 5 and ract with eac n to how different w to write tests for an application. 4.2 o write easily testable code than it is to . 4.2 n with refactoring wizards in tools like Eclipse, you must alw de and you should be very careful of the projects and commercial products that usually do not even know of each other’s exi ethods. For non-public object interactions. If a white box test does interact with another object, that object is usually a 6.), If you want to thourouly test your application, including how runtime objects inte h other, you need to use black box integration tests as well as white box tests. We have completed our overview of code coverage and Cobertura to see precisely which part of an application unit tests exercise. Let us now move o implementation techniques affect ho Writing testable code This chapter is dedicated to best practices in software testing. We introduced JUnit (in chapter 1 and 2) and discussed different types of tests (in chapter 3). We are now ready to get to the next level: writing code that is easy to test. Sometimes writing a single test case is easy, and sometimes it is not. It all depends on the level of complexity of the application. A best practice avoids complexity as much as possible; code should be readable and testable. In this section, we will discuss some best practices to improve your architecture and code. Remember that it is always easier t refactor existing code to make it easily testable .1 Benefit from access modifiers One of the principles in the open source world says that you “never change the signature of a public method.” An application code review will show that most calls are made to public APIs. If you change the signature of a public method, then you need to change every call site in the application and unit tests. Eve ays perform this task with care. In the open source world, and for any API made public by a commercial product, life can get even more complicated –many people use your co changes you make to stay backwards compatible. Public methods become the articulation points of an application between components, open source stence. Imagine a public method that takes a distance as a double parameter and a black box test to verify a computation. At some point, the meaning of the parameter changes from miles to kilometers. Your code still compiles, but the runtime breaks. Without a unit test to fail and tell you what is wrong, you may spend a lot of time debugging and talking to angry customers. This example illustrates that you must test all public m methods, you need to go to a deeper level and use white box tests. 4.2.2 Don’t mix object instantiation in your application logic Remember that unit tests verify your code in isolation. What you want to do in your unit tests is to instantiate the class that you want to test and assert for correctness against it. Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 ill need to use some complicated mock-objects solution (see chapter 6 for (factories), and the one that has only pure business logic in it. Consider the following listing Your test-cases should be simple. What happens when your class instantiates a whole new set of objects with the new keyword, somewhere in the logic? That makes your class dependent to those classes. In order to achieve a testable code you want to reduce dependencies as much as possible. Because, if your classes depend on a lot of other classes that need to be instantiated and set up to some state, then your tests will be very complicated – you w mock objects). The solution to reducing the dependencies is to separate your code in two parts – the one that can instantiate new objects Lis the dependencies in your code n hasDriver) { this.hasDriver = hasDriver; } to have the interface passed to the class, like in the following listing: ting 4.1 Reducing class Vehicle { Driver d = new Driver(); boolean hasDriver = true; ivate void setHasDriver(boolea pr } Every time we instantiate the Vehicle object, we also instantiate the Driver object. In that case we say that we have mixed the concepts. The solution would be Driver Vehicle Lis e Driver to the Vehicle class { ; ) { this.d = d; n hasDriver) { this.hasDriver = hasDriver; } tation – JuniorDriver, SeniorDriver, etc. and we can pass it to the Vehicle class. ting 4.2 Passing th class Vehicle Driver d; boolean hasDriver = true hicle(Driver d Ve } ivate void setHasDriver(boolea pr } This way we can produce a mock Driver object (see chapter 6) and pass it to the Vehicle class at instantiation. Furthermore, at any other moment in the future we can mock any other type of Driver implemen Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 4.2.3 Don’t work in constructor We always strive for a better test coverage. This leads us to adding more and more test cases. In each of these test-cases, all that we do is ƒ Instantiate the class to test. ƒ Set the class into a particular state, by calling some methods, or setting some values. ƒ Assert the final state of the class. By doing some work in the constructor (other than populating your class-variables), you mix the first and the second point in our list. It is a bad practice not only from architectural point of view (you will do the same work every time you instantiate your class), but also because you always get your class in a pre-defined state. This code is hard to maintain, and untestable. 4.2.4 Follow “The Principle of Least Knowledge” The “Law of Demeter,” or “Principle of Least Knowledge,” is a design guideline that states that one class should know only as much as it needs to know. For example consider the following: Listing 4.3 Law of Demeter violation class { Car private Driver driver; Car(Context context) { this.driver = context.getDriver(); } } As you see here we pass the Car constructor a Context object. This is strict violation of the Demeter law, because the Car class needs to know that the Context object has a getDriver() method. If we want to test this constructor we need to get hold of a valid Context object before calling the constructor. Bad things will happen when the Context object has a lot of variables and methods. Then, we are forced to use mock objects (see chapter 6) to simulate the context, but since the Context object is big enough this will bloat out code. The proper solution is to apply the principle of the least knowledge and pass references to methods and constructors only when you need to do so. In our example you should pass the Driver reference to the Car constructor. That is a key-concept that you need to remember – ask for things (don’t search for things), and only ask for those things that you really need! Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 4.2.5 Avoid hidden dependencies and global state You, as a developer, should be very careful with global state. And this is simply because global state makes it possible for some objects to get hold of other global objects without declaring it in the API. This leads to hidden dependencies in your code. For instance look at the following code listing. Listing 4.4 Global state in action public void substraction() { DBManager manager = new DBManager(); manager.initDatabase(); Reservation r = new Reservation(); r.reserve(); } As you can see the DBManager implies a global state. Without instantiating the database, first, you will not be able to make the reservation. The Reservation uses the DBManager secretly to access the database, because the DBManager is in a global state. I say it uses the DBManager secretly, because it never defines that usage in the API, and thus leads to a hidden dependency. The correct definition is shown in listing 4.5. Listing 4.5 Avoiding global state @Test public void substraction() { DBManager manager = new DBManager(); manager.initDatabase(); Reservation r = new Reservation(manager); r.reserve(); } You should avoid global state, because you never see what happens beneath the surface. You never know which objects use the “global objects”. And also any internal objects inside the “global-state objects” become global-state objects as well. As Miško Hevery says in his blog: “Think of it another way. You can live in a society where everyone (every class) declares who their friends (collaborators) are. If I know that Joe knows Mary but neither Mary nor Joe knows Tim, then it is safe for me to assume that if I give some information to Joe he may give it to Mary, but under no circumstances will Tim get hold of it. Now, imagine that everyone (every class) declares some of their friends (collaborators), but other friends (collaborators which are singletons) are kept secret. Now you are left wondering how in the world did Tim got hold of the information you gave to Joe. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Here is the interesting part. If you are the person who built the relationships (code) originally, you know the true dependencies, but anyone who comes after you is baffled, since the friends which are declared are not the sole friends of objects, and information flows in some secret paths which are not clear to you. You live in a society full of liars.” 4.2.6 The bad side of singletons What we tried to explain in the previous section global state is bad and should be avoided. On the other hand singletons enforce a global state by means of protected constructors and global instance variables. Design patterns in action: Singleton Singleton1 is a design pattern with the intent to ensure that a class has only one instance and also to provide a global point for accessing the class. The concept of singleton is sometimes generalized to restrict that only several instances of the class are used. Most often the implementation is done with the means of a protected constructor and global instance variables. The class itself declares a method that creates instance of the class, only if such an instance does not exist. In case an instance exists, it simply returns it. public class Singleton { public static final Singleton SINGLETON_INSTANCE; protected Singleton() {} public static Singleton getInstance() { if(SINGLETON_INSTANCE==null) { SINGLETON_INSTANCE = new Singleton(); } return SINGLETON_INSTANCE; } } The Singleton design pattern needs to make sure the object does not get instantiated any other way, but only by calling the getInstance method. So what we do is hide the 1 More on the Singleton pattern you can find in “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson, John M. Vlissides Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 constructor by making it private. However, this code would be impossible to test, and so we hide the constructor by making it protected, which is hidden enough and at the same time provides us with the flexibility of our tests accessing the code we want to test. Singletons have another bad side - they enforce you to use global-state. As you can see the SINGLETON_INSTANCE variable in the previous example is a global variable. Well, you would probably respond to this one by stating that the global variable is an immutable one and there is no harm at all, but remember that this could change in the future, so you are, again, bounding your code to use global state, which is something that we want to avoid. 4.2.7 The bad side of static methods Static methods are hard to unit-test. The reasons behind that statement are several. Remember that unit testing is testing in isolation. In order to achieve isolation you need some “joints” in your code, where you can easily substitute your code with the test code. These joints occur in polymorphism. With polymorphism (the ability of one object to appear as another object) the method you are calling is not determined at compile time. So you can easily use polymorphism to substitute your code with the test code to force certain code patterns to be tested. The opposite situation occurs when you use nothing but static methods. Then you practice procedural programming, and all of your method calls are determined at compile- time. You no longer have these joints that you can substitute. Sometimes the harm of static methods to your test is not big. Especially when you chose some method that ends the execution graph, like Math.sqrt(). On the other side, you can choose a method that lies in the heart of your application logic. In that case every method that gets executed inside that static method becomes hard to test. 4.2.8 Favor composition over inheritance A lot of people choose inheritance as code reuse mechanism. I am not saying that is wrong, but in hell a lot of the situations you can use composition which is easier to test. At runtime there is no chance to change the inheritance, but you can always choose a different composition. What we strive for is to make our code as flexible as possible at run-time. This way we can be sure that it is easy to switch from one state of our objects to another and that makes out code easily testable. For example consider it bad practice to have all of your servlets extend AuthenticatedServlet, simply because you will always need to instantiate the credentials for the user in your tests. On the other hand you could add an instance variable Credentials to those servlets that really need it, and make your classes easier to test by instantiating the Credentials variable, only when you really need it. 4.2.9 Favor polymorphism over conditionals As I mentioned previous in the section all that you do in your tests is: Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 ƒ Instantiate the class to test. ƒ Set the class into a particular state, by calling some methods, or setting some values. ƒ Assert the final state of the class. Difficulties may arise at any of the points listed above. For example it would be difficult to instantiate your class if it is too complex. That said, you need to avoid complexity in your code, otherwise it would be untestable. One of the main ways to decrease complexity is to try to avoid long switch and if constructions. Consider the following listing: Listing 4.6 Example of a bad design with conditionals public class DocumentPrinter { […] public void printDocument() { switch (document.getDocumentType()) { case Documents.WORD_DOCUMENT: printWORDDocument(); break; case Documents.PDF_DOCUMENT: printPDFDocument(); break; case Documents.TEXT_DOCUMENT: printTextDocument(); break; default: printBinaryDocument(); break; } } […] } This design is awful for several reasons. Apart from the fact that long conditionals lead to poor performance, this code is also hard to test and maintain. Every time you want to add a new document-type you will add additional switch statements and if that happens often in your code you will have to change it in every place that it occurs. Every time you see long conditional statements you immediately have to think of polymorphism! Polymorphism is a natural way to avoid long conditionals, by breaking your code into several smaller components or classes, and each one of it clearly relate to each other. And we all have to agree that several smaller components are easier to test than one large complex component. In the given example the conditional can be avoided by simply calling creating different document-types like a WordDocument, PDFDocument or an XMLDocument, each one of which having a printDocument() method. This will decrease the complexity of your code and will make it easier to read. Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 4.3 Test-Driven Development In chapter 3, you designed an application controller and quickly wrote some tests to prove your design. As you wrote the tests, the tests helped improve the initial design. As you write more unit tests, positive reinforcement encourages you to write them earlier. Pretty soon, as you design the implementation, it becomes natural to wonder about how you will test a class. Following this methodology, more developers are making the quantum leap from test- friendly designs to Test-Driven Development. DEFINITION Test-Driven Development (TDD) — Test-Driven Development is a programming practice that instructs developers to write new code only if an automated test has failed, and to eliminate duplication. The goal of TDD is “clean code that works.” 4.3.1 Tweaking the cycle When you develop code, you design an application programming interface (API) and then implement the behavior promised by the interface. When you unit-test code, you verify the promised behavior through a method’s API. The test is a client of the method’s API, just as your domain code is a client of the method’s API. The conventional development cycle goes something like this: [code, test, (repeat), commit]. Developers practicing TDD make a seemingly slight but surprisingly effective adjustment: [test, code, (repeat), commit]. (More on this later.) The test drives the design and becomes the method’s first client. Listing 4.7 illustrates how unit tests can help design the implementation. The getBalanceOk method shows that the getBalance method of Account returns the account balance as a long and that this balance can be set in the Account constructor. At this point, the implementation of Account is purely hypothetical, but writing the unit tests allows you to focus on the design of the code. As soon as you implement the class, you can run the test to prove that the implementation works. If the test fails, then you can continue working on the implementation until it passes the test. When the test passes, you know that your contract is fulfilled and that the code works as advertised. Listing 4.7 Unit tests as a design guide import org.junit.Test; import static org.junit.Assert.assertEquals; public class TestAccount { @Test public void getBalanceOk () { long balance = 1000; Account account = new Account(balance); long result = account.getBalance(); assertEquals(balance, result); Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } } When you use the test as the method’s first client, it becomes easier to focus purely on the API. Writing the tests first provides the following: ƒ Means to design the code ƒ Documentation as to how the code works ƒ Unit tests for the code Someone new to the project can understand the system by studying the functional test suite (with the help of some high-level UML diagrams, for example). To analyze a specific portion of the application in detail, someone can drill down into individual unit tests. 4.3.2 The TDD two-step Earlier, we said that TDD tweaks the development cycle to go something like [test, code, (repeat), ship]. The problem with this chant is that it leaves out a key step. It should go more like this: [test, code, refactor, (repeat), ship]. The core tenets of TDD are to: 1. Write a failing automatic test before writing new code 2. Eliminate duplication The eliminate duplication step ensures that you write code that is not only testable but also maintainable. When you eliminate duplication, you tend to increase cohesion and decrease dependency. These are hallmarks of code that is easier to maintain over time. Other coding practices have encouraged us to write maintainable code by anticipating change. In contrast, TDD encourages us to write maintainable code by eliminating duplication. Developers following this practice have found that testbacked, well-factored code is, by its very nature, easy and safe to change. TDD gives us the confidence to solve today’s problems today and tomorrow’s problems tomorrow. Carpe diem! JUnit best practice: write failing tests first If you take the TDD development pattern to heart, an interesting thing happens: Before you can write any code, you must write a test that fails. Why does it fail? Because you haven’t written the code to make it succeed. Faced with a situation like this, most of us begin by writing a simple implementation to let the test pass. Now that the test succeeds, you could stop and move on to the next problem. Being a professional, you would take a few minutes to refactor the implementation to remove redundancy, clarify intent, and optimize the investment in the new code. But as long as the test succeeds, technically, you’re done. Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The end game? If you always test first, you will never write a line of new code without a failing test. 4.4 Testing in the development cycle Testing occurs at different places and times during the development cycle. Let’s first introduce a development life cycle and then use it as a base for deciding what types of tests are executed when. Figure 4.5 shows a typical development cycle we have used effectively in small to large teams. The life cycle is divided into four or five platforms: ƒ Development platform—This is where the coding happens. It consists of developers’ workstations. One important rule is usually to commit (or check in, depending on the terminology used) several times per day to your common Source Control Management (SCM) tool (SVN, CVS, ClearCase, etc.). Once you commit, others can begin using what you have committed. However, it is important to only commit something that “works.” In order to know if it works, a typical strategy is to have an automated build (see chapters 8, 9 and 10) and run it before each commit. ƒ Integration platform—The goal of this platform is to build the application from its different pieces (which may have been developed by different teams) and ensure that they all fit together. This step is extremely valuable, because problems are often discovered here. It is so valuable that we want to automate it. It is then called continuous integration (see http://www.martinfowler.com/articles/continuousIntegration.html) and can be achieved by automatically building the application as part of the build process (more on that in chapter 10 and later). Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 4.5 A typical application development life cycle using the continuous integration principle ƒ Acceptance platform / stress test platform—Depending on how rich your project is, this can be one or two platforms. The stress test platform exercises the application under load and verifies that it scales correctly (with respect to size and response time). The acceptance platform is where the project’s customers accept (sign off on) the system. It is highly recommended that the system be deployed on the acceptance platform as often as possible in order to get user feedback. ƒ (Pre-)production platform—The pre-production platform is the last staging area before production. It is optional, and small or noncritical projects can do without it. Let’s see how testing fits in the development cycle. Figure 4.6 highlights the different types of tests you can perform on each platform: Figure 4.6 The different types of tests performed on each platform of the development cycle ƒ On the development platform, you execute logic unit tests (tests that can be executed in isolation from the environment). These tests execute very quickly, and you usually execute them from your IDE to verify that any change you have brought to the code has not broken anything. They are also executed by your automated build before you commit the code to your SCM. You could also execute integration unit tests; however, they often take much longer, because they need some part of the environment to be Licensed to Alison Tyler Download at Boykma.Com 18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 set up (database, application server, and so on). In practice, you would execute only a subset of all integration unit tests, including any new integration unit tests you have written. ƒ The integration platform usually runs the build process automatically to package and deploy the application and then executes unit and functional tests. Usually, only a subset of all functional tests is run on the integration platform, because compared to the target production platform it is a simple platform that lack elements (for example, it may be missing a connection to an external system being accessed). All types of unit tests are executed on the integration platform (logic unit tests, integration unit tests, and functional unit tests). Time is less important, and the whole build can take several hours with no impact on development. ƒ On the acceptance platform / stress test platform, you re-execute the same tests executed by the integration platform; in addition, you run stress tests (performance and load tests). The acceptance platform is extremely close to the production platform, and more functional tests can also be executed. ƒ It is always a good habit to try to run on the (pre-)production platform the tests you ran on the acceptance platform. Doing so acts as a sanity check to verify that everything is set up correctly. The human being is a strange creature – always tending to neglect details. In a perfect world we would have all the four platforms to run our tests on. In real world, however, most of the software companies try to skip some of the platforms we listed, or the concept of testing as a whole. As a developer who bought this book, you already made the right decision – more tests, less debugging! Now, again, it is up to you – are you going to strive for perfection, stick to everything that you learned so far and let your code benefit from that? JUnit best practice: continuous regression testing Most tests are written for the here and now. You write a new feature, you write a new test. You see if the feature plays well with others, and if the users like it. If everyone is happy, you can lock the feature and move on to the next item on your list. Most software is written in a progressive fashion: You add one feature and then another. Most often, each new feature is built over a path paved by existing features. If an existing method can service a new feature, you reuse the method and save the cost of writing a new one. Of course, it’s never quite that easy. Sometimes you need to change an existing method to make it work with a new feature. When this happens, you need to confirm that all the old features still work with the amended method. A strong benefit of JUnit is that the test cases are easy to automate. When a change is made to a method, you can run the test for that method. If that one passes, then you can ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 run the rest. If any fail, you can change the code (or the tests) until everyone is happy again. Using old tests to guard against new changes is a form of regression testing. Any kind of test can be used as a regression test, but running unit tests after every change is your first, best line of defense. The best way to ensure that regression testing is done is to automate your test suites. See part III of the book for more about automating JUnit. 4.5 Summary This chapter was dedicated mainly on some advanced techniques in unit testing – checking your test coverage and improving it, designing your code from architectural point of view to be easily testable, and practicing Test Driven Development (TDD). These advanced techniques come naturally once you have completed the introduction to testing (chapter 1), once you already have the deep-knowledge in software testing (chapter 2) and software tests as a whole (chapter 3). The next chapter starts the second part of the book, which will take you to the next level of testing your software. This next level involves using not only JUnit as a testing framework, but also including other frameworks and tools, and introducing the new concepts of mocking. So let’s start! Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Part II Different testing strategies This part of the book reveals the different strategies and techniques in testing. Here we take a more scientific and theoretical approach to explain what the differences are. We describe incorporating mock objects, stubs and dive into details of what in-container testing means. The fifth chapter of the book is stubs-dedicated. We take a look into another solution to isolate the environment and make our tests seamless. The sixth chapter starts explaining what mock-objects are. We give a thorough overview of how to construct and use mock-objects. We also give a real-world example showing not only where do mock-objects fit best, but also how to benefit by using them integrated with your JUnit tests. The last chapter describes a totally different technique - executing tests inside a container. This solution is very different from the previous ones and, just like them it has its pros and cons. We start by making an overview of what in-container means, how it is achieved, and at the end of the chapter we compare the mocks-stubs approach versus the in-container one. Along with the theoretical benefit, this chapter will serve as a very good starting point to understand chapters 13 and 16. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 05 Coarse-grained testing with stubs This chapter covers ƒ Introducing stubs ƒ Using an embedded server in place of a real web-server ƒ Unit-testing an HTTP connection with stubs Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 And yet it moves. —Galileo As you develop your applications, you will find that the code you want to test depends on other classes, which themselves depend on other classes, which then depend on the environment. For example, you might be developing an application that uses JDBC to access a database, a J2EE application (one that relies on a J2EE container for security, persistence, and other services), an application that accesses a file system, or an application that connects to some resource using HTTP, SOAP, or another protocol. In the previous chapters, we introduced the JUnit framework. Starting in this chapter, we will look at using JUnit to test an application that depends on external resources. For applications that depend on a specific runtime environment, writing unit tests is a challenge. Your tests need to be stable, and when run repeatedly, need to yield the same results. You need a way to control the environment in which the tests run. One solution is to set up the real required environment as part of the tests and run the tests from within that environment. In some cases, this approach is practical and brings real benefits (see chapter 7, which discusses in-container testing). However, it works well only if you can set up the real environment on your development and build platforms, which is not always feasible. For example, if your application uses HTTP to connect to a web server provided by another company, you usually will not have that server application available in your development environment. Therefore, you need a way to simulate that server so you can still write and run tests for your code. Alternatively, suppose you are working with other developers on a project. What if you want to test your part of the application, but the other part is not ready? One solution is to simulate the missing part by replacing it with a fake that behaves the same way. There are two strategies for providing these fake objects: stubbing and using mock objects. Stubs, the original solution, are still very popular, mostly because they allow you to test code without changing it to make it testable. This is not the case with mock objects. This chapter is dedicated to stubbing, while chapter 6 covers mock objects. 5.1 Introducing stubs Stubs are a mechanism for faking the behavior of real code or code that is not ready yet. In other words, stubs allow you to test a portion of a system without the other part being available. Stubs usually do not change the code you are testing but instead adapt to provide seamless integration. DEFINITION stub — A stub is a piece of code that is inserted at runtime in place of the real code, in order to isolate the caller from the real implementation. The intent is to replace a complex behavior with a simpler one that allows independent testing of some part of the real code. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Here are some examples of when you might use stubs: ƒ When you cannot modify an existing system because it is too complex and fragile ƒ For coarse-grained testing, such as integration testing between different subsystems Stubs usually provide very good confidence in the tested system. With stubs, you are not modifying the objects under test, and what you are testing is the same as what will execute in production. A build or developer usually executes tests involving stubs in their running environment, providing additional confidence. On the downside, stubs are usually hard to write, especially when the system to fake is complex. The stub needs to implement the same logic as the code it is replacing, and that is difficult to get right for complex logic. Here are some cons of stubbing: ƒ Stubs are often complex to write and need debugging themselves. ƒ Stubs can be difficult to maintain because they are complex. ƒ A stub does not lend itself well to fine-grained unit testing. ƒ Each situation requires a different stubbing strategy. In general, stubs are better adapted for replacing coarse-grained portions of code. You usually use stubs to replace a full-blown external system like a file system, a connection to a server, a database, and so forth. Stubs can replace a method call to a single class, but it is more difficult. (We will demonstrate how to do this with mock objects in chapter 6.) 5.2 Stubbing an HTTP connection To demonstrate what stubs can do, let us build some stubs for a simple application that opens an HTTP connection to a URL and reads its content. Figure 5.1 shows the sample application (limited to a WebClient.getContent method) opening an HTTP connection to a remote web resource. The remote web resource is a servlet, which generates an HTML response. The web resource in Figure 5.1 is what we called the "real code" in the stub definition. Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 5.1 The sample application opens an HTTP connection to a remote web resource. The web resource is the "real code" in the stub definition. Our goal in this chapter is to unit-test the getContent method by stubbing the remote web resource, as demonstrated in figure 5.2. You replace the servlet web resource with the stub, a simple HTML page returning whatever you need for the TestWebClient test case. This approach allows you to test the getContent method independently of the implementation of the web resource (which in turn could call several other objects down the execution chain, possibly down to a database). Figure 5.2 Adding a test case and replacing the real web resource with a stub The important point to notice with stubbing is that we did not modify getContent to accept the stub. The change is transparent to the application under test. In order to allow Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 stubbing, the target code to needs to have a well-defined interface and allow plugging in of different implementations (a stub, in our case). In the figure 5.1 example, the interface is actually the public abstract class java.net.URLConnection, which cleanly isolates the implementation of the page from its caller. Let us see a stub in action using the simple HTTP connection example. Listing 5.1 from the example application demonstrates a code snippet opening an HTTP connection to a given URL and reading the content found at that URL. Imagine the method is one part of a bigger application that you want to unit-test. Listing 5.1 Sample method opening an HTTP connection […] import java.net.URL; import java.net.HttpURLConnection; import java.io.InputStream; import java.io.IOException; public class WebClient { public String getContent(URL url) { StringBuffer content = new StringBuffer(); try { HttpURLConnection connection = (HttpURLConnection) (1) url.openConnection(); (1) connection.setDoInput(true); (1) InputStream is = connection.getInputStream(); (2) byte[] buffer = new byte[2048]; (2) int count; (2) while (-1 != (count = is.read(buffer))) { (2) content.append(new String(buffer, 0, count)); (2) } (2) } catch (IOException e) { return null; (3) } return content.toString(); } } We start (1) by opening an HTTP connection using the HttpURLConnection class. We then read the stream content until there is nothing more to read (2). If an error occurs, we return null (3). One might argue that a better implementation should throw an exception. However, for testing purposes, returning null is fine. 5.2.1 Choosing a stubbing solution There are two possible scenarios in the example application: the remote web server (see figure 5.1) could be located outside of the development platform (such as on a partner site), or it could be part of the platform where you deploy the application. However, in both cases, you need to introduce a server into your development platform in order to be able to unit- test the WebClient class. One relatively easy solution would be to install an Apache test Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 server and drop some test web pages in its document root. This is a typical, widely used stubbing solution. However, it has several drawbacks: ƒ Reliance on the environment—You need to be sure the full environment is up and running before the test starts. If the web server is down, and you execute the test, it will fail and you will spend time debuging why it is failing. You will discover that the code is working fine and it was only a set up issue generating a false failure. When you are unit testing, it is important to be able to control as much as possible of the environment in which the tests execute, such that test results are reproducible. ƒ Separated test logic—The test logic is scattered in two separate locations: in the JUnit test case and in the test web page. You need to keep both types of resources in sync for the tests to succeed. ƒ Tests are difficult to automate—Automating the execution of the tests is difficult because it involves deploying the web pages on the web server, starting the web server, and then running the unit tests. Fortunately, an easier solution exists using an embedded web server. Since we are testing in Java, the easiest solution is to use a Java web server that you can embed in the test case. You can use the free and open source Jetty server for this exact purpose. In this book, we will use Jetty to set up our stubs. For more information about Jetty, visit http://www.eclipse.org/jetty/. We use Jetty because it is fast (important when running tests), it is lightweight, and it can be completely controlled in Java from test cases. Additionally, Jetty is a very good web, servlet, and JSP container that you can use in production. You do not need this much for most tests, but it is always nice to use best-of-breed technology. Using Jetty allows you to eliminate the drawbacks outlined above: The JUnit test case starts the server, you write the tests in Java in one location, and automating the test suite is a nonissue. Thanks to Jetty’s modularity, the real point of the exercise is only to stub the Jetty handlers and not the whole server from the ground up. 5.2.2 Using Jetty as an embedded server In order to understand how to set up and control Jetty from your tests, let us implement a simple example. Listing 5.2 shows how to start Jetty from Java and how to define a document root (/) from which to start serving files. Listing 5.2 Starting Jetty in embedded mode—JettySample class […] import org.mortbay.jetty.Server; import org.mortbay.jetty.handler.ResourceHandler import org.mortbay.jetty.servlet.Context; public class JettySample { Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 public static void main(String[] args) throws Exception { Server server = new Server(8080); (1) Context root = new Context(server, “/”); (2) root.setResourceBase(“./pom.xml”); | root.setHandler(new ResourceHandler()); (2) server.start(); (3) } } We start by creating the Jetty Server object (1) and specifying in the constructor which port to listen to for HTTP requests (port 8080). Next, we create a Context object (2) that processes the HTTP requests and passes them to various handlers. You map the context to the already created server instance, and to the root (/) URL. The setResourceBase method sets the document root from which to serve resources. On the next line, we attach a ResourceHandler handler to the root to serve files from the file system. Because this handler will return an HTTP 403-Forbidden error if we try list the content of a directory, we specify the resource-base to be a file. In this example, we specify the file pom.xml in the project’s directory. Finally, we start the server (3). If you start the program from listing 5.2 and navigate your browser to http://localhost:8080, you should be able to see the content of the pom.xml file (see figure 5.3). Figure 5.3 Testing the JettySample in a browser. These are the results when you run listing 5.2 and open a browser on http://localhost:8080. Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 5.3 Stubbing the web server’s resources Now that you know how to easily start and configure Jetty, let us focus on the HTTP connection unit test. You will write a first test that verifies you can call a valid URL and get its content. 5.3.1 Setting up the first stub test To verify that the WebClient works with a valid URL, you need to start the Jetty server before the test, which you can implement in a test case setUp method. You can also stop the server in a tearDown method. Listing 5.3 shows the code. Listing 5.3 Skeleton of the first test to verify that the WebClient works with a valid URL […] import java.net.URL; import org.junit.test; import org.junit.Before; import org.junit.After; public class TestWebClientSkeleton { e@Befor public void setUp() { // Start Jetty and configure it to return "It works" when // the http://localhost:8080/testGetContentOk URL is // called. } @After public void tearDown() { // Stop Jetty. } @Test public void testGetContentOk() throws Exception { WebClient client = new WebClient(); String result = client.getContent(new URL( "http://localhost:8080/testGetContentOk")); assertEquals ("It works", result); } } In order to implement the @Before and @After methods, you have two options. You can prepare a static page containing the text "It works", which you put in the document root (controlled by the call to context.setResourceBase(String) in listing 5.2). Alternatively, you can configure Jetty to use your own custom Handler that returns the string "It works" instead of getting it from a file. This is a much more powerful technique, because it lets you unit-test the case when the remote HTTP server returns an error code to your WebClient client application. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 CREATING A JETTY HANDLER Listing 5.4 shows how to create a Jetty Handler that returns the string "It works". Listing 5.4 Create a Jetty Handler that returns It works when called private class TestGetContentOkHandler extends AbstractHandler { (1) @Override (1) public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException {(1) OutputStream out = response.getOutputStream(); (2) ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(); (2) writer.write("It works"); (3) writer.flush(); (3) response.setIntHeader(HttpHeaders.CONTENT_LENGTH, writer.size()); (4) writer.writeTo(out); (4) out.flush(); (4) } This class creates a handler (1) by extending the Jetty AbstractHandler class, and implementing a single method, handle. Jetty calls the handle method to forward an incoming request to our handler. After that, we use the Jetty ByteArrayISO8859Writer class (2) to send back the string "It works" which we write in the HTTP response (3). The last step is to set the response content length to be the length of the string written to the output stream (this is required by Jetty), and then send the response (4). Now that this handler is written, you can tell Jetty to use it by calling context.setHandler(new TestGetContentOkHandler()). You are almost ready to run your test. The last issue to solve is the one involving the @Before and @After methods. The solution shown in listing 5.3 is not optimal because JUnit will start and stop the server for every single test method. Even though Jetty is fast, this process is not necessary. A better solution is to start the server only once for all the tests By using the JUnit annotations we described in the second chapter of the book: @BeforeClass and @AfterClass. These annotations let you execute code before and after all @Test methods in a class. Isolating each test vs. performance considerations In previous chapters, we went to great lengths to explain why each test should run in a clean environment (even to the extent of using a new class loader instance). However, sometimes there are other considerations to take into account. Performance is a typical one. In the case of Jetty, even if starting the server takes only 1 second, once you have 300 tests, it will add an overhead of 300 seconds (5 minutes). Test suites that take a long time to execute are a handicap; you will be tempted not to execute them often, which negates the regression feature of unit testing. You must be aware of this tradeoff. Depending on the situation, you may choose to have longer-running tests that execute in Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 a clean environment, or instead tune the tests for performance by reusing some parts of the environment. In the example at hand, you use different handlers for different tests, and you can be mostly confident they will not interfere with each other. WRITING THE TEST CLASS We can now easily write the test class using the @BeforeClass annotation, as demonstrated in listing 5.5. Listing 5.5 Putting it all together […] import java.net.URL; […] public class TestWebClient { e@Befor Class public static void setUp() throws Exception() { Server server = new Server(8080); TestWebClient t = new TestWebClient(); Context contentOkContext = new Context(server, “/testGetContentOk”); contentOkContext.setHandler(t.new TestGetContentOkHandler()); server.setStopAtShutDown(true); server.start(); } @Test public void testGetContentOk() throws Exception { WebClient client = new WebClient(); String result = client.getContent(new URL( "http://localhost:8080/testGetContentOk")); assertEquals("It works", result); } @AfterClass public static void tearDown() { //Empty } private class TestGetContentOkHandler extends AbstractHandler { //Listing 5.4 here. } } The test class has become quite simple. The @BeforeClass setUp method constructs the Server object the same way as in listing 5.2. Then we have our @Test methods and we Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 leave our @AfterClass method empty intentionally because we programmed the server to stop at shutdown. If you run the test in Eclipse, you see the result in Figure 5.4 – our test passes. Figure 5.4 Result of the first working test using a Jetty stub. JUnit starts the server before the first test and the server shuts itself down after the last test. 5.3.2 Testing for failure conditions Now that you have the first test working, let us see how to test for server failure conditions. The WebClient.getContent(URL) method returns a null value when a failure occurs. You need to test for this possibility too. With the infrastructure you have put in place, you simply need to create a new Jetty Handler class that returns an error code and register it in the @Before method of the TestWebClientSetup1 class. Let us add a test for an invalid URL—that is, a URL pointing to a file that does not exist. This case is quite easy, because Jetty already provides a NotFoundHandler handler class Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 for that purpose. You only need to modify the TestWebClientSetup1 setUp method in as follows (changes are in bold): @BeforeClass public static void setUp() throws Exception { Server server = new Server(8080); TestWebClient t = new TestWebClient(); Context contentOkContext = new Context(server, “/testGetContentOk”); contentOkContext.setHandler(t.new TestGetContentOkHandler()); Context contentNotFoundContext = new Context(server, “/testGetContentNotFound”); contentNotFoundContext.setHandler(t.new TestGetContentNotFoundHandler()); server.start(); } Here is the code for the TestGetContentNotFoundHandler class: private class TestGetContentNotFoundHandler extends AbstractHandler { public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException { response.sendError(HttpServletResponse.SC_NOT_FOUND); } } Adding a new test in TestWebClient is also a breeze: @Test public void testGetContentNotFound() throws Exception { WebClient client = new WebClient(); String result = client.getContent(new URL( "http://localhost:8080/testGetContentNotFound")); assertNull(result); } In similar fashion, you can easily add a test to simulate the server having trouble. Returning a 5XX HTTP response code indicates this problem. To do so, you will need to write a Jetty Handler class, using HttpServletResponse.SC_SERVICE_UNAVAILABLE, and register it in the @Before method of TestWebClientSetup1 class. A test like this would be very difficult to perform if you did not choose an embedded web server like Jetty. 5.3.3 Reviewing the first stub test You have now been able to fully unit-test the getContent method in isolation by stubbing the web resource. What have you really tested? What kind of test have you achieved? You have done something quite powerful: You have unit-tested the method, but at the same time, you have executed an integration test. In addition, not only have you tested the code Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 logic, you have also tested the connection part that is outside the code (through the Java HttpURLConnection class). The drawback to this approach is that it is complex. It can take a Jetty novice half a day to learn enough about Jetty to set it up correctly. In some instances, you will have to debug stubs to get them to work properly. Keep in mind that the stub must stay simple and not become a full-fledged application that requires tests and maintenance. If you spend too much time debugging your stubs, a different solution may be called for. In these examples, you need a web server—but another example and stub will be different and will need a different setup. Experience helps, but different cases usually require different stubbing solutions. The example tests are nice because you can both unit-test the code and perform some integration tests at the same time. However, this functionality comes at the cost of complexity. More solutions that are lightweight focus on unit testing the code without performing integration tests. The rationale is that while you need integration tests, they could run in a separate test suite or as part of functional tests. In the next section, we will look at another solution that can still be qualified as stubbing. It is simpler in the sense that it does not require you to stub a whole web server. It brings you one step closer to the mock-object strategy, which is described in the following chapter. 5.4 Stubbing the connection So far, you have stubbed the web server’s resources. Next, we will stub the HTTP connection instead. Doing so will prevent you from effectively testing the connection, but that is fine because it is not your real goal at this point. You want to test your code in isolation. Functional or integration tests will test the connection at a later stage. When it comes to stubbing the connection without changing the code, we benefit from Java’s URL and HttpURLConnection classes, which let you plug in custom protocol handlers to process any kind of communication protocol. You can have any call to the HttpURLConnection class redirected to your own class, which will return whatever you need for the test. 5.4.1 Producing a custom URL protocol handler To implement a custom URL protocol handler, you need to call the URL method setURLStreamHandlerFactory and pass it a custom URLStreamHandlerFactory. Whenever the URL openConnection method is called, the URLStreamHandlerFactory class is called to return a URLStreamHandler. Listing 5.6 shows the code to perform this feat. The idea is to call the URL static method setURLStreamHandlerFactory in the JUnit setUp method. (A better implementation would use a TestSetup class, such that this is performed only once during the whole test suite execution.) Listing 5.6 Providing custom stream handler classes for testing […] Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 import java.net.URL; import java.net.URLStreamHandlerFactory; import java.net.URLStreamHandler; import java.net.URLConnection; import java.io.IOException; public class TestWebClient1 { @BeforeClass public static void setUp() { TestWebClient1 t = new TestWebClient1(); URL.setURLStreamHandlerFactory(t.new StubStreamHandlerFactory()); (1) } private class StubStreamHandlerFactory implements (2) URLStreamHandlerFactory { | | public URLStreamHandler createURLStreamHandler(String protocol) { | return new StubHttpURLStreamHandler(); | } | } (2) private class StubHttpURLStreamHandler extends URLStreamHandler { (3) protected URLConnection openConnection(URL url) | throws IOException { | return new StubHttpURLConnection(url); | } | } (3) @Test public void testGetContentOk() throws Exception { WebClient client = new WebClient(); String result = client.getContent(new URL("http://localhost")); assertEquals("It works", result); } } We use several (inner) classes ((2) and (3)) to be able to use the StubHttpURLConnection class. We start by calling setURLStreamHandlerFactory (1) with our first stub class, StubStreamHandlerFactory. In StubStreamHandlerFactory, we override the createURLStreamHandler method (2), in which we return a new instance of our second private stub class, StubHttpURLStreamHandler. In StubHttpURLStreamHandler, we override one method, openConnection, to open a connection to the given URL (3). You could also use anonymous inner classes for conciseness, but that approach would make the code more difficult to read. Note that you have not written the StubHttpURLConnection class yet, which is the topic of the next section. 5.4.2 Creating a JDK HttpURLConnection stub The last step is to create a stub implementation of the HttpURLConnection class so you can return any value you want for the test. Listing 5.7 shows a simple implementation that returns the string "It works" as a stream to the caller. Listing 5.7 Stubbed HttpURLConnection class Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 […] import java.net.HttpURLConnection; import java.net.ProtocolException; import java.net.URL; import java.io.InputStream; import java.io.IOException; import java.io.ByteArrayInputStream; public class StubHttpURLConnection extends HttpURLConnection { private boolean isInput = true; protected StubHttpURLConnection(URL url) { super(url); } public InputStream getInputStream() throws IOException { if (!isInput) { (1) throw new ProtocolException( "Cannot read from URLConnection" + " if doInput=false (call setDoInput(true))"); } ByteArrayInputStream bais = new ByteArrayInputStream( new String("It works").getBytes()); return bais; } public void disconnect() {} public void connect() throws IOException {} public boolean usingProxy() { return false; } } HttpURLConnection is an abstract public class that does not implement an interface, so you extend it and override the methods wanted by the stub. In this stub, you provide an implementation for the getInputStream method as it is the only method used by your code under test. Should the code to test use more APIs from HttpURLConnection, you would need to stub these additional methods. This is where the code would become more complex—you would need to reproduce completely the same behavior as the real HttpURLConnection. For example, at (1), you test that if setDoInput(false) has been called in the code under test, then a call to the getInputStream method returns a ProtocolException. (This is the behavior of HttpURLConnection.) Fortunately, in most cases, you only need to stub a few methods and not the whole API. 5.4.3 Running the test Let us run the TestWebClient1 test, which uses the StubHttpURLConnection. Figure 5.5 shows the result of the execution of the test in Eclipse. Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 5.5 Result of executing TestWebClient1 (which uses the StubHttpURLConnection). As you can see, it is much easier to stub the connection than to stub the web resource. This approach does not bring the same level of testing (you are not performing integration tests), but it enables you to more easily write a focused unit test for the WebClient logic. 5.5 Summary In this chapter, we have demonstrated how using a stub has helped us unit-test code accessing a remote web server using the Java HttpURLConnection API. In particular, we have shown how to stub the remote web server by using the open source Jetty server. Jetty’s embeddable nature lets you concentrate on stubbing only the Jetty HTTP request handler, instead of having to stub the whole container. We also demonstrated a more lightweight solution by stubbing the Java HttpURLConnection class. The next chapter will demonstrate a technique called mock objects that allows fine- grained unit testing, which is completely generic, and (best of all) forces you to write good code. Although stubs are very useful in some cases, they are more a vestige of the past, when the consensus was that tests should be a separate activity and should not modify existing code. The new mock objects strategy not only allows modification of code, it favors it. Using mock objects is more than a unit-testing strategy: it is a completely new way of writing code. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 06 Mock objects This chapter covers ƒ Introducing and demonstrating mock objects ƒ Performing different refactorings ƒ Practicing on an HTTP connection sample application ƒ Introducing the EasyMock and the JMock libraries Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning. - Rich Cook Unit-testing each method in isolation from the other methods or the environment is certainly a nice goal. But how do you perform this feat? You saw in chapter 5 how the stubbing technique lets you unit-test portions of code by isolating them from the environment (for example, by stubbing a web server, the file system, a database, and so on). But what about fine-grained isolation like being able to isolate a method call to another class? Is that possible? Can you achieve this without deploying huge amounts of energy that would negate the benefits of having tests? The answer is, “Yes! It is possible.” The technique is called mock objects. Tim Mackinnon, Steve Freeman, and Philip Craig first presented the mock objects concept at XP2000. The mock-objects strategy allows you to unit-test at the finest possible level and develop method by method, while providing you with unit tests for each method. 6.1 Introducing mock objects Testing in isolation offers strong benefits, such as the ability to test code that has not yet been written (as long as you at least have an interface to work with). In addition, testing in isolation helps teams unit-test one part of the code without waiting for all the other parts. But perhaps the biggest advantage is the ability to write focused tests that test only a single method, without side effects resulting from other objects being called from the method under test. Small is beautiful. Writing small, focused tests is a tremendous help; small tests are easy to understand and do not break when other parts of the code are changed. Remember that one of the benefits of having a suite of unit tests is the courage it gives you to refactor mercilessly—the unit tests act as a safeguard against regression. If you have large tests and your refactoring introduces a bug, several tests will fail; that result will tell you that there is a bug somewhere, but you won’t know where. With fine-grained tests, potentially fewer tests will be affected, and they will provide precise messages that pinpoint the exact cause of the breakage. Mock objects (or mocks for short) are perfectly suited for testing a portion of code logic in isolation from the rest of the code. Mocks replace the objects with which your methods under test collaborate, thus offering a layer of isolation. In that sense, they are similar to stubs. However, this is where the similarity ends, because mocks do not implement any logic: They are empty shells that provide methods to let the tests control the behavior of all the business methods of the faked class. We will discuss when to use mock objects in section 6.6 at the end of this chapter, after we show them in action on some examples. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 6.2 Mock tasting: a sample example Let’s taste our first mock! Imagine a very simple use case where you want to be able to make a bank transfer from one account to another (figure 6.1 and listings 6.1–6.3). Figure 6.1 In this simple bank account example, you will use a mock object to test an account transfer method. The AccountService class offers services related to Accounts and uses the AccountManager to persist data to the database (using JDBC, for example). The service that interests us is materialized by the AccountService.transfer method, which makes the transfer. Without mocks, testing the AccountService.transfer behavior would imply setting up a database, presetting it with test data, deploying the code inside the container (J2EE application server, for example), and so forth. Although this process is required to ensure the application works end to end, it is too much work when you want to unit-test only your code logic. Listing 6.1 presents a very simple Account object with two properties: an account ID and a balance. Listing 6.1 Account.java […] public class Account { private String accountId; private long balance; public Account(String accountId, long initialBalance) { this.accountId = accountId; this.balance = initialBalance; } public void debit(long amount) { this.balance -= amount; } public void credit(long amount) { this.balance += amount; } public long getBalance() { Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 return this.balance; } } Listing 6.2 introduces the AccountManager interface, whose goal is to manage the life cycle and persistence of Account objects. (You are limited to finding accounts by ID and updating accounts.) Listing 6.2 AccountManager.java […] public interface AccountManager { Account findAccountForUser(String userId); void updateAccount(Account account); } Listing 6.3 shows the transfer method for transferring money between two accounts. It uses the AccountManager interface you previously defined to find the debit and credit accounts by ID and to update them. Listing 6.3 AccountService.java […] public class AccountService { private AccountManager accountManager; public void setAccountManager(AccountManager manager) { this.accountManager = manager; } public void transfer(String senderId, String beneficiaryId, long amount) { Account sender = this.accountManager.findAccountForUser(senderId); Account beneficiary = this.accountManager.findAccountForUser(beneficiaryId); sender.debit(amount); beneficiary.credit(amount); this.accountManager.updateAccount(sender); this.accountManager.updateAccount(beneficiary); } } You want to be able to unit-test the AccountService.transfer behavior. For that purpose, you use a mock implementation of the AccountManager interface (listing 6.4). You do this because the transfer method is using this interface, and you need to test it in isolation. Listing 6.4 MockAccountManager.java […] import java.util.HashMap; public class MockAccountManager implements AccountManager { private Map accounts = new HashMap(); public void addAccount(String userId, Account account) { (1) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 this.accounts.put(userId, account); (1) } (1) public Account findAccountForUser(String userId) { (2) return this.accounts.get(userId); (2) (2) } public void updateAccount(Account account) { (3) // do nothing (3) } (3) } The addAccount method uses an instance variable to hold the values to return (1). Because you have several account objects that you want to be able to return, you store the Account objects to return in a HashMap. This makes the mock generic and able to support different test cases: One test could set up the mock with one account, another test could set it up with two accounts or more, and so forth. In (2) we implement a method to retrieve the account from the accounts map – we can only retrieve accounts that have been added before that. The updateAccount method updates an account but does not return any value (3). Thus you simply do nothing. When it is called by the transfer method, it will do nothing, as if the account had been correctly updated. JUnit best practices: don’t write business logic in mock objects The single most important point to consider when writing a mock is that it should not have any business logic. It must be a dumb object that only does what the test tells it to do. In other words, it is purely driven by the tests. This characteristic is exactly the opposite of stubs, which contain all the logic (see chapter 5). There are two nice corollaries. First, mock objects can be easily generated, as you will see in following chapters. Second, because mock objects are empty shells, they are too simple to break and do not need testing themselves. You are now ready to write a unit test for AccountService.transfer. Listing 6.5 shows a typical test using a mock. Listing 6.5 Testing transfer with MockAccountManager […] public class TestAccountService { @Test public void testTransferOk() { MockAccountManager mockAccountManager = new MockAccountManager(); (1) Account senderAccount = new Account("1", 200); | Account beneficiaryAccount = new Account("2", 100); | mockAccountManager.addAccount("1", senderAccount); | mockAccountManager.addAccount("2", beneficiaryAccount); | Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 AccountService accountService = new AccountService(); | accountService.setAccountManager(mockAccountManager); (1) accountService.transfer("1", "2", 50); (2) assertEquals(150, senderAccount.getBalance()); (3) assertEquals(150, beneficiaryAccount.getBalance()); (3) } } As usual, a test has three steps: the test setup (1), the test execution (2), and the verification of the result (3). During the test setup, you create the MockAccountManager object and define what it should return when called for the two accounts you manipulate (the sender and beneficiary accounts). You have succeeded in testing the AccountService code in isolation of the other domain object, AccountManager, which in this case did not exist, but which in real life could have been implemented using JDBC. JUnit best practices: only test what can possibly break You may have noticed that you did not mock the Account class. The reason is that this data-access object class does not need to be mocked—it does not depend on the environment, and it’s very simple. Your other tests use the Account object, so they test it indirectly. If it failed to operate correctly, the tests that rely on Account would fail and alert you to the problem. At this point in the chapter, you should have a reasonably good understanding of what a mock is. In the next section, we will show you that writing unit tests with mocks leads to refactoring your code under test—and that this process is a good thing! 6.3 Using Mock objects as a refactoring technique Some people used to say that unit tests should be totally transparent to your code under test, and that you should not change runtime code in order to simplify testing. This is wrong! Unit tests are first-class users of the runtime code and deserve the same consideration as any other user. If your code is too inflexible for the tests to use, then you should correct the code. For example, what do you think of the following piece of code? […] import java.util.PropertyResourceBundle; import java.util.ResourceBundle; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; […] public class countManager implements AccountManager { DefaultAc private static final Log LOGGER = (1) LogFactory.getLog(AccountManager.class); (1) public Account findAccountForUser(String userId) { LOGGER.debug("Getting account for user [" + userId + "]"); Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 ResourceBundle bundle = (2) PropertyResourceBundle.getBundle("technical"); (2) String sql = bundle.getString("FIND_ACCOUNT_FOR_USER"); // Some code logic to load a user account using JDBC […] } […] } (1) You get a Log object using a LogFactory that creates it. (2) Use a PropertyResourceBundle to retrieve an SQL command. Does the code look fine to you? We can see two issues, both of which relate to code flexibility and the ability to resist change. The first problem is that it is not possible to decide to use a different Log object, as it is created inside the class. For testing, for example, you probably want to use a Log that does nothing, but you can’t. As a general rule, a class like this should be able to use whatever Log it is given. The goal of this class is not to create loggers but to perform some JDBC logic. The same remark applies to the use of PropertyResourceBundle. It may sound OK right now, but what happens if you decide to use XML to store the configuration? Again, it should not be the goal of this class to decide what implementation to use. An effective design strategy is to pass to an object any other object that is outside its immediate business logic. The choice of peripheral objects can be controlled by someone higher in the calling chain. Ultimately, as you move up in the calling layers, the decision to use a given logger or configuration should be pushed to the top level. This strategy provides the best possible code flexibility and ability to cope with changes. And, as we all know, change is the only constant. 6.3.1 Easy refactoring Refactoring all code so that domain objects are passed around can be time-consuming. You may not be ready to refactor the whole application just to be able to write a unit test. Fortunately, there is an easy refactoring technique that lets you keep the same interface for your code but allows it to be passed domain objects that it should not create. As a proof, let’s see how the refactored DefaultAccountManager class could look (modifications are shown in bold): public class DefaultAccountManager implements AccountManager { private Log logger; (1) private Configuration configuration; (1) public DefaultAccountManager() { this(LogFactory.getLog(DefaultAccountManager.class), new DefaultConfiguration("technical")); } public DefaultAccountManager(Log logger, Configuration configuration) { this.logger = logger; Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 this.configuration = configuration; } public Account findAccountForUser(String userId) { this.logger.debug("Getting account for user [" + userId + "]"); this.configuration.getSQL("FIND_ACCOUNT_FOR_USER"); // Some code logic to load a user account using JDBC [...] } [...] } Notice that at (1), you swap the PropertyResourceBundle class from the previous listing in favor of a new Configuration interface. This makes the code more flexible because it introduces an interface (which will be easy to mock), and the implementation of the Configuration interface can be anything you want (including using resource bundles). The design is better now because you can use and reuse the DefaultAccountManager class with any implementation of the Log and Configuration interfaces (if you use the constructor that takes two parameters). The class can be controlled from the outside (by its caller). Meanwhile, you have not broken the existing interface, because you have only added a new constructor. You kept the original default constructor that still initializes the logger and configuration field members with default implementations. With this refactoring, you have provided a trapdoor for controlling the domain objects from your tests. You retain backward compatibility and pave an easy refactoring path for the future. Calling classes can start using the new constructor at their own pace. Should you worry about introducing trapdoors to make your code easier to test? Here’s how Extreme Programming guru Ron Jeffries explains it: My car has a diagnostic port and an oil dipstick. There is an inspection port on the side of my furnace and on the front of my oven. My pen cartridges are transparent so I can see if there is ink left. And if I find it useful to add a method to a class to enable me to test it, I do so. It happens once in a while, for example in classes with easy interfaces and complex inner function (probably starting to want an Extract Class). I just give the class what I understand of what it wants, and keep an eye on it to see what it wants next.1 Design patterns in action: Inversion of Control (IOC) Applying the IOC pattern to a class means removing the creation of all object instances for which this class is not directly responsible and passing any needed instances instead. 1 Ron Jeffries, on the TestDrivenDevelopment mailing list: http://groups.yahoo.com/group/testdrivendevelopment/message/3914 Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The instances may be passed using a specific constructor, using a setter, or as parameters of the methods needing them. It becomes the responsibility of the calling code to correctly set these domain objects on the called class2. IOC makes unit testing a breeze. To prove the point, let’s see how easily you can now write a test for the findAccountByUser method: public void testFindAccountByUser() { MockLog logger = new MockLog(); (1) MockConfiguration configuration = new MockConfiguration(); (2) configuration.setSQL("SELECT * [...]"); (2) DefaultAccountManager am = new DefaultAccountManager(logger, (3) configuration);(3) Account account = am.findAccountForUser("1234"); // Perform asserts here [...] } (1) Use a mock logger that implements the Log interface but does nothing. (2) Create a MockConfiguration instance and set it up to return a given SQL query when Configuration.getSQL is called. (3) Create the instance of DefaultAccountManager that you will test, passing to it the Log and Configuration instances. You have been able to completely control your logging and configuration behavior from outside the code to test, in the test code. As a result, your code is more flexible and allows for any logging and configuration implementation to be used. You will see more of these code refactorings in this chapter and later ones. One last point to note is that if you write your test first, you will automatically design your code to be flexible. Flexibility is a key point when writing a unit test. If you test first, you will not incur the cost of refactoring your code for flexibility later. 6.4 Practicing on an HTTP connection sample To see how mock objects work in a practical example, let’s use a simple application that opens an HTTP connection to a remote server and reads the content of a page. In chapter 5 we tested that application using stubs. Let’s now unit-test it using a mock-object approach to simulate the HTTP connection. In addition, you’ll learn how to write mocks for classes that do not have a Java interface (namely, the HttpURLConnection class). We will show a full scenario in which you start with an initial testing implementation, improve the implementation as you go, and modify 2 See the Jakarta Avalon framework for a component framework implementing the IOC pattern (http://avalon.apache.org) Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 the original code to make it more flexible. We will also show how to test for error conditions using mocks. As you dive in, you will keep improving both the test code and the sample application, exactly as you might if you were writing the unit tests for the same application. In the process, you will learn how to reach a simple and elegant testing solution while making your application code more flexible and capable of handling change. Figure 6.2 The sample HTTP application before introducing the test Figure 6.2 introduces the sample HTTP application, which consists of a simple WebClient.getContent method performing an HTTP connection to a web resource executing on a web server. You want to be able to unit-test the getContent method in isolation from the web resource. 6.4.1 Defining the mock objects Figure 6.3 illustrates the definition of a mock object. The MockURL class stands in for the real URL class, and all calls to the URL class in getContent are directed to the MockURL class. As you can see, the test is the controller: It creates and configures the behavior the mock must have for this test, it (somehow) replaces the real URL class with the MockURL class, and it runs the test. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 6.3 The steps involved in a test using mock objects Figure 6.3 shows an interesting aspect of the mock-objects strategy: the need to be able to swap-in the mock in the production code. The perceptive reader will have noticed that because the URL class is final, it is actually not possible to create a MockURL class that extends it. In the coming sections, we will demonstrate how this feat can be performed in a different way (by mocking at another level). In any case, when using the mock-objects strategy, swapping-in the mock instead of the real class is the hard part. This may be viewed as a negative point for mock objects, because you usually need to modify your code to provide a trapdoor. Ironically, modifying code to encourage flexibility is one of the strongest advantages of using mocks, as explained in section 6.3.1. 6.4.2 Testing a sample method The example in listing 6.6 demonstrates a code snippet that opens an HTTP connection to a given URL and reads the content found at that URL. Let’s imagine that it’s one method of a bigger application that you want to unit-test, and let’s unit-test that method. Listing 6.6 A sample method that opens an HTTP connection […] import java.net.URL; import java.net.HttpURLConnection; import java.io.InputStream; import java.io.IOException; public class WebClient { Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 public String getContent(URL url) { StringBuffer content = new StringBuffer(); try { HttpURLConnection connection = (1) (HttpURLConnection) url.openConnection(); (1) connection.setDoInput(true); Inpu int count; (1) tStream is = connection.getInputStream(); (1) while (-1 != (count = is.read())) { (2) content.append( new String( Character.toChars( count ) ) ); (2) } (2) } catch tion e) { (IOExcep return null; } return content.toString(); } } (1) Open an HTTP connection using the HttpURLConnection class. (2) Read the content until there is nothing more to read. If an error occurs, you return null. Admittedly, this is not the best possible error-handling solution, but it is good enough for the moment. (And your tests will give you the courage to refactor later.) 6.4.3 Try #1: easy method refactoring technique The idea is to be able to test the getContent method independently of a real HTTP connection to a web server. If you map the knowledge you acquired in section 6.2, it means writing a mock URL in which the url.openConnection method returns a mock HttpURLConnection. The MockHttpURLConnection class would provide an implementation that lets the test decides what the getInputStream method returns. Ideally, you would be able to write the following test: @Test public void testGetContentOk() throws Exception { MockHttpURLConnection mockConnection = new MockHttpURLConnection(); (1) mockConnection.setupGetInputStream( (1) new ByteArrayInputStream("It works".getBytes())); (1) MockURL mockURL = new MockURL(); (2) mockURL.setupOpenConnection(mockConnection); (2) WebClient client = new WebClient(); String result = client.getContent(mockURL); (3) assertEquals("It works", result); (4) } (1) Create a mock HttpURLConnection that you set up to return a stream containing It works when the getInputStream method is called on it. (2) Do the same for creating a mock URL class and set it up to return the MockURLConnection when url.openConnection is called. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 (3) Call the getContent method to test, passing to it your MockURL instance. (4) Assert the result. Unfortunately, this approach does not work! The JDK URL class is a final class, and no URL interface is available. So much for extensibility… You need to find another solution and, potentially, another object to mock. One solution is to stub the URLStreamHandlerFactory class. We explored this solution in chapter 5, so let’s find a technique that uses mock objects: refactoring the getContent method. If you think about it, this method does two things: It gets an HttpURLConnection object and then reads the content from it. Refactoring leads to the class shown in listing 6.7 (changes from listing 6.6 are in bold). We have extracted the part that retrieved the HttpURLConnection object. Listing 6.7 Extracting retrieval of the connection object from getContent public class WebClient { public String getContent(URL url) { StringBuffer content = new StringBuffer(); try { HttpURLConnection connection = createHttpURLConnection(url); (1) InputStream is = connection.getInputStream(); int count; while (-1 != (count = is.read())) { content.append( new String( Character.toChars( count ) ) ); } } catch (IOException e) { return null; } return content.toString(); } protected HttpURLConnection createHttpURLConnection(URL url) (1) throws IOException { (1) return (HttpURLConnection) url.openConnection(); } } (1) Refactoring. You now call the createHttpURLConnection method to create the HTTP connection. How does this solution let you test getContent more effectively? You can now apply a useful trick, which consists of writing a test helper class that extends the WebClient class and overrides its createHttpURLConnection method, as follows: private class TestableWebClient extends WebClient { private URLConnection connection; Http public void setHttpURLConnection(HttpURLConnection connection) { this.connection = connection; } public HttpURLConnection createHttpURLConnection(URL url) Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 throws IOException { return this.connection; } } In the test, you can call the setHttpURLConnection method, passing it the mock HttpURLConnection object. The test now becomes the following (differences are shown in bold): @Test public void testGetContentOk() throws Exception { MockHttpURLConnection mockConnection = new MockHttpURLConnection(); mockConnection.setupGetInputStream( new ByteArrayInputStream("It works".getBytes())); TestableWebClient client = new TestableWebClient(); (1) client.setHttpURLConnection(mockConnection); (1) String result = client.getContent(new URL("http://localhost")); (2) assertEquals("It works", result); } (1) Configure TestableWebClient so that the createHttpURLConnection method returns a mock object. (2) The getContent method accepts a URL as parameter, so you need to pass one. The value is not important, because it will not be used; it will be bypassed by the MockHttpURLConnection object. This is a common refactoring approach called Method Factory refactoring, which is especially useful when the class to mock has no interface. The strategy is to extend that class, add some setter methods to control it, and override some of its getter methods to return what you want for the test. In the case at hand, this approach is OK, but it isn’t perfect. It’s a bit like the Heisenberg Uncertainty Principle: The act of subclassing the class under test changes its behavior, so when you test the subclass, what are you truly testing? This technique is useful as a means of opening up an object to be more testable, but stopping here means testing something that is similar to (but not exactly) the class you want to test. It isn’t as if you’re writing tests for a third-party library and can’t change the code— you have complete control over the code to test. You can enhance it, and make it more test- friendly in the process. 6.4.4 Try #2: refactoring by using a class factory Let’s apply the Inversion of Control (IOC) pattern, which says that any resource you use needs to be passed to the getContent method or WebClient class. The only resource you use is the HttpURLConnection object. You could change the WebClient.getContent signature to public String getContent(URL url, HttpURLConnection connection) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 This means you are pushing the creation of the HttpURLConnection object to the caller of WebClient. However, the URL is retrieved from the HttpURLConnection class, and the signature does not look very nice. Fortunately, there is a better solution that involves creating a ConnectionFactory interface, as shown in listings 6.8 and 6.9. The role of classes implementing the ConnectionFactory interface is to return an InputStream from a connection, whatever the connection might be (HTTP, TCP/IP, and so on). This refactoring technique is sometimes called a Class Factory refactoring3. Listing 6.8 ConnectionFactory.java […] i mport java.io.InputStream; public interface ConnectionFactory { InputStream getData() throws Exception; } The WebClient code then becomes as shown in listing 6.9. (Changes from the initial implementation in listing 6.6 are shown in bold.) Listing 6.9 Refactored WebClient using ConnectionFactory […] import java.io.InputStream; public class WebClient { public String getContent(ConnectionFactory connectionFactory) { StringBuffer content = new StringBuffer(); try { InputStream is = connectionFactory.getData(); int count; while (-1 != (count = is.read())) { content.append( new String( Character.toChars( count ) ) ); } } catch (Exception e) { return null; } return content.toString(); } } This solution is better because you have made the retrieval of the data content independent of the way you get the connection. The first implementation worked only with URLs using the HTTP protocol. The new implementation can work with any standard protocol (file://, http://, ftp://, jar://, and so forth), or even your own custom protocol. For 3 J. B. Rainsberger calls it Replace Subclasses with Collaborators: http://www.diasparsoftware.com/template.php?content=replaceSubclassWithCollaborator Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 example, listing 6.10 shows the ConnectionFactory implementation for the HTTP protocol. Listing 6.10 HttpURLConnectionFactory.java […] import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; public class HttpURLConnectionFactory implements ConnectionFactory { private URL url; public HttpURLConnectionFactory(URL url) { this.url = url; } public InputStream getData() throws Exception { HttpURLConnection connection = (Htt return connection.getInputStream(); pURLConnection) this.url.openConnection(); } } Now you can easily test the getContent method by writing a mock for ConnectionFactory (see listing 6.11). Listing 6.11 MockConnectionFactory.java […] import java.io.InputStream; public class MockConnectionFactory implements ConnectionFactory { private InputStream inputStream; public void setData(InputStream stream) { this.inputStream = stream; } public InputStream getData() throws Exception { return this.inputStream; } } As usual, the mock does not contain any logic and is completely controllable from the outside (by calling the setData method). You can now easily rewrite the test to use MockConnectionFactory as demonstrated in listing 6.12. Listing 6.12 Refactored WebClient test using MockConnectionFactory […] import java.io.ByteArrayInputStream; public class TestWebClient { @Test public void testGetContentOk() throws Exception { Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 MockConnectionFactory mockConnectionFactory = new MockConnectionFactory(); mockConnectionFactory.setData( new ByteArrayInputStream("It works".getBytes())); WebClient client = new WebClient(); String result = client.getContent(mockConnectionFactory); assertEquals("It works", result); } } You have achieved your initial goal: to unit-test the code logic of the WebClient.getContent method. In the process you had to refactor it for the test, which led to a more extensible implementation that is better able to cope with change. 6.5 Using mocks as Trojan horses Mock objects are Trojan horses, but they are not malicious. Mocks replace real objects from the inside, without the calling classes being aware of it. Mocks have access to internal information about the class, making them quite powerful. In the examples so far, you have only used them to emulate real behaviors, but you haven’t mined all the information they can provide. It is possible to use mocks as probes by letting them monitor the method calls the object under test makes. Let’s take the HTTP connection example. One of the interesting calls you could monitor is the close method on the InputStream. You have not been using a mock object for InputStream so far, but you can easily create one and provide a verify method to ensure that close has been called. Then, you can call the verify method at the end of the test to verify that all methods that should have been called, were called (see listing 6.13). You may also want to verify that close has been called exactly once, and raise an exception if it was called more than once or not at all. These kinds of verifications are often called expectations. DEFINITION expectation — When we’re talking about mock objects, an expectation is a feature built into the mock that verifies whether the external class calling this mock has the correct behavior. For example, a database connection mock could verify that the close method on the connection is called exactly once during any test that involves code using this mock. To demonstrate the expectations, take a look at the following listing. Listing 6.13 Mock InputStream with an expectation on close […] import java.io.IOException; import java.io.InputStream; public class MockInputStream extends InputStream { private String buffer; private int position = 0; Licensed to Alison Tyler Download at Boykma.Com 18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 private int closeCount = 0; public void setBuffer(String buffer) { (1) this.buffer = buffer; (1) } (1) public int read() throws IOException { if (position == this.buffer.length()) { return -1; } return this.buffer.charAt(this.position++); } public void close() throws IOException { closeCount++; (2) super.close(); } public void verify() throws java.lang.AssertionError { (3) if (closeCount != 1) { (3) throw new AssertionError ("close() should " (3) + "have been called once and once only"); (3) } (3) } } (1) Tell the mock what the read method should return. (2) Count the number of times close is called. (3) Verify that the expectations are met. In the case of the MockInputStream class, the expectation for close is simple: You always want it to be called once. However, most of the time, the expectation for closeCount depends on the code under test. A mock usually has a method like setExpectedCloseCalls so that the test can tell the mock what to expect. Let’s modify the TestWebClient.testGetContentOk test method to use the new MockInputStream: […] public class TestWebClient { @Test public void testGetContentOk() throws Exception { MockConn new MockConnectionFactory(); ectionFactory mockConnectionFactory = MockInputStream mockStream = new MockInputStream(); mockStream.setBuffer("It works"); mockConnectionFactory.setData(mockStream); WebClient client = new WebClient(); String result = client.getContent(mockConnectionFactory); assertEquals("It works", result); mockStream.verify(); } } Instead of using a real ByteArrayInputStream as in previous tests, you now use the Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 MockInputStream. Note that you call the verify method of MockInputStream at the end of the test to ensure that all expectations are met. The result of running the test is shown in figure 6.4. The test fails with the message close() should have been called once and once only. Why? Because you have not closed the input stream in the WebClient.getContent method. The same error would be raised if you were closing it twice or more, because the test verifies that it is called once and only once. Figure 6.4 Running TestWebClient with the new close expectation Let’s correct the code under test (see listing 6.14). You now get a nice green bar (figure 6.5). Figure 6.5 Working WebClient that closes the input stream Listing 6.14 WebClient closing the stream public class WebClient { public String getContent(ConnectionFactory connectionFactory) throws IOException { String result; StringBuffer content = new StringBuffer(); InputStream is = null; try { is = connectionFactory.getData(); Licensed to Alison Tyler Download at Boykma.Com 20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 int count; while (-1 != (count = is.read())) { content.append( new String( Character.toChars( count ) ) ); } result = content.toString(); } catch (Exception e result = null; ) { } // Close the stream if (is != null) { (1) try { | is.close(); | } | catch (IOException e) { | result = null; | } | } (1) return result; } } (1) Close the stream and return null if an error occurs when you’re closing it. There are other handy uses for expectations. For example, if you have a component manager calling different methods of your component life cycle, you might expect them to be called in a given order. Or, you might expect a given value to be passed as a parameter to the mock. The general idea is that, aside from behaving the way you want during a test, your mock can also provide useful feedback on its usage. The next section demonstrate the usage of some of the most popular open-source mocking frameworks – they are powerful enough for our needs, and we don’t need implement our mocks from the beginning. 6.6 Introducing the frameworks So far we have been implementing the mock objects we need from scratch. As you see it’s not a tedious task, but rather a very recurring one. You might guess that we don’t need to reinvent the wheel every time we need a mock. And you are right – there are a lot of good projects already written that are out there to help us facilitate the usage of mocks in our projects. In this section we will take a closer look on two of the most widely-used mock frameworks – the EasyMock and the JMock. We will try to rework the example HTTP connection application, so that we can demonstrate how to use the two frameworks. 6.6.1. Reworking some of the examples using EasyMock. Easymock (http://easymock.org/) is an open-source framework that provides useful classes for mocking objects. To use the framework you need to download the zip archive from the Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 web-site of the project, unpack it somewhere, and include the contained easymock.jar in your classpath. To show you how easy it is to construct mock objects in your test-cases using EasyMock, let’s try and revise some of the mocks we constructed in the previous sections. We will start with a very simple one – reworking the AccountService test from listing 6.5 Listing 6.15 Reworking the TestAccountService test using EasyMock [...] import static org.easymock.EasyMock.createMock; (1) import static org.easymock.EasyMock.replay; | import static org.easymock.EasyMock.expect; | import static org.easymock.EasyMock.verify; (1) public class TestAccountServiceEasyMock { private AccountManager mockAccountManager; (2) @Before public void setUp() { mockAccountManager = createMock( “mockAccountManager”, AccountManager.class ); (3) } @Test public void testTransferOk() { Account senderAccount = new Account( "1", 200 ); (4) Account beneficiaryAccount = new Account( "2", 100 ); (4) // Start defining the expectations mockAccountManager.updateAccount( senderAccount ); (5) mockAccountManager.updateAccount( beneficiaryAccount ); (5) expect( mockAccountManager.findAccountForUser( "1" ) ) .andReturn( senderAccount ); (6) expect( mockAccountManager.findAccountForUser( "2" ) ) .andReturn( beneficiaryAccount ); (6) // we’re done defining the expectations replay( mockAccountManager ); (7) AccountService accountService = new AccountService(); accountService.setAccountManager( mockAccountManager ); accountService.transfer( "1", "2", 50 ); (8) assertEquals( 150, senderAccount.getBalance() ); (9) assertEquals( 150, beneficiaryAccount.getBalance() ); (9) } @After public void tearDown() { Licensed to Alison Tyler Download at Boykma.Com 22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 verify( mockAccountManager ); (10) } } As you see the given listing is pretty much the same size, as listing 6.5, but we spare the writing of any additional mock classes. We start the listing defining the imports from the EasyMock library that we need (1). As you see EasyMock relies heavily on static-import feature of Java5+. In (2) we declare the object that we would like to mock. Notice that our AccountManager is an interface; the reason behind this is simple – the core EasyMock framework can mock only interface objects. In (3) we call the createMock method to create a mock of the class that we want. In (4), just like in listing 6.5, we create two account objects that we are going to use in our tests. After that we start declaring our expectations. With EasyMock you declare the expectations in two ways. When the method return type is void, you simply call it on the mock-object (as in (5)), or when the method returns any kind of object, then you need to use the expect-andReturn methods from the EasyMock API (6). Once you are finished defining the expectations, you need to call the replay method to announce it (7). In (8) we call the transfer method to transfer some money, between the two accounts, and in (9) we assert the expected result. The @After method which gets executed after every @Test method, holds the verification of the expectations. With EasyMock you can call the verify method with any mock object (10), to verify that the method-call expectations we declared were triggered. That was pretty easy, wasn’t it? So how about moving a step forward and revising a bit more complicated example. No problem, the next listing shows the reworked WebClient test from listing 6.12. What we would like is to test the getContent method of the WebClient. For this purpose we need to mock all the dependencies to that method. In this example we have two dependencies – one is the ConnectionFactory and one is the InputStream. It looks like there is a problem because EasyMock can mock only interfaces and the InputStream is a class. To be able to mock the InputStream class we are going to use the classextensions extension of EasyMock. The classextensions is an extension project of EasyMock that lets you generate mock objects4 for classes and interfaces. You can download it separately from the EasyMock website. Listing 6.16 Reworking the WebClient test using EasyMock [...] import static org.easymock.classextension.EasyMock.createMock; (1) import static org.easymock.classextension.EasyMock.replay; | import static org.easymock.classextension.EasyMock.verify; (1) public class TestWebClientEasyMock 4 final and private methods cannot be mocked. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 { private ConnectionFactory factory; (2) private InputStream stream; (2) @Before public void setUp() { factory = createMock( “factory”, ConnectionFactory.class ); (3) stream = createMock( “stream”, InputStream.class ); (3) } @Test public void testGetContentOk() throws Exception { expect( factory.getData() ).andReturn( stream ); expect( stream.read() ).andReturn( new Integer( (byte) 'W' ) ); (4) expect( stream.read() ).andReturn( new Integer( (byte) 'o' ) ); | expect( stream.read() ).andReturn( new Integer( (byte) 'r' ) ); | expect( stream.read() ).andReturn( new Integer( (byte) 'k' ) ); | expect( stream.read() ).andReturn( new Integer( (byte) 's' ) ); | expect( stream.read() ).andReturn( new Integer( (byte) '!' ) ); | expect( stream.read() ).andReturn( -1 ); (4) stream.close(); (5) replay( factory ); (6) replay( stream ); (6) WebClient2 client = new WebClient2(); String result = client.getContent( factory ); (7) assertEquals( "Works!", result ); (8) } [...] @Test public void testGetContentCannotCloseInputStream() throws Exception { expect( factory.getData() ).andReturn( stream ); expect( stream.read() ).andReturn( -1 ); stream.close(); (9) expectLastCall().andThrow(new IOException("cannot close")); (10) replay( factory ); replay( stream ); WebClient2 client = new WebClient2(); String result = client.getContent( factory ); assertNull( result ); } @After public void tearDown() { verify( factory ); verify( stream ); } } Licensed to Alison Tyler Download at Boykma.Com 24 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 We start the listing by importing the objects that we need (1). Notice that since we use the classextensions extension of EasyMock, we now need to import the org.easymock.classextension.EasyMock object instead of org.easymock.EasyMock. That’s it! Now you are ready to create mock objects of classes and interfaces using the statically imported methods of the classextensions. In (2), just as the previous listings, we declare the objects that we want to mock, and in (3) we call the createMock method to initialize them. JUnit best practices: EasyMock object creation Here is a nice-to-know tip on the createMock method. If you check the API of EasyMock you will see that the createMock method comes with numerous of signatures. The signature that we use is createMock(String name, Class claz); but there’s also createMock(Class claz); So which one should we use? The second one is better. If you use the first one and your expectations don’t get met, then you get an error message like the following: java.lang.AssertionError: Expectation failure on verify: read(): expected: 7, actual: 0 As you see this message is not as descriptive as we want it to be. If you use the first signature, instead, and we map the class to a given name, we would get something like the following: java.lang.AssertionError: Expectation failure on verify: name.read(): expected: 7, actual: 0 Back on listing 6.16 - in (4) we define the expectation of the stream when the read method is invoked (notice that to stop reading from the stream, the last thing to return is a -1), and in (5) we expect the close method to be called on the stream. Now we need to denote that we have done declaring our expectations – we do this by calling the replay method (6). The rest is just invoking the method under test (7), and assert the expected result (8). We also add another test to simulate a condition when we cannot close the InputStream. We define an expectation where we expect the close method of the stream Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 25 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 to be invoked (9), and right on the next line we declare that an IOException should be raised if this call occurs (10). As the name of the framework suggests, using EasyMock is very easy, and you should use it whenever is possible. But to make you aware of the whole mocking picture we would like to introduce another framework, so you have a better taste on what the mocking is. 6.6.2 Reworking some of the examples using JMock So far we saw how to implement our own mock-objects and how to use the EasyMock framework. In this section we introduce the JMock framework (http://jmock.org/), so that we can have the full view on the different mocking techniques. Just like in the previous section we will start with a very simple example – reworking listing 6.5 by means of JMock. Listing 6.17 Reworking the TestAccountService test using JMock [...] import org.jmock.Expectations; (1) import org.jmock.Mockery; | import org.jmock.integration.junit4.JMock; | import org.jmock.integration.junit4.JUnit4Mockery; (1) @RunWith( JMock.class ) (2) public class TestAccountServiceJMock { private Mockery context = new JUnit4Mockery(); (3) private AccountManager mockAccountManager; (4) @Before public void setUp() { mockAccountManager = context.mock( AccountManager.class ); (5) } @Test public void testTransferOk() { final Account senderAccount = new Account( "1", 200 ); (6) final Account beneficiaryAccount = new Account( "2", 100 ); (6) context.checking( new Expectations() (7) { { oneOf( mockAccountManager ).findAccountForUser( "1" ); (8) will( returnValue( senderAccount ) ); (8) oneOf( mockAccountManager ).findAccountForUser( "2" ); will( returnValue( beneficiaryAccount ) ); oneOf( mockAccountManager ).updateAccount( senderAccount ); oneOf( mockAccountManager ).updateAccount( beneficiaryAccount ); } } ); Licensed to Alison Tyler Download at Boykma.Com 26 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 AccountService accountService = new AccountService(); accountService.setAccountManager( mockAccountManager ); accountService.transfer( "1", "2", 50 ); (9) assertEquals( 150, senderAccount.getBalance() ); (10) assertEquals( 150, beneficiaryAccount.getBalance() ); } } As always we start the listing by importing all the necessary objects we need (1). As you can see unlike EasyMock, the JMock framework does not rely on any static import features. Luckily enough the JMock framework provides a JUnit4 runner5, that will facilitate us a lot. In (2) we instruct JUnit to use the JMock runner that comes with the framework. In (3) we declare the context Mockery object that will serve us to create mocks and to define expectations. In (4) we declare the AccountManager that we would like to mock. Just like EasyMock, the core JMock framework provides mocking only of interfaces. In the @Before method, that gets executed before each of the @Test methods, we create the mock by means of the context object (5). Just like in any of the previous listings, we declare two accounts that we are going to use to transfer money inbetween (6). Notice that this time the accounts are declared final. This is because we will use them in an inner class defined in a different method. In (7) we start declaring the expectations, by constructing a new Expectations object. In (8) we declare the first expectation, each expectation having the form: invocation-count (mock-object).method(argument-constraints); inSequence(sequence-name); when(state-machine.is(state-name)); will(action); then(state-machine.is(new-state-name)); All the clauses are optional, except for the bold ones – invocation-count and mock-object. We need to specify how much invocations will occur and on which object. After that, in case the method returns some object we can declare the return object by using the will(returnValue()) construction. In (9) we start the transfer from the one account to the other, and after that we assert the expected results (10). It’s just as simple as that! But wait, what happened with the verification of the invocation count? In all of the previous examples we needed to verify that the invocations of the expectations really happened the expected number of times. Well, with JMock you don’t have to do that – the JMock JUnit runner takes care of this, and in case any of the expected calls were not made the test will fail. Following the pattern from the previous section about EasyMock, let’s try and rework listing 6.12, showing the WebClient test, this time using JMock. 5 How to implement a custom JUnit runner you can see in Appendix B of the book. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Listing 6.18 Reworking the TestWebClient test using JMock [...] @RunWith( JMock.class ) (1) public class TestWebClientJMock { private Mockery context = new JUnit4Mockery() (2) { | { | setImposteriser( ClassImposteriser.INSTANCE ); | } | }; (2) @Test public void testGetContentOk() throws Exception { final ConnectionFactory factory = context.mock( ConnectionFactory.class ); (3) final InputStream mockStream = context.mock( InputStream.class ); (3) context.checking( new Expectations() { { oneOf( factory ).getData(); (4) will( returnValue( mockStream ) ); (4) atLeast( 1 ).of( mockStream ).read(); (5) will( onConsecutiveCalls( | returnValue( new Integer( (byte) 'W' ) ), | returnValue( new Integer( (byte) 'o' ) ), | returnValue( new Integer( (byte) 'r' ) ), | returnValue( new Integer( (byte) 'k' ) ), | returnValue( new Integer( (byte) 's' ) ), | returnValue( new Integer( (byte) '!' ) ), | returnValue( -1 ) ) ); (5) oneOf( mockStream ).close(); } } ); WebClient2 client = new WebClient2(); String result = client.getContent( factory ); (6) assertEquals( "Works!", result ); (7) } @Test public void testGetContentCannotCloseInputStream() throws Exception { final ConnectionFactory factory = context.mock( ConnectionFactory.class ); final InputStream mockStream = context.mock( InputStream.class ); Licensed to Alison Tyler Download at Boykma.Com 28 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 context.checking( new Expectations() { { oneOf( factory ).getData(); will( returnValue( mockStream ) ); oneOf( mockStream ).read(); will( returnValue( -1 ) ); oneOf( mockStream ).close(); (8) will( throwException( new IOException( "cannot close" ) ) ); (9) } } ); WebClient2 client = new WebClient2(); String result = client.getContent( factory ); assertNull( result ); } } Once again we start the test-case by instructing JUnit to use the JMock test-runner (1). This will save us the explicit verification of the expectations. To tell JMock to create mock- objects not only for interfaces, but also for classes, we need to set the imposteriser property of the context (2). That’s all – now we can continue creating mocks the normal way. In (3) we declare and initialize the two objects we would like to create mocks of. In (4) we start declaration of the expectations. Notice the fine way we declare the consecutive execution of the read() method of the stream (5), and also the returned values. In (6) we call the method under test and in (7) we assert the expected result. For a full view of how to use the JMock mocking library, we also provide another @Test method, which tests our WebClient under exceptional conditions. In (8) we declare the expectation of the close() method being triggered and in (9) we instruct JMock to raise an IOException when this trigger happens. As you can see the JMock library is as easy to use as the EasyMock one. Whichever you prefer to use is up to you, as long as you remember that what increases your software quality is not the framework you use, but rather how much you use it. 6.7 Summary This chapter has described a technique called mock objects that lets you unit-test code in isolation from other domain objects and from the environment. When it comes to writing fine-grained unit tests, one of the main obstacles is to abstract yourself from the executing environment. We have often heard the following remark: “I haven’t tested this method because it’s too difficult to simulate a real environment.” Well, not any longer! In most cases, writing mock-object tests has a nice side effect: It forces you to rewrite some of the code under test. In practice, code is often not written well. You hard-code unnecessary couplings between the classes and the environment. It’s easy to write code that is hard to reuse in a different context, and a little nudge can have a big effect on other Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 29 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 classes in the system (similar to the domino effect). With mock objects, you must think differently about the code and apply better design patterns, like Interfaces and Inversion of Control (IOC). Mock objects should be viewed not only as a unit-testing technique but also as a design technique. A new rising star among methodologies called Test-Driven Development advocates writing tests before writing code. With TDD, you don’t have to refactor your code to enable unit testing: The code is already under test! (For a full treatment of the TDD approach, see Kent Beck’s book Test Driven Development6. For a brief introduction, see chapter 4.) Although writing mock objects is easy, it can become tiresome when you need to mock hundreds of objects. In the following chapters we will present several open source frameworks that automatically generate ready-to-use mocks for your classes, making it a pleasure to use the mock-objects strategy. 6 Kent Beck, Test Driven Development: By Example (Boston: Addison-Wesley, 2003). Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 07 In-container testing This chapter covers ƒ The drawbacks of mock objects ƒ In-container testing ƒ Comparing stubs, mock objects, and in-container testing Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The secret of success is sincerity. Once you can fake that you’ve got it made. —Jean Giraudoux This chapter examines one approach to unit-testing components in an application container: in-container unit testing, or integration unit testing. We will discuss in-container testing pros and cons and show what can be achieved using the mock objects approach introduced in chapter 6, where mock objects fall short, and how in-container testing enables you to write integration unit tests. Finally, we will compare the stubs, mock objects, and in- container approaches we already covered in this second part of this book. 7.1 Limitations of standard unit testing Let us start with the example servlet in listing 7.1, which implements the HttpServlet method isAuthenticated, the method we want to unit-test. Listing 7.1 Servlet implementing isAuthenticated […] import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; public class leServlet extends HttpServlet { Samp public boolean isAuthenticated(HttpServletRequest request) { HttpSession session = request.getSession(false); if o null) { (sessi n == return false; } String authenticationAttribute = (String) session.getAttribute("authenticated"); return Boolean.valueOf(authenticationAttribute).booleanValue(); } } This servlet, while simple enough, will allow us to show the limitation of standard unit testing. In order to test the method isAuthenticated, you need a valid HttpServletRequest. Since HttpServletRequest is an interface, you cannot just call new HttpServletRequest. The HttpServletRequest life cycle and implementation is provided by the container (in this case, a servlet container.) The same is true for other server-side objects like HttpSession. JUnit alone is not enough to write a test for the isAuthenticated method and for servlets in general. DEFINITION component and container— In this chapter, a component executes in a container. A container offers services for the components it is hosting, such as life cycle, security, transaction, distribution, and so forth. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 In the case of servlets and JSPs, the container is a servlet container like Jetty or Tomcat. There are other types of containers, for example: EJB, database, and OSGi containers. As long as a container creates and manages objects at runtime, we cannot use standard JUnit techniques to test those objects. 7.2. The Mock objects solution We will look at several solutions for in-container testing. The first solution for unit testing the isAuthenticated method (listing 7.1) is to mock the HttpServletRequest class using the approach described in chapter 6. While mocking works, you need to write a lot of code to create a test. You can achieve the same result more easily using the open source EasyMock1 framework (see chapter 6) as listing 7.2 demonstrates. Listing 7.2 Testing a serlvet with EasyMock […] import javax.servlet.http.HttpServletRequest; (1) import static org.easymock.EasyMock.createStrictMock; | import static org.easymock.EasyMock.expect; | import static org.easymock.EasyMock.replay; | import static org.easymock.EasyMock.verify; | import static org.easymock.EasyMock.eq; | import static org.junit.Assert.assertFalse; | import static org.junit.Assert.assertTrue; (1) […] public class EasyMockSampleServletTest { private SampleServlet servlet; private HttpServletRequest mockHttpServletRequest; (2) private HttpSession mockHttpSession; (2) @Before public void setUp() { (3) servlet = new SampleServlet(); mockHttpServletRequest = createStrictMock(HttpServletRequest.class); (3) mockHttpSession = createStrictMock(HttpSession.class); (3) } @Test public void testIsAuthenticatedAuthenticated() { expect(mockHttpServletRequest.getSession(eq(false))) (4) .andReturn(mockHttpSession); (4) expect(mockHttpSession.getAttribute(eq("authenticated"))) (4) .andReturn("true"); (4) replay(mockHttpServletRequest); (5) replay(mockHttpSession); (5) assertTrue(servlet.isAuthenticated(mockHttpServletRequest)); (6) } 1 http://easymock.org/ Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 @Test public void testIsAuthenticatedNotAuthenticated() { expect(mockHttpSession.getAttribute(eq("authenticated"))) .andReturn("false"); replay(mockHttpSession); expect(mockHttpServletRequest.getSession(eq(false))) .andReturn(mockHttpSession); replay(mockHttpServletRequest); assertFalse(servlet.isAuthenticated(mockHttpServletRequest)); } @Test public void testIsAuthenticatedNoSession() { expect(mockHttpServletRequest.getSession(eq(false))).andReturn(null); replay(mockHttpServletRequest); replay(mockHttpSession); assertFalse(servlet.isAuthenticated(mockHttpServletRequest)); } @After public void tearDown() { (7) verify(mockHttpServletRequest); (8) verify(mockHttpSession); (8) } } We start by importing the necessary classes and methods using Java 5 static imports (1). We use the EasyMock class extensively, which has similar syntax to the JUnit Hamcrest matchers. Next, we declare instance variables for the objects (2) we want to mock, HttpServletRequest and HttpSession. The setUp method annotated with @Before (3) runs before each call to @Test methods; this is where we instantiate all of our mock objects. Next, we implement our tests following the pattern: 1. Set our expectations using the EasyMock API (4). 2. Invoke the replay method to finish declaring our expectations (5). 3. Assert test conditions on the servlet (6). Finally, the @After method (7) (executed after each @Test method) calls the EasyMock verify API (8) to check the mocked objects met all of our programmed expectations. Mocking a minimal portion of a container is a valid approach to testing components. However, mocking can be complicated and require a lot of code. As with other kinds of tests, when the servlet changes the test expectations must change to match. Next, we will look at easing this task. 7.3 In-container testing Another approach to testing the SampleServlet is to run the test cases where the HttpsServetRequest and HttpSession objects live, in the container itself. This avoids the need to mock any objects, we simply access the objects and methods we need in the real container. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 For our example, we need the HttpServletRequest and HttpSession to be real objects managed by the container. Using a mechanism to deploy and execute our tests in a container, we have in-container testing. Next we will see what options are available to implement in-container tests. 7.3.1 Implementation strategies We have two architectural choices to drive in-container tests: server-side and client side. As we stated above, we can drive the tests directly by controlling the server-side container and the unit tests. Alternatively, we can drive the tests from the client-side as shown in figure 7.1. Figure 7.1 Lifecycle of a typical in-container test Once the tests are packaged and deployed in the container and to the client, the JUnit test runner executes the test classes on the client (1). A test class opens a connection via a protocol like HTTP(S) and calls the same test case on the server-side (2). The server-side test case operates on server-side objects, which are normally available (like HttpServletRequest, HttpServletResponse, HttpSession, BundleContext, etc…), and tests our domain objects (3). The server returns the result from the tests back to the client (4), which an IDE or Ant can gather. Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 7.3.2 In-container testing frameworks As we just saw, in-container testing is applicable when code interacts with a container, and tests cannot create valid container objects (HttpServletRequest in the previous example). Our example uses a servlet container, but there are many different types of containers: servlet, database, OSGi, SIP, etc. In all of these cases, the in-container testing strategy can be applied. In the third part of the book, we will present different open source projects that use this strategy. Table 7.1 lists the type of containers and the testing frameworks we will be using in later chapters. Table 7.1 Containers and testing framworks. Container type In-container testing framework to use Detail description in chapter: HTTP container Cactus (for testing Servlets, JSPs, Tag libraries, Filters, and EJBs) Chapter 13 HTTP container JSFUnit (for testing JSF components) Chapter 14 OSGi container JUnit4OSGi (For testing OSGi modules) Chapter 15 Database Container DBUnit Chapter 16 While the open source world offers other projects, we cover the more mature projects listed in the table above. Next, we will compare stubs, mock objects, and in-container testing. 7.4 Comparing stubs, mock objects, and in-container testing In this section we will compare2 the different approaches we presented to test components. This section draws from the many questions in forums and mailing lists asking about the pros and cons of stubs, mock-objects, and in-container testing. 7.4.1 Stubs pros and cons We introduced stubs as our first out-of-container testing technique in chapter 5. Stubs work well to isolate a given class for testing and asserting the state of its instances. For example, stubbing a servlet container allows us to track how many requests were made, what the state of the server is, or what URLs where requested. Using mocks, however, we can test the state of the server and its behavior. When using mock objects, we code and verify expectations; we check at every step that tests execute domain methods and how many times tests call these methods. 2 For an in-depth comparison of the stubs and mocks technology , see ”Mocks Aren’t Stubs” by Martin Fowler: http://martinfowler.com/articles/mocksArentStubs.html Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 One of the biggest advantages of stubs over mocks is that stubs are easier to understand. Stubs isolate a class with little extra code compared to mock objects, which require an entire framework to function. The drawback of stubs is that they rely on external tools and hacks, and do not track the state objects they fake. Going back to chapter 5, we easily faked a servlet container with stubs, doing so with mock objects would be much harder since we would need to fake container objects with state and behavior. Here is a summary of stubs pros and cons. Pros: ƒ Fast and lightweight. ƒ Easy to write and understand. ƒ Powerful. ƒ Tests are more coarse-grained. Cons: ƒ Specialized methods are required to verify state. ƒ Does not test the behavior of faked objects. ƒ Time consuming for complicated interactions. ƒ Requires more maintenance when the code changes. 7.4.2 Mock objects pros The biggest advantage of mock-objects3 over in-container testing is that mocks do not require a running container in order to execute tests. Tests can be set up quickly and run fast. The main drawback is that the tested components do not run in the container in which you will deploy them. The tests cannot verify the interaction between components and container. The tests also do not test the interaction between the components themselves as they run in the container. You still need a way to perform integration tests. Writing and running functional tests could achieve this. The problem with functional tests is that they are coarse-grained and test only a full use case—you lose the benefits of fine-grained unit testing. You will not be able to test as many different cases with functional tests as you will with unit tests. There are other disadvantages to mock-objects. For example, you may have many mock objects to set up, which may prove to be non-negligible overhead. Obviously, the cleaner the code (small and focused methods), the easier tests are to set up. Another important drawback to mock-objects is that in order to set up a test, you usually must know exactly how the mocked API behaves. It is easy to know the behavior of your own API, but it may not be so easy for another API, like the Servlet API. 3 For an in-depth comparison of the mocks and in-container testing, see “Mock objects vs. In-container testing”: http://jakarta.apache.org/cactus/mock_vs_cactus.html Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Even though containers of a given type all implement the same API, not all containers behave the same way. For example, consider the following servlet method: public void doGet(HttpServletRequest request, HttpServletResponse response) { response.setContentType("text/xml"); } This example seems simple enough, but if you run it in Tomcat (http://tomcat.apache.org/) and in Orion prior to version 1.6.0 (http://www.orionserver.com/), you will notice different behaviors. In Tomcat, the returned content type is text/xml, but in Orion, it is text/html. This is an extreme example, all servlet containers implement the Servlet API in pretty much the same way. However, the situation is far worse for the various JavaEE APIs— especially the EJB API. Implementors can interpret an API specification differently and a specification can be inconsistent, making it difficult to implement. In addition, of course, containers have bugs; the example above may have been a bug in Orion 1.6.0. While unfortunate, you will have to deal with bugs, tricks and hacks for various third party libraries in any project. To wrap up this section, let us summarize the drawbacks of unit testing with mock objects. ƒ Do not test interactions with the container or between the components ƒ Do not test the deployment of components ƒ Need excellent knowledge of the API to mock, which can be difficult (especially for external libraries) ƒ Do not provide confidence that the code will run in the target container ƒ More fine-grained, which may lead you to being swamped with interfaces ƒ Like stubs, require maintenance when code changes. 7.4.3 In-container testing pros and cons So far, we have described the advantages of in-container unit testing. However, there are also a few disadvantages, which will now discuss. SPECIFIC TOOLS REQUIRED A major drawback is that although the concept is generic, the tools that implement in- container unit testing are specific to the tested API, like Apache Jakarta Cactus for JavaEE testing. If you wish to write integration unit tests for another component model, chances are that such a framework exists. With mock-objects, since the concept is generic you can test almost any API. NO GOOD IDE SUPPORT A significant drawback of most of the in-container testing frameworks is the lack of good IDE integration. In most cases, you can use Ant or Maven to execute tests, which also provides Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 the ability to run a build in a Continuous Integration server (CIS, see chapter 10). Alternatively, IDEs can execute tests that use mock objects as normal JUnit tests. We strongly believe that in-container testing falls in the category of integration testing. This means that you do not need to execute your in-container tests as often as normal unit tests and will most likely run them in a CIS, alleviating the need for IDE integration. LONGER EXECUTION TIME Another issue is performance. For a test to run in a container, you need to start and manage the container, which can be time consuming. The overhead in time (and memory) depends on the container: Jetty can start in less than 1 second, Tomcat in 5 seconds, and WebLogic in 30 seconds. The startup overhead is not limited to the container. For example, if a unit test hits a database, the database must be in an expected state before the test starts (see database application testing in chapter 17). In terms of execution time, integration unit tests cost more than mock objects. Consequently, you may not run them as often as logic unit tests. COMPLEX CONFIGURATION The biggest drawback to in-container testing is that tests are complex to configure. Since the application and its tests run in a container, your application must be packaged (usually as a war or ear file) and deployed to the container. You must then start the container and run the tests. On the other hand, since you must perform these exact same tasks for production, it is a best practice to automate this process as part of the build and reuse it for testing purposes. As one of the most complex task of a JavaEE project, providing automation for packaging and deployment becomes a win-win. The need to provide in-container testing will drive the creation of this automated process at the beginning of the project, which will in addition facilitate continuous integration. To further this goal, most in-container testing frameworks include support for build tools like Ant and Maven. This will help hide the complexity involved in building various runtime artifacts as well as with actually running tests and gathering reports. 7.4.4 In-container versus out-of-container testing A standard design goal is to separate presentation from business layers. For example, you should implement the code for a tag that retrieves a list of customers from a database with two classes. One class implements the tag and depends on the Taglib API while the other implements the database access and does not depend on the Taglib API but on database classes like JDBC. The separation of concerns strategy not only permits the reuse of classes in more than one context, it also simplifies testing. You can test the business logic class with JUnit and mock objects and use in-container testing to validate the tag class. In-container testing requires more setup than mock objects but is well worth the effort. You may not run the in-container tests as often, but they can confirm that your tags will work in the target environment. While you may be tempted to skip testing a component, like Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 a taglib -- reasoning that functional tests would eventually test the tag as a side effect -- we recommend that you fight this temptation. All components benefit from unit testing. We outline these benefits below. ƒ Fine-grained tests can be run repeatedly and tell you when, where and why your code breaks. ƒ The ability to test completely your components, not only for normal behavior, but also for error conditions. For example, when testing a tag accessing a database, you should confirm that the tag behaves as expected when the connection with the database is broken. This would be hard to test in automated functional tests, but is easy to test when you combine in-container testing and mock objects. 7.5 Summary When it comes to unit-testing an application in a container, we have seen that standard JUnit tests come up short. While testing with mock objects work, they miss some scenarios like integration tests to verity component interactions in and with a container. In order to verify behavior in a container, we need a technique that addresses testing from an architectural point of view: in-container testing. While complex, in-container testing addresses these issues and provides developers with the confidence necessary to change and evolve their applications. This chapter provides the foundation for the last part of this book where we will continue to explore such testing frameworks. Next, we start the third part of this book by integrating JUnit into the build process, a tenet of Test Driven Development. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Part III JUnit and the build process This part of the book deals with a very important aspect in the development cycle of every project - the build. The importance of the build is reconsidered more and more these days - especially in large projects. That is the reason why we dedicate a whole part of the book to integration between JUnit and two of the most important build tools nowadays - Ant and Maven. The eighth chapter gives you a very quick introduction to Ant and its terminology - tasks, targets and builds. We will talk on how to start your tests as part of your Ant build lifecycle, and also how to produce some fancy reports with the results of the JUnit execution. This chapter will let you serve as a basis for most of the rest of the chapters, since you need a good knowledge of Ant to be able to grasp all of the Ant integration sections in the further chapters. The ninth chapter will guide you through the same concepts, but this time by the means of another popular tool called Maven. We will show you how to include the execution of your tests in the Maven build lifecycle and how to produce nice HTML reports by means of some of the Maven plugins. The last chapter in this part of the book is devoted to Continuous Integration (CI) tools. This practice was highly recommended by extreme programmers, and helps you maintain a code repository and automate the build upon it. This is really helpful in building large projects that depend on a lot of other projects that change very often (like any open-source project). Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 08 Running JUnit tests from Ant This chapter covers ƒ Introduction to Ant and Ivy ƒ Ant JUnit tasks ƒ Producing fancy reports with the JUnitReport task Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 It’s supposed to be automatic, but you still have to press the button. —John Brunner In this chapter, we will look at one of the products with direct support for JUnit - Ant. Ant is a build tool that can be used with any Java programming environment. We will demonstrate how you can be productive with JUnit and these environments and how to automate running JUnit tests. At the end of this chapter, you will know how to set up your environment on your machine to build Java projects, including managing their dependencies, executing JUnit tests and generating JUnit reports. 8.1 A day in the life For unit tests to be effective, they should be part of the development routine. Most development cycles begin by checking out a module from the project’s source-code repository. Before making any changes, prudent developers first run the full unit-test suite. Many teams have a rule that all the unit tests on the working repository must pass. Before starting any development of your own, you should see for yourself that no one has broken the all-green rule. You should always be sure that your work progresses from a known baseline. The next step is to write the code for a new use case (or modify an existing one). If you are a Test-Driven Development (TDD) practitioner, you’ll start by writing new tests for the use case. (For more about TDD, see chapter 3.) Generally, the test will show that your use case isn’t supported and either will not compile or will display a red bar when executed. Once you write the code to implement the use case, the bar turns green, and you can check in your code. Non-TDD practitioners will implement the use case and then write the tests to prove it. Once the bar turns green, the code and the tests can be checked in. In any event, before you move on to code the next feature, you should have a test to prove the new feature works. After you code the next feature, you can run the tests for the prior feature too. In this way, you can ensure that new development does not break old development. If the old feature needs to change to accommodate the new feature, then you update its test and make the change. If you test rigorously, both to help you design new code (TDD) and to ensure that old code works with new (regression testing1), you must continually run the unit tests as a normal part of the development cycle. The test runners must become your best friends. And, like any best friend, the test runners should be on speed dial. You need to be able to run the tests automatically and effortlessly throughout the day. In chapter 1, section 1.4, we discussed running JUnit from the command line. Running a single JUnit test case against a single class is not difficult. But it is not a practical approach for running continuous tests against a project with hundreds or even thousands of classes. 1 About regression testing see chapter 4. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 A project that is fully tested has at least as many test classes as production classes. Developers can’t be expected to run an entire set of regression tests every day by hand. So, you must have a way to run key tests easily and automatically, without relying on already- overworked human beings. Because you are writing so many tests, you need to write and run tests in the most effective way possible. Using JUnit should be seamless, like calling a build tool or plugging in a code highlighter. Ant is the de facto standard tool for building Java applications; it is an excellent tool for managing and automating JUnit tests. Ant will happily build your applications, but it doesn’t help writing them. 8.2 Running tests from Ant Compiling and testing a single class, like the DefaultController class from chapter 3, is not difficult. Compiling a larger project with multiple classes can be a huge headache if your only tool is the stock javac compiler. Increasing numbers of classes refer to each other, and so more classes need to be on the classpath where the compiler can find them. On any one build, only a few classes will change, so there is also the issue of which classes to build. Re-running your JUnit tests by hand after each build can be equally inconvenient, for all the same reasons. Happily, the answer to both problems is the fabulous tool called Ant. Ant is not only an essential tool for building applications, but also a great way to run your JUnit regression tests. 8.3 Ant, indispensable Ant Apache’s Ant product (http://ant.apache.org/) is a build tool that lets you easily compile and test applications (among other things). It is the de facto standard for building Java applications. One reason for Ant’s popularity is that it is more than a tool: Ant is a framework for running tools. In addition to using Ant to configure and launch a Java compiler, you can use it to generate code, invoke JDBC queries, and, as you will see, run JUnit test suites. Like many modern projects, Ant is configured through an XML document. This document is referred to as the buildfile and is named build.xml by default. The Ant buildfile describes each task that you want to apply on your project. A task might be compiling Java source code, generating Javadocs, transferring files, querying databases, or running tests. A buildfile can have several targets, or entry points, so that you can run a single task or chain several together. Let’s look at using Ant to automatically run tests as part of the build process. If (gasp!) you don’t have Ant installed, see the following sidebars. For full details, consult the Ant manual (http://ant.apache.org/manual/). Installing Ant on Windows Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 To install Ant on Windows, follow these steps: 1 Unzip the Zip distribution file to a directory on your computer system (for example, C:\Ant). 2 Under this directory, Unzip creates a subdirectory for the Ant distribution you downloaded—for example, C:\Ant\apache-ant-1.7.1. Add an ANT_HOME variable to your environment with this directory as the value. For example: Variable Name: ANT_HOME Variable Value: C:\Ant\apache-ant-1.7.0 3 Edit your system’s PATH environment variable to include the ANT_HOME\bin folder: Variable Name: PATH Variable Value: %ANT_HOME%\bin;… 4 We recommend that you also specify the location of your Java Developer’s Kit (JDK) as the JAVA_HOME environment variable: Variable Name: JAVA_HOME Variable Value: C:\jdk1.6.0_03 This value, like the others, may vary depending on where you installed the JDK on your system. 5 To enable Ant’s JUnit task, you can follow several paths. Your first option would be to place the junit.jar in the in the ANT_HOME\lib folder. This way it will be added to your classpath every time Ant loads your buildfile. For now we will stick with this choice and later on we will show you how to deal with the JUnit dependency in a more subtle manner. Installing Ant on UNIX (bash) To install Ant on UNIX (or Linux), follow these steps: 1 Untar the Ant tarball to a directory on your computer system (for example, /opt/ant). 2 Under this directory, tar creates a subdirectory for the Ant distribution you downloaded—for example, /opt/ant/apache-ant-1.7.1. Add this subdirectory to your environment as ANT_HOME. For example: export ANT_HOME=/opt/ant/apache-ant-1.7.1 3 Add the ANT_HOME/bin folder to your system’s command path: export PATH=${PATH}:${ANT_HOME}/bin Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 4 We recommend that you also specify the location of your JDK as the JAVA_HOME environment variable: export JAVA_HOME=/usr/java/jdk1.6.0_03/ 5 To enable Ant’s JUnit task, you can follow several paths. Your first option would be to place the junit.jar in the in the ANT_HOME\lib folder. This way it will be added to your classpath every time Ant loads your buildfile. For now we will stick with this choice and later on we will show you how to deal with the JUnit dependency in a more subtle manner. 8.4 Ant targets, projects, properties, and tasks When you build a software project, you are often interested in more than just binary code. For a final distribution, you may want to generate Javadocs along with the binary classes. For an interim compile during development, you may skip that step. Sometimes, you want to run a clean build from scratch. Other times, you want to build the classes that have changed. To help you manage the build process, Ant lets you create a buildfile for each of your projects. The buildfile may have several targets, encapsulating the different tasks needed to create your application and related resources. To make the buildfiles easier to configure and reuse, Ant lets you define dynamic property elements. These Ant essentials are as follows: ƒ Buildfile—Each buildfile is usually associated with a particular development project. Ant uses the project XML tag as the outermost element in build.xml. The project element defines a project. It also lets you specify a default target, so you can run Ant without any parameters. ƒ Target—When you run Ant, you can specify one or more targets for it to build. Targets can also declare that they depend on other targets. If you ask Ant to run one target, the buildfile might run several others first. This lets you create a distribution target that depends on other targets like clean, compile, javadoc, and war. ƒ Property elements—Many of the targets within a project will share the same settings. Ant lets you create property elements to encapsulate specific settings and reuse them throughout your buildfile. If a buildfile is carefully written, the property elements can make it easy to adapt the buildfile to a new environment. To refer to a property within a buildfile, you place the property within a special notation: ${property}. To refer to the property named target.dir, you would write ${target.dir}. As mentioned, Ant is not so much a tool as a framework for running tools. You can use property elements to set the parameters a tool needs and a task to run the tool. A great Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 number of tasks come bundled with Ant, and you can also write your own. For more about developing with Ant, we highly recommend Ant in Action2. Listing 8.1 shows the top of the buildfile for the sampling project from chapter 3. This segment of the buildfile sets the default target and the properties your tasks will use. Listing 8.1 The ant buildfile project and property elements. (1) (2) (3) (3) (3) (4) (4) (4) […] (1) Give the project the name sampling and set the default target to test. (The test target appears in listing 8.3.) (2) You include a build.properties file. This file contains Ant properties that may need to be changed on a user’s system because they depend on the executing environment. For example, these properties can include the locations of redistributable jars. Because programmers may store jars in different locations, it is good practice to use a build.properties file to define them. Many open source projects provide a build.properties.sample file you can copy as build.properties and then edit to match your environment. For this project, you won’t need to define any properties in it. (3) As you will see, your targets need to know the location of your production and test source code. You use the Ant property task to define these values so that they can be reused and easily changed. At (3), you define properties related to the source tree; at (4), you define those related to the output tree (where the build-generated files will go). Notice that you use different properties to define where the compiled production and tests classes will be put. Putting them in different directories is a good practice because it allows you to easily package the production classes in a jar without mixing test classes. An interesting thing about Ant properties is that they are immutable—once they are set, they cannot be modified. For example, if any properties are redefined after the build.properties file is loaded, the new value is ignored. The first definition always wins. 8.4.1 The javac task For simple jobs, running the Java Compiler (javac) from the command line is easy enough. But for multipackage products, the care and feeding of javac and your classpath becomes a 2 The “Ant In Action” is a title by Steve Loughran and Erik Hatcher published by Manning Publications (ISBN: 1- 932394-80-X) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Sisyphean task. Ant’s javac task tames the compiler and its classpath, making building projects effortless and automatic. The Ant javac task is usually employed from within a target with a name like compile. Before and after running the javac task, you can perform any needed file management as part of the target. The javac task lets you set any of the standard options, including the destination directory. You can also supply a list of paths for your source files. The latter is handy for projects with tests, because you may tend to keep production classes in one folder and test classes in another. Listing 8.2 shows the compile targets that call the Java Compiler for the sampling project, both for the production code and for the test code. Listing 8.2 The buildfile compile targets (1) (2) (3) (4) (5) (5) (5) (5) (6) (6) (6) (7) First, declare the target (1) to compile the java production sources, naming it compile.java. Ensure that the directory where you will generate your production class files exists (2). Ant resolves the property you set at the top of the buildfile (see listing 8.1) and inserts it in place of the variable notation ${target.classes.java.dir}. If the directory already exists, Ant quietly continues. Call the Java Compiler (javac) and pass it the destination directory to use (3). Tell the javac task what sources to compile (4). Compile the test sources exactly the same way you just did for the production sources (5). Your compile.test target has a dependency on the compile.java target, so you must add a depends element to that compile.test target definition (depends="compile.java"). You may have noticed that you don’t explicitly add the JUnit jar to the classpath. Remember that when you installed Ant, you put the JUnit jar in ANT_HOME/lib (this is necessary in order to use the junit Ant task). As a consequence, junit.jar is already on your classpath, and you don’t need to specify it in the javac task to properly compile your tests. Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 You need to add a nested classpath element (6) in order to add the production classes you just compiled to the classpath. This is because test classes call production classes. Create a compile target (7) that automatically calls the compile.java and compile.test targets. 8.4.2 The JUnit task In chapter 3, you ran the DefaultController tests by hand. That meant between any changes, you had to ƒ Compile the source code ƒ Run the TestDefaultController test case against the compiled classes You can get Ant to perform both these steps as part of the same build target. Listing 8.3 shows the test target for the sampling buildfile. Listing 8.3 The buildfile test target. (1) (2) (3) (4) (5) Give the target a name and declare that it relies on the compile target (1). If you ask Ant to run the test target, it will run the compile target before running test. At (2) you get into the JUnit-specific attributes. The printsummary attribute says to render a one-line summary at the end of the test. By setting fork to yes, you force Ant to use a separate Java Virtual Machine (JVM) for each test. This is always a good practice as it avoids interferences between test cases. The haltonfailure and haltonerror attributes say that the build should stop if any test returns a failure or an error (an error is an unexpected error, whereas a failure happens if one of the test asserts does not pass). Configure the junit task formatter to use plain text and output the test result to the console (3). Provide the class name of the test you want to run (4) . Finally, extend the classpath to use for this task to include the classes you just compiled (5). That's it; you've assembled your build file test target. 8.5 Putting Ant to the task. Now that you’ve assembled the buildfile, you can run it from the command line by changing to your project directory and entering ant. Figure 8.1 shows what Ant renders in response. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Fig. 8.1 Running the buildfile from the command line. You can now build and test the sampling project all at the same time. If any of the tests fail, the haltonfailure/haltonerror settings will stop the build, bringing the failure to your attention. Running the junit optional task The junit task is an optional component bundled in Ant’s ant-junit.jar. The ant- junit.jar file should already be in your ANT_HOME/lib directory. Ant does not bundle a copy of JUnit, so you must be sure that junit.jar is on your system classpath or in the ANT_HOME/lib directory. (The ant-junit.jar file is for the task itself). For more about installing Ant, see the “Installing Ant” caret in section 8.3. If you have any trouble running the Ant buildfiles presented in this chapter, make sure the Ant ant-junit.jar is in the ANT_HOME/lib folder and junit.jar is either on your system classpath or also in the ANT_HOME/lib folder. So far we have seen a way to execute our tests with Ant. It’s time now to take a look on one other aspect of the build process – the dependency management. Every build needs a way to manage its dependencies and we are going to introduce now the Ivy project to you – one of the coolest things that happened lately to Ant. Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 8.6 Dependency management with Ivy When your build is pretty small it is relatively easy to cope with the jars your project depends on. For instance in the buildfile from the previous listings, the only jar we needed to compile our classes was the junit.jar. In the open-source world, however, it is a common problem that your project depends on, sometimes dozens of other open-source projects. In most of the cases, it is also inconvenient to redistribute those dependencies with your project, and a great burden for the users of your project to tell them before compiling your project to get the other several dependencies from somewhere in the internet. You need to deal somehow with this problem, i.e. you need to make a better dependency management. The dependency management that the Maven team (we cover Maven in the next chapter) first introduced was based on a central repository somewhere in the internet, containing lots of different jars that you might need in your buildfile. What you, as a programmer, do is simply list all the dependencies you need in the configuration file and the dependency manager will download them for you in a local repository on you hard disk. From there you can add them to the classpath of your application, and you can easily change the version you want to use by changing it in the configuration file. Ivy (http://ant.apache.org/ivy) is a popular dependency management tool (recording, tracking, resolving and reporting dependencies) focusing on flexibility and simplicity. While available as a standalone tool, Ivy works particularly well with Apache Ant providing a number of powerful Ant tasks ranging from dependency resolution to dependency reporting and publication. It is out of the scope for this book to cover Ivy in depth, but I would like to dedicate a few pages on this tool and rework our buildfile with the proper dependency management. The installation of Ivy is pretty straightforward – just download the zip from the main website, extract it somewhere and copy the ivy-xxx.jar(where xxx stands for the version of Ivy you are using) in the ANT_HOME/lib folder. Ivy works the way we described in a few paragraphs paragraphs earlier. Ivy uses the Maven central repository for resolving and downloading your dependencies. The dependencies to your project need to be specified in a separate file, named by default ivy.xml. From there the tool, once started, will download all the dependencies listed in the ivy.xml file into a local cache directory. Here is the listing of the buildfile with the changes marked in bold. Listing 8.4 Adding the Ivy changes to the buildfile (1) […] (2) (3) […] Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 (4) […] (5) […] What we actually do here is simply declare the ivy namespaces at (1), and then call the Ivy Ant task to retrieve the dependencies listed in the ivy.xml file from the public repository (2). After we retrieve the junit.jar with Ivy, the archive is placed in the lib folder in our project. In (3) we define the junit.jar property that points to the jar we just downloaded. After that in (4) and (5) we include it in the classpath of the javac task (so that the compilation of the tests can be done) and in the classpath of the junit task (so that the execution of the tests can be done). We no longer need the junit.jar in the ANT_HOME/lib folder, so we can delete it from there. The listing with ivy.xml in which we describe our dependencies follows: Listing 8.5 The ivy.xml file with the listed dependencies. (1) (2) (3) It seems pretty easy to follow, but still let’s give some clarifications. First in the root tag (1) we define the version of ivy we want to use (in this case 2.0). Then with the info tag (2) we denote the organization and the module name for which we are defining dependencies. Finally, the dependencies nested element (3) is where we specify our dependencies. Our module has only one dependency listed – the junit module. Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 After invoking the ant file again by changing to the project directory and entering Ant we should see the same result (Figure 8.2) as we did before, when the junit.jar was in the ANT_HOME/lib folder, but this time we should see Ivy being invoked and resolve the appropriate dependencies. Fig 8.2 Output of Ivy resolving the JUnit dependency. 8.7 Pretty printing with JUnitReport A report like the one in figure 8.1 is fine when you are running tests interactively. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 But what if you want to run a test suite and review the results later? For example, the tests might be run automatically every day by a cron job (whether you liked it or not!) or a Continuous integration server (we discuss continuous integration servers in chapter 10). Another optional Ant task, junitreport, is designed to output the result of the tests as XML. To finish the job, junitreport renders the XML into HTML using an XSL stylesheet. The result is an attractive and functional report that you (or your boss) can peruse with any browser. A junitreport page for the sampling project is shown in figure 8.3. Fig. 8.3 Output of the junitreport Ant task Listing 8.4 shows the changes (in bold) necessary in the buildfile to generate this report. To execute the script, type ant report on the command line in the sampling project. Listing 8.4 Adding a JUnitReport task to the build file. […] (1) […] (2) Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 (3) (4) (5) (6) (7) (8) (9) First, define a property holding the target location where your reports will be generated (1), then create that directory (2). You need to modify the junit task so that it outputs the test results as XML (3). The junitreport task works by transforming the XML test result into an HTML report. Tell the junit task to create a report file in the ${target.report.dir} directory (4), then introduce a new report target that generates the HTML report (5). You begin by creating the directory where the HTML will be generated (6). Call the junitreport task to create the report (7). The junitreport task works by scanning the list of XML test results you specify as an Ant fileset (8). Last, tell the junitreport task where to generate the HTML report (9). Future of the JUnit reports As we just saw the Ant junit task can produce an XML output containing the results of the execution of your JUnit tests. This is pretty nice because the generated XML is a very detailed, and it is used widely – not only produced by a lot of tools – ant’s junit task, maven’s surefire plugin, etc, but also consumed by the majority of tools out there (Ant’s junitreport task, maven-surefire-reports, different CI servers, etc.). After using Ant and its junit-related tasks for some time, you may notice that the generated HTML report, for instance, does not denote skipped tests. The reason behind that is that the XML produced was introduced before JUnit4 was out. And in the previous version of JUnit there used to be no special status for skipped tests. This and some other problems connected with the XML being produced led to the general concept that the XML was good at the time, but as testing has evolved a lot, it needs to be redesigned and the format needs to be evolved. There is a pretty major discussion on the Apache Ant wiki focusing on how the new design should look like. By the time this book is written the xml-migration is still on phase Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 discussing what the new design will look like. Lots of new features and improvements are discussed and proposed on the wiki. It is very interesting to see which of these will be finally implemented. Who knows what will the future might be … 8.8 Automatically finding the tests to run The buildfile you have written is using the test element of the junit task to tell JUnit what test to execute. Although this is fine when there are only a few test cases, it becomes tiresome when your test suite grows. The biggest issue then becomes ensuring that you haven’t forgotten to include a test in the buildfile. Fortunately, the junit task has a convenient batchtest element that lets you specify test cases using wildcards. Listing 8.5 shows how to use it (changes from listing 8.4 are shown in bold). Listing 8.5 A better buildfile using batchtest [...] (1) (2) (2) (2) (2) (2) […] (1) You may wonder why you define a property here when you could have put the wildcards directly into the fileset element at (2). Using this trick, you can define the tests property on the command line and run a single test (or a specific set of tests) instead. This is an easy way to run a test against the class you are working on right now. Of course, once it’s working, you still run the full test suite to be sure everyone is on the same page. Here is an example that only executes the TestDefaultController test case: ant –Dtests=TestDefaultController test Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 (2) You improve the buildfile by making the test target more flexible. Whereas before you had to explicitly name the different tests you wanted to execute, here you leverage the junit task’s nested batchtest element. With batchtest, you can specify the test to run as a fileset, thus allowing the use of wildcards. Add the always-useful clean target to remove all build-generated files. Doing so lets you start with a fresh build with no side effects from obsolete classes. Typically, a dist target that generates the project distributable depends on the clean target. It is a common good practice to name your test-cases in some way that they can hint to the other developers that this is actually a test-case. As you saw in the first chapter you can give your test-cases any name you want. Despite this, most of the people tend to name theirs with the Test*.java pattern. And you already know why this would be a good idea – because later on you can easily construct a pattern to select your test in the batchtest nested element. Are automated unit tests a panacea? Absolutely not! Automated tests can find a significant number of bugs, but manual testing is still required to find as many bugs as possible. In general, automated regression tests catch 15–30% of all bugs found; manual testing finds the other 70–85% (http://www.testingcraft.com/regression-testbugs.html). Are you sure about that? Some test-first design/unit testing enthusiasts are now reporting remarkably low numbers of bug counts, on the order of one or two per month or fewer. But these results need to be substantiated by formal studies and replicated by other teams. Your mileage will definitely vary. 8.9 Summary In this chapter we introduced you to one of the best tools for building your software – Ant. We saw not only the basics of any Ant buildfile, but we also described the two of its very important tasks – the javac task and the junit task. We also introduced Ivy and the junitreport task. Now you should be able to execute you tests with Ant, and also produce nice reports of the test execution. In the following chapters, we will continue to explore the continuous integration paradigm by getting familiar with another tool for building software – Maven. We will start by getting familiar with the core concepts that biuld Maven and we are going to explore the two most important plugins to us – the maven-surefire plugin and the maven-surefire-report plugin. We will also see the way Maven handles the management of the dependencies, and we will compare it to the Ivy way that we just saw. So let’s go on! Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 09 Running JUnit tests from Maven2 This chapter covers ƒ Introduction to Maven ƒ Dependency management the Maven way ƒ Maven Surefire Plugin ƒ Maven Surefire-Report Plugin Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The conventional view serves to protect us from the painful job of thinking. -John Kenneth Galbraith In this chapter we will discuss and reveal another very common build system tool called Maven. We will show you how Maven differs from Ant, also we will make a brief introduction to this build system, which will be very useful if you are new to it, or just need a way to start your tests continuously. Very often people come to Maven thinking that it would be something like Ant. And once they discover that it is a totally different, they get frustrated. Don’t worry, maybe you are not alone in the boat. This is the reason why we will spend the first few pages of this chapter on explaining what is the most essential in order to understand Maven, how it is different from Ant. Right after that we are going to show you some real-world examples of compiling your test-cases and running them, and also producing fancy reports. At the end of this chapter, you will know how to set up your environment on your machine to build Java projects with Maven, including managing their dependencies, executing JUnit tests and generating JUnit reports. 9.1 Maven’s features Once you have used Ant on several projects, you’ll notice that most projects almost always need the same Ant scripts (or at least a good percentage). These scripts are easy enough to reuse through cut and paste, but each new project requires a bit of fussing to get the Ant buildfiles working just right. In addition, each project usually ends up having several subprojects, each of which requires you to create and maintain an Ant buildfile. Maven (http://maven.apache.org/) picks up where Ant leaves off, making it a natural fit for many teams. Like Ant, Maven is a tool for running other tools, but Maven is designed to take tool reuse to the next level. If Ant is a source-building framework, Maven is a source-building environment. In order to understand better how Maven works you need to understand the key points (principles) that stand behind Maven. Maven was designed to take the build systems on the next level, beyond Ant. So you need to get familiar what are the things that the Maven community did not like in Ant and how it tried to escape them designing Maven, i.e. what is the core reason for starting Maven as a whole new project. From the very beginning of the Maven project a certain ground rules were placed for the whole software architecture of the project. These rules aim to simplify the development with Maven and to make it easier for us as developers to implement the build system. One of the fundamental ideas of Maven is that the build system should be as simple as possible – the software engineers should not spend a lot of time implementing the build system. It should be easy enough to start a new project from scratch and then rapidly begin developing the software, and not spending valuable time of designing and implementing a build system. In this section we describe each one of the core Maven principles in details and we explain what they really mean for us, from a developer’s point of view. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 9.1.1 Convention over configuration This feature is actually software design principle which aims to decrease the number of configurations a software engineer needs to make, in favour of introducing a number of conventional rules that need to be strictly followed by the developers. This way you, as a developer can skip the tedious configuration that needs to be done for every single project, and you can focus on the more important parts of your work. The convention-over-configuration is one of the strongest principles of the Maven project. As an example of its application we can mention the folder structure of the build process. When Maven project was started some of the initial Maven developers noticed that for every single Ant buildfile the person who writes the buildfile has to design the folder structure – he has to declare the source folders, the build folders and a bunch of other folders you need for the build itself. And although the Ant community tries to imply some folder names and folder structure, there is still no official specification (convention) of how the folder should be named. For instance lots of people declare the target.dir property to note the folder which holds your compiled classes, others may have accustomed to the build.dir property and it seems very unnatural to them to use the target.dir. Also lots of people place their source code in the src/ folder, but others do that in the src/java/ folder. So the Maven team decided that instead of leaving the software engineers to choose every time the build structure themselves, they would introduce a convention for this. So at the end they were left with what is now called “Maven convention of directory structure”. So with Maven instead of defining all the directories you need you have them defined for you – like the src/main/java/ is the Maven convention where your java code for the project resides, in src/main/test/ the unit-tests for the project reside, target is the build folder, and so on…. And it is not just the folder structure, later on when we see the plugins themselves, we will see how every plugin has a default state already defined, so you don’t need to define it again. That sounds great but aren’t we losing from the flexibility of the project? What if I want to use Maven and my source code resides in another folder? Maven is really great at this - it provides the convention, but you still can, at any point of time, override the convention and use any configuration of your choice. 9.1.2 Strong dependency management This is the second key point that Maven introduced. At the time the project was started de facto build system for Java projects was Ant. And with Ant you have to distribute the dependencies of your project, with the project itself. Maven introduced the notion of a central repository – a location in the internet where all kinds of artifacts (dependencies) are stored and the Maven build tool resolves them by reading your project’s build descriptor, downloading the necessary versions of the artifacts and then including them in the classpath of your application. This way you only need to list your dependencies in the dependencies section of your build descriptor Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 junit junit 4.5 jmock jmock 1.0.1 after which you are free to build the software on any other machine – no need to bundle the dependencies with your project and so forth. But Maven introduced also the concept of the local repository. This is a folder on your hard disk (~/.m2/repository/ on UNIX and C:\Documents and Settings\\.m2\repository\ on Windows) on your hard disk where Maven keeps the artifacts that it has just downloaded from the central repository. Also, after you have built your project your artifacts get installed in the local repository for later usage, by some other projects – simple and neat. 9.1.3 Maven build lifecycles Another very strong principle in Maven is the build lifecycle. The Maven project is build around this idea of defining the process of building, testing and distributing a particular artifact. Maven projects can produce only one artifact. This way we can use Maven for building the project’s artifact, or cleaning the project’s folder structure or generating the project’s documentation. The activities we use Maven for, define the three built-in lifecycles of Maven ƒ default - for generating project’s artifact ƒ clean - for cleaning the project ƒ site - for generating project’s documentation. Every one of these lifecycles is composed by several phases and in order to pass through a certain lifecycle, the build follows its phases. Here is a list of all the phases of the default lifecycle: ƒ validate - validate the project is correct and all necessary information is available ƒ compile - compile the source code of the project ƒ test - test the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed ƒ package - take the compiled code and package it in its distributable format, such as a JAR. ƒ integration-test - process and deploy the package if necessary into an environment Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 where integration tests can be run ƒ verify - run any checks to verify the package is valid and meets quality criteria ƒ install - install the package into the local repository, for use as a dependency in other projects locally ƒ deploy - done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects. If you remember well with Ant we had the targets with almost the same names. Well, yes, the targets in Ant are the analogue of the phases in Maven, with one exception. In Ant you write the targets and you specify which target depends on which target. With Maven, we see, again, the convention-over-configuration principle here. These phases are already defined for you in the order we listed them. And not only this, but also Maven invokes these phases in a very strict order – they get executed sequentially one after another in the order we have listed them, to complete the lifecycle. This means that if you invoke any of them – for example if you type: mvn compile on the command line in your project’s home directory Maven will first validate the project and then try to compile the sources of your project. One last thing – it is really useful to think of all these phases as extension points. At any moment you can attach additional Maven plugins to the phases and orchestrate the order and the way these plugins get executed. 9.1.4 Plugin based architecture The last feature of Maven that we will mention is its plugin-based architecture. At the beginning of this chapter we mentioned that Ant is a source-building framework and Maven is a source-building environment. More specifically Maven is a plugin-execution source- building environment. The core of the project is very small, but the architecture of the project allows multiple different plugins to get attached to the core and so Maven builds an environment where different plugins can get executed. Each one of the phases in a given lifecycle has a number of plugins attached to that phase and Maven invokes them when passing through the given phase in the order the plugins are declared. Here is a list of some of the core Maven plugins: ƒ clean plugin - Clean up after the build. ƒ compiler plugin - Compiles Java sources. ƒ deploy plugin - Deploy the built artifact to the remote repository. ƒ install plugin - Install the built artifact into the local repository. ƒ resources plugin - Copy the resources to the output directory for including in the JAR. Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 ƒ site plugin - Generate a site for the current project. ƒ surefire plugin - Run the Junit tests in an isolated classloader. ƒ verifier plugin- Useful for integration tests - verifies the existence of certain conditions. Apart from these core Maven plugins there are also dozens of other Maven plugins for every kind of situation you may need – maven war plugin, maven javadoc plugin, maven antrun plugin… you name it. Plugins are declared in the plugins section of your build configuration file. For instance: org.apache.cactus cactus.integration.maven2 1.8.1-SNAPSHOT As you can see every plugin declaration specifies groupId, artifactId and version. With this the plugins look like dependencies, don’t they? Yes, they do, and in fact they are handled the same way – they get downloaded in your local repository the same the dependencies do. When specifying a plugin the groupId and version are optional parameters – if you don’t declare them Maven will look for a plugin with the specified artidfactId, and one of the following groupIds – org.apache.maven.plugins or org.codehaus.mojo. The version is optional, indeed. If you are using Maven version pre- 2.0.9 maven will try and download the latest version available. However as of Maven version 2.0.9 the versions of most plugins are locked down in the super pom, so it won't download the latest version anymore. Locking down plugin versions is highly recommended to avoid auto-updating and non-reproducible builds. There are tons of additional plugins that are outside the Maven project, but can be used with Maven. The reason about this is that it is extremely easy to write plugins for Maven. 9.1.5 The Maven Project Object Model (POM) If you remember well Ant has a buildfile, by default named build.xml that holds all of the information for our build. In that buildfile we specify all the things that we want to get accomplished in the form of tasks and targets. So what is the analogue in Maven of Ant’s build.xml? Maven also has a build descriptor that is by default called pom.xml (shortened for Project Object Model). In contrast to Ant in Maven’s project descriptor we don’t specify the things we want to do – we specify general information for the project itself. Like in the following listing: Listing 9.1 Very simple pom.xml Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 4.0.0 com.manning.junitbook example-pom jar 2.0-SNAPSHOT It looks really simple, doesn’t it? I believe one big question rises at this moment – “How is even Maven capable of building our source code with that little information?” The answer lies in the inheritance feature of the pom.xmls - every simple pom.xml inherits most of its functionality from a Super POM. Just like in Java every Object inherits certain methods from the java.lang.Object object, the Super POM empowers each one of our pom.xmls with the Maven features. You see the analogue between Java and Maven. We can find this analogue even further – Maven pom.xmls can inherit from each other – just like in Java some classes can act as parents for others. For instance if we want to use the pom from listing 9.1 for our parent all we have to do is change its packaging value to pom – parent and aggregation (multi- module) projects can only have pom as a packaging value. We also need to define in our parent what the children modules are: Listing 9.2 Children modules for parent pom.xml 4.0.0 com.manning.junitbook example-pom pom (1) 2.0-SNAPSHOT example-module (2) The listing is an extension of listing 9.1. We declare that this pom is an aggregation module by declaring the package to be of pom type (1), and also adding a modules section (2). The modules section lists all the child-modules that our module has, by providing the relative path to the project folder (example-module). The next listing shows the child pom.xml. Listing 9.3 pom.xml that inherits the parent pom.xml 4.0.0 com.manning.junitbook example-pom 2.0-SNAPSHOT Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 example-child Remember that this pom.xml resides in the folder that the parent xml has declared (example-module). There are two things are worth noticing here – first since we inherit from some other pom there’s no need to specify groupId and version for the child pom, maven expects they are the same as the parent’s. Going with the analogue of Java it seems like a reasonable question to ask “what kind of objects can poms inherit from their parents?” Here is a list of all the elements that a pom can inherit from its parent: ƒ dependencies ƒ developers and contributors ƒ plugin lists ƒ reports lists ƒ plugin executions with matching ids ƒ plugin configuration And again, each one of these elements specified in the parent pom get automatically specified in the child pom. We will go on and discuss the poms further in the upcoming sections. 9.2 Setting up a Maven project Now that we have seen what the differences between Ant and Maven are, it’s time to move on and start building our projects with Maven. But first let’s see the installation process: Installing Maven Installing Maven is a three-step process: 1 Download the latest distribution from http://maven.apache.org/ and unzip/untar it in the directory of your choice (for example, c:\maven on Windows or /opt/maven on UNIX). 2 Define a M2_HOME environment variable pointing to where you have installed Maven. 3 Add M2_HOME\bin (M2_HOME/bin on UNIX) to your PATH environment variable so that you can type mvn from any directory. You are now ready to use Maven. The first time you execute a plugin, make sure your Internet connection is on, because Maven will automatically download from the Web all the third-party jars the plugin requires. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Let’s navigate to the c:\junitbook\ folder. This is our work directory and here we will set up the Maven examples. Type the following on the command line: mvn archetype:create -DgroupId=com.manning.junitbook -DartifactId=maven- sampling -DarchetypeArtifactid=maven-artifact-mojo After you press Enter and wait for appropriate artifacts to be downloaded you should see a folder named maven-sampling being created. If you look inside that folder you should see the folder structure being created as shown exactly on Figure 9.1. Fig 9.1 Folder structure after creating the project. So what happened here? We invoked the maven-archetype-plugin from the command line and told it to create a new project from scratch with the given parameters. As a result this maven plugin created a new project with a new folder structure, following the convention of the folder structure. And even more it created a sample App.java class with a main method and a corresponding AppTest.java file that is a unit test for our application. So now I bet, after looking at this folder structure you get quite familiar what files stay in src/main/java and what files stay in src/test/java. But it gets even more automated – if you noticed the Maven plugin also generated a pom.xml file for you. Let us open it and explain the different parts of the descriptor. Listing 9.4. Pom.xml for the maven-sampling project Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 xsi:schemaLocation="http://maven.apache.org/ http://maven.apache.org/maven-v4_0_0.xsd"> POM/4.0.0 n.apache.org h f the model of the pom being used. Currently ps together different projects from one organization, nd li nts the name that the aging our project will use? We specify here denotes that this artifact is still in development mode This section is used to enlist your dependencies. e ptive, but it will also be included later on when we build the web-site. 4.0.0 com.manning.junitbook artifactId> maven-samplingjar 1.0-SNAPSHOT maven-sampling http://mave junit tId> junit 3.8.1 test cies> < This is the build descriptor for our project. It starts with a global tag wit the appropriate namespaces. Inside which we place all of our components. ƒ modelVersion – this is the version o the only supported version is 4.0.0. ƒ groupId – The groupId of our project. Notice that this is the value that we provided on the command line when invoking maven. The groupId acts as the java packaging in the file-system – it grou company, group of people… ƒ artifactId – The artifactid of our project. Again the value here is the one we specified on the comma ne. The artifactId represe project is known by. ƒ packaging – What kind of artifact-pack jar, but it also could be pom, ear or war. ƒ version – The current version of our project (or our project’s artifact). Notice the – SNAPSHOT ending. This ending – we have not released it yet. dependencies –ƒ Now that we have our project descriptor let’s start and improve it a little bit. First we need to change the version of the junit dependency, since we are using 4.5 and the one that the plugin generated is 3.8.1. After that we can add some additional info to make the pom.xml more descriptive, like a developers section. This information not only makes th pom.xml more descri Listing 9.5 Additional metadata to the pom.xml Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Petar Tahchiev ptahchiev Apache Software Foundation Java Developer Stan Silvert ssilvert JBoss Foundation Java Developer Felipe Leme felipeal Apache Software Foundation Java Developer and also organization, description and inceptionYear: Listing 9.6 Description elements to the pom.xml “JUnit in Action II” book, the sample project for the “Running Junit tests from Maven” chapter. Manning Publications http://manning.com/ 2008 But let’s move on and start developing our software. We want to use our favourite Java IDE – Eclipse or IntelliJIDEA. No problem – Maven offers additional plugins to import the project in your favourite IDE. For instance we will use Eclipse to show you how this import happens. Again, open a terminal and navigate to the directory that contains your project descriptor (pom.xml). Once there type the following and hit Enter: mvn eclipse:eclipse -DdownloadSources=true Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 This will invoke the maven-eclipse-plugin which on its turn, after downloading the necessary artifacts will produce the two files (.project and .classpath) that Eclipse needs in order to recognize your project as an Eclipse project. The downloadSources parameter that we specify in the command line is optional. By using it we instruct the plugin to download also source attachments. There is also ability to download the JavaDOC attachments by setting the optional downloadJavadocs parameter with a true value on the command line. Now you can go on and import your project in Eclipse and have a look at it, you will notice that all of the dependencies that are listed in the pom.xml are now added to your buildpath. Amazing, isn’t it? To continue let’s generate some documentation for the project. But wait a second, how are we supposed to do that – we don’t have any files to generate the documentation from? This is another one of Maven’s great sides – with that little configuration and description that we have, we can produce a full functional web-site skeleton. Just type in mvn site on the command line where your pom.xml is. Maven should start downloading its plugins and after their successful installation it will produce the nice web-site you can see at fig 9.2. Figure 9.2 Maven produces nice web-site documentation for the project. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 This web-site is generated in Maven’s build directory – another convention. Maven uses the target/ folder for all the needs of the build itself. The convention goes even under this folder - source code is compiled in the target/classes/ folder and the documentation is generated in target/site/. After you examine the project you will probably notice that this web-site is more like a skeleton of a web-site. That’s absolutely true, still remember that we entered a small amount of data in first place. We could enter more data and web-pages in the src/site and Maven will include it in the web-site, thus generating a full-blown documentation. 9.3 Introduction to Maven plugins So far so good – we have seen what Maven is and how to use it to start a project from scratch. We have also seen how to generate the project’s documentation and how to import our project in Eclipse. To continue, we can get the source code from the first part of the book and just place it in the src/main/java folder, where Maven expects it to be. Also we can get the tests for the sampling project and place them in the src/test/java folder (again a convention). Now it’s time to invoke Maven and instruct it to compile the source code and compile the tests, and also invoke the tests execution. But first of all we need to clean the project from our previous activities: mvn clean This will cause Maven to go through the clean phase and invoke all of the plugins that are attached to this phase – in particular the maven-clean-plugin, which will delete the target/ folder, where our generated site resides. 9.3.1 Maven compiler plugin As any other build system Maven is supposed to build your projects – compile your software and package in an archive. As we mentioned in the beginning of the chapter, every task in Maven in done by an appropriate plugin, the configuration of which happens in the section of our project descriptor. To compile your source code all you need to do is invoke the compile phase on the command line: mvn compile which will cause Maven to execute all of the plugins that are attached to the compile phase (in particular it will invoke the maven-compiler-plugin). But before invoking the compile phase, as already discussed maven will go through the validate phase and will download all of the dependencies that are listed in the pom.xml and will include them in the classpath of the project. So after the compilation process is completed you can go to the target/classes/ folder and you should see the compiled classes there. Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Let’s move on and try to configure the compiler plugin. Notice the italics in the previous sentence? Yeah, that’s right – we will escape from the “convention over configuration principle” and will try to configure the compiler plugin. So far we have used the conventional compiler plugin and it all worked well. But what if we need to include the –source and –target attributes in the compiler invocation to generate class files for specific version of the JVM. We should add the following lines in the section of your buildfile: Listing 9.7 Configuring the maven-compiler-plugin maven-compiler-plugin 1.4 1.4 This is a general way to configure each one of your Maven plugins – you enter a section in your section. There you enlist each of the plugins that you want to configure – in our case it’s the maven-compiler-plugin. You need to enter the configuration parameters in the plugin’s configuration section. You can get a list of parameters for every plugin from the Maven web-site. As you see in the declaration of the maven-compiler-plugin in the previous listing we have not set the groupId parameter. That is because the maven-compiler-plugin is one of the core Maven plugins that has a org.apache.maven.plugins groupId, and as we mentioned in the beginning of the chapter plugins with such a groupId can have it skipped. 9.3.2 Maven surefire plugin. Ant uses the javac task to compile the tests that we have selected, the same way Maven uses the maven-compiler-plugin to compile all of the source code that is in src/main/java/. The exact same thing happens with the process of unit testing your project – ant uses the junit task and executes the test-cases that we have selected, while Maven uses ... guess what – a plugin of course. The Maven plugin executing the unit tests is called maven-surefire-plugin. Notice the italics in the previous sentence – the surefire plugin is used to execute the unit tests for your code, but these unit tests are not necessary JUnit tests. There are also other frameworks for unit testing and the surefire plugin can execute their tests, too. Now the conventional way to start the maven-surefire-plugin is very simple – all you need to do is invoke the test phase of Maven. This way Maven will first invoke all of the phases that are supposed to come before the test phase (validate and compile phases) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 and then invoke all of the plugins that are attached to the test phase, and this way invoking the maven-surefire-plugin. So by calling mvn clean test Maven first cleans the target/ directory, then compiles the source code and the tests and finally executes all of the tests that are in the src/test/java directory (remember the convention). The output should be similar to the one shown in figure 9.3 Fig 9.3 Execution of JUnit tests with Maven2. That’s great, but that if we don’t want to execute all of our tests? What if we want to execute only a single test-case? Well, this is something unconventional so we need to configure the maven-surefire-plugin to do it. Hopefully there is a parameter for the plugin that allows us to specify a pattern of test-cases that we want to get executed. The configuration of the surefire plugin is done in absolutely the same way as the configuration of the compiler plugin and it is listed in the following Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Listing 9.8 Configuration of the maven-surefire-plugin […] maven-surefire-plugin **/Test*.java […] As you can see we have specified the includes parameter to denote that we want only the test-cases matching the given pattern to get executed. Yes, but how do we know what parameters does the maven-surefire-plugin accept? Of course, no one knows all the parameters by heart, but you can always consult the maven-surefire-plugin documentation (and any other plugin documentation) on the maven web-site (http://maven.apache.org/) 9.3.3 HTML JUnit reports with Maven. As we saw in the previous chapter Ant has a task for generating nice reports out of the JUnit’s XML output. The same thing applies for Maven, too. And since Maven, by default produces plain and XML formatted output (by convention they go in the target/surefire-reports folder) we don’t need any other configuration to produce HTML surefire-reports for the JUnit tests. As you already guess, the job for producing these reports is done by a Maven plugin. The name of the plugin is maven-surefire-report-plugin, and is, by default, not attached to any of the core phases that we already know (a lot of people don’t need HTML reports every time they build their software). This means that we can’t invoke it by running a certain phase (like we did with both the compiler plugin and the surefire plugin), but instead we will have to call it directly from the command line: mvn surefire-report:report By doing so Maven will try to compile the source files and the test-cases, and then invoke the surefire plugin to produce the plain text and XML formatted output of the tests. After that the surefire-report plugin will try to transform all of the XMLs from target/surefire- reports/ directory into an HTML report that will be placed in target/site directory (remember that this is the convention for the folder to keep all of the generated documentation of the project, and the HTML reports are considered documentation). If you try to open the generated HTML report it should look something like the one shown in figure 9.4: Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 9.4. HTML report from the maven-surefire-report plugin. 9.4 The bad side of Maven So far we have discussed Maven as the tool that is going to take the place of Ant, as a subsequent Ant. But as all the other things in life – it’s not all a bed of roses. Maven has its bad sides, too. Before you get too excited, just think about it – Maven has been out since 2002, and still Ant is the de facto standard for building software. All of the people that have used Maven agree that it is really easy to start up with it. And the idea behind the project is amazing. However, things seem to break when you need to do some of the unconventional things. What is great about Maven is that it will set up a frame for you and will constrain you to think inside that frame – to think the Maven way, and do things the Maven way. When you work with Maven for a certain period of time, you will inevitably need to copy a file from one place to another. Well, I think you will be surprised to see that there is no copy plugin in Maven, on contrast with Ant where we have the copy task. So you have to deal with the situation and if you investigate further and think “the Maven way”, it may turn out that you never really needed to copy that file. In most of the cases Maven won’t let you do any nonsense. It will restrict you and show you the way things need to be done. Ant works on the other way – it is a very, very powerful tool, and you can do whatever you need. However it can be dangerous in inappropriate Licensed to Alison Tyler Download at Boykma.Com 18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 hands. And, again, it’s up to you to decide which one of these tools you want to use. Some companies hire build engineers, that are considered to have the appropriate knowledge, so for them Ant is no danger at all, it’s just a powerful tool. To finish this chapter I will tell you a story. I have a friend of mine that once had to do a job interview. “Well, is it possible for Ant to do …” – he was asked. Without even letting the interviewer to finish his sentence my friend replied “Yes it is.” I am not so sure what his answer would have been if he was questioned about Maven. 9.5 Summary In this chapter we made a very brief introduction to what Maven is and how to use it in a development environment to build your source code. We discussed in details all of the features of Maven, that make it unique compared to any other build system. We also saw two of the Maven’s plugins in details – the compiler plugin and the surefire plugin. You should be able not only to start and execute your tests but also to produce, nice HTML reports of the test results. In the next chapter we close the automation part of the book, by introducing different continuous integration build tools, like CruiseControl and Hudson. We will show you the benefit of using such a tool, and also how to install and configure each one of these tools. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 10 Continuous integration tools This chapter covers Practicing continuous integration Introduction to CruiseControl Introduction to Hudson Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 “Life is a continuous exercise in creative problem solving.” — Michael J. Gelb In the two previous chapters we described ways to execute your tests automatically by using tools such as Ant and Maven. Our tests were then triggered by the build. Now it’s time to go to the next level – automatically executing the build and the tests on a regular interval of time by using some other popular tools. In this chapter we will get to know the paradigm of continuous integration and will show you how to schedule your project to be built automatically on a certain period of time. 10.1 A taste of continuous integration Integrating the execution of JUnit tests as part of your development cycle - code : run : test : code (or test : code : run : test if you are test-first inclined) - is a very important concept in the sense that JUnit tests are unit tests – i.e. they test a single component of your project in isolation. A great deal of the projects out there, however, have modular architecture, where different developers of the team work on different modules of the project. Each developer takes care developing his own module and his own unit-tests to make sure his module is well-tested. Different modules interact with each other, so we need to have all the different modules get assembled to see how they work together. In order for the application to be test-proven we need another sort of tests – integration or functional tests. As we already saw in chapter 3 these tests test the interaction between different modules. But almost always integration tests are time-consuming, and also as a single developer you may not have all the different modules built on your machine. Therefore it makes no sense to run all the integration tests during development time. That is because at development time we are only focused on our module and all we want to know is that it works as a single unit. During development time we care we care mostly that providing the right input data, the module not only behaves as expected, but also produces the expected result. Test-driven-development taught us to test early and to test often. Executing all of our unit, integration, and functional tests every time we make a small change will slow us immensely. To avoid this we execute at development time only the unit tests – as early and as often as reasonable. What happens with the integration tests? 10.1.1 Integration testing continuously Integration tests should be executed independently from the development process. The best is to have them executed in a regular interval of time (say 15 minutes). This way if something gets broken you will hear about it in the next 15 minutes and there is a better chance for you to fix it. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 DEFINITION1: continuous integration (CI) — "Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily - leading to multiple integrations per day. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible. Many teams find that this approach leads to significantly reduced integration problems and allows a team to develop cohesive software more rapidly." To get the integration tests executed on a regular interval of time we need to have also the modules of the system, be prepared and built. After the modules are built and the integration tests are executed, we would like to see the results of the execution as quickly as possible. This way we get to the point that we need a software tool to do all of the following steps automatically: 1. Check out the project from the source control system; 2. Build each of the modules and execute all of the unit tests to verify that the different modules work as expected in isolation; 3. Execute integration tests to verify that different modules integrate with each other in the expected way; 4. Publish the results from the tests executed in step 3; Now several questions may arise at this point. First, what is the difference between a human executing all these steps and a tool doing so? The answer is: there is no difference and there shouldn’t be! Apart from the fact that no one can bear such a job, if you take a close look at the first thing we do in the list above, you see that we simply checkout the project from the source-control system. We do that as if we were a new member of the team and we just started with the project – with a clean checkout in an empty folder. Then, before moving on we want to make sure that all of the modules work proper in isolation, because if they don’t it doesn’t make much sense to test if they integrate well with the other modules, does it? The last step in the proposed scenario is to notify the developers about the test results. The notification could be done with an email, or an ICQ message, or simply by publishing the reports from the tests on a web server. This overall interaction can be seen in figure 10.1. The CI tool interacts with the Source Control System to get the project (1). After that it uses the build tool, that your project is using, and thus builds your project and executes different kinds of tests (2 and 3). Finally (4) the CI tool publishes the results and blows the whistle so that everybody can see it. 1 This definition is taken from a marvelous article by Martin Fowler and Matthew Foemmel. It can be found here: http://www.martinfowler.com/articles/continuousIntegration.html Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Fig 10.1 Continuous integration scheme. The four steps we have listed above are very general and they could be improved a lot. For instance it would be better to check and see if any changes have been made in the source-control system before we start building. Otherwise we would waste the CPU power of our machine, knowing for sure that we will get the same results. Now that we agree we certainly need a tool to continuously integrate our projects let’s start and see what are the open-source solutions we might want to use (there makes no sense of reinventing the wheel from scratch, when there are good tools already made for us). 10.2 CruiseControl (CC) to the rescue The first open-source2 project we are going to look at is called CruiseControl (http://cruisecontrol.sourceforge.net/) and is currently the de-facto standard when it comes to continuous build process. This project was created inside a company called ThoughtWorks and was actually the first continuous integration server available. 10.2.1 Getting started with CruiseControl The first thing to do before starting to build our code continuously is managing our resources. By this we mean finding a suitable host machine for our CruiseControl (CC) server. You need to manage the resources you have in such manner to dedicate a separate machine just for continuous integration. You may not need this at the beginning, but with the time your project will get bigger and bigger, and the CI build will get longer and longer, so it’s always a better choice of having a separate machine for the integration build. 2 The CruiseControl framework is distributed under its own BSD-style license. The software is OSI-certified open- source solution. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Another good practice is to create an ad-hoc user on the host machine for the CC server. This user will have the right permissions to start the continuous server and execute the builds. Once you find the host for the CruiseControl server, it’s time to install it. The installation procedure is pretty simple - you go to the CruiseControl web-site (http://cruisecontrol.sourceforge.net/) and download the zip distribution of your choice. By the time this book is written the latest version of CruiseControl is 2.7.3 and we are going to use the binary distribution. Once you download the distribution, you need to extract it in a folder (probably the best place to extract the zip is in the home folder of the CC user you just created), which from now on we will refer as $CC_HOME. In the $CC_HOME folder you should see the content we have explained in table 10.1. Table 10.1 Default content of the $CC_HOME folder File/folder Used for apache-ant-distribution Distribution of Apache Ant project that comes bundled with the CruiseControl server distribution. You could choose to use this distribution or any other later on. docs Documentation of the CruiseControl project lib Different third-party libraries to the project logs This folder is used to keep the logs of the continuous builds. projects This folder stores all the projects that you want to build continuously. By default there is a sample project already setup in this folder called connectfour. webapp This folder is used to store a GUI to the build server. config.xml The main configuration file of CruiseControl. cruisecontrol script This .bat or .sh script (depending on the architecture you are using) is used to start CruiseControl. There are several things to notice in the folder structure in the $CC_HOME folder. As you can see the CruiseControl package comes with an Apache Ant distribution. However, this Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 does not mean that you can’t use Maven as a build system. You can use whichever one you like more. We will talk further on this topic later on. Another thing that is worth mentioning is the projects folder. This folder is the place you store your projects you want to build continuously. If you look in that folder you will see a sample project called connectfour. This is a checked out project and before moving on it is good to take a brief look at it. So now that we have found a host for our CriuseControl installation and we have seen how the project is structured, let’s finally start using it by setting up a project to be built. 10.2.2 Setting up a sample project Setting up a project in CruiseControl is pretty straightforward – all you need to do is three things: Check out your project in the projects folder; Configure build configurations and whistles of CruiseControl for the current build; Start CruiseControl; Now let’s have a look at all these steps one by one. Usually we keep the source in a central repository using a source-control system, and that system has the responsibility of managing the version of your software. If you are using Subversion as your revision control system, you can easily checkout your project by first navigating to the $CC_HOME\projects folder and then executing the following command there: svn co http://url.of.your.repository.here/ theNameOfMyProject You need to specify the URL of your source repository and also the name of the folder in which to checkout the project. After that Subversion will check out a local copy of your project in the theNameOfMyProject folder. 10.2.3 The CruiseControl config file explained Now that we have the project checked out in the projects folder it is time to configure CruiseControl to build it the way we want. The configuration itself is done with a file called config.xml. The config script you can find in the $CC_HOME folder, and if you look at it, you should see something like the one in listing 10.1. Listing 10.1. CruiseControl’s config.xml (1) (2) (3) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 (4) (6) (7) (8) (9) This config file describes the build for the connectfour project. We will walk over it and explain what the different parts mean, so then later you can modify the script with your project’s configurations. You start with a global cruisecontrol tag (1), which is required to be there, and inside that global tag you enlist all the projects you want to build (2). You can have multiple projects being built, but that means that you must give all the projects a distinct name (using the name attribute of the project tag). The listeners element (3) is used mainly to enlist different pluggable listener instances. These listeners get notified on every project build event (like start of the project, end of the project, etc.). For instance in our config.xml we define a currentbuildstatuslistener which we use to write the project’s statuses in a file, specified by the file attribute. Listeners in CruiseControl are an interesting creature, so I would like here to spend a few lines on them. As you have probably noticed building the project on a regular interval of time wastes a lot of resources. CruiseControl is very smart in that way that it does not fire a new Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 build, unless it detects some changes in the source-control system. Otherwise the build would be pointless, because we would get the same results. But how do we distinguish of whether CruiseControl server was up, and skipped the build because there were no changes in the source-control system, or on the other hand the server was shut? The answer to this is the listeners. You can configure CruiseControl’s listeners to log everything in a file, somewhere on the file-system. The good side is that the events that activate the listeners are triggered regardless of the state of the source-control system. This way you can keep track of whether a build was attempted or not. The next thing in our config.xml is the bootstrappers element (4). This element is a container element to define some actions that need to be taken before the build is executed. Again, the bootstrappers are run regardless of whether a build is necessary or not. In our config file we have specified an antbootstrapper (5) that will simply invoke the clean target of our project’s build descriptor, and thus will clean all the resources we have used during the previous build. The modificationset element (6) is there to define all the sets of files and folders that CruiseControl will monitor for changes. One thing to remember here is that build is attempted only when change is detected on any of the sets listed in modificationset element. The schedule element (7) is actually the one that schedules the build. The interval parameter specifies the build time interval (in seconds), and inside the schedule element we list what is the type of build-system we are using. We need to specify the home-folder of the build-system, and also the buildfile to execute. In our config file we have specified ant, but you can also use maven without any problems, as well. The log section (8) is optional and is used to specify where the logs of the execution should be stored. The merge element inside it, tells CruiseControl what logs of the build execution are valuable and should be stored in the log directory of the CruiseControl execution. In our example we only care for the xml files from the JUnit execution, so we are going to store them in the log folder. The last section is the publishers section (9) and it is used to make the final steps of the scheduled build. The same ways as bootstrappers are executed every time before the build, the publishers are executed every time after the build, regardless of the result of the build. In publishers we can specify what whistles to be blown as the build finishes. It could be sending an email, publishing the produced artifacts somewhere on the internet, or simply posting a message on the Jabber Messenger. In the example shown above we publish the result from the build (the jar file from the target folder) into the artifacts folder. We do that only in case the result from the execution was successful (see the onsuccess element). So far so good - we are able to start CruiseControl and according to the configuration in the listing it will build our project every 300 seconds (in case there is a change in the source- control system, of course). So let’s do it. Navigate to the $CC_HOME folder and start the CruiseControl server by issuing the following command: Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 cruisecontrol.bat or in case of UNIX3: cruisecontrol.sh After that CruiseControl will be executed and will look for the config.xml file to read. In case you have done all the things right, after execution you should see something similar to figure 10.2. Figure 10.2 Executing CruiseControl for the first time. Now it is time to do a change in the repository, and hopefully after 300 seconds you should see the build being executed again. Here is an interesting question: how often should I build my projects? This is a tough question and it all depends on you and on the time you need to make a new build. There doesn’t make much sense to make a build every minute, when you need more than a minute to execute the build itself, does it? Keep in mind that this is a book about software testing and we all propagate a lot of testing – not only using JUnit, but also using all kinds of integration and functional testing tools. This means that usually production builds tend to be time-consuming. CruiseControl also provides you with a way to control your scheduled builds through a nice GUI, by starting a Jetty instance. You can check the GUI if you visit the http://localhost:8080/ URL in a browser. You should be able to see something like the one 3 You need to check that the .sh script has executable modifiers set. Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 displayed in figure 10.3. You can see there some nice details on how many builds were iterated, some detailed statistics on how many of them failed, and of course the JUnit logs. There’s also an RSS feed that you could subscribe to, to get the results from the execution. Figure 10.3 CruiseControl control panel in the browser. Let’s move on. So things are working now, but currently the way they are doesn’t give us much data. If we look in the console or the GUI we can see the build is going well, but it is a tedious task to look there all the time, isn’t it? What if we had some way to get the results from the execution straight into our email, or even better - get the email from CruiseControl only in case things go bad. This is absolutely possible, and all we have to do is add another publisher in our config.xml. The section we want to add is shown in the following listing: Listing 10.2 CruiseControl’s HtmlEmail notification (4) The htmlemail publisher defines what notification emails to be sent. We start by defining the mailhost to use for sending the emails (1), and also from what address the emails are coming (2). These two, along with the buildresultsurl parameter (the location at which Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 our build results reside) are required and must be present. All the rest parameters are optional – we are able to specify custom CSS stylesheet (3) and also the path to your logs (4). The last touch would be to create aliases to which persons the notifications should go. You use the map element to map the alias to an email. After that you specify on what occasion you want those guys to receive email notifications. By default CruiseControl delivers notification on both success and failure. But that’s too much for the development team, and that’s why we have listed them to receive only emails on failure and when the things get fixed. Now it is time to restart the CruiseControl server, and break the build on purpose. Simply commit something that is breaking the build! In only 300 seconds you should get an email, looking like the one shown in figure 10.3. Figure 10.3 Email notification from CruiseControl server. As you can see the report gives you not only information on what JUnit test failed, but also who was the last guy to make last commit in the source-control system. So it’s pretty easy to follow which member of the team gets credit for breaking the integration. CruiseControl has a very pluggable architecture, and as you saw you can plug different listeners, bootstrappers, to do stuff before the build execution. You can also specify different publishers for different ways of notifying the results from the build execution – along with the htmlemail way we already covered; there is also a publisher to send an instant message on the Yahoo! Messenger, or on a Jabber Messenger, or posting the results on a blog and collecting them through an RSS feed. Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Let us now move on and take a look on another continuous integration server called Hudson. After we have covered both of them you can compare them and choose whichever you want to use. 10.3 Another Neat Tool – Hudson As we mentioned in the beginning of the chapter CruiseControl was probably one of the first continuous integration servers ever available. But there are a whole bunch of other software tools out there, trying to compete with CruiseControl by introducing some interesting new features. Some of those tools are not even free (like AntHill Pro, BeetleJuice 4or Cruise5), and those include not only the product you purchase, but also different trainings and sup ou fact that you decided to practice continuous integration! port. For the sake of completeness we need to cover another tool. This way you can choose whichever you like. Just remember – your software quality will not improve from the tool y chose to use, but rather from the 10.3.1 Introducing Hudson Hudson (http://hudson.dev.java.net/) is an open source project for continuous build. Like any other software for continuous build it lies on the idea of being able to continuously poll the source code from the Source Control System and in case changes were detected fire up a build. Why do we cover it in this chapter? First of all because it grew a lot popular, and different from CruiseControl we already saw. ke sure your JAVA_HOME environment variable points to the place you have installed Jav r, so you can simply start the server from the command line with the following command: ava –jar hudson.war second of all it is very 10.3.2 Installation Before installing Hudson make sure you have J2SE version 1.5 or higher already installed. Also ma a. The installation procedure itself is very easy. You go to the project’s web-site and download the latest version of Hudson. By the time of writing this book the latest version is 1.262. Hudson distribution comes as a single war file, as opposed to CruiseControl where the distribution is a zip. You don’t need to extract the war file, because Hudson comes with a Winstone servlet-containe j Note that if you want to start Hudson this way, all of your logs will go to the console. 4 BeetleJuice is free for open-source projects. 5 Cruise and CruiseControl are not the same! Although they both originated from the same company – ThoughtWorks, CruiseControl was open-sourced and is free to use, while Cruise is still commercial software. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Now in order to start using the server, you need to navigate to the http://localhost:8080/ UR at is sh e application is started. lso - if you don’t want to use the Winstone servlet container, you can use any other ou stick with that solution you will be forced to follow the the Servlet container you use. L. In case no errors occurred you should be able to see something similar to wh own on figure 10.4. Figure 10.4 Hudson initial startup screen. There is also a way to specify different command-line parameters, like the one to redefine the port on which the server is started or the root under which th A servlet container you want. If y installation procedures specific to 10.3.3 Configuring Hudson Hudson’s feature over CruiseControl is easier configuration. Configuration is all done through the web interface. And now that you have already installed Hudson it is time to start the configuration. Open a browser and navigate to the http://localhost:8080/ URL. You should see the Hudson welcome screen and there should be a “Manage Hudson” link on the left side. Once you click it you would be given a list of additional links leading to the different parts of the installation you want to configure. Click on the first one that says “Configure System” and it would open a web-page similar to the one shown in figure 10.5. Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 10.5 Hudson configuration screen. As you already saw Hudson, on contrast to CruiseControl, comes with no Ant installation. This way the tool needs to know where you have installed Ant, Maven, JDK and so forth. All this you need to specify through the configuration page shown on figure 10.5. The first line on the configuration page is named “Home directory”. The home directory of Hudson is an interesting creature so we will spend a subsection on it. HUDSON HOME DIRECTORY The home directory of Hudson is used to maintain the source, perform builds and keep some archives there. By default it is located in $USER_HOME/.hudson ($USER_HOME is interpreted as /home/ in the UNIX systems and as C:\Documents and Settings\\ in Windows). The default location of the Hudson home directory can be changed either by setting the “HUDSON_HOME” environment variable, or by setting the “HUDSON_HOME” servlet- container property. If you take a sneak in the HUDSON_HOME directory you should see a folder structure similar to this one: Listing 10.3 Hudson home directory folder structure [HUDSON_HOME] Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 +- config.xml (hudson root configuration) (1) +- fingerprints (stores fingerprint records) +- plugins (stores plugins) (2) +- jobs (3) +- [JOBNAME] (sub directory for each job) +- config.xml (job configuration file) +- workspace (working directory for the version control system) +- latest (symbolic link to the last successful build) +- builds +- [BUILD_ID] (for each build) +- build.xml (build result summary) +- log (log file) +- changelog.xml (change log) Inside the home directory Hudson keeps a configuration file (1), different plugins (2) and all the jobs that it runs (3). The jobs, as known in Hudson are different projects that you build. Each job can have multiple builds so that you can easily follow which one failed, and what was the cause for the failure. Moving forward in the configuration page there are also some options given to specify the path to your Ant installation (in case the project you want to build uses Ant) or your Maven installation (in case your project is being built by Maven). You can also specify a JDK installation, a CVS installation and E-mail notification installations (like the Email server, username and password). Take a note here that you don’t specify the path to your build.xml files, but instead you point to the place where Ant was installed, so that later on Hudson can to talk to that Ant installation and issue the ant –f build.xml command. 10.3.4 Configuring a project in Hudson Now that you have configured Hudson to find the installations of Ant, Maven, and the rest you can move on and configure a new job. To configure a new job with Hudson, first navigate to the main screen and select the “New job” link from the list on the left side. After that you will be presented with a sample form to fill-in. You need to specify a name for the job, and make sure you choose one of the presented build-options. If your build is Maven2- based make sure you select a “Build a maven2 project,” otherwise go with the “Build a free- style software project” option. After you click the OK button you will be presented the job- configuration screen shown on figure 10.6. Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 10.6 Job-configuration screen in Hudson Here you are given the ability to configure the way you want to build your job. The first lines you use to specify/change the name and the description of the job. After that there are also some options regarding the Source Control Management (SCM) System you use (Subversion, CVS, etc.). The next section tunes the settings for the build triggers – on what occasion you want to trigger your build. You are presented with several options – poll the SCM system to check if build is needed, or build the project periodically, or build it after some dependent projects were built, etc. Let us select the “Poll the SCM” trigger – a field opens where we need to specify on what interval of time we want the poll to happen. This field uses a very nice syntax that follows the syntax of the UNIX cron tool. We would like to have our project get executed every hour so we simply specify @hourly in the field. You can learn more about the cron syntax if you click on the corresponding question mark next to the trigger. The next section deals with invoking the build itself. You can specify either to execute a shell script, or Windows batch file, or Ant build file, or Maven build file. On any of these you can specify any parameters, targets, goals, etc. You can also arrange multiple build steps, like first invoking a shell script and then running Ant. There is also the ability to rearrange all these steps by dragging and dropping. The last section is to configure the post-build triggers. The options listed here will help you to publish the artifact, or publish the javadoc, or build some other project, or send an email with the build results, or anything else you need. There is also an option of selecting multiple triggers. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 After all this you can go on and save the job-configuration. This will lead you to the project’s home-page (shown on figure 10.7). Figure 10.7 Hudson job homepage From job’s homepage you have the ability to keep track of the current job. From the menu on the left side you can select to see the changes one has made on the job, inspect the workspace of the job, delete the project, configure it or simply schedule another build. You can also subscribe for the build-results RSS feeds. We don’t want to wait another hour for the build to be triggered, so let’s execute it right now. On the job’s homepage click the “Build now” link and wait for the build to finish. After that you can see the results of the build execution on the job’s home page. There you can see not only when the last build was run, but also when the last successful build happened. After clicking on any build number that happened you can explore the build itself – what modules were build, what tests failed or succeeded, and most important – why, etc (see figure 10.8). Licensed to Alison Tyler Download at Boykma.Com 18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 10.8 Hudson build results After spending some time using Hudson you will probably find it a lot easier to use – its entire configuration is done through a nice web-interface, and it is relatively easy. But the nicest thing is that the web-interface is very intuitive. That is why we don’t cover Hudson in details. Another reason for this is that Hudson currently undergoes a very rapid development. It is a very mature project with a large community of developers that constantly improve the codebase – from that point of view it is very interesting to see how the project will evolve in time. 10.4 Benefits of continuous integration As a general benefit the software tests are there to help you find your own errors. You execute the unit tests every time you have made any changes on the code you develop. This way they cover your back and will alert you whenever you introduce a new bug into the system. The continuous integration servers serve exactly the same reason – they cover your back and alert you the moment you break the integration with the other modules of the system. And since you can’t run the CI tools manually every time you make a small change in the system, they run on a separate host and try to continuously integrate your software. But again – they cover your back and will alert you on a change that breaks the integration. I have heard a lot of excuses from different people on why you shouldn't use CI tools, and looks like the winner is this one – “I don’t see the profit in using it.” And usually this comes from people that have never used a CI server. We all make errors – face it! You and me, and everybody else – it’s human to err. And no matter how good you are, you will sometimes make a mistake and introduce a bug into the system. Knowing this it seems reasonable to have something to prevent us from errors by Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 notifying us when those errors occur. The software tests and CI tools are exactly this. Once you start using a CI tool, no matter which one – a CruiseControl or Hudson or anything else, you will see how good it is to know that something is watching your back and that an empty mailbox means that nothing is screwed. So to sum up I would say: CI tools – free Installation and configuration– a couple of hours work. Knowing your build is well integrated – priceless ☺ 10.5 Summary In this chapter we looked at two of the most popular CI tools out there – CruiseControl and Hudson. As you probably saw, they are totally different. So why do we cover exactly those tools? To make you understand that continuous integration is a very important concept in the modern software development lifecycle, and that there is absolutely no difference whichever tool you use, but instead it makes a great difference whether you use CI or not. With this chapter we closed the part of the book that deals with integrating JUnit with the build process. You should be now ready to run your build and execute your tests with Ant or Maven, and not only that, but also to set up a continuous integration build and execute your builds and tests on a scheduled basis. This way you have JUnit tests that keep your modules from new bugs, and you have also a workspace where you execute continuously your build and run your tests to see if anything got broken during the integration time. You are now fully automated and ready to move on with the next chapters. The next part of the book deals with testing different layers of your application. We will look at some examples on how to execute tests against the presentation layer and also on the database and persistence layer. We will see a way to test your GUI components, and of course, you can include those tests and run them in the continuous integration environment you just learned about. Let’s move on! Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Part IV JUnit extensions This last part of the book deals with different kinds of JUnit extensions. We cover all kinds of external projects that try to extend JUnit to the point where the testing framework comes short. They all test different aspects and layers of an enterprise application. We start by introducing the HTMLUnit and the Selenium projects in the eleventh chapter. We will show you how to test your presentation layer with these projects. We go into details of not only how to setup your projects but also some best-practices in testing your presentation layer. Chapter twelve is dedicated, again, on testing your frontend. In that chapter we discuss on how to test the AJAX part of your application. We also discuss on how to test GWT applications with tools like Mozilla MCP. On testing your server-side Java code is dedicated chapter thirteen and fourteen. The first one introduces the Cactus project which is dedicated on testing your J2EE core components (JSPs, Servlets, EJBs, …). Chapter fourteen describes the JSFUnit project, which tests your JSF-based applications. Chapter fifteen is a small chapter that scatters around testing component-oriented applications. We describe different techniques for testing your OSGi services. Chapter sixteen and seventeen are all dedicated on testing the persistence layer. These chapters talk about the DBUnit project and give recipes of how to test JPA part of an application. The last chapter describes on how to create your own extensions by means of the Unitils and JUnit-Addons projects. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 11 Presentation Layer Testing This chapter covers: • Introducing presentation layer testing • Testing with HtmlUnit • Testing with Selenium Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 If debugging is the process of removing software bugs, then programming must be the process of putting them in. - Edsger Dijkstra Simply stated, presentation layer testing is finding bugs in the graphical user interface (GUI) of an application. Finding errors here is as important as finding errors in other application tiers. A bad user experience can lose a customer or discourage a web surfer from visiting your site again. Furthermore, bugs in the user-interface may cause other parts of the application to malfunction. Due to its nature and interaction with a person, GUI testing presents unique challenges and requires its own set of tools and techniques. This chapter will cover testing web application user interfaces. We address here what can be objectively, that is programmatically, asserted about the GUI. Outside the scope of this discussion are whether the choice of subjective elements like fonts, colors, and layout cause an application to be difficult or impossible to use. What we can test is the content of web pages to any level of detail (we could include spelling), the application structure or navigation (following links to their expected destination for example), and the ability to verify user-stories with acceptance tests1. We can also verify that the site works with required browsers and operating systems. 11.1 Choosing a Testing Framework We will look at two free open source tools to implement presentation layer tests within JUnit: HtmlUnit and Selenium. HtmlUnit is a 100% Java headless browser framework that runs in the same virtual machine as your tests. Use HtmlUnit when your application is independent of operating system features and browser specific implementations of JavaScript, DOM, CSS, etc, not accounted for by HtmlUnit. Selenium drives various web browsers programmatically and checks the results from JUnit. Selenium also provides a simple IDE to record and playback tests and can generate test code. Use Selenium when you require validation of specific browsers and operating systems, especially if the application takes advantage of or depends on a browser’s specific implementation of JavaScript, DOM, CSS, etc. Let us start with HtmlUnit. 11.2 Introducing HtmlUnit HtmlUnit is an open-source Java headless browser framework. It allows tests to imitate programmatically the user of a browser-based web application. HtmlUnit tests do not display a user interface. The framework lets you test all aspects of a web application, we will 1 Extreme programming acceptance tests http://www.extremeprogramming.org/rules/functionaltests.html Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 describe here the most common tasks, for the rest, you will find the API quite intuitive and easy to use. In the remainder of this HtmlUnit section when we talk about “testing with a web browser,” it is with the understanding that we are really “testing by emulating a specific web browser.” To install HTMLUnit, see Appendix E: Installing Software. 11.2.1 A live example Let us jump in with an example you can test now assuming you can connect to the internet. We will go to the HtmlUnit web site, navigate the Javadoc, and make sure a class has the proper documentation. Listing 11.1 Our first HtmlUnit example […] public class JavadocPageTest { @Test public void testClassNav() throws IOException { WebClient webClient = new WebClient(); (1) HtmlPage mainPage = (HtmlPage) webClient.getPage( (2) "http://htmlunit.sourceforge.net/apidocs/index.html"); (2) HtmlPage packagePage = (HtmlPage) mainPage.getFrameByName( (3) "packageFrame").getEnclosedPage(); (3) HtmlPage bVerPage = packagePage.getAnchorByHref( (4) "com/gargoylesoftware/htmlunit/BrowserVersion.html").click(); (4) HtmlParagraph p = (HtmlParagraph) bVerPage.getElementsByTagName( (5) "p").item(0); (5) Assert.assertTrue("Unexpected text", p.asText().startsWith( (6) "Objects of this class represent one specific version of a given")); webClient.closeAllWindows(); (7) } } Let us step through the example: We always start by creating a HtmlUnit web client (1), which gives us an Internet Explorer 7 web client by default. We get to the home page from the web client (2), then to the list of classes on the page in the bottom left frame (3). Next, we get the link for the class we are interested in, and click on it as a user would (4). This gives us a new page for the link we clicked, which we then query for the first paragraph element (5). Finally, we check that the paragraph starts with the text we expect should be there (6) and release resources (7). This example covers the basics: getting a web page, navigating the HTML object model, and asserting results. You will notice a lack of standard JUnit assertions in the code that navigates the HTML model, if HtmlUnit does not find an element or encounters a problem; it will throw an exception on our behalf. Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 11.3 Writing HtmlUnit tests When you write an HtmlUnit test, you write code that simulates the action of a user sitting in front of a web browser: you get a web page, enter data, read text, and click on buttons and links. Instead of manually manipulating the browser, you programmatically control an emulated browser. At each step, you can query the HTML object model and assert that values are what you expect. The framework will throw exceptions if it encounters a problem, which allows your test cases to avoid checking for these errors, reducing clutter. 11.3.1 HTML Assertions As we are familiar with by now, JUnit provides a class called Assert to allow tests to fail when they detect an error condition. Assert is the bread and butter of any unit test. HtmlUnit provides a class in the same spirit called WebAssert, which contains standard assertions for HTML like assertElementPresent, assertLinkPresent and assertTextPresent. HtmlUnit itself uses WebAssert notNull extensively to guard against null parameters. Make sure to check the WebAssert class before you write code that may duplicates its functionality. If a method you need is absent, you should consider creating your own assert class for additional HTML assertions. You should also consider creating an application specific assertion class to reuse across your unit tests. Remember, code duplication is the enemy. 11.3.2 Testing for a specific web browser HtmlUnit, as of version 2.5, supports the following browser: Web Browser and Version HtmlUnit BrowserVersion Constant Firefox 2 BrowserVersion.FIREFOX_2 Firefox 3 BrowserVersion.FIREFOX_3 Internet Explorer 6 BrowserVersion.INTERNET_EXPLORER_6 Internet Explorer 7 BrowserVersion.INTERNET_EXPLORER_7 Table 11.1 HTMLUnit supported browsers. By default, WebClient emulates Internet Explorer 7. In order to specify which browser to emulate you provide the WebClient constructor with a BrowserVersion. For example, for Firefox 3, use: WebClient webClient = new WebClient(BrowserVersion.FIREFOX_3); 11.3.3 Testing more than one web browser You will probably want to test your application with the most common version of Internet Explorer and Firefox. For our purposes, we define our test matrix to be all HtmlUnit supported web browsers. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The following example uses the JUnit Parameterized feature to drive the same test with all browsers in our text matrix. Let us see it and step through it. Listing 11.2 Testing for all HtmlUnit supported browsers […] @RunWith(value = Parameterized.class) (1) public class JavadocPageAllBrowserTest { private BrowserVersion browserVersion; (3) e@Param ters public static Collection getBrowserVersions() { (2) return Arrays.asList(new BrowserVersion[][]{ {BrowserVersion.FIREFOX_2}, {BrowserVersion.FIREFOX_3}, {BrowserVersion.INTERNET_EXPLORER_6}, {BrowserVersion.INTERNET_EXPLORER_7}}); } public JavadocPageAllBrowserTest(BrowserVersion browserVersion) { (4) this.browserVersion = browserVersion; } @Test public void testSearchPage() throws Exception { (5) WebClient webClient = new WebClient(this.browserVersion); // same as before… } } Based on our previous example, we have made the following changes: We use the Paramerized JUnit test runner (1). We have added the method getBrowserVersions (2) to return a list of BrowserVersion objects corresponding to the browsers we want to test. The signature of this method must be @Parameters public static java.util.Collection, without parameters. The Collection elements must be arrays of identical lengths. This array length must match the number of arguments of the only public constructor. In our case, each array contains one element since the public constructor has one argument. We have added a BrowserVersion instance variable (3) to track the browser context. Let us step through the test: JUnit calls the static method getBrowserVersions (3). JUnit loops for each array in the getBrowserVersions collection (3). JUnit calls the only public constructor (4). If there is more than one public constructor, JUnit will throw an assertion error. JUnit calls the constructor (4) with an argument list built from the array elements. In our case, JUnit calls the one argument constructor (4) with the only element in the array. JUnit then calls each @Test method (5) as usual. Repeat the process for the next array in the getBrowserVersions collection (2). When you compare the test results with the previous example, you see that instead of running one test, the parameterized JUnit test runner ran the same method four times, once for each value in our @Parameters collection. Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 11.3.4 Creating stand-alone tests You may not always want to use actual URL addressed pages as test fixtures, http, file, or otherwise. Next, we will show you how to embed and run HTML in the unit test code itself. The framework allows you to plug in a mock2 HTTP connection into a web client. In the next example, we set up a mock connection with a default HTML response String. The test can then get this default page by using any URL value. Listing 11.3 Configuring a stand-alone test […] public class InLineHtmlFixtureTest { @Test public void testInLineHtmlFixture() throws Exception { final String expectedTitle = "Hello 1!"; String html = "" + expectedTitle + ""; WebClient webClient = new WebClient(); MockWebConnection conn = new MockWebConnection(); (1) conn.setDefaultResponse(html); (2) webClient.setWebConnection(conn); (3) HtmlPage page = webClient.getPage("http://page"); Assert.assertEquals(expectedTitle, page.getTitleText()); webClient.closeAllWindows(); } […] Let us step through the example: We start by defining our expected HTML page title and HTML test fixture. Then we create the web client, a MockWebConnection (1), and install the HTML fixture as the default response for the mock connection (2). We can then set the web client’s connection to our mock connection (3). We are now ready to go, and we get the test page. Any URL here will do here since we set up our HTML fixture as the default response. We finally check that the page title matches our HTML fixture. To configure a test with multiple pages, you call one of the MockWebConnection setResponse methods for each page. The next example sets up three web pages in a mock connection. Listing 11.4 Configuring a test with multiple page fixtures @Test public void testInLineHtmlFixtures() throws Exception { ient webClient = new WebClient(); WebCl final URL page1Url = new URL("http://Page1/"); final URL page2Url = new URL("http://Page2/"); final URL page3Url = new URL("http://Page3/"); MockWebConnection conn = new MockWebConnection(); conn.setResponse(page1Url, 2 See Chapter 6: Mock Objects Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 "Hello 1!"); (1) conn.setResponse(page2Url, "Hello 2!"); conn.setResponse(page3Url, "Hello 3!"); webClient.setWebConnection(conn); HtmlPage page1 = webClient.getPage(page1Url); (2) Assert.assertEquals("Hello 1!", page1.getTitleText()); (3) HtmlPage page2 = webClient.getPage(page2Url); Assert.assertEquals("Hello 2!", page2.getTitleText()); HtmlPage page3 = webClient.getPage(page3Url); Assert.assertEquals("Hello 3!", page3.getTitleText()); webClient.closeAllWindows(); } This example installs three pages (1) in the mock connection and tests getting each page (2) and verifying each page title (3). Common Pitfall Do not forget the trailing / in the URL; “http://Page1/” will work but “http://Page1” will not be found in the mock connection and therefore throw an exception. 11.3.5 Navigating the object model HtmlUnit provides an object model that parallels the HTML object model. You will use it to navigate through your application’s web pages. Let us explore it now. To get to an HTML page, you always start with a WebClient and call getPage. WebClient webClient = new WebClient(); HtmlPage page = (HtmlPage) webClient.getPage("http://www.google.com"); HtmlPage is HtmlUnit’s model of an HTML page returned from a server. Once you have a page, you access its contents in one of three ways: • Call methods reflecting specific HTML concepts like forms, anchors, and frames. • Call methods that address HTML elements by references using names and ids. • Call methods using XPath, a web standard for addressing XML document nodes. We will now look at each technique. 11.3.6 Accessing elements by specific element type The HtmlPage API provides methods reflecting the HTML element model: For anchors, getAnchorByName, getAnchors , and others; for a Body, getBody; for forms: getFormByName, getForms; for frames: getFrameByName, getFrames; for meta tags, getMetaTags. We will specifically explore how to work with form and frame elements in sections below. Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 11.3.7 Accessing elements by name vs. index This discussion applies to all methods that return a List: getAnchors, getForms, and getFrames. You should consider the implication of addressing these lists with indices. For example, consider the following snippet. HtmlForm form = page.getForms().get(0); The index access creates an assumption in your test that the HTML form you want to test will always be the first form in the list. If the page changes and the search form changes position, your test will fail, even though the page’s functionality may not have changed. By addressing the form by index, you are explicitly testing the form order on the page. Only address an element through a list index if you want to test the order of an element in that list. To make the code resistant to list order change, replace: HtmlForm form = page.getForms().get(0); With: HtmlForm form = page.getFormByName("f"); Well, you might say, now you have a dependency on the form name “f” instead of on form position 0. The benefit is that when you change the form order on a page, the form name does not have to change, but the form index must. Lists are useful when the order of its elements matter. You may want to assert that an anchor list is alphabetical or that a product list in ascending price order. 11.3.8 Accessing elements with references As we just saw, HtmlPage allows you to get specific elements by name. HtmlPage also lets you get to any element by name, id or access key with any the methods starting with getElementBy like getElementById, getElementsByName and others. These methods allow you to ask generic questions about the HTML model. For example, when we wrote: HtmlForm form = page.getFormByName("f"); We ask specifically for a form named “f”. We can also write: HtmlForm form = (HtmlForm) page.getElementsByName("f").get(0); Which asks for all elements named “f”, and then asks for the first element of that list. Note two changes in the code: First, we cast the result to the desired type unless you can work with an HtmlElement. Second, since element names are not unique in a page, getElementsByName returns a List of HtmlElement; which is why we have the call to get. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 If you can address the desired element by ID, you can use getElementById and do away with the get call. Of course, calling get introduces some brittleness to this test since we are introducing a dependency on the list order. If we wanted a more resilient test, and the element did not contain an id, we would need to resort to one of the following: • Traverse the list until you find the right element. • Use getChildren or getChildNodes to navigate down to the desired element. Both options are not appealing, so the lesson here is to use HTML IDs if you can. This will allow you to create tests more resistant to change. In general, for each HTML {Element}, there is a class called Html{Element}, for example, HtmlForm. Some class names are more explicit than their HTML element name, for the HTML element “a”, the class is HtmlAnchor, for “h1”, the class is HtmlHeading1. 11.3.9 Using XPath Use XPath3 for complex searches to reduce test code complexity. XPath is a language specified by the W3C for querying nodes in an XML document. We will not cover the XPath language itself here; we will focus on its usage in HtmlUnit to perform two types of tasks: getting to a specific element and gathering data. ACCESSING ELEMENTS WITH XPATH You call one of two methods to run an XPath query: getByXPath returns a list of elements and getFirstByXPath returns a single element. Since DomNode implements both methods, it is accessible not only to HtmlPage but to all DomNode subclasses, which includes the HTML classes. Knowing what XPath expression to use can involve a lot of trial and error. Fortunately, you can inspect any web site with XPath Checker4 or Firebug5, free open source Firefox add- ons, and create an XPath from the current selection. For example, to access the text input field on Google’s home page, use: /html/body/center/form/table/tbody/tr/td[2]/input[2] Note that expressions generated automatically from such tools usually suffer from the same indexing issue we discussed earlier in the section Accessing elements by name vs. index. By inspecting the code, you can create the following expression that is more resilient to changes in the page: //input[@name='q'] 3 XPath 1.0: http://www.w3.org/TR/xpath 4 XPath Checker: http://slesinsky.org/brian/code/xpath_checker.html 5 Firebug: http://getfirebug.com/ Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 We all know there is no such thing as a free lunch6, and this expression’s gain in page change resilience and brevity comes with a small performance price: XPath must find all input elements on the page that match the criteria [@name='q'] and then give us the first one. This is in contrast to the first expression which just drills down to a know spot in the page or fails along the way if an element is missing. To run this XPath query, the call is: page.getFirstByXPath("//input[@name='q']"); We will look next at a very powerful XPath feature supported by the HtmlUnit API, the ability to collect data. DATA GATHERING WITH XPATH An extremely powerful feature of XPath is its ability to return a set of nodes. This feature allows us to perform, with one expression, a query that returns a data set. This is a great way to gather values on a page, whether or not the values are formally present in a list-like structure like an HTML table, list, or form. For example, this expression returns an anchor list from the Java 6 Javadoc page for all package names: //a[contains(@href, 'package-frame.html') and @target='packageFrame'] To see this XPath expression in action, go to the Java 6 Javadoc page: client = new WebClient(); mainPage = (HtmlPage) client.getPage("http://java.sun.com/javase/6/docs/api/index.html"); Then to the package list page: HtmlPage packageListPage = (HtmlPage) mainPage.getFrameByName("packageListFrame").getEnclosedPage(); From that page, we can gather all links that point to a Java package: List anchors = (List) packageListPage.getByXPath("//a[contains(@href, 'package-frame.html') and @target='packageFrame']"); Beware that there is an XPath version 1.07 and 2.08 specification. HtmlUnit includes Apache Xalan XPath implementation, which only supports 1.0. If you want to use XPath 2.0 features, you need to get an XPath 2.0 engine, which usually means an XSL 2.0 engine, like Saxon. You will also need to write some code, an advanced endeavor. 6 TANSTAAFL: http://en.wikipedia.org/wiki/TANSTAAFL 7 XPath 1.0: http://www.w3.org/TR/1999/REC-xpath-19991116 8 XPath 2.0: http://www.w3.org/TR/xpath20/ Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 11.3.10 Test failures and exceptions Tests check for error condition with the JUnit Assert class, the HtmlUnit WebAssert class and by letting the HtmlUnit API throw unchecked exceptions. We have already covered the WebAssert class in the section HTML Assertions. For example, if you query for a form with an invalid name by calling HtmlPage getFormByName, you will get the exception: com.gargoylesoftware.htmlunit.ElementNotFoundException: elementName=[form] attributeName=[name] attributeValue=[unknown_element] If you call WebClient getPage and the page does not exist, you will get the exception: java.net.UnknownHostException: unknown_page. HtmlUnit defines exceptions like ElementNotFoundException in the package com.gargoylesoftware.htmlunit. To verify that a method throws an expected exception, annotate the method with the expected attribute: @Test(expected = ElementNotFoundException.class) Since these exceptions are all unchecked, you do not have to throw them from your methods but you will need to remember to catch them if you want to examine the particular state of a failure. For example, the exception ElementNotFoundException contains specific information as to what exactly caused the failure: the name of the element, the name of attribute, and the attribute value. While not explicitly documented in the WebAssert Javadoc, WebAssert methods will throw exceptions for unexpected conditions. In fact, many WebAssert methods throw ElementNotFoundException. JAVASCRIPT AND SCRIPTEXCEPTION By default, all JavaScript errors will throw a ScriptException and cause your unit test to fail. This may not be acceptable, especially if you are testing integration with third party sites or if the exception is due to a shortcoming in the Mozilla JavaScript library or in HtmlUnit itself. You can avoid aborting your unit test on a JavaScript error by calling setThrowExceptionOnScriptError on a web client: webClient.setThrowExceptionOnScriptError(false); 11.3.11 Application and Internet Navigation You can navigate through an application and the web in general by getting an HTML page, then clicking on a link or clicking on a user interface element like a button. The API can perform all forms of navigation. Let us look at various types of navigation. PAGE NAVIGATION Getting a page is performed with the WebClient getPage() methods. You can get a page by URL or URL String. For example: Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 WebClient webClient = new WebClient(); webClient.setThrowExceptionOnScriptError(false); HtmlPage page = (HtmlPage) webClient.getPage("http://www.google.com"); HtmlPage page2 = (HtmlPage) webClient.getPage(new URL("http://www.google.com")); If a page is absent or is not reachable, the API throws an exception. See the section Test failures and exceptions. CLICK NAVIGATION The click and dblClick methods conveniently navigate through a link or any “clickable” user interface element. For example, continuing from above, we enter a web query and click on the search button: HtmlForm form = page.getFormByName("f"); HtmlTextInput queryText = (HtmlTextInput) form.getInputByName("q"); queryText.setValueAttribute("Manning Publications Co."); HtmlSubmitInput searchButton = (HtmlSubmitInput) form.getInputByName("btnG"); HtmlPage resultPage = (HtmlPage) searchButton.click(); You can call the click and dblClick methods on all classes descending from ClickableElement. Click methods simulate clicking on an element (remember HtmlUnit is an emulator) and return the page in the window that has the focus after the element has been clicked. The HTML 4.01 specification9 defines "clickable" HTML elements. ClickableElement is the base class for all HTML elements except: applet, base, basefront, bdo, br, font, frame, frameset, head, html, iframe, isindex, meta, param, script, style, and title. See the ClickableElement Javadoc10 or select ClickableElement in Eclipse and hit F4 to display the class hierarchy. KEYBOARD NAVIGATION To simulate the user hitting the Enter key instead of clicking the Search button, replace getting and clicking the search button with the following. HtmlPage resultPage = (HtmlPage) queryText.type('\n'); You can code the Enter key with the '\n' character. You can also simulate the user tabbing around the page with the HtmlPage methods tabToNextElement and tabToPreviousElement. Hitting the Enter key or any key may not be enough or the right process to test. You can set the focus to any element with the HtmlPage method 9 http://www.w3.org/TR/html401/ 10 http://htmlunit.sourceforge.net/apidocs/com/gargoylesoftware/htmlunit/html/ClickableElement.html Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 setFocusedElement. Be aware that this will trigger any onfocus and onblur event handlers. Let us now put these concepts together with another example and test forms. 11.3.12 Testing forms HTML form support is built into the HtmlPage API where form elements can be accessed with getForms (returns List) to get all form elements and getFormByName to get the first HtmlForm with a given name. You can call one of the HtmlForm getInput methods to get HTML input elements, and then simulate user input with setValueAttribute. The following example will focus on the HtmlUnit mechanics of driving a form. First, we create a simple page to display a form with an input field and submit button. We include form validation via JavaScript alerts in the example as a second path to test, which is described in the section Testing JavaScript Alerts. Listing 11.5 Example form page Form
Value:
This form looks like the following when you click the button without input. Figure 11.1 Sample form and alert Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 We test normal user interaction with the form as follows. Listing 11.6 Testing a form @Test public void testForm() throws IOException { WebClient client = new WebClient(); HtmlPage page = (HtmlPage) client.getPage("file:src/main/webapp/formtest.html"); HtmlForm form = page.getFormByName("validated_form"); HtmlTextInput input =(HtmlTextInput) form.getInputByName("in_text"); input.setValueAttribute("typing..."); (1) HtmlSubmitInput submitButton = (HtmlSubmitInput) form.getInputByName("submit");(2) HtmlPage resultPage = (HtmlPage) submitButton.click(); (2) WebAssert.assertTitleEquals(resultPage, "Result"); // more asserts… } Let us step through the example. We create the web client, get the page containing the form, and get the form. Next, we get the input text field from the form, emulate the user typing in a value (1), then get and click the submit button (2). We get a page back from clicking the button and we make sure we it is the expected page. If at any step, the framework does not find an object, the API throws an exception and your test automatically fails. This allows you to focus on the test and let the framework handle failing your test if the page or form is not as expected. The section Testing JavaScript Alerts completes this example. 11.3.13 Testing frames HTML frame support is built into the HtmlPage API where frames can be accessed with getFrames (returns List) to get all iframes and frames and getFrameByName to get the first iframe or frame with a given name. You then call FrameWindow getEnclosedPage to get the HTML page in that frame. The following example navigates through the Java 6 Javadoc. Listing 11.7 Page navigation through frames @Test public void testFramesByNames() throws IOException { WebClient webClient = new WebClient(); HtmlPage mainPage = (HtmlPage) webClient.getPage("http://java.sun.com/javase/6/docs/api/index.html"); // Gets page of the first Frame (upper left) HtmlPage packageListPage = (HtmlPage) mainPage.getFrameByName("packageListFrame").getEnclosedPage(); packageListPage.getAnchorByHref("java/lang/package- frame.html").click(); // get page of the Frame named 'packageFrame' (lower left) HtmlPage packagePage = (HtmlPage) mainPage.getFrameByName("packageFrame").getEnclosedPage(); Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 packagePage.getAnchors().get(1).click(); // get page of the Frame named 'classFrame' (right) HtmlPage classPage = (HtmlPage) mainPage.getFrameByName("classFrame").getEnclosedPage(); webClient.closeAllWindows(); } This example uses getFrameByName to get frames and then calls getEnclosedPage. Test can use the list API getFrames as well but we point you to the issues discussed in Accessing elements by name vs. index earlier in this chapter. The intermediary FrameWindow returned by getFrameByName is not used in this example. Just note that it represents the actual web window for a frame or iframe and provides APIs to dig deeper through the GUI like getFrameElement, which returns a BaseFrame. BaseFrame in turn provides access to attributes like longdesc, noresize, scrolling, etc. By now, you should have the hang of using the API, so let us move on to JavaScript, CSS, and other topics. 11.3.14 Testing JavaScript HtmlUnit processes JavaScript automatically. Even when, for example, HTML is generated with Document.write(), you follow the usual pattern: call getPage, find an element, call click on it, and check the result. You can toggle JavaScript support on and off in a web client by calling setJavaScript Enabled. HtmlUnit enables JavaScript support by default. You can also set how a long a script is allowed to run before being terminated with setJavaScriptTimeout and passing it a timeout in milliseconds. To deal with JavaScript alert and confirm calls, you can provide the framework with callbacks routines. We will explore these next. TESTING JAVASCRIPT ALERTS Your tests can check which JavaScript alerts have taken place. We will re-use our form example from Testing forms, which includes JavaScript validation code to alert the user of empty input values. The following test loads our form page and checks calling the alert when the form detects an error condition. In a second example, we will enhance our existing test from Testing forms to ensure that normal operation of the form does not raise any alerts. Our test will install an alert handler that gathers all alerts and will check the result after the page has been loaded. The stock class CollectingAlertHandler saves alert messages for later inspection. Listing 11.8 Asserting expected alerts @Test public void testFormAlert throws IOException { () WebClient webClient = new WebClient(); CollectingAlertHandler alertHandler = new CollectingAlertHandler(); (1) webClient.setAlertHandler(alertHandler); (2) Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 HtmlPage page = (HtmlPage) webClient.getPage("file:src/main/webapp/formtest.html"); (3) HtmlForm form = page.getFormByName("validated_form"); (3) HtmlSubmitInput submitButton = (HtmlSubmitInput) (3) form.getInputByName("submit"); (3) HtmlPage resultPage = (HtmlPage) submitButton.click(); (3) assertEquals(resultPage.getTitleText(), page.getTitleText()); assertEquals(resultPage, page); List collectedAlerts = alertHandler.getCollectedAlerts(); (4) List expectedAlerts = Collections.singletonList("Please enter a value."); assertEquals(expectedAlerts, collectedAlerts); webClient.closeAllWindows(); } Let us step through the example: We start by creating the web client and alert handler (1), which we install in the web client (2). Next, we get the form page, get the form object, get the submit button and click on it (3). This invokes the JavaScript, which calls alert. Clicking the button returns a page object, which we use to check that the page has not changed by comparing current and previous page titles. We also check that the page has not changed by comparing current and previous page objects. Note that this comparison uses Object equals, so we are really asking if the page objects are identical. This might not be a great test if a future version of the framework implements equals in an unexpected manner. Finally, we get the list of alert messages that were raised (4), create a list of expected alert messages, and compare the expected and actual lists. JUnit Tip When using any assertion that use the equals methods, make sure you understand the semantics of the equals implementation of the objects you are comparing. The default implementation of equals in Object returns true if the objects are the same. Next, let us rewrite the original form test to make sure that normal operation raises no alerts. Listing 11.9 Asserting no alerts under normal operation @Test public void testFormNoAlert() throws IOException { WebClient webClient = new WebClient(); CollectingAlertHandler alertHandler = new CollectingAlertHandler(); (1) webClient.setAlertHandler(alertHandler); (1) HtmlPage page = (HtmlPage) webClient.getPage("file:src/main/webapp/formtest.html"); HtmlForm form = page.getFormByName("validated_form"); HtmlTextInput input = (HtmlTextInput) form.getInputByName("in_text"); input.setValueAttribute("typing..."); (2) HtmlSubmitInput submitButton = (HtmlSubmitInput) form.getInputByName("submit"); HtmlPage resultPage = (HtmlPage) submitButton.click(); WebAssert.assertTitleEquals(resultPage, "Result"); Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 assertTrue("No alerts expected", alertHandler.getCollectedAlerts().isEmpty());(3) webClient.closeAllWindows(); } The differences with the original test are that at the beginning of the test (1) we install a CollectingAlertHandler in the web client; we simulate a user entering a value (2) and at the end of the test (line 11), we check that the alert handler’s list of messages is empty (3). To customize the alert behavior, you need to implement your own AlertHandler. The following example will cause your test to fail when a script raises the first alert. Listing 11.10 Custom alert handler client.setAlertHandler(new AlertHandler() { public void handleAlert(final Page page, final String message) { fail("JavaScript alert: " + message); } }); You can apply the same principles to test JavaScript confirm calls by installing a confirm handler in the web client with setConfirmHandler. 11.3.15 Testing CSS You can toggle CSS support on and off in a web client by calling setCssEnabled. By default, HtmlUnit enables CSS support. When calling APIs, the standard HtmlUnit behavior is to throw an exception when encountering a problem. In contrast, when HtmlUnit detects a CSS problem, it does not throw an exception; instead, it reports problems to the log through the Apache Commons Logging11 library. You can customize this behavior in a WebClient with a org.w3c.css.sac.ErrorHandler. There are two ErrorHandler implementation provided with HtmlUnit: • DefaultCssErrorHandler is the default handler and logs all CSS problems. • SilentCssErrorHandler ignores all CSS problems. To install an error handler use the setCssErrorHandler method on a web client. For example, the following causes all CSS problems to be ignored: webClient.setCssErrorHandler(new SilentCssErrorHandler()); If you want any CSS problem to cause test failures, create an error handler that always re- throws the CSSException it is given. 11.3.16 SSL errors You will find that many web sites have expired or incorrectly configured SSL certificates. By default, the Java runtime throws exceptions it detects errors. If this gets in the way of your testing, you can call WebClient.setUseInsecureSSL(true) to allow the test to 11 Apache Commons Logging: http://commons.apache.org/logging/ Licensed to Alison Tyler Download at Boykma.Com 18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 proceed. Using this API causes HtmlUnit to use an insecure SSL handler, which trusts everyone. Now, that we have covered testing from the client point of view, let us go to the server- side and examine how HtmlUnit can be used for in-container testing with the Cactus framework. 11.4 Integrating HtmlUnit with Cactus Cactus12 is a free open-source test framework for unit testing server-side Java code including Servlets, EJBs, and much more. Chapter 13 Server-side Java testing with Cactus discusses Cactus in detail. Where does HtmlUnit fit in? Let us look at the various opportunities to test an application from the inside out. • JUnit can test the data tier. • The business middle tier is where Cactus testing takes place by extending JUnit. • The presentation tier is where HtmlUnit lives. In the standard HtmlUnit unit test scenario, HtmlUnit drives the test. More specifically, JUnit invokes your unit test classes and methods, from which you call HtmlUnit to emulate a web browser to test your application. Cactus unit testing manages a different interaction; Cactus calls your HtmlUnit unit tests at just the right time to verify the web pages returned to the client. The main difference here is that HtmlUnit unit testing takes place in-container instead of through an emulated web client. To install HtmlUnit in Cactus, see Appendix E: Installing Software. For details on managing Cactus tests with tools like Ant and Maven, we refer you to Chapter 13. 11.4.1 Writing tests in Cactus Since HtmlUnit tests normally work with HtmlPage objects, we need to plug into the Cactus test execution at the point where a page is about to be returned to the client. Cactus tests for Java-based code like Servlets are subclasses of org.apache.cactus.ServletTestCase. If the test class contains a method whose name starts with end, Cactus will call this method with a WebResponse, which contains the contents of the server’s response. Take great care to import the appropriate WebResponse class for your tests, as there are three variations supported: • com.meterware.httpunit.WebResponse for HtmlUnit 1.6 • com.gargoylesoftware.htmlunit.WebResponse for HtmlUnit 2.5. • org.apache.cactus.WebResponse for Cactus itself Your boilerplate test class should look as follows. Listing 11.11 Boilerplate HtmlUnit ServletTestCase subclass 12 Cactus site: http://jakarta.apache.org/cactus/ Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 […] public class HtmlUnitServletTestCase extends ServletTestCase { public static Test suite() { return new TestSuite(HtmlUnitServletTestCase.class); } public HtmlUnitServletTestCase(String name) { super(name); } public void end(WebResponse webResponse) { // asserts } public void test() throws ServletException { SampleServlet servlet = new SampleServlet(); servlet.init(this.config); // asserts } } A couple of things to note in this example: • The ServletTestCase provides the following instance variables for your use: o AbstractServletConfigWrapper config o AbstractHttpServletRequestWrapper request o HttpServletResponse response o HttpSession session • The test method creates the servlet to test and initializes it. • Cactus integrates with JUnit 3, it does not provide JUnit4 niceties. Cactus Tip If your test class does not contain a begin method, the end method name must be end. If you your test class includes a begin method, the end method name MUST match, for example beginFoo and endFoo, otherwise the end method will not be called. Next, let us create a simple servlet. Listing 11.12 A simple servlet […] public class SampleServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("Hello World

Hello World

"); } } This servlet returns an HTML document with a title and a single paragraph. The next step is to flesh out our end method; we need get an HtmlPage from the WebResponse argument Licensed to Alison Tyler Download at Boykma.Com 20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 and validate its contents. Getting an HtmlPage from a WebResponse requires parsing the HTML. To do so, we use the HtmlUnit HTMLParser class. Listing 11.13 Converting a WebResponse into an HtmlPage public void end(WebResponse webResponse) throws IOException { WebClient webClient = new WebClient(); HtmlPage page = HTMLParser.parse(webResponse, webClient.getCurrentWindow()); WebAssert.assertTitleEquals(page, "Hello World"); webClient.closeAllWindows(); } We only create a WebClient in order to fulfill the needs of the HTMLParser API, which requires a WebWindow, which the WebClient holds. Cactus Tip In Cactus, you do not use getPage to get an HtmlPage, you parse it from the WebResponse with HTMLParser.parse. Once you have an HtmlPage, you are back to using the standard HtmlUnit API. We have finished covering HtmlUnit for this chapter; the API is intuitive and straight forward, so we invite you to explore the rest on your own. Let us now look at Selenium, a testing framework that differs from HtmlUnit in a fundamental way: instead of emulating a web browser, Selenium drives real a web browser process. 11.5 Introducing Selenium Selenium13 is a free open source tool suite used to test web applications. Selenium’s strength lies in its ability to run tests against a real browser on a specific operating system, this is unlike HtmlUnit, which emulates the browser in the same VM as your tests. This strength comes at a cost: the complexity of setting up and managing the Selenium runtime. While Selenium provides many components, we will consider the following components: the Selenium Remote Control Server, IDE, and client driver API. The Selenium IDE is a Firefox add-on used to record, playback and generate tests in many languages including Java. The Selenium client driver API is what tests call to drive the application, it communicates to the Remote Control Server, which in turns drives the web browser. 13 Selenium site: http://seleniumhq.org/ Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 11.2 The main Selenium IDE window. The client driver connects to the Server over TCP/IP; the Server does not need to run in the JVM, or even on the same physical machine. Selenium recommends14 running the Server on many different machines, with different operating systems and browser installations. A test connects to a server by specifying a host name and port number to the DefaultSelenium class. Figure 11.2 shows the main Selenium IDE (1.0 Beta 2) window. The IDE generates code against the client driver API but does not support change management. You should consider the IDE a one-way, use-once tool you to get yourself started for any given test case. You must handle any change in the application by manually changing the generated tests. To use the IDE, start by choosing a language (Java for us), click the red record button, use the browser like a user and click the record button again to stop. Because the IDE records everything you do, you should plan in advance what user stories you want to verify, and create one or more test case for each. At any point in the recording, you can ask the IDE to generate an assertion from the browser’s context menu; the current web page selection determines the choices. Figure 11.3 shows an example context menu. 14 Selenium Server set up: http://seleniumhq.org/documentation/remote-control/languages/java.html Licensed to Alison Tyler Download at Boykma.Com 22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 11.2 Selenium IDE context menu To install Selenium, see Appendix E: Installing Software. 11.6 Generating Selenium tests The Selenium IDE is a great way to get up and running fast. Before you record a test, edit the package name and class name in the IDE Source pane to match the directory and Java file name you desire. Note that the generated code is JUnit 3 code, and as such, subclasses the Selenium class SeleneseTestCase. 11.6.1 A live example The same user interaction as our first HtmlUnit test generated the following example: Go to Google, enter a query, and click to go to the expected site. Listing 11.14 Our first Selenium example […] public class FirstTestJUnit3 extends SeleneseTestCase { @Override public void setUp() throws Exception { (1) setUp("http://www.google.com/", "*iexplore"); } public void testSearch() throws Exception { selenium.open("/"); (2) assertEquals("Google", selenium.getTitle()); selenium.type("q", "Manning Publishing Co."); (3) selenium.click("btnG"); (4) selenium.waitForPageToLoad("30000"); assertEquals("Manning Publishing Co. - Google Search", selenium.getTitle()); selenium.click("link=Manning Publications Co."); (5) selenium.waitForPageToLoad("30000"); assertEquals("Manning Publications Co.", selenium.getTitle()); } } Let us step trough this example: First, the setUp method (1) calls the super’s setUp method with the base URL for the tests and the browser launcher to use, in this case, Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Internet Explorer (see Testing for a specific web browser for other settings.) This initializes the selenium instance variable to a DefaultSelenium instance. In the test method, we start by opening the home page (2) and check that we landed on the right page by verifying the page title. Next, we set the value of the input field (3), as if a user typed it in and we click on the Search button (4). The click argument is a Selenium locator, which here is simply the button name (more on the locator concept later.) We wait for the new page to load and assert the opened page’s title. Then, we click on a link (5) using a Selenium link locator. Again, we wait for the new page to load and assert the opened page’s title. Selenium tests subclass SeleneseTestCase, which in turn subclasses JUnit’s TestCase class. You will note that methods are not annotated; Selenium generated tests are JUnit 3 tests. The immediate issue raised by running within the JUnit 3 framework is the performance of a test class. Each time JUnit calls a test method, JUnit also calls the setUp and tearDown methods, this means starting, and stopping a web browser, which is very slow. We remedy this performance problem in the section Selenium and JUnit 4. Another issue to consider when using the Selenium IDE is that you are recording tests in Firefox 3. If your browser requirements are different from Firefox 3, what you recorded may not to playback the same in a different browser. Web pages can behave differently, sometimes in subtle manners, from browser to browser. In addition, pages can contain scripts to customize behavior based on the host browser. Server-side code can customize replies based on the agent making the request. Consider these issues before generating code from Firefox with the Selenium IDE; you may need to write the tests from scratch, a la HtmlUnit, if your application has code paths for a non-Firefox browser, like Internet Explorer or Safari. Next, we will look at what is takes to run Selenium tests. 11.7 Running Selenium tests Now that we know the basic concepts surrounding a Selenium test case, we describe the set up and mechanics of running Selenium tests: managing a Selenium server and integrating Selenium with JUnit 4 11.7.1 Managing the Selenium Server To run Selenium tests, you must use the Selenium server included in the Selenium Remote Control download. Selenium: Under the hood The Selenium server launches the web browser and acts as a proxy server to your tests, the server then runs the test on your behalf. This architecture works for any browser and Licensed to Alison Tyler Download at Boykma.Com 24 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 operating system combination; you can also use it to test Ajax applications. This proxy server set up is why you may get certificate warnings. To start the server manually, open a command line window in the server directory, for example: selenium-remote-control-1.0-beta-2\selenium-server-1.0-beta-2. Assuming the JVM is on your PATH type the following: java -jar selenium-server.jar You will see, for example: 22:14:11.367 INFO - Java: Sun Microsystems Inc. 11.2-b01 22:14:11.382 INFO - OS: Windows XP 5.1 x86 22:14:11.382 INFO - v1.0-beta-2 [2571], with Core v1.0-beta-2 [2330] 22:14:11.539 INFO - Version Jetty/5.1.x 22:14:11.554 INFO - Started HttpContext[/selenium-server/driver,/selenium- server/driver] 22:14:11.554 INFO - Started HttpContext[/selenium-server,/selenium-server] 22:14:11.554 INFO - Started HttpContext[/,/] 22:14:11.570 INFO - Started SocketListener on 0.0.0.0:4444 22:14:11.585 INFO - Started org.mortbay.jetty.Server@109a4c You are now ready to run tests. When you run tests, you will see two browser windows open and close. The first will contain the tested application, the second will display commands sent to the browser and log entries if you have logging enabled. If you are building with Ant or Maven, you can manage the lifecycle of the Selenium server from these tools. We recommend that you manage the server from the test class or suite directly as we will see next. This allows you, as a developer, to run the tests directly from the command line or an IDE like Eclipse. 11.7.2 Running Selenium tests with JUnit 4 The Selenium requirement for JUnit is version 3, as of Selenium version 1.0 Beta 2, there is no out-of-the-box integration with JUnit 4. This is a problem because the performance associated with a default SeleneseTestCase is bad; JUnit starts and stops a browser around each test method invocation through the setUp and tearDown methods. We present a two-stage solution to this problem by first managing a server for all test methods in a given class and then managing a server for all classes in a test suite. To do so, you will need to add the server jar file to your classpath, for example, selenium-remote- control-1.0-beta-2\selenium-server-1.0-beta-2\selenium-server.jar. In our first solution, JUnit starts and stops the server once per class run in @BeforeClass and @AfterClass methods as the following example demonstrates. Listing 11.15 Managing the Selenium Server from a test […] public class ManagedSeleniumServer { protected static Selenium selenium; (2) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 25 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 private static SeleniumServer seleniumServer; (1) @BeforeClass public static void setUpOnce() throws Exception { (3) startSeleniumServer(); startSeleniumClient(); } public static void startSeleniumClient() throws Exception { selenium = new DefaultSelenium("localhost", 4444, "*iexplore", "http://www.google.com/"); selenium.start(); } public static void startSeleniumServer() throws Exception { seleniumServer = new SeleniumServer(); seleniumServer.start(); } public static void stopSeleniumClient() throws Exception { if (selenium != null) { selenium.stop(); selenium = null; } } public static void stopSeleniumServer() throws Exception { if (seleniumServer != null) { seleniumServer.stop(); seleniumServer = null; } } @AfterClass public static void tearDownOnce() throws Exception { (4) stopSeleniumClient(); stopSeleniumServer(); } } Let us look over this code. The test manages 2 static variables, a Selenium server (1), and a Selenium client driver (2). The @BeforeClass method starts the Selenium server, then the Selenium client (3). The @AfterClass method stops the client, then the server (4). We can now run tests from ManagedSeleniumServer subclasses, as this next example demonstrates. This class does not subclass SeleneseTestCase to avoid inheriting its setUp and tearDown methods, which respectively start and stop a web browser. If you want to subclass SeleneseTestCase, make sure you override the setUp and tearDown methods to do nothing. Listing 11.16 Using a ManagedSeleniumServer public class ManagedTestJUnit4v2 extends ManagedSeleniumServer { Licensed to Alison Tyler Download at Boykma.Com 26 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 @Test public void testSearch() { // test… } } As a ManagedSeleniumServer subclass, this class only needs test methods. JUnit will call the @BeforeClass methods declared in superclasses before those of the current class and the @AfterClass methods in superclasses after those of the current class. If you are not going to manage a Selenium server farm for different browsers and operating systems, using this class as a super class for tests offers a simple solution to get you up and running managing the Selenium server within your tests and VM. The drawback to this approach is that JUnit starts and stops the Selenium Server for each test class. To avoid this you could create a test suite with a first and last test class which start and stop the server, but you will need to remember to do this for each suite and you will also need to share the Selenium server through what amounts to a global variable. We will take care of this problem next. Our second solution creates a JUnit Suite class to manage a Selenium server. This custom suite will start the Selenium server, run all the test classes in the suite, and then stop the server. Listing 11.17 A test suite to manage a Selenium server […] public class ManagedSeleniumServerSuite extends Suite { private static SeleniumServer seleniumServer; public static void startSeleniumServer() throws Exception { (1) ManagedSeleniumServerSuite.stopSeleniumServer(); seleniumServer = new SeleniumServer(); seleniumServer.start(); } public static void stopSeleniumServer() { (2) if (seleniumServer != null) { seleniumServer.stop(); seleniumServer = null; } } public ManagedSeleniumServerSuite(Class klass, Class[] (3) suiteClasses) throws InitializationError { super(klass, suiteClasses); } public ManagedSeleniumServerSuite(Class klass, List (3) runners) throws InitializationError { super(klass, runners); } public ManagedSeleniumServerSuite(Class klass, RunnerBuilder (3) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 builder) throws InitializationError { super(klass, builder); } public ManagedSeleniumServerSuite(RunnerBuilder builder, Class (3) klass, Class[] suiteClasses) throws InitializationError { super(builder, klass, suiteClasses); } public ManagedSeleniumServerSuite(RunnerBuilder builder, Class[] (3) classes) throws InitializationError { super(builder, classes); } @Override public void run(final RunNotifier notifier) { (4) EachTestNotifier testNotifier = new EachTestNotifier(notifier, this.getDescription()); try { ManagedSeleniumServerSuite.startSeleniumServer(); (5) Statement statement = this.classBlock(notifier); statement.evaluate(); } catch (AssumptionViolatedException e) { testNotifier.fireTestIgnored(); } catch (StoppedByUserException e) { throw e; } catch (Throwable e) { testNotifier.addFailure(e); } finally { ManagedSeleniumServerSuite.stopSeleniumServer(); (6) } } } The key to this class is our implementation of the run method (4). We have cloned the method from the super class and inserted calls to our methods to start (5) and stop (6) the Selenium server. The startSeleniumServer (1) and stopSeleniumServer (2) methods are straightforward enough. The rest of the code consists of duplicating constructors from the superclass (3). This allows us to write our test suite simply and succinctly as follows. Lising 11.18 A managed test suite @RunWith gedSeleniumServerSuite.class) (Mana @SuiteClasses( { FirstTestJUnit3.class, UnmanagedFirstTestJUnit4.class }) Unmanaged public class ManagedExampleSuiteTest {} Each test class in the suite is responsible for connecting to the local server. Just check that classes, like UnmanagedFirstTestJUnit4, connect to the server with the hostname “localhost” and the default port 4444. You can of course further enhance the suite to customize these settings. Licensed to Alison Tyler Download at Boykma.Com 28 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 11.8 Writing Selenium Tests With an efficient test infrastructure in place, we can now explore writing individual tests with Selenium. We will look at how to test for more than one browser, how to navigate the object model, and work through some example tests. 11.8.1 Testing for a specific web browser Selenium, as of version 1.0 Beta 2, supports the following browser launch strings: Web Browser SeleneseTestCase and DefaultSelenium browser strings Chrome *googlechrome Firefox *firefox Firefox *firefoxproxy Firefox Chrome URL15 *chrome Internet Explorer *iexplore Internet Explorer HTML Application16 *iehta Internet Explorer *iexploreproxy Opera *opera Safari *safari Specific executable c:\\path\\to\\a\\browser.exe” Table 11.2 Selenium browser launcher strings If you do not call SeleneseTestCase’s setUp(String,String), the default web browser launch string used on Windows is *iexplore, and *firefox for all other operating systems. If you use the class DefaultSelenium, you must provide a browser launch string. For example: selenium = new DefaultSelenium("localhost", 4444, "*iexplore", "http://www.google.com/"); Note that experimental17 browser launchers exist for elevated security privileges and proxy injection. 11.8.2 Testing more than one web browser We can apply the same JUnit @Parameterized feature we used with HtmlUnit in order to run the same test class with more than one browser. Next, we rework our previous example with class level and instance level JUnit initialization in order to combine the ability to run all tests with one client driver instance and then repeating the test for different browsers. 15 Chrome URL: http://xulplanet.com/tutorials/xultu/chromeurl.html 16 IE HTML Application: http://msdn.microsoft.com/en-us/library/ms536496(VS.85).aspx 17 Experimental browser launchers: http://seleniumhq.org/documentation/remote-control/experimental.html Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 29 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Listing 11.19 Running the test class for more than one browser [File 1] @RunWith(ManagedSeleniumServerSuite.class) @SuiteClasses({UnmanagedAllBrowsersTest.class}) public class ManagedAllBrowsersSuiteTest {} [File 2] @RunWith(value = Parameterized.class) (1) public class UnmanagedAllBrowsersTest { (1) private static Map SeleniumMap; (2) @Parameters public static Collection getBrowsers() { (3) return Arrays.asList(new String[][]{{"*iexplore"}, {"*firefox"}}); } private static Selenium getSelenium(String key) { (4) Selenium s = getSeleniumMap().get(key); if (s != null) { return s; } stopDrivers(); // only let one driver run (5) s = new DefaultSelenium("localhost", 4444, key, "http://www.google.com/"); getSeleniumMap().put(key, s); s.start(); return s; } private static Map getSeleniumMap() { if (SeleniumMap == null) { SeleniumMap = new HashMap(); } return SeleniumMap; } @AfterClass public static void stopDrivers() { (6) for (Selenium s : getSeleniumMap().values()) { s.stop(); } SeleniumMap = null; } private String browserStartCommand; private Selenium selenium; (7) public nmanagedAllBrowsersTest(String browserStartCommand) { U this.browserStartCommand = browserStartCommand; } @Before (8) public void setUp() throws Exception { this.selenium = getSelenium(this.browserStartCommand); } Licensed to Alison Tyler Download at Boykma.Com 30 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 @Test public void testGoogleSearch() { this.selenium.open("/"); SeleneseTestCase.assertEquals("Google", this.selenium.getTitle()); this.selenium.type("q", "Manning Publishing Co."); this.selenium.click("btnG"); this.selenium.waitForPageToLoad("30000"); SeleneseTestCase.assertEquals("Manning Publishing Co. - Google Search", this.selenium.getTitle()); this.selenium.click("link=Manning Publications Co."); this.selenium.waitForPageToLoad("30000"); SeleneseTestCase.assertEquals("Manning Publications Co.", this.selenium.getTitle()); } } Let us examine this more complex set up. The test class is annotated with @RunWith(value = Parameterized.class) (1) which directs JUnit to run the test class as many times as there are values returned from our @Parameters method getBrowsers (3). By contract with JUnit, this method must return a Collection of arrays, in our case we return a list of browser launch strings, one for each browser we want to test. JUnit will run all test methods with the test class initialized with "*iexplore", then do it all over again with "*firefox". You will need to have both browsers installed on your machine for this to work. Let us walk through this JUnit subtlety more carefully. When running the test class, JUnit creates test class instances for the cross product of the test methods and the test collection elements. One instance of the class is created for "*iexplorer" and for a single @Test methods in the class. JUnit runs that @Test method and repeats the process for all @Test methods in the class. JUnit then repeats that whole process with "*firefox" and so on for all elements in the @Parameters collection (3). We no longer have an @BeforeClass method, instead we use an @Before method (8) to initialize the selenium instance variable (7) for each test method. The selenium instance variable gets its value from a lazy-initialized static variable (2). In fact, this can only work by using an @Before method and lazy-initializing our client driver. Remember, we want our test class to reuse the same driver instance for each test method in a given parameterized run. We have an @AfterClass method (6) to clean up the driver at the end of the class run. Even though we use a static Map (2) to save our driver across test runs, there is only one driver in the map at any given time. The getSelenium method (4) can safely stop (5) the current driver when creating a new driver since we know that JUnit finished one of its parameterized runs. Now that we know how to efficiently run tests for a browser suite, let us survey the API used to navigate an application. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 31 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 11.8.3 Application and internet navigation Unlike HtmlUnit, there is no Selenium HTML object model to navigate; instead, you call the com.thoughtworks.selenium.Selenium interface, using a locator string to address elements (see Accessing elements with references.) This interface contains over 140 methods and provides all of the services and setting toggles needed to write tests. While there is no object model per se, the API provides some methods to work with certain types of elements. For example, getAllFields returns the IDs of all input fields on a page. Here is a brief sample of how tests can manipulate page elements: • Call click and doubleClick to click on an element and get the resulting page. • Call check and uncheck to toggle a radio button or check box. • Call type to set the value of an input field. We will now look at the different ways to access elements. 11.8.4 Accessing elements with references In our first example, we saw HTML elements referred to by locators. Selenium provides a String format to address elements with different schemes. The two locators we saw are the default scheme, id, and link, used to find anchor elements. The format for API arguments that take a locator is LocatorType=Argument. The following table describes the different locators and their formats. Locator Type Argument Example css W3C CSS2 and CSS3 selectors css=a[href="#AnId"] dom JavaScript expression dom=document.forms['f1'].intxt id @id attribute id=AnId identifier @id attribute or the first element with @name attribute is id identifier=AnId link anchor element matching the text link=I’m feeling lucky name First element with the @name attribute name=lucky ui Uses a Selenium UI-Element ui=loginPages::loginButton() xpath XPath expression xpath=//*[text()="lucky"] To get the value of a field, for example, you would write: String actualMsg = selenium.getText("name=serverMessage"); If you run into XPath cross-browser compatibility issue, you can either refactor tests with browser-specific XPath expressions or call allowNativeXpath(false) to force expressions to be evaluated in Selenium’s Java JavaScript library. 11.8.5 Failing tests with Exceptions While a generated test method throws Exception, it does not, in fact, throw any checked exceptions, nor do APIs you use to write tests. The generated code and APIs will throw Licensed to Alison Tyler Download at Boykma.Com 32 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 unchecked exceptions to make sure you tests fail under the proper conditions. Even though Selenium defines SeleniumException and SeleniumCommandTimedOutException as unchecked exceptions, some APIs also throw RuntimeException. Let us now look at various examples of using the API and navigating an application. 11.8.6 Testing forms The API does not provide explicit support for forms; instead, you work with forms like any other elements, calling APIs for typing, clicking, and pressing keys. The following example will recast the HtmlUnit example from the Testing forms section to the Selenium API. To remind you, in the HtmlUnit section, we created a simple page to display a form with an input field and submit button. We included form validation via JavaScript alerts in the example as a second path to test, as described in the section Testing JavaScript Alerts. We test normal user interaction with the form as follows. Listing 11.20 Testing a form @Test public void testForm() throws IOException { selenium.open("file:///C:/path/to/src/main/webapp/formtest.html"); selenium.type("id=in_text", "typing..."); selenium.click("id=submit"); SeleneseTestCase.assertEquals("Result", selenium.getTitle()); } Let us step through the example. We open the form page, type in a value and click the submit button to go to the next page. Finally, we make sure we land on the right page. If at any step, Selenium cannot find an object, the framework throws an exception and your test automatically fails. This allows you to focus on the test and let the framework handle failing your test if the page or form is as expected. 11.8.7 Testing JavaScript Alerts A test can check if a JavaScript alert has taken place. We will re-use our form example from Testing forms, which includes JavaScript validation code to alert the user of empty input values. The following test loads our form page and checks that the browser raised the alert when the error condition occurred. The key method is getAlert, which returns the most recent JavaScript alert message. Calling getAlert has the same effect as clicking OK in the dialog box. Listing 11.21 Asserting expected alerts @Test public void testFormAlert() throws IOException { selenium.open("file:///C:/path//to/src/main/webapp/formtest.html"); String title = selenium.getTitle(); selenium.click("id=submit"); SeleneseTestCase.assertEquals("Please enter a value.", Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 33 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 selenium.getAlert()); (1) SeleneseTestCase.assertEquals(title, selenium.getTitle()); } Let us step through the example. We open the form page, save the current page title and click the submit button. This will raise the alert since we did not type in a value. Next, we call getAlert (1) to check that the correct alert was raised. Finally, we make sure we are still on the same page by comparing the new page title with the saved title. We do not need to create a test to check that no alert has taken place during normal operation of our page. If the test generates an alert but getAlert does not consume it, the next Selenium action will throw a SeleniumException, for example: com.thoughtworks.selenium.SeleniumException: ERROR: There was an unexpected Alert! [Please enter a value.] Selenium Tip As of version Selenium 1.0 Beta 2, JavaScript alerts will not pop up a visible alert dialog box. JavaScript alerts generated from a page's onload event handler are not supported. If this happens, JavaScript will open a visible dialog and Selenium will wait until someone actually clicks the OK button. 11.8.8 Capturing a screenshot for a JUnit 3 test failure Selenium provides the ability to capture a screenshot at the time of failure to subclases of SeleneseTestCase. Selenium disables this feature by default, to enable it, call setCaptureScreenShotOnFailure(true). By default, the screenshot is written to a PNG file in the Selenium server directory with the same name as the test name given to the SeleneseTestCase String constructor. 11.8.9 Capturing a screenshot for a JUnit 4 test failure To access this feature from a JUnit 4 test case, you will need to modify the search example as follows. Listing 11.22 Capturing a screenshot on JUnit 4 test failure private void captureScreenshot(Throwable t) throws Throwable { (1) if (selenium != null) { String filename = this.getClass().getName() + ".png"; try { selenium.captureScreenshot(filename); (2) System.out.println("Saved screenshot " + filename + " for " + t.toString()); } catch (Exception e) { System.err.println("Exception saving screenshot " + filename + " for " + t.toString() + ": " + e.toString()); e.printStackTrace(); } throw t; } Licensed to Alison Tyler Download at Boykma.Com 34 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } public void testSearch() { // Same as before… } @Test public void testSearchOnErrSaveScreen() throws Throwable { (3) try { this.testSearch(); } catch (Throwable t) { this.captureScreenshot(t); } } We have added a new method called captureScreenshot (1), which takes a Throwable argument and calls the Selenium captureScreenshot method (2). We refactored our test method by creating a new method testSearchOnErrSaveScreen (3), removing @Test from testSearch and added it to the new method instead. To avoid repeating this code pattern in every method that wants to capture a screenshot on failure requires extending JUnit, which is beyond the scope of this section. This concludes our Selenium survey; next, we contrast and compare HtmlUnit and Selenium before presenting our chapter summary. 11.9 HtmlUnit vs. Selenium Here is a recap of the similarities and differences you will find between HtmlUnit and Selenium. The similarities are that both are free and open source and both require Java 5 as the minimum platform requirement. The major difference between the two is that HtmlUnit emulates a specific web browser while Selenium drives a real web browser process. When using Selenium, the browser itself provides support for JavaScript. In HtmlUnit 2.5, Mozilla’s Rhino18 1.7 Release 2 engine provides JavaScript support and specific browser behavior is emulated. The HtmlUnit pros are that it is a 100% Java solution, it is easy to integrate in a build process, and Cactus can integrate HtmlUnit code for in-container testing as can other frameworks. HtmlUnit provides an HTML object model, which can validate web pages to the finest level of detail. HtmlUnit also supports XPath to collect data; Selenium XPath support is limited to referencing elements. The Selenium pros are that the API is simpler and drives native browsers, which guarantees that the behavior of the tests is as close as possible to a user installation. Finally: 18 Mozilla Rhino: http://www.mozilla.org/rhino/ Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 35 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Use HtmlUnit when… Use HtmlUnit when your application is independent of operating system features and browser specific implementations of JavaScript, DOM, CSS, etc, not accounted for by HtmlUnit. Use Selenium when… Use Selenium when you require validation of specific browsers and operating systems, especially if the application takes advantage of or depends on a browser’s specific implementation of JavaScript, DOM, CSS, etc. 11.10 Summary In this chapter, we examined presentation layer testing and explored the use of two free open source tools to test the user interface of a web application: HtmlUnit and Selenium. HtmlUnit is a 100% Java solution with no external requirements; it offers a complete HTML object model, which, while creating rather verbose test code, offers great flexibility. Selenium is a more complex offering; it includes a simple IDE and many complementary components. The IDE generates test code, but does not maintain it. The strength of the product comes from its architecture, which allows the embeddable Selenium Remote Control Server to control different browsers on assorted operating systems. The Selenium API is much simpler and flat than with HtmlUnit, resulting in more concise test code. Use HtmlUnit when your application is independent of operating system features and browser specific implementations of JavaScript, DOM, CSS, etc. Use Selenium when you require validation of specific browsers and operating systems, especially if the application takes advantage of or depends on a browser’s specific implementation of JavaScript, DOM, CSS, etc. In the next chapter, we will add a layer of complexity by considering Ajax technologies in our applications and test cases. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 12 Ajax Testing This chapter covers: ƒ Introducing Ajax testing ƒ Testing the Ajax Stack ƒ Testing JavaScript ƒ Testing Server Services ƒ Testing Google Web Toolkit applications Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration. - Stan Kelly-Bootle This chapter covers testing Ajax applications, it is a continuation of Chapter 11, which discusses presentation layer testing in general and introduces two of the libraries and tools used in this chapter: HtmlUnit and Selenium. We will describe a divide and conquer approach by breaking up tests into three groups: testing client-side scripts, testing server services and functional testing. We will end by looking at the unique testing challenges presented by GWT application. 12.1 Ajax introduction In 2005, the article A New Approach to Web Applications1 coined the term “Ajax” to describe the architecture of a new generation of web application like Google Maps2 and Google Suggest3. These new applications were richer, more interactive, and responsive than their predecessors. Critically for the user experience, they left behind the need to constantly reload or refresh an entire web page to keep any portion of its information updated. While still browser-based, these applications started to give the web the look and feel of what had strictly been the domain of desktop applications. While Ajax is often associated with its all upper case sibling AJAX, the acronym, it is today much more than Asynchronous JavaScript and XML. An Ajax application is built combining the following technologies: CSS, DOM, JavaScript, Server-side scripting, HTML, HTTP, and web remoting (XMLHttpRequest) Beyond its associated technologies, Ajax reflects the mindset of a new breed of web applications built on standards and designed to give users a rich and interactive experience. In this chapter, we will study how to test these applications. 12.2 Why are Ajax applications difficult to test? To understand the challenge of testing an Ajax application, let us look at a web-classic interaction and then step through the stages of an Ajax application interaction. In a web-classic interaction, the user opens the browser on a page and each time the page needs data, it asks the server for a new page. Figure 12.1 illustrates this process. 1 http://www.adaptivepath.com/ideas/essays/archives/000385.php 2 http://maps.google.com/ 3 http://www.google.com/webhp?complete=1&hl=en Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 12.1 Web-classic application interactions. In an Ajax application, the page communicates with the server to get data for the part of the page that needs updating and then only updates that part of the page. Figure 12.2 illustrates this process. Web Browser Web Server Initial Page The user clicks to get data and waits. Page 2 The server returns a completely new page. Click. Page 3 Each time, the server returns a new page. Click. Page 4 The user waits for the server again. Click. Page 5 This is slow and forces the user to wait. Time Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 12.2 Ajax web application interactions. The user starts by opening an Ajax application’s start page in a browser; this causes the HTML page to load. The browser displays the HTML using any associated CSS and runs client-side JavaScript code to set up the page’s event handlers. The page is now ready to respond to user interactions. The user interacts with the page, triggering a JavaScript event handler. In an application like Google Suggest, each keystroke creates a server request for a list of suggestions that are displayed a drop-down list box. The JavaScript event handler builds an XHR object and calls the server with a specific request using HTTP. The XHR objects includes a JavaScript callback function the browser will invoke when results are ready. The server processes the request and returns a response using HTTP. The browser invokes the XHR callback and uses the data returned by the server, in the form of XML or text, to update the page in the browser. In order to update the page, the callback function uses the DOM API to modify the model, which the browser displays immediately. This interaction is quite different from the web-classic architecture where a page is loaded, a user interacts with the page causing another page to load, and then repeats the cycle. With Ajax, the page is loaded once, and everything happens within that page. JavaScript code runs in the page to perform I/O with the server and updates the in-memory DOM of the page, which the browser displays to the user. The challenge in writing tests for the application interaction described above is the asynchronous aspect of HTTP communications and the DOM manipulation by JavaScript Web Browser and Initial Page Web Server Click 5 The browser redraws a portion of the page . Click 1 Click to get data, the page stays as is. Click 2 The browser redraws a portion of the page . Click 3 A script sorts data for example. Click 4 Click, the user asks for more data. Ajax Time Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 code. The difficulty then is how to both drive a self-changing application when those changes are asynchronous to the test itself. In addition to testing the traditional page state described in Chapter 11, you should also test an Ajax application’s best practices features4 like drag and drop, form validation and submission, event handling, back button, refresh button, undo and redo commands, fancy navigation, state management and caching, and user friendliness (latency, showing progress, timing out, and multiple clicks.) Further complicating matters, different implementations of Ajax component technologies like JavaScript, DOM, and XMLHttpRequest exist in different browsers from different vendors. While various free and open source libraries abstract these differences away, an application is nonetheless more complicated to test. You may want to ensure test coverage for all code paths for all supported browsers on all supported operating systems. Next, we will look at how to split up testing such a complex application stack into more manageable tiers. 12.3 Testing Patterns for Ajax Let us now survey the testing patterns we use to verify the various aspects of an Ajax application. We can orchestrate testing with three types of tests: Client-side script unit testing covers the JavaScript scripts running in the browser. The application page hosting the script is not tested. Service testing verifies services provided by the server and accessed from JavaScript XHR objects. Functional testing drives the whole application from the client-browser and usually ends up exercising all application layers. Let us look at these types of tests in more detail. 12.3.1 Client-side script unit testing Client-side script unit testing covers JavaScript scripts running in the browser. Here we divide the scripts into two piles: the ones that use XHR to manipulate a DOM and the ones that do not. While we can test some script functions and libraries independently from their hosting page, where scripts call XHR objects and modify the DOM of the current page, we need a JavaScript engine and browser; the browser in turn may be emulated or live. For these scripts, we should use a functional test (see below.) For other scripts, we are testing a library of functions and we prefer to deliver these functions in standalone files as opposed to embedded in HTML pages. While we can test all scripts through functional tests of the pages that call on them, we want to provide a lighter-weight test pass that is more along the line of a true unit test. Heavier-weight and slower functional tests using web browsers should ideally be reserved when scripts cannot be otherwise verified. 4 Ajax in Action: http://www.manning.com/crane/ Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 So let us look at JavaScript unit testing. What should you look for in a JavaScript testing framework? From the TDD point of view, the most important feature to look for is the ability to automate tests. We must be able to integrate JavaScript testing in our Ant or Maven build. Second, we want the ability to run the tests from JUnit. You’ll find many JavaScript frameworks that provide creating and running tests by embedding test invocation in an HTML page (JsUnit5, JsUnitTest6, script.aculo.us7.) while others do it all from JavaScript (RhinoUnit8). The best integration for our purposes must include support for JUnit. While JUnit or Ant integration can be custom coded for a specific JavaScript testing framework, JsUnit provide this functionality out of the box. JsUnit is a free open source framework integrated with JUnit that goes one step further by providing advanced Selenium-type distributed configuration options. JsUnit is to JavaScript testing what Selenium is to web application testing. JsUnit allows you to test JavaScripts, from JUnit, by controlling a web browser process on a local or remote machine. We will see JsUnit in action in the section Testing JavaScript with JsUnit. 12.3.2 Service testing Service testing verifies services provided by the server accessed from JavaScript XHR objects. The HTML page and JavaScripts are not tested. Since HTTP is the standard used by XHR objects to communicate with the server, we can use any HTTP client to test the service independently of XHR and the browser. Free open source Java HTTP libraries suitable for unit testing include HtmlUnit, HttpUnit and Apache Commons HttpClient. We will see HttpClient in action in the section Testing Services with HttpClient. In this chapter, we test server services for Ajax applications, which we distinguish from testing web services, a different web standard. 12.3.3 Functional testing Functional testing drives the whole application from the client-browser and usually ends up exercising all application layers. As we discussed in Chapter 11, a functional test simulates a user and checks the state of visited web pages. You can choose to have the tests emulate a web browser (HtmlUnit) or drive a live web browser (Selenium.) As always with browser emulation software, the key is how well it supports JavaScript. 12.4 Functional Testing with Selenium We will now look at testing the application stack by continuing our demonstration of Selenium started in the previous chapter on GUI testing. We will also show you how to use HtmlUnit to write the same tests. We will show how to deal with the asynchronous aspect of 5 http://www.jsunit.net/ 6 http://jsunittest.com/ 7 http://script.aculo.us/ 8 http://code.google.com/p/rhinounit/ Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 an Ajax application before going on to test specific components that make up the stack like JavaScript libraries and server-provided services (as opposed to ‘Web Services.’) The key aspect of writing tests for an Ajax application is to know when an action causes a change in the DOM. Once a page is loaded in the browser, Selenium sees that version of page, to see the updated DOM you must use different API than what we showed you in the previous chapter. Let us take as an example the form in figure 12.1. When you click the “Get Message” button, the page queries the server and returns a simple text message: “Hello World”. JavaScript code then updates the DOM with the message and the browser displays the input field value, as we can see in the screenshot. Figure 12.1 A simple Ajax form Listing 12.1 shows the HTML source for the page, which includes the JavaScript to perform the HTTP XML Request. Listing 12.1 testform.html - A simple form (client)
Message:
Let us point out the key elements in the HTML, the form (5) defines a button, which when clicked, invokes the JavaScript function setMessage (2). The first thing the setMessage does is call newHXR (1) which creates a new HTTP XML Request object. setMessage then calls the server (4) and updates the DOM (3) when the response is ready. Listing 12.1 shows the server-side source for this example. Listing 12.2 hello-world.asp - A simple form (server) <% response.expires=-1 response.write("Hello World") (1) %> The response is the string “Hello World” (1). To run this example locally with IIS, you will need to create a virtual directory for the example webapp directory and use the IIS Permission Wizard to grant it default rights. In order to test the form and check that the message is what we expect it to be, we will use the same JUnit scaffolding from the previous chapter and first set up a test suite to manage the Selenium server. Listing 12.3 – ManagedFormTest.java […] @RunWith(ManagedSeleniumServerSuite.class) @Suite lasse public class ManagedFormTest { C s( { UnmanagedFormTester.class }) // See annotations. } Listing 12.3 shows the test suite ManagedFormTest that runs the test case UnmanagedFormTester containing the actual @Test methods. The JUnit test runner ManagedSeleniumServerSuite manages the Selenium server and drives the unit test. Without knowing anything about Ajax, you might create the test method testFormNo in listing 12.4. Listing 12.4 – UnmanagedFormTester.java bad test method /** * Tests a form. The Selenium server must be managed elsewhere. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 *

* Maven will not pick up this test because it does not start or end * with "Test" or end with "TestCase". *

*

* To test the form in IE, create a virtual directory in IIS and point your * browser and tests to http://localhost/webapp/formtest.html *

* * @author Gary Gregory * @version $Id$ */ public class a rmTester { Unman gedFo private static final String APP_WINDOW = "selenium.browserbot.getCurrentWindow()"; private static final String EXPECTED_MSG = "Hello World"; /** * The directory /ch13-ajax/src/main/webapp has been configured as * an IIS virtual directory for this test. */ private static final String TEST_URL = "http://localhost/webapp/"; private static final String TEST_PAGE = "formtest.html"; private static Selenium selenium; @BeforeClass public static void setUpOnce() throws Exception { selenium = new DefaultSelenium("localhost", 4444, "*iexplore", TEST_URL); selenium.start(); } @AfterClass public static void tearDownOnce() throws Exception { if (selenium != null) { selenium.stop(); } selenium = null; } @Test public void testFormNo() throws IOException { selenium.open(TEST_PAGE); selenium.click("name=getMsgBtn"); String actualMsg = selenium.getText("name=serverMessage"); (1) // The message is not there! Assert.assertFalse(EXPECTED_MSG.equals(actualMsg)); (2) } } All is well from the beginning of the file UnmanagedFormTester.java until the call to getText (1). The call takes a Selenium locator to retrieve the content of the element named Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 “serverMessage”. The return value is the empty string (2) because the DOM model in the object model is not up to date. Perhaps we need to wait for the server to do its work and the result to come back. Calling waitForPageToLoad does not work since the code does not reload the page, recall that this is an Ajax application; pages do not reload. Calling Thread.sleep after clicking the button does not work either. Fortunately, Selenium provides a single powerful API just for this purpose: waitForCondition. Here it is in action. Listing 12.5 – UnmanagedFormTester.java good test @Test public void testFormWithJavaScript() throws IOException { selenium.open(TEST_PAGE); selenium.click("name=getMsgBtn"); selenium.waitForCondition(APP_WINDOW + ".document.helloForm.serverMessage.value=='" + EXPECTED_MSG + "'", "1000"); } Alternatively, without the refactoring, the JavaScript expression in the first argument to waitForCondition reads: selenium.browserbot.getCurrentWindow().document.helloForm.serverMessage.val ue == 'Hello World’ The waitForCondition method waits for a given condition to become true or a timeout to expire. The method takes two arguments; the first is JavaScript code where the last expression must evaluate to a Boolean, the second is a timeout String expressed in milliseconds. WAIT FOR CONDITION API TIP In order to JavaScript to access the application window, you must use the following expression: selenium.browserbot.getCurrentWindow() For example, to get to the document, use: selenium.browserbot.getCurrentWindow().document The art of testing Ajax with Selenium is about embedding JavaScript in your JUnit code. This can be confusing since you are embedding JavaScript in Java, but it is what is required since the Selenium server controlling the web browser will run the JavaScript for you. In contrast, let us go back to HtmlUnit and see how this test looks in that framework. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 12.5 Functional Testing with HtmlUnit We return now to testing with HtmlUnit, which we document in Chapter 11. Though the HtmlUnit API is more verbose than Selenium, you write tests entirely in Java. Listing 12.6 tests the form with HtmlUnit. Listing 12.6 Testing the same form with HtmlUnit public class FormTest { private static final String EXPECTED_MSG = "Hello World"; private static final String TEST_URL = "http://localhost/webapp/formtest.html"; @Test public void testForm() throws IOException { WebClient webClient = new WebClient(); try { webClient.setAjaxController(new NicelyResynchronizingAjaxController()); (1) HtmlPage page = (HtmlPage) webClient.getPage(TEST_URL); (2) HtmlButtonInput button = (HtmlButtonInput) page.getFirstByXPath("/html/body/form/input[1]"); HtmlPage newPage = button.click(); (3) HtmlInput reply = (HtmlInput) newPage.getFirstByXPath("/html/body/form/input[2]"); Assert.assertTrue(EXPECTED_MSG.equals(reply.asText())); (4) } finally { webClient.closeAllWindows(); } } } Let us walk through this example: As usual, we start by creating an HtmlUnit web client and getting the application’s start page (2). We get our button, click on it and the result is a new page (3). From this page, we get the entry field which was updated through DOM by the XHR call and assert that the contents are what we expect (4). The general pattern with HtmlUnit is to get a page, find the element, click on it, and check the resulting page contents. Since the test thread can finish before HtmlUnit reads the Ajax response from the server, you must synchronize the test code with the response to guarantee predictable results from run to run. While a simple approach is to sleep the thread for a while, HtmlUnit provides APIs to guarantee that Ajax tests are synchronous and predictable. The example above illustrates this with the call to setAjaxController (1). HtmlUnit Tip Create predictable tests by synchronizing Ajax calls. To do so, set the web client’s Ajax controller to an instance of NicelyResynchronizingAjaxController: Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 webClient.setAjaxController(new NicelyResynchronizingAjaxController()); The setAjaxController method and the NicelyResynchronizingAjaxController class work together to turn asynchronous Ajax calls into synchronous Ajax calls. The class NicelyResynchronizingAjaxController is the only subclass of the AjaxController class delivered with HtmlUnit. By default, a web client initializes itself with an instance of AjaxController, which leaves Ajax calls asynchronous. If you want finer grained control over the behavior of tests, the framework provides experimental9 APIs to wait for various JavaScript tasks to complete. The method waitForBackgroundJavaScript waits for all background JavaScript tasks to complete execution; defined as tasks scheduled for execution via window.setTimeout, window.setInterval and asynchronous XMLHttpRequest. For example, you call waitForBackgroundJavaScript after an action invokes a script with a timeout in milliseconds: HtmlPage newPage = button.click(); int jobs = this.webClient.waitForBackgroundJavaScript(1000); The return value is the number of jobs still executing or scheduled for execution. If you have an idea of when your background JavaScript is supposed to start executing, but you are not necessarily sure how long it will take to execute, use the method waitForBackgroundJavaScriptStartingBefore. You should use the wait methods instead of the methods internal to HtmlUnit provided by JavaScriptJobManager. We will move on now to testing other elements of the Ajax stack, JavaScript and web services. 12.6 Choosing a JavaScript Testing Framework Here, you face the same choice you had between HtmlUnit and Selenium: do you want to emulate a browser or drive a live browser? We will look next at two JavaScript testing frameworks, RhinoUnit and JsUnit. RhinoUnit is like HtmlUnit, a 100% Java solution, and JsUnit is akin to Selenium in that it drives local or remote web browsers. 12.7 JavaScript Testing with RhinoUnit RhinoUnit allows you to run JavaScript unit tests from Ant. You write unit tests in JavaScript and invoke them from Ant with the help of the Java Scripting framework and the Mozilla Rhino JavaScript engine included in Java 6. If you are on older versions of Java, you will need the Apache Bean Scripting Framework10 (BSF) and the Mozilla Rhino JavaScript engine as documented in Appendix E: Installing Software. As a bonus, RhinoUnit includes JsLint11, which allow you to check from Ant that your JavaScript code follows best practices. 10 http://jakarta.apache.org/bsf/ 9 HtmlUnit 2.5 Javadoc warns that these APIs may change behavior or may not exist in future versions. 11 http://www.jslint.com/ Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Download and unzip RhinoUnit12, if you are on Java 6, you are done; if not some additional steps are documented in Appendix E: Installing Software. Let us create a simple function to test; the following listing defines a factorial function. Listing 12.7 factorial.js: A factorial function in need of testing function factorial(n) { if ((n === 0) || (n === 1)) { return 1; } return n * factorial(n - 1); } The following JavaScript unit tests the factorial function. Listing 12.8 test-example.js: A RhinoUnit test eval(loadFile("src/main/webapp/factorial.js")); (1) testCases(test, (2) function test15() { assert.that(factorial(15), eq(1307674368000)); (3) }, function test16() { assert.that(factorial(16), eq(20922789888000)); (4) } ); We start the test by including the library of functions we want to test (1) with a call to eval. Note that the path to the file is relative to where we are running the test from; in this case, it is the project’s root directory. Next we must call testCases passing in test as the first variable (2), followed by our actual test functions. You can pass in any number of functions; not the “,” separating the test functions. For this simple test, we call our factorial function from two different tests and make sure that the computation results in the expected value (3)(4), for example, checking that calling factorial(15), yields 1307674368000 (3). The assert.that call is the how to make an assertion in RhinoUnit. The first value is the value we are testing, the actual value; and the second value, the predicate, defines the actual test. Here we use eq to test for equality to its argument, 1307674368000. The general format is: assert.that(actual, predicate) The RhinoUnit site lists13 the functions you can use in addition to eq; these are the most widely used: 12 http://code.google.com/p/rhinounit/ 13 http://code.google.com/p/rhinounit/wiki/APIDescription Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 ƒ The function eq(expected) uses === to compare actual and expected values. ƒ The function matches(regExp) tests the actual value against the given regular expression. ƒ The function isTrue(message) tests that the actual value is true, displaying an optional message if it is not. ƒ The function isFalse(message) tests that the actual value is false, displaying an optional message if it is not. ƒ The function not(predicate) inverts the predicate. ƒ The function isNull(message) tests that the actual value is null, returning the message if it isn't. The following example shows how to match regular expressions. Listing 12.9 Matching regular expressions var actual = "JUnit in Action"; assert.that(actual, matches(/in/)); (1) assert.that(actual, not(matches(/out/))); (2) The example first calls the match function to assert that “in” is in the test string (1) and then to check that “out” is not (2). The assert object contains other useful functions: fail is like the JUnit fail method. The various mustCall functions check that the tests caused the given functions have or have not been invoked. In order to run the tests, you will need an Ant build script. Here is the sample build script used to run our examples. Listing 12.10 Ant build.xml for JavaScript unit tests (1) (1) (1) (1) (1) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 We start our Ant script by defining properties for the locations of directories and files (1) relative to where we will run the Ant build. Next, we define a rhinounit Ant script and its arguments by loading its source from rhinoUnitAnt.js. We call the script (3) with a fileset pointing to our JavaScript unit test source, where we include all files with the “js” extension. RhinoUnit Tip When you invoke the rhinounit Ant script, make sure you point the rhinoUnitUtilPath argument to the location of the rhinoUnitUtil.js file. For example: rhinoUnitUtilPath="${rhinounit.src}/rhinoUnitUtil.js" This takes care of JavaScript unit testing in the build file; please consult the RhinoUnit web site for additional documentation. We now move on to checking our code for best practices with JsLint. As we did for the unit testing script, we start by defining an Ant script for JsLint (4) and then calling the script (5) and passing it the source location to our JavaScript library directory. JSLint Tip Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 When you invoke the jslintant Ant script, make sure you point the jslintpath argument to the location of the fulljslint.js file. For example: jslintpath="${jslint.src}/fulljslint.js" You run this example script by typing “ant” on the command line in the directory for this chapter example, which displays the results below. Listing 12.11 Ant build results Buildfile: build.xml run-unit-tests: [rhinounit] Testsuite: test-all-valid.js [rhinounit] Tests run: 17, Failures: 0, Errors: 0 [rhinounit] [rhinounit] Testsuite: test-example.js [rhinounit] Tests run: 2, Failures: 0, Errors: 0 [rhinounit] run-js-lint: [jslintant] Attribute options = {eqeqeq : true, white: true, plusplus : false, bitwise : true, evil: true, passfail: false} [jslintant] jslint: No problems found in ch13- rhinounit\src\main\webapp\factorial.js run-all-tests: BUILD SUCCESSFUL Total time: 0 seconds JsLint is quite verbose and comprehensive in its output; please see the JsLint web site for details. Let us move on now to JavaScript testing with JsUnit. 12.8 JavaScript Testing with JsUnit JsUnit is a JavaScript unit-testing framework written in JavaScript and in Java. We will use it from Ant to drive a web browser in order to validate the same JavaScript we just tested in the previous section. JsUnit is similar to Selenium in that it controls a web browser and the tests run in that browser’s JavaScript engine. Let us start by showing you how to run a test from JsUnit and then automating the test from Ant. JsUnit is included in the jsunit directory in example source for this chapter, for details please see Appendix E: Installing Software. Writing JsUnit tests You write a JsUnit test by creating an HTML page containing JavaScript test functions. You then run the test from a web browser. You use HTML only as the container for the JavaScript. Here is our factorial test. Listing 12.12 jsFactorialTests.html Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 JsUnit Factorial Tests - Chapter 12 (1) (1) (2)

JsUnit Chapter 12 Tests

(5)

This page contains tests for the Chapter 12 examples.

To see the tests, view the source for this page.

You define the jsUnit test in the HTML head element and start with the references needed to bring in the JsUnit framework (1) and the JavaScript code to test (2). JsUnit tip The references in the link href and script src attributes are relative to the location of the HTML test file. A JavaScript script element defines the test functions for our factorial (3) and regular expression (4) tests. Like JUnit 3, we define test functions with the function name prefix “test”. The set of JsUnit assert functions is smaller than JUnit assert methods and are listed below. Like JUnit, the API defines a version of assert functions with and without a message argument. The square brackets in the list below denote that the argument is optional. ƒ assert([message,] booleanValue) ƒ assertTrue([message,] booleanValue) ƒ assertFalse([message,] booleanValue) Licensed to Alison Tyler Download at Boykma.Com 18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 ƒ assertEquals([message,] expectedValue, actualValue) ƒ assertNotEquals([message,] expectedValue, actualValue) ƒ assertNull([message,] value) ƒ assertNotNull([message,] value) ƒ assertUndefined([message,] value) ƒ assertNotUndefined([message,] value) ƒ assertNaN([message,] value) ƒ assertNotNaN([message,] value) ƒ fail(message) Like JUnit, JsUnit lets you use setUp and tearDown functions. JsUnit calls your setUp function before each test function and your tearDown function after each test function. JsUnit supports an equivalent to @BeforeClass if you define a function called setUpPage. JsUnit calls setUpPage once after the page is loaded but before it calls any test functions. When your setUpPage function ends, it must set the variable setUpPageStatus to “complete” to indicate to JsUnit that it can proceed to execute the page. JsUnit vs. JUnit JsUnit differs from JUnit in that JsUnit does not define the order of test function invocation; in JUnit, the order of methods in the source file defines the invocation order. In addition, while JUnit creates a new test object instance to invoke each method, JsUnit does not use a corresponding action, like reloading the page, which means that JsUnit preserves page variable values across test function invocations. 12.8.1 Writing JsUnit test suites Like in JUnit, you can group JsUnit tests into a suite. In this example, we wrap our previous test page into a test suite. Listing 12.13 jsUnitTestSuite.html JsUnit Test Suite - Chapter 12

JsUnit Test Suite for Chapter 12

This page contains a suite of tests for testing JsUnit.

To see the tests, view the source for this page.

To define a test suite, create a function called suite (1), which returnx a JsUnitTestSuite object. You then build up a suite object by adding test pages or other suite objects. In our example, we add one page (2), the page we previously definedby calling the addTestPage function. The rest of the code in this HTML page is the same as our previous example with the exception that we do not need to refer to our JavaScript factorial library. JsUnit addTestPage tip The addTestPage argument is a location relative to the test runner page you will use. In our examples, we use the test runner jsunit/testRunner.html. To add a test suite to another test suite, create a new JsUnitTestSuite object, and call the addTestSuite API. This allows you to organize your tests just as you can in JUnit. In the next example, we define and add two test suites to a main test suite. Listing 12.14 Building a JsUnit test suite function featureASuite() { var result = new JsUnitTestSuite(); result.addTestPage("../tests/featureA/Test1.html"); result.addTestPage("../tests/featureA/Test2.html"); return result; } function featureBSuite() { var result = new JsUnitTestSuite(); result.addTestPage("../tests/featureB/Test1.html"); result.addTestPage("../tests/featureB/Test2.html"); return result; } function suite() { var newsuite = new JsUnitTestSuite(); newsuite.addTestSuite(featureASuite()); newsuite.addTestSuite(featureASuite()); return newsuite; } Licensed to Alison Tyler Download at Boykma.Com 20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 We will now show you how to run the tests manually, then through Ant. 12.8.2 Running JsUnit tests manually To run your tests manually, open a web browser on jsunit/testRunner.html, enter in the “file” entry field a URL (file:// or http://) or file reference (c:\path\to\a\file) to an HTML test page for a test or a suite, and click the Run button. In this screenshot, we show the result of running our test suite with the familiar ‘green bar.’ Figure 12.2 The JsUnit test runner You can run a self-test on JsUnit itself by running the test suite jsunit/tests/jsUnitTestSuite.html. The result will show you the ‘green bar’ with 90 successful tests. JsUnit TIP: Status “Aborted” or tests time-out JsUnit does not give you much feedback when something goes wrong. If you see ‘Aborted’ in the JsUnit Status field, check your paths starting with link href and script src and then addTestPage. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Now that we have a manual way to run tests, let us move on to automating tests with Ant. 12.8.3 Running JsUnit tests with Ant JsUnit includes the file jsunit/build.xml, which you can use as a template to call JsUnit tests from Ant. The Ant build file will manage web browsers, invoke tests, and create reports. The following build.xml file excerpts invoke our test suite. Listing 12.15 JsUnit build.xml excerpts (1) (3) (4) (6) Licensed to Alison Tyler Download at Boykma.Com 22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 We start our build.xml file by defining the location of the JsUnit installation directory (1), in this case, it is a subdirectory of the directory containing our example build.xml. Then we define which web browsers JsUnit will use to test our code with the property browserFileNames (2). This property is a comma-separated list of browser executable paths. Next, we define logsDirectory (3) to hold the directory location for test report XML files. The property timeoutSeconds (4) is a timeout in seconds to wait for a test run to complete, if absent, the default value is 60 seconds. The url property (5) defines which test runner to use and which test or test suite it should invoke. It is worth breaking down this URL into its component parts. The example URL is: http://localhost:8080/jsunit/jsunit/testRunner.html?testPage=http://localho st:8080/jsunit/src/test/webapp/jsUnitTestSuite.html The URL starts with http://localhost:8080 since we are running our tests locally. The next segment, jsunit specifies the jsunit servlet and from there, the location to the JsUnit stock test runner, relative to build.xml which is jsunit/testRunner.html. All of this yields the first part of the URL: http://localhost:8080/jsunit/jsunit/testRunner.html. The testPage URL Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 parameter points to the test page or suite page to run. It too starts with the same local server plus the jsunit serlvet prefix and is followed by the path to the test suite page relative to where the test is run. Put it all together and we have the complete URL. Next, we give Ant all of the jar files needed to run JsUnit (6), and then we can proceed to running our test with the target standalone_test (7), which we invoke from the command line in the build.xml directory with a simple call to ant by typing in a console: ant standalone_test Ant starts and you will see the web browser open, run the tests in the test runner page, and close. You will also see a couple of pages of Ant and JsUnit output on the console detailing the test run, too much to reproduce here. We can just look for the next to last line of Ant output for the familiar BUILD SUCCESSFUL message. You can even add to your test run the JsUnit self-tests as a sanity check by adding the target jsunit_self_test to your ant invocation: ant jsunit_self_test standalone_test For the self-test to work in your build, make sure that your jsunit_self_test target points to the JsUnit test suite located in jsunit/tests/jsUnitTestSuite.html as our example build.xml does. JsUnit Firefox Tip: Permission denied If you get a permission denied error in Firefox, set the security.fileuri.strict_origin_policy to false. We will wrap up this section by noting that more advanced test configurations are possible with JsUnit since it provides support for driving farms of JsUnit servers. A JUnit server is what allows tests to be performed from Ant, it acts under the covers of our examples to drive web browsers on the local machine or on remote machines and also creates the result logs. 12.9 RhinoUnit vs. JsUnit Should you use RhinoUnit or JsUnit? The answer to this question is quite similar to the HtmlUnit vs. Selenium question, which we presented in the previous chapter. The similarity is that both are free and open source. The major difference between the two is that RhinoUnit emulates a web browser while JsUnit drives a real web browser process. When using JsUnit, the browser itself provides Licensed to Alison Tyler Download at Boykma.Com 24 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 support for JavaScript. In RhinoUnit, Java 6 or Apache BSF plus the Mozilla Rhino14 engine provides JavaScript support. The RhinoUnit pros are that it is a 100% Java solution and is easy to integrate in a build process. The JsUnit pros are that it drives native browsers, and can manage a farm of JsUnit test servers. Finally: Use RhinoUnit when… Use RhinoUnit when your application is independent of operating system features and browser specific implementations of JavaScript, DOM, CSS, etc. Use JsUnit when… Use JsUnit when you require validation of specific browsers and operating systems, especially if the application takes advantage of or depends on a browser’s specific implementation of JavaScript, DOM, CSS, etc. We have seen how to test client-side scripts with RhinoUnit and JsUnit, next, we will show how server-side services can be tested with HttpClient. 12.10 Testing Services with HttpClient The idea behind testing the application services layer separately is to validate each service independently from HTML, JavaScript, DOM, and how the application uses the data. An application calls a service from JavaScript through the XMLHttpRequest object. Here, we use XMLHttpRequest to refer to the standard JavaScript XMLHttpRequest object and to the Microsoft ActiveX XMLHTTP objects Microsoft.XMLHTTP and Msxml2.XMLHTTP. Our goal is to emulate an XMLHttpRequest object by using HTTP as the transport mechanism and XML and JSON as example data formats. We will use the Apache Commons HttpClient to provide HTTP support, Java’s built-in XML support, and jslint4java to check that JSON documents are well formed. 12.10.1 Calling an XML service To simplify this example, we have made the chapter’s example webapp directory an IIS virtual directory so that we can run the unit tests from Ant locally. A production Ant build would start and stop a web container like Jetty around the unit test invocations. Our first XML service test will make sure that we are getting back from the server the expected XML document. A second test will validate the document. 14 http://www.mozilla.org/rhino/ Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 25 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Listing 12.16 An XML service test @Test public void testGetXmlBasicCheck() throws IOException { HttpClient httpClient = new HttpClient(); (1) GetMethod get = new GetMethod( (2) "http://localhost/ch13personal/personal.xml"); (2) String responseString; try { httpClient.executeMethod(get); (3) InputStream input = get.getResponseBodyAsStream(); (3) responseString = IOUtils.toString(input, "UTF-8"); (4) } finally { get.releaseConnection(); (5) } Assert.assertTrue(responseString, responseString.startsWith( (6) "")); (6) // more... } The test starts by creating an HttpClient (1) and defining the HTTP GET method (2) with a URL for our XML document fixture. The URL specified in the GetMethod constructor is application-specific and must include parameters if appropriate for a given test. The test then executes the HTTP GET method (3) and reads the data back from the server. Note the use of the Apache Commons IO API IOUtils.toString to read the response stream in a String as a one-liner (4). The code does this synchronously, unlike a standard Ajax application. We then guarantee that HttpClient resources are freed by calling releaseConnection from a finally block (5). We can now check that the data from the server is as expected. For this first test, all we do is a simple string comparison of the start of the document (6). You could also use Java regular expressions to do some further XML string-based checks; instead, we will use an important XML feature: XML Validation. 12.10.2 Validating an XML response If you can parse an XML document, you know that it is well formed, meaning that the XML syntax is obeyed, nothing more. XML provides a standard called XML Schema, which you use to define a vocabulary of XML, specifying which elements and attributes make up a valid document. In this next example, the schema is stored in a file called personal.xsd. The example uses the standard Java XML APIs. Listing 12.17 An validating XML service test @Test public void testGetXmlAndValidateXmlSchema() throws IOException, ParserConfigurationException, SAXException { HttpClient httpClient = new HttpClient(); GetMethod get = new GetMethod( "http://localhost/ch13personal/personal.xml"); Document document; try { httpClient.executeMethod(get); Licensed to Alison Tyler Download at Boykma.Com 26 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 InputStream input = get.getResponseBodyAsStream(); // Parse the XML document into a DOM tree DocumentBuilder parser = DocumentBuilderFactory (1) .newInstance().newDocumentBuilder(); (1) document = parser.parse(input); (1) } finally { get.releaseConnection(); } // Create a SchemaFactory capable of understanding XSD schemas SchemaFactory factory = SchemaFactory.newInstance( (2) XMLConstants.W3C_XML_SCHEMA_NS_URI); (2) // Load the XSD schema in a Schema instance Source schemaFile = new StreamSource(new File( (3) "src/main/webapp/personal.xsd")); (3) Schema schema = factory.newSchema(schemaFile); (3) // Create a Validator, which we use to validate an the document Validator validator = schema.newValidator(); (4) validator.validate(new DOMSource(document)); (4) } This example starts as the previous one did, but after the test executes the HTTP GET method, we read the server response directly with an XML parser (1). If the DOM document parses successfully, we know the document is well formed, a nice sanity-check. If we do not get a valid XML document from the server, we might have a server error message in the response or a bug in server-side XML document generation. We can now move to the meat of the test, XML validation. We create a schema factory for the kind of grammar to use, in our case, XML Schema (2) and load the XSD schema file in a Schema instance (3). Finally, we can create an XML Validator for our schema and validate the DOM document (4). At this point, we know that our document is valid and well formed. The next step would be to check that the application data is as expected. The DOM document API can be painful to use, so at this point you have several options we outline. The JDOM15 API is a friendlier interface to XML than DOM. You can use Java’s XPath16 support to check the contents of a document. You can also use Sun’s JAXB17 framework, while not trivial, to transform XML into POJOs. Next, let us consider JSON as the data format. 12.10.3 Validating a JSON response JSON18 (JavaScript Object Notation) is a data-interchange language based on a subset of the JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 199919. 15 http://www.jdom.org/ 16 http://java.sun.com/j2se/1.5.0/docs/api/javax/xml/xpath/package-summary.html 17 https://jaxb.dev.java.net/ 18 http://www.json.org/ 19 http://www.ecma-international.org/publications/files/ecma-st/ECMA-262.pdf Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Applications use JSON instead of XML as their data format because it is text-based, and easy for people and machines to read and understand. In this first example, we will see a simple check of a JSON document. Listing 12.18 A JSON service test private static final String URL_FIXTURE = "http://localhost/ch13personal/glossary.json"; @Test public void testGetJsonBasicCheck() throws IOException { HttpClient httpClient = new HttpClient(); GetMethod get = new GetMethod(URL_FIXTURE); String responseString; try { httpClient.executeMethod(get); InputStream input = get.getResponseBodyAsStream(); responseString = IOUtils.toString(input, "UTF-8"); } finally { get.releaseConnection(); } String responseNoWs = StringUtils.deleteWhitespace(responseString); String response1Line = "{ \"glossary\": { \"title\": \"example glossary\", \"GlossDiv\": { \"title\": \"S\", \"GlossList\": { \"GlossEntry\": { \"ID\": \"SGML\", \"SortAs\": \"SGML\", \"GlossTerm\": \"Standard Generalized Markup Language\", \"Acronym\": \"SGML\", \"Abbrev\": \"ISO 8879:1986\", \"GlossDef\": { \"para\": \"A meta-markup language, used to create markup languages such as DocBook.\", \"GlossSeeAlso\": [\"GML\", \"XML\"] }, \"GlossSee\": \"markup\" } } } } }"; String response1LineNoWs = StringUtils.deleteWhitespace(response1Line); Assert.assertTrue(responseString, (2) responseNoWs.equals(response1LineNoWs)); (2) } In this test, we perform the same steps as our first XML example: we create an HttpClient, an HTTP GET method that we execute and then read the results from the server into a String. Unlike XML, JSON has no APIs to support checks for well-formed and valid documents. We simply strip whitespaces from our JSON document fixture, the server response and compare the two (2). While this check is brute force, we use it to provide a simple check that is free of formatting issues. The Apache Commons Lang API StringUtils.deleteWhitespace performs the whitespace removal. The next best thing we can do is implement a well-formed check by parsing the document. While www.json.org lists many libraries, including Java libraries to parse JSON, we will use the jslint wrapper jslint4java20 to go beyond a simple well-formed check. As we 20 Jslint4java site: http://code.google.com/p/jslint4java/ Licensed to Alison Tyler Download at Boykma.Com 28 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 saw earlier in the RhinoUnit section, jslint provides lint-style reporting for JavaScript. In this example, we call jslint through jslint4java and check its results. Listing 12.19 A JSON service test with jslint @Test public void testGetJsonAndValidate() throws IOException, ParserConfigurationException, SAXException { HttpClient httpClient = new HttpClient(); GetMethod get = new GetMethod(URL_FIXTURE); String responseString; try { httpClient.executeMethod(get); InputStream input = get.getResponseBodyAsStream(); responseString = IOUtils.toString(input, "UTF-8"); } finally { get.releaseConnection(); } JSLint jslint = new JSLint(); (1) List issues = jslint.lint(URL_FIXTURE, responseString); (2) StringBuilder builder = new StringBuilder(); (3) String eol = System.getProperty("line.separator"); (3) for (Issue issue : issues) { (3) builder.append(issue.toString()); (3) builder.append(eol); (3) } (3) Assert.assertEquals(builder.toString(), 0, issues.size()); (4) } Our test starts as usual, and we check for results using a JsLint object (1). We do not provide options in this example, but the JsLint class provides an addOption method to support the underlying jslint options. The test calls the lint method by specifying two arguments: a String describing the source location and another String for the actual JavaScript code to check, in this case, a JSON document (2). The test uses the lint results to create a message String (3) used in the Assert call. If there was a problem, the test provides the assertEquals call with a full description of all issues jslint found. You will notice that jslint4java is always behind jslint in terms of features and fixes. This is because jslint4java embeds jslint (fulljslint.js) in its jar file. If you need to use a newer or different version of jslint in jslint4java, you will need to download jslint4java, drop in the version of jslint (fulljslint.js) you need on top of the existing one, and rebuild jslint4java. In this section, we have seen how to validate server-side services that participate in an Ajax application independently of the pages and code using them. We have used Apache Commons HttpClient as our HTTP communication library, Java’s XML APIs and jslint through jslint4java. We have separated our tests along the boundary of the Ajax architecture. We have now tested the full Ajax application stack. Let us now consider a different way to build, run, and test an Ajax application with the Google Web Toolkit. 12.11 Testing Google Web Toolkit applications Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 29 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The Google Web Toolkit21 (GWT) is a free open source framework used to create JavaScript front-ends to web applications. GWT application development has a twist though: you write your applications in Java. To this end, Google provides the Google Plugin for Eclipse; you develop and test in Java and, when ready for deployment, GWT translates your Java into JavaScript. GWT allows you to run and test your application in hosted mode which runs in Java and in web mode, where you application is translated to JavaScript and then is run in a browser. 12.11.1 Choosing a testing framework for GWT application GWT supports JUnit with the GWTTestCase and GWTTestSuite classes, which both extend the JUnit TestCase class. GWT normally integrates with JUnit 3.8.2 and works with 4.6. GWT includes junitCreator, a program used to generate empty GWT test cases for a given GWT module. As a bonus, GWT can also benchmark your application. Since Java and JavaScript are not the same, you should test in both hosted and web modes. It is important to understand that GWTTestCase does not account for testing the user interface of an application. You use GWTTestCase to test the asynchronous portions of the application normally triggered by user actions. This means that you must factor your application and test cases with this element in mind. You can think of GWT tests as integration tests. The tests cannot rely on any user interface element driving the application. Testing the GUI requires using the techniques presented in this and the previous chapters; you can create functional GUI tests with Selenium or HtmlUnit. GWTTestCase tip You use the GWTTestCase class to test the application logic of the web client, not the user interface. While seemingly an obstacle, this forces you to factor your GWT application cleanly between code for user interaction and application logic. This is a design best practice that you should follow. Let us first look at how to create a GWTTestCase manually before we show how to use junitCreator. The example we use in this section is adapted from the GWT StockWatcher example22, and extended with an RPC call. Figure 12.3 shows what the application looks like running in hosted mode. Our example will test the StockWatcher Remote Procedure Call (RPC) to get stock price information for an array of stock symbols. We will focus on RPC, as it is the heart of GWT JUnit testing. 21 http://code.google.com/webtoolkit/ 22 http://code.google.com/webtoolkit/tutorials/1.6/projects/GettingStarted.zip Licensed to Alison Tyler Download at Boykma.Com 30 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 12.3: Running StockWatcher in hosted mode. 12.11.2 Creating a GWTTestCase manually We will start our asynchronous GWT RPC example test in familiar GWT territory with listing 12.20 where the refreshWatchList method performs a standard GWT RPC call. Listing 12.20 The asynchronous RPC call void refreshWatchList() { // Initialize the service proxy. if (this.stockPriceSvc == null) { this.stockPriceSvc = GWT.create(StockPriceService.class); (1) } // Set up the callback object. AsyncCallback callback = new AsyncCallback() { public void onFailure(Throwable caught) { StockWatcher.this.setLastRefreshThrowable(caught); (2) } public void onSuccess(StockPrice[] result) { StockWatcher.this.updateTable(result); (3) } }; // Make the call to the stock price service. this.stockPriceSvc.getPrices( (4) this.getStocks().toArray(new String[0]), callback); (4) } Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 31 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The implementation of refreshWatchList follows the standard pattern for GWT RPC, the method creates a new StockPriceService instance (1) and defines the service callback. The callback defines two methods, in onFailure (2) we save the given exception, and in onSuccess (3), which is typed for our application model (StockPrice[]), we update the application. Next, we call service’s getPrices method (4) with input data and our callback. The key point to remember is that the call to the getPrices method is asynchronous, so the call to refreshWatchList is also asynchronous. Next, we will test this method with a unit test. To create a GWT test case, you start by creating a subclass of GWTTestCase, along the lines of listing 12.21. Listing 12.21: StockWatcherTest.java - Testing GWT RPC […] public class StockWatcherTest extends GWTTestCase { (1) @Override public String getModuleName() { (2) return "com.google.gwt.sample.stockwatcher.StockWatcher"; (2) } (2) public void testStockPrices() { final StockWatcher stockWatcher = new StockWatcher(); final ArrayList stocks = stockWatcher.getStocks(); stocks.add("S1"); (3) stocks.add("S2"); (3) stocks.add("S3"); (3) stockWatcher.refreshWatchList(); (4) Timer timer = new Timer() { (5) private void assertStockPrice(FlexTable stocksFlexTable, int row, String price, String change) { assertEquals(price, stocksFlexTable.getText(row, 1)); assertEquals(change, stocksFlexTable.getText(row, 2)); } @Override public void run() { Throwable lastRefreshThrowable = (6) stockWatcher.getLastRefreshThrowable(); (6) if (lastRefreshThrowable != null) { (6) this.throwUnchekedException(lastRefreshThrowable); (6) } (6) FlexTable stocksFlexTable = stockWatcher.getStocksFlexTable(); assertEquals("Symbol", stocksFlexTable.getText(0, 0)); (7) assertEquals("Price", stocksFlexTable.getText(0, 1)); (7) assertEquals("Change", stocksFlexTable.getText(0, 2)); (7) this.assertStockPrice(stocksFlexTable, 1, (8) "101.00", "+0.01 (+0.01%)"); (8) this.assertStockPrice(stocksFlexTable, 2, (8) "102.00", "+0.02 (+0.02%)"); (8) this.assertStockPrice(stocksFlexTable, 3, (8) "103.00", "+0.03 (+0.03%)"); (8) StockWatcherTest.this.finishTest(); (9) Licensed to Alison Tyler Download at Boykma.Com 32 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } private void throwUnchekedException( Throwable lastRefreshThrowable) { String msg = lastRefreshThrowable.toString(); if (lastRefreshThrowable instanceof StatusCodeException) { msg = "HTTP status code " + ((StatusCodeException) lastRefreshThrowable).getStatusCode() + ": " + msg; } throw new IllegalStateException(msg, lastRefreshThrowable); } }; this.delayTestFinish(5000); (10) timer.schedule(100); (11) } } A GWT test case must extend the GWT class GWTTestCase (1) and implement a method with the signature public String getModuleName() to return the name of the module being tested (2). We start the test method by instantiating the model class and initializing it with input data (3), the three stock symbols “S1”, “S2”, and “S3”. Next, we call the refreshWatchList method in listing 12.20, which performs the asynchronous RPC (4). We need to allow the test method to complete while allowing assertions to run. To do so, we use a GWT Timer (5) to schedule our assertions. Our Timer’s run method starts by checking that the stock watcher RPC was able to run in the first place. We do this by checking if the asynchronous callback caught an exception (6). This arrangement is helpful in determining an incorrect test set up; in particular, as it relates to the class path and module file (see the tip in the section on running tests.) If no exception is present, the validity checks on the StockWatcher object can proceed. We check that table headers are still there (7) and then check the contents of the table (8) for values we expect to be returned from the service. In this test, we have changed the stock GWT example to return predictable values instead of randomly generated values. Now that the Timer object is in place, we call delayTestFinish to tell GWT run this test in asynchronous mode (10). You give the method a delay period in milliseconds much larger than is expected to run the test setup, do the RPC, and perform the assertions. When the test method exits normally, GWT does not mark the test a finished; instead, the delay period starts. During the delay period, GWT will check for the following: 1. If the test calls the GWTTestCase finishTest()method before the delay period expires, then the test succeeds. 2. If an exception propagates to GWT, then the test fails with that exception. 3. If the delay period expires and neither of the above conditions is true, then the test fails with a TimeoutException. The last task in the test is to get the job off and running with a call to the Timer’s schedule method (11). The argument is a delay in milliseconds, after which control returns to the caller. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 33 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 We just saw asynchronous testing in GWT; next, we will see how to use junitCreator to create starter tests and then how to run the tests. 12.11.3 Creating a GWTTestCase with junitCreator The junitCreator utility allows you to create a GWTTestCase based on a module to which you then add your own test methods. junitCreator is a good place to get started for your first GWT test. For subsequent tests, you may prefer to clone a template class or write test case classes from scratch. junitCreator also creates Eclipse launch configurations and command line scripts for hosted and web modes. To get your command processor to find junitCreator and other GWT programs, remember to add GWT to your path. Here is a sample invocation for our example. Listing 12.22 Calling junitCreator junitCreator -junit /java/junit4.6/junit-4.6.jar -module com.google.gwt.sample.stockwatcher.StockWatcher -eclipse ch13-gwt- StockWatcher com.google.gwt.sample.stockwatcher.client.StockWatcherTest The (abbreviated) console output is: Created file test\com\google\gwt\sample\stockwatcher\client\StockWatcherTest.java Created file StockWatcherTest-hosted.launch Created file StockWatcherTest-web.launch Created file StockWatcherTest-hosted.cmd Created file StockWatcherTest-web.cmd The .launch files are Eclipse launch configurations and the .cmd files are command line scripts. Use these files to invoke the generated test case in web or hosted mode. You may need to adapt the scripts for you location of the JUnit and GWT jar files. 12.11.4 Running test cases Using Eclipse, running a test is easy, just right-click on a test case class and choose Run As… or Debug As… and then choose GWT Unit Test to run the test in hosted mode and GWT Unit Test (web mode) to run the test in web mode. To use Ant, you will need to make sure your build file points to the GWT SDK. Using a GWT example build file as a template, edit the gwt.sdk property to point to the GWT directory. 12.11.5 Setup and tear down A GWTTestCase subclass can override the JUnit methods setUp and tearDown with the following restrictions: Licensed to Alison Tyler Download at Boykma.Com 34 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 ƒ You cannot call JSNI methods. ƒ You cannot call code that depends on deferred binding, which includes most of the UI library. GWT 1.5 remedies these issues with by the addition of two methods available for overriding: ƒ gwtSetUp runs before each test case method. ƒ gwtTearDown runs after each test case method. 12.11.6 Creating a test suite The benefit of using a test suite with GWT goes beyond grouping related tests together. A performance gain is possible by using a GWT test suite. GWT sorts test cases in a suite by module name as returned by getModuleName. This causes all tests with the same module to run one after the next. To create a test suite, you can start with the Eclipse JUnit Test Suite wizard. For example, to create a test suite that includes all test cases in a package, go to the Packages view, right-click on a package and choose New, then Other. In the New dialog, open the JUnit node and choose JUnit Test Suite. The listing below shows the generated code with two changes we explain next. Listing 12.23 A GWT test suite public class AllTests extends GWTTestSuite { (1) public static Test suite() { TestSuite suite = new TestSuite( "com.google.gwt.sample.stockwatcher.client.AllTests"); (2) // $JUnit-BEGIN$ suite.addTestSuite(StockPriceTest.class); suite.addTestSuite(StockWatcherTest.class); // $JUnit-END$ return suite; } } We made the class extend GWTTestSuite (1) to make this suite a GWT test suite. We have also replaced the String in the TestSuite constructor with the class name of the generated class (2). This allows us to double-click on the class name in the JUnit view and jump to an editor for that test suite. The rest is standard JUnit code; we call addTestSuite with a class object to add a test case to the suite. 12.11.7 Running a test suite In addition to requirements for running a GWT test case, you must configure a GWT test suite with more memory than the default settings allocate. Configure the Java VM running the tests with at least 256 megabytes of RAM. With the Sun JVM, use the following option: - Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 35 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Xmx256M. You must also add to the classpath the source directories for application and test code. To wrap up GWT testing, recall that GWT test cases verify the asynchronous aspects of your application, not the actual user interface. To test the GUI, use functional tests with Selenium or HtmlUnit. While we are done with our brief tour of GWT testing, it is worth noting that GWT includes features to allow you to perform benchmarking with reports by using the Benchmark class. You can also see a graphical representation of benchmarks with the benchmarkViewer utility. 12.12 Summary In this chapter, we have built on what we have learned in Chapter 11 about testing the presentation layer of applications, this time, specifically as it relates to Ajax applications. We have seen that ajax application use many technologies layered in broad tiers: HTML and JavaScript on the client; HTTP, XML and JSON provide communication and data services; and the server side is viewed as a black box implementing services accessed over the internet with HTTP. We have divided our testing strategies along similar patterns. We used functional tests for the whole application stack as it appears to a user by driving and testing the application with HtmlUnit and Selenium. We isolated JavaScript into libraries and tested those independently with RhinoUnit and JsUnit. We validated server-side XML and JSON services using Apache Commons HttpClient, jslint, and jslint4java. You use RhinoUnit when your application is independent of operating system features and browser specific implementations of JavaScript, DOM, CSS, etc; and use JsUnit when you require validation of specific browsers and operating systems, especially if the application takes advantage of or depends on a browser’s specific implementation of JavaScript, DOM, CSS, etc. Finally, we looked at the unique challenge posed by GWT, a framework that you develop for in Java and that translates your code to JavaScript. This chapter concludes our survey of user interface testing and we now move to the server-side and testing with Cactus. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 13 Server-side Java testing with Cactus This chapter covers ƒ Drawbacks of mock objects ƒ Introducing testing inside the container with Cactus ƒ In-depth description of how Cactus works ƒ Integration of Cactus with other projects – Ant, Maven, … Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Good design at good times. Make it run, make it run right. —Kent Beck, Test Driven Development: By Example In the second part of the book we saw what mock objects are and how to benefit from using them. We also described different techniques for unit-testing your server-side code, and we even compared these techniques against each other. The one thing that you should be aware of now is that there is no absolute truth – the best techniques to use depend on the situation you are currently in. For example in most of the cases maybe you will find server-side testing with mocks and stubs sufficient, however, as you saw, this technique suffers some significant drawbacks. That is why we cover the in-container testing approach deeper in the book. Furthermore this chapter will focus on the in-container testing methodologies by means of one of the most popular in-container testing frameworks – Cactus. We will start by introducing the Cactus framework, and show you some real-world examples of how exactly to use Cactus. We will start by explaining what is so special about Cactus, and also what is the order of execution of Cactus tests. We will then build a sample application that uses some components from the J2EE spec, and we will write the tests for those components with the help of Cactus. The next thing would be to execute those tests – we will show a sample integration between Cactus and some of the most popular tools for continuous integration (Ant, Maven …). But we'll go a bit further than that – we will demonstrate you how to demonstrate from the tight integration between Cactus and other projects, like Cargo and Jetty. So, let’s start! 13.1 What is Cactus? Cactus is an open-source framework (http://jakarta.apache.org/cactus/) for in-container testing server-side Java code (mostly J2EE components in the current version of Cactus). It is an extension of JUnit that was created by Vincent Massol in 1998. Before we go any further I would like to clarify the definitions just mentioned. When we say Cactus is a framework itself, we actually mean that it provides some API that you have to extend in order to use it. Also, what in-container really means is that (as you will see later in the chapter) the tests get executed inside the Virtual Machine of the container. And finally, Cactus is an extension of JUnit because of two reasons – first of all it extends JUnit by empowering it with new functionality (Cactus makes JUnit tests get executed inside the container, something which otherwise wouldn’t be possible), and even more - Cactus’s API extends JUnit’s API in low- level software engineering terms – it extends some of JUnit’s classes and overrides some of JUnit’s methods. Let’s see Cactus in action. In later sections, we’ll explain in more detail how it works. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 13.2 Testing with Cactus This section is more theoretical. You need this knowledge before you move on and experiment on a sample application, because Cactus is different from the normal unit-testing frameworks. Cactus executes the tests inside the container, which on its own raises a lot of questions, so we will try to answer all of them here. 13.2.1 Java components that you can test with Cactus As we mentioned in the previous section the Cactus project is used for testing the core J2EE components (JSPs, Taglibraries, Servlets, Filters and EJBs). What is worth mentioning is that this is the only focus of the Cactus project. It does not test any specific framework (look at the next chapter if your application is framework specific), because it is not intended to do so. A lot of the emails that come to the Cactus mailing list ask if people can use Cactus for testing an application based on a specific framework (like Struts, JSF or Spring). Actually there are quite a lot of tools, dedicate on such testing and we cover some of them later in the book. Most of those tools are based on Cactus and require Cactus in their classpath, but again Cactus is designed for in-container testing of the components from the J2EE spec. 13.2.2 General principles As Cactus is an extension of JUnit, every Cactus test is a JUnit test by itself. The other way around is not true – none of the JUnit tests are actually Cactus tests. So what actually distinguishes the Cactus tests from the JUnit tests? There are a couple of certain rules that you need to stick to in order to use Cactus. We already discussed in chapter 7 what in-container testing means. Back then, we had a web application that uses servlets, and that you wish to unit-test the isAuthenticated method in listing 13.1 from a SampleServlet servlet. Listing 13.1 Sample of a servlet method to unit-test […] import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; public class SampleServlet extends HttpServlet { public boolean isAuthenticated(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return false; } String authenticationAttribute = (String) session.getAttribute("authenticated"); return Boolean.valueOf(authenticationAttribute).booleanValue(); } } In order to be able to test this method, you need to get hold of a valid HttpServletRequest object. Unfortunately, it is not possible to call new Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 HttpServletRequest to create a usable request. The life cycle of HttpServletRequest is managed by the container. JUnit alone is not enough to write a test for the isAuthenticated method. So what do we do in order to get hold of a valid HttpServletRequest? Wouldn’t it be just perfect if we had an HttpServletRequest object already instantiated in our test- cases? And how can this get achieved? What if we always had to extend a certain class that actually takes care of providing us the server-side objects that are otherwise managed by the container (such as HttpServletRequest). Here is a corresponding cactus-test that tests the given servlet. 13.2 Using Cactus to unit-test the SampleServlet […] import org.apache.cactus.ServletTestCase; import org.apache.cactus.WebRequest; public class TestSampleServletIntegration extends ServletTestCase { private SampleServlet servlet; protected void setUp() { servlet = new SampleServlet(); } public void testIsAuthenticatedAuthenticated() { session.setAttribute("authenticated", "true"); (1) assertTrue(servlet.isAuthenticated(request)); } public void testIsAuthenticatedNotAuthenticated() { assertFalse(servlet.isAuthenticated(request)); } public void beginIsAuthenticatedNoSession(WebRequest request) { request.setAutomaticSession(false); (2) } public void testIsAuthenticatedNoSession() { assertFalse(servlet.isAuthenticated(request)); } } Now I bet the question you are asking is: ”At what place do the session (1) and request (2) objects get declared and initialized?” Well the answer is pretty straightforward – they come from the base class, ServletTestCase, which is part of the Cactus API. As you can see the Cactus test-case meets all of our requirements – they give us access to the container objects, inside our JUnit test-cases. Seen from the previous listing writing a cactus test case involves several key-points: ƒ The Cactus test case must extend one of the following: ServletTestCase, JSPTestCase, FilterTestCase depending on what type of component you are testing. This is a must-do rule – since Cactus extends the 3.8.x version of JUnit your Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 test-cases always have to extend one of the latter classes. ƒ The Cactus framework exposes the container objects (in this case the HttpServletRequest and HttpSession objects) to your tests, making it easy and quick to write unit tests. ƒ You get a chance to implement two new methods – beginXXX and endXXX. These two new methods are executed on the client-side and you can use them to place certain values in the request object or to get certain values from the response object. ƒ In order for Cactus to expose the container objects, Cactus needs to get them from the JVM they live in, and since the container is the only one managing the lifecycle of these objects, Cactus tests need to interact straight with the container. This leads us to the conclusion that cactus tests need to get deployed inside the container. The last of the upper points tells us that Cactus tests live in the container JVM. This brings us to the next issue: if Cactus tests live in the container JVM, how do they get executed? Also, how do we see the result from their execution? 13.2.3 How Cactus works Before we rush into the details, you need to understand a bit more about how Cactus works. The life cycle of a Cactus test is shown in figure 13.1. Figure 13.1 Life cycle of a cactus test. We’ll describe the different steps using the TestSampleServletIntegration Cactus test from listing 13.2. Say now we have a sample servlet, that we want to test, and also a test written for that particular servlet. What we need to do now is package the servlet and the test, along with the necessary Cactus libraries, and deploy the package in the server. After we start the server, we have the test and the servlet in both – deployed in the Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 container and in our workspace on the client side. You can submit the client-side Cactus test to a JUnit test runner, and the runner starts the tests. EXECUTING CLIENT-SIDE AND SERVER-SIDE STEPS The life cycle is divided into steps that are executed on the client side and others that are executed on the server side (inside the container JVM). Client side refers to the JVM in which you have started the JUnit test runner. On the client side, the Cactus logic is implemented in the YYYTestCase classes that your tests extend (where YYY can be Servlet, Jsp, or Filter). More specifically, YYYTestCase overrides JUnit TestCase.runBare, which is the method called by the JUnit test runner to execute one test. By overriding runBare, Cactus can implement its own test logic, as described later. On the server side, the Cactus logic is implemented in a proxy redirector (or redirector for short). STEPPING THROUGH A TEST For each test (testXXX methods in the YYYTestCase classes), the six steps shown in figure 13.2 take place. Let’s step through all six. Step 1: execute beginXXX If there is a beginXXX method, Cactus executes it. The beginXXX method lets you pass information to the redirector. The TestSampleServletIntegration example extends ServletTestCase and connects to the Cactus servlet redirector. The servlet redirector is implemented as a servlet; this is the entry point in the container. The Cactus client side calls the servlet redirector by opening an HTTP connection to it. The beginXXX method sets up HTTP-related parameters that are set in the HTTP request received by the servlet redirector. This method can be used to define HTTP POST/GET parameters, HTTP cookies, HTTP headers, and so forth. For example: public void beginXXX(WebRequest request) { request.addParameter("param1", "value1"); request.addCookie("cookie1", "value1"); [...] } In the TestSampleServletIntegration class, we have used the beginXXX method to tell the redirector not to create an HTTP session (the default behavior creates one): public void beginIsAuthenticatedNoSession(WebRequest request) { request.setAutomaticSession(false); } Step 2: open the redirector connection Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The YYYTestCase opens a connection to its redirector. In this case, the ServletTestCase code opens an HTTP connection to the servlet redirector (which is a servlet). Step 3: create the server-side TestCase instance The redirector creates an instance of the YYYTestCase class. Note that this is the second instance created by Cactus; a first one has been created on the client side (by the JUnit TestRunner). Then, the redirector retrieves container objects and assigns them in the YYYTestCase instance by setting class variables. In the servlet example, the servlet redirector creates an instance of TestSampleServletIntegration and sets the following objects as class variables in it: HttpServletRequest, HttpServletResponse, HttpSession, and so forth. The servlet redirector is able to do this because it is a servlet. When it’s called by the Cactus client side, it has received a valid HttpServletRequest, HttpServletResponse, HttpSession, and other objects from the container and is passing them to the YYYTestCase instance. It acts as a proxy/redirector (hence its name). The redirector then starts the test (see step 4). Upon returning from the test, it stores the test result in the ServletConfig servlet object along with any exception that might have been raised during the test, so the test result can later be retrieved. The redirector needs a place to temporarily store the test result because the full Cactus test is complete only when the endXXX method has finished executing (see step 5). Step 4: call setUp, testXXX, and tearDown on the server The redirector calls the JUnit setUp method of YYYTestCase, if there is one. Then it calls the testXXX method. The testXXX method calls the class/methods under test, and finally the redirector calls the JUnit tearDown method of the TestCase, if there is one. Step 5: execute endXXX Once the client side has received the response from its connection to the redirector, it calls an endXXX method (if it exists). This method is used so that your tests can assert additional results from the code under test. For example, if you’re using a ServletTestCase, FilterTestCase, or JspTestCase class, you can assert HTTP cookies, HTTP headers, or the content of the HTTP response: public void endXXX(WebResponse response) { assertEquals("value", response.getCookie("cookiename").getValue()); assertEquals("...", response.getText()); [...] } Step 6: Gathering the test result In step 3, the redirector saves the test result in a variable stored with the ServletConfig object. The Cactus client side now needs to retrieve the test result and tell the JUnit test runner whether the test was successful, so the result can be displayed in the test runner GUI Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 or console. To do this, the YYYTestCase opens a second connection to the redirector and asks it for the test result. This process may look complex at first glance, but this is what it takes to be able to get inside the container and execute the test from there. Fortunately, as users, we are shielded from this complexity by the Cactus framework. You can simply use the provided Cactus front ends to start and set up the tests. Now that we’ve seen what cactus tests are, and how they work, let’s get a closer look on more component-specific tests. 13.3 Testing Servlets and Filters As we already saw Cactus is designed for testing the core components from the J2EE spec. This testing, however, has some component-specific characteristics. In this section we are about to explore these characteristics. When you unit-test servlet and filter code, you must test not only these objects, but also any Java class calling the Servlet/Filter API, the JNDI API, or any back-end services. Starting from this section we’ll build a real-life sample application that will help demonstrate how to unit-test each of the different kinds of components that make up a full-blown web application. This section focuses on unit-testing the servlet and filter parts of that application. Later subsections test the other common components (JSPs and EJBs). 13.3.1 Presenting the Administration application The goal of this sample Administration application is to let administrators perform database queries on a relational database. Suppose that the application it administers already exists. Administrators can perform queries such as listing all the transactions that took place during a given time interval, listing the transactions that were out of Service Level Agreement (SLA), and so forth. We set up a typical web application architecture (see figure 13.2) to demonstrate how to unit-test each type of component (filter, servlet, JSP, and EJB). The application first receives from the user an HTTP request containing the SQL query to execute. The request is caught by a security filter that checks whether the SQL query is a SELECT query (to prevent modifying the database). If not, the user is redirected to an error page. If the query is a SELECT, the AdminServlet servlet is called. The servlet performs the requested database query and forwards the result to a JSP page, which displays the results. The page uses JSP tags to iterate over the returned results and to display them in HTML tables. JSP tags are used for all the presentation logic code. The JSPs contain only layout/style tags (no Java code in scriptlets). You’ll start by unit-testing the AdminServlet servlet. Then, in the following subsections, you’ll test the other components of the Administration application. 13.3.2 Writing servlet tests with Cactus In this section, we’ll focus on using Cactus to unit-test the AdminServlet servlet (see fig. 13.2) from the Administration application. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Fig. 13.2 The sample Administration application. You’ll use it as a base sample in this chapter and following chapters to see how to unit-test servlets, filters, JSPs, taglibs, and database applications. Let’s test AdminServlet by writing the tests before you write the servlet code. This strategy is called Test-Driven Development (TDD) or Test-First, and it’s very efficient for designing extensible and flexible code and making sure the unit test suite is as complete as possible. (See chapter 3 for an introduction to TDD.) Before you begin coding the test, let’s review the requirement for AdminServlet. The servlet should extract the needed parameter containing the command to execute from the HTTP request (in this case, the SQL command to run). Then it should fetch the data using the extracted command. Finally, it should pass the control to the JSP page for display, passing the fetched data. Let’s call the methods corresponding to these actions getCommand, executeCommand, and callView, respectively. DESIGNING THE FIRST TEST Listing 13.3 shows the unit tests for the getCommand method. Remember that you have not yet written the code under test. The AdminServlet class doesn’t exist, and your code doesn’t compile (yet). 13.3 Designing and testing the getCommand method […] import javax.servlet.ServletException; import org.apache.cactus.ServletTestCase; import org.apache.cactus.WebRequest; public class TestAdminServlet extends ServletTestCase { (1) Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 public void beginGetCommandOk(WebRequest request) { request.addParameter("command", "SELECT..."); (2) } public void testGetCommandOk() throws Exception { AdminServlet servlet = new AdminServlet(); String command = servlet.getCommand(request); assertEquals("SELECT...", command); (3) } public void testGetCommandNotDefined { AdminServlet servlet = new AdminServlet(); try { servlet.getCommand(request); fail("Command should not have existed"); } catch (ServletException expected) { assertTrue(true); } } } This is a typical Cactus test-case. We have extended the ServletTestCase (1), because the component that we want to test is a servlet. We also set a parameter in the beginXXX method (2) that we assert to be present in the testXXX method (3). After we have written the test-case we can go on and implement the bare minimum of a code that will allow us to compile the project. We need to implement a sample servlet with a getCommand method. The next 13.4 listing shows us the code. Listing 13.4 Minimum code to make the TestAdminServlet compile. […] public class AdminServlet extends HttpServlet { public String getCommand(HttpServletRequest request) throws ServletException { return null; } } This is the minimum code that allows the TestAdminServlet to compile successfully. The code compiles ok, but there is one more thing that we have to think about. What you probably notice at this point is that if this test get executed it will fail, because of the null object that we return. Tests are used to prevent us from making mistakes. That said we always have to ensure that tests fail if we provide corrupt data, like in the above example. At this point we need to ensure that the error is reported successfully. And after that, when you implement the code under test, the tests should succeed, and we’ll know we’ve accomplished something. It’s a good practice to ensure that the tests fail when the code fails. JUnit best practice: always verify that the test fails when it should fail Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 It’s a good practice to always verify that the tests you’re writing work. Be sure a test fails when you expect it to fail. If you’re using the Test-Driven Development (TDD) methodology, this failure happens as a matter of course. After you write the test, write a skeleton for the class under test (a class with methods that return null or throw runtime exceptions). If you try to run your test against a skeleton class, it should fail. If it doesn’t, fix the test (ironically enough) so that it does fail! Even after the case is fleshed out, you can vet a test by changing an assertion to look for an invalid value that should cause it to fail. But let’s get back to the test. Listing 13.5 shows the code for getCommand. It’s a minimal implementation that passes the tests. Listing 13.5 Implementation of getCommand that makes the tests pass. […] public String getCommand(HttpServletRequest request) throws ServletException { String command = request.getParameter(COMMAND_PARAM); if (command == null) { throw new ServletException("Missing parameter [" + COMMAND_PARAM + "]"); } return command; } […] The code from the listing is a very simple implementation, but it is enough for our needs. We want our code not only to compile, but also to pass the tests. JUnit best practice: use TDD to implement The Simplest Thing That Could Possibly Work The Simplest Thing That Could Possibly Work is an Extreme Programming (XP) principle that says over-design should be avoided, because you never know what will be used effectively. XP recommends designing and implementing the minimal working solution and then refactoring mercilessly. This is in contrast to the monumental methodologies, which advocated fully designing the solution before starting development. When you’re developing using the TDD approach, the tests are written first— you only have to implement the bare minimum to make the test pass, in order to achieve a fully functional piece of code. The requirements have been fully expressed as test cases, and thus you can let yourself be led by the tests when you’re writing the functional code. FINISHING THE CACTUS SERVLET TESTS At the beginning of the previous subsection, we mentioned that you need to write three methods: getCommand, executeCommand, and callView. You implemented getCommand Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 in listing 13.5. The executeCommand method is responsible for obtaining data from the database. We’ll defer this implementation until the last section, “Testing EJBs”. That leaves the callView method, along with the servlet’s doGet method, which ties everything together by calling your different methods. One way of designing the application is to store the result of the executeCommand method in the HTTP servlet request. The request is passed to the JSP by the callView method (via servlet forward). The JSP can then access the data to display by getting it from the request (possibly using a useBean tag). This is a typical MVC Model 2 pattern used by many applications and frameworks. You still need to define what objects executeCommand will return. The BeanUtils package in the Apache Commons (http://commons.apache.org/beanutils/) includes a DynaBean class that can expose public properties, like a regular JavaBean, but you don’t need to hard-code getters and setters. In a Java class, you access one of the dyna-properties using a map-like accessor: DynaBean employee = ... String firstName = (String) employee.get("firstName"); employee.set("firstName", "Petar"); The BeanUtils framework is nice for the current use case because you’ll retrieve arbitrary data from the database. You can construct dynamic JavaBeans (or dyna beans) that you’ll use to hold database data. The actual mapping of a database to dyna beans is covered in the last section. TESTING THE CALLVIEW METHOD There’s enough in place now that you can write the tests for callView, as shown in listing 13.6. To make the test easier to read, you create a createCommandResult private method. This utility method creates arbitrary DynaBean objects, like those that will be returned by executeCommand. In testCallView, you place the dyna beans in the HTTP request where the JSP can find them. Listing 13.6 Unit tests for callView [...] public class TestAdminServlet extends ServletTestCase { [...] private Collection createCommandResult() throws Exception { List results = new ArrayList(); DynaProperty[] props = new DynaProperty[] { new DynaProperty("id", String.class), new DynaProperty("responsetime", Long.class) }; BasicDynaClass dynaClass = new BasicDynaClass("requesttime", null, props); DynaBean request1 = dynaClass.newInstance(); Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 request1.set("id", "12345"); request1.set("responsetime", new Long(500)); results.add(request1); DynaBean request2 = dynaClass.newInstance(); request1.set("id", "56789"); request1.set("responsetime", new Long(430)); results.add(request2); return results; } public void testCallView() throws Exception { AdminServlet servlet = new AdminServlet(); // Set the result of the execution of the command in the // HTTP request so that the JSP page can get the data to // display request.setAttribute("result", createCommandResult()); servlet.callView(request); } } There is nothing you can verify in testCallView, so you don’t perform any asserts there. The call to callView forwards to a JSP. However, Cactus supports asserting the result of the execution of a JSP page. So, you can use Cactus to verify that the JSP will be able to display the data that you created in createCommandResult. Because this would be JSP testing, we’ll show how it works in subsection 13.4 (“Unit-testing JSPs”). Listing 13.7 shows the callView method that we use to forward the execution to the JSP in order to display the results. Listing 13.7 Implementation of callView that makes the test pass [...] public class AdminServlet extends HttpServlet { [...] public void callView(HttpServletRequest request) { request.getRequestDispatcher("/results.jsp") .forward(request, response); } } You don’t have a test yet for the returned result, so not returning anything is enough. That will change once you test the JSP. TESTING THE DOGET METHOD Let’s design the unit test for the AdminServlet doGet method. To begin, you need to verify that the test results are put in the servlet request as an attribute. Here’s how you can do that: Collection results = (Collection) request.getAttribute("result"); assertNotNull("Failed to get execution results from the request", results); Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 assertEquals(2, results.size()); This code leads to storing the command execution result in doGet. But where do you get the result? Ultimately, from the execution of executeCommand—but it isn’t implemented yet. The typical solution to this kind of deadlock is to have an executeCommand that does nothing in AdminServlet. Then, in your test, you can implement executeCommand to return whatever you want: AdminServlet servlet = new AdminServlet() { public Collection executeCommand(String command) throws Exception { return createCommandResult(); } }; You can now store the result of the test execution in doGet: public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException { try { Collection results = executeCommand(getCommand(request)); request.setAttribute("result", results); } catch (Exception e) { throw new ServletException("Failed to execute command", e); } } Notice that you need the catch block because the Servlet specification says doGet must throw a ServletException. Because executeCommand can throw an exception, you need to wrap it into a ServletException. If you run this code, you’ll find that you have forgotten to set the command to execute in the HTTP request as a parameter. You need a beginDoGet method to do that, such as this: public void beginDoGet(WebRequest request) { request.addParameter("command", "SELECT..."); } With this method we are ready to complete the test. The doGet code is shown in listing 13.8. Listing 13.8 Implementation of doGet that makes the tests pass [...] public class AdminServlet extends HttpServlet { [...] public Collection executeCommand(String command) throws Exception { throw new RuntimeException("not implemented"); (1) } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException { try { Collection results = executeCommand(getCommand(request)); request.setAttribute("result", results); Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } catch (Exception e) { throw new ServletException("Failed to execute command", e); } } } There are two points to note. First, the call to callView is not present in doGet; the tests don’t yet mandate it. (They will, but not until you write the unit tests for your JSP.) Second, you throw a RuntimeException object if executeCommand is called (1). You could return null, but throwing an exception is a better practice. An exception clearly states that you have not implemented the method. If the method is called by mistake, there won’t be any surprises. JUnit best practice: throw an exception for methods that aren’t implemented When you’re writing code, there are often times when you want to execute the code without having finished implementing all methods. For example, if you’re writing a mock object for an interface and the code you’re testing uses only one method, you don’t need to mock all methods. A very good practice is to throw an exception instead of returning null values (or not returning anything for methods with no return value). There are two good reasons: Doing this states clearly to anyone reading the code that the method is not implemented and ensures that if the method is called, it will behave in such a way that you cannot mistake skeletal behavior for real behavior. So far we discussed the Administrator application and we showed how to test one part of it - the Servlet part. Enough for this, now it is time to move on, and concentrate on probably the most difficult-to-test part of the application – the frontend. 13.4 Testing JSPs In this section, we’ll continue with the Administration application we introduced in the previous section. Here, we concentrate on testing the view components—namely the Java Server Pages (JSPs). 13.4.1 Revisiting the Administration application You call the application by sending an HTTP request (from your browser) to the AdminServlet (fig. 13.2). You pass an SQL query to run as an HTTP parameter, which is retrieved by the AdminServlet. The security filter intercepts the HTTP request and verifies that the SQL query is harmless (that is, it’s a SELECT query). Then, the servlet executes the query on the database, stores the resulting objects in the HTTP Request object, and calls the Results View page. The JSP takes the results from the Request and displays them, nicely formatted, using custom JSP tags from your tag library. Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 13.4.2 What is JSP unit testing? First, let’s remove any doubt: What we call unit-testing a JSP is not about unit-testing the servlet that is generated by the compilation of the JSP. We also assume that the JSP is well designed, which means there is no Java code in it. If the page must handle any presentation logic, the logic is encapsulated in a JavaBean or in a taglib. You can perform two kinds of tests to unit-test a JSP: test the JSP page itself in isolation and/or test the JSP’s taglibs. You can isolate the JSP from the back end by simulating the JavaBeans it uses and then verifying that the returned page contains the expected data. We’ll use Cactus to demonstrate this type of test. Because mock objects (see chapter 6) operate only on Java code, you can’t use a pure mock-objects solution to unit-test your JSP in isolation. You could also write functional tests for the JSP using a framework such as HttpUnit. However, doing so means going all the way to the back end of the application, possibly to the database. With a combination of Cactus and mock objects, you can prevent calling the back end and keep your focus on unit-testing the JSPs themselves. You can also unit-test the custom tags used in the JSP. 13.4.3 Unit-testing a JSP in isolation with Cactus The strategy for unit-testing JSPs in isolation with Cactus is defined in figure 13.3. Figure 13.3 Strategy to unit-tests JSPs with Cactus. Here is what happens. The Cactus test case class must extend ServletTestCase (or JspTestCase): 1. In the testXXX method (called by Cactus from inside the container), you create the mock objects that will be used by the JSP. The JSP gets its dynamic information either from the container - implicit object (HttpServletRequest, HttpServletResponse, or ServletConfig) or from a taglib. 2. Still in testXXX, you perform a forward to call the JSP under test. The JSP then executes, getting the mock data set up in 1) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 3. Cactus calls endXXX, passing to it the output from the JSP. This allows you to assert the content of the output and verify that the data you set up found its way to the JSP output, in the correct location on the page. 13.4.4 Executing a JSP with SQL results data Let’s see some action on the Administration application. In the servlet section (“Testing servlets”), you defined that the results of executing the SQL query would be passed to the JSP by storing them as a collection of DynaBean objects in the HttpServletRequest object. Thanks to the dynamic nature of dyna beans, you can easily write a generic JSP that will display any data contained in the dyna beans. Dynabeans provide metadata about the data they contain. You can create a generic table with columns corresponding to the fields of the dyna beans, as shown in listing 13.9. 13.9 Results View JSP (results.jsp) <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://jakarta.apache.org/taglibs/core" %> <%@ taglib prefix="d" uri="/dynabeans" %> Results Page
You use both JSTL tags and custom taglibs to write the JSP: The JSTL tag library is a standard set of useful and generic tags. It’s divided into several categories (core, XML, formatting, and SQL). The category used here is the core, which provides output, management of variables, conditional logic, loops, text imports, and URL manipulation. Licensed to Alison Tyler Download at Boykma.Com 18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 You also write two custom tags ( and ), which are used to extract information from the dyna beans. extracts the name of all properties of a dyna bean, and extracts the value of a given dyna bean property. There are two reasons for writing these custom tags. The primary reason is that it isn’t possible to extract dyna bean information without (ouch!) embedding Java code in the JSP (at least, not with the current implementation of the JSTL tags and the DynaBean package). The second reason is that it gives you a chance to write and unit-test custom taglibs of your own. WRITING THE CACTUS TEST Now let’s write a Cactus ServletTestCase for the JSP. The callView method from the AdminServlet forwards control to the Results View JSP, as shown in listing 13.10. Listing 13.10 shows a unit test for callView that sets up the DynaBean objects in the Request, calls callView, and then verifies that the JSP output is what you expect. Listing 13.10 TestAdminServlet.java: unit tests for results.jsp […] public class TestAdminServlet extends ServletTestCase { private Collection createCommandResult() throws Exception { (1) List results = new ArrayList(); DynaProperty[] props = new DynaProperty[] { new DynaProperty("id", String.class), new DynaProperty("responsetime", Long.class) }; BasicDynaClass dynaClass = new BasicDynaClass("requesttime",null,props); DynaBean request1 = dynaClass.newInstance(); (2) request1.set("id", "12345"); request1.set("responsetime", new Long(500)); results.add(request1); DynaBean request2 = dynaClass.newInstance(); (2) request2.set("id", "56789"); request2.set("responsetime", new Long(430)); results.add(request2); return results; } public void testCallView() throws Exception { AdminServlet servlet = new AdminServlet(); (3) request.setAttribute("results", createCommandResult()); (4) servlet.callView(request, response); (5) } public void endCallView(com.meterware.httpunit.WebResponse response) throws Exception { (6) assertTrue(response.isHTML()); (7) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 […] assertEquals("12345", response.getTables()[0].getCellAsText(1, 0)); (7) assertEquals("500", response.getTables()[0].getCellAsText(1, 1)); (7) assertEquals("56789", response.getTables()[0].getCellAsText(2, 0)); (7) assertEquals("430", response.getTables()[0].getCellAsText(2, 1)); (7) } } We start by defining the createCommand method (1), which puts several DynaBeans in the request (2). Then in the testCallView (3) method (remember that it is executed on the server-side) we instantiate the servlet to test (4), set the DyneBeans in the request (4) and call the jsp (5) to present the display the result. The endCallView (6), which is executed on the client-side, has a com.meterware.httpunit.WebResponse parameter, holding the response from the server. In (7) we assert different statements against the response of the server, in order to verify that the jsp displays the results properly. You use the Cactus HttpUnit integration in the endCallView method to assert the returned HTML page. When Cactus needs to execute the endXXX method, first it looks for an endXXX (org.apache.cactus.WebResponse) signature. If this signature is found, Cactus calls it; if it isn’t, Cactus looks for an endXXX (com.meterware.httpunit.WebResponse) signature and, if it’s available, calls it. Using the org.apache.cactus.WebResponse object, you can perform asserts on the content of the HTTP response, such as verifying the returned cookies, the returned HTTP headers, or the content. The Cactus org.apache.cactus.WebResponse object supports a simple API. The HttpUnit web response API (com.meterware.httpunit.WebResponse) is much more comprehensive. With HttpUnit, you can view the returned XML or HTML pages as DOM objects. In listing 13.10, you use the provided HTML DOM to verify that the returned web page contains the expected HTML table. In this section we just described how to test the frontend part of the Administrator application. What we are still missing is a few pages that will reveal us how to unit-test the AdministratorBean EJB, that actually executes our queries upon the database. The secrets of the EJB testing are covered in the next section. 13.5 Testing EJBs Testing EJBs has a reputation of being a difficult task. One of the main reasons is that EJBs are components that run inside a container. Thus, you either need to abstract out the container services used by your code or perform in-container unit testing. In this section, we’ll demonstrate different techniques that can help you write EJB unit tests. We’ll also continue developing our Administrator application, showing you the module that actually executes the SQL queries. Licensed to Alison Tyler Download at Boykma.Com 20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The Architecture of the Administrator application goes this way – the command to be executed gets through the filter, which determines if it is a SELECT query. After that the AdminServlet eventually receives the command/query. The execution flow starts in the AdminServlet doGet method. It receives the HTTP requests and calls the getCommand method to extract the SQL query from it. It then calls executeCommand to execute the database call (using the extracted SQL query) and return the results as a Collection. The results are then put in the HTTP request (as a request attribute) and, at last, doGet calls callView to invoke the JSP page that presents the results to the user. So far we have given no implementation of the executeCommand method. The idea behind it would be to call a given EJB, which would execute the query upon a given database. One simple implementation of the executeCommand method would be: public Collection executeCommand(String command) throws Exception { Context context = new InitialContext(); IAdministratorLocal administrator = (IAdministratorLocal) context.lookup(“AdministratorBean”); return administrator.execute(command); } And the EJB itself is listed here: Listing 13.11 AdministratorEJB […] @Stateless public class AdministratorBean implements IAdministratorLocal { public Collection execute(String sql) throws Exception { Connection connection = getConnection(); // For simplicity, we'll assume the SQL is a SELECT query ResultSet resultSet = connection.createStatement().executeQuery(sql); (1) RowSetDynaClass rsdc = new RowSetDynaClass(resultSet); (2) resultSet.close(); connection.close(); return rsdc.getRows(); (3) } private Connection getConnection() throws NamingException, SQLException { //RETURN SOME DATABASE CONNECTION } } What we actually do is call the execute method from the servlet with the given query; there we try to get hold of a valid connection and execute the query (1). After that we create a RowSetDynaClass object form the ResultSet (2) and we return its rows (3). Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 In order to test the EJB with Cactus you have to instantiate it and then assert against the result of the execution. Of course you can use, again, Mock objects to simulate the JNDI lookup, but this approach is unnecessary complicated, so we won’t list it here. Let’s have a look at the test-case for the EJB and then go through it and discuss. Listing 13.12 Test-case for AdministratorEJB […] public class TestAdministratorEJB extends ServletTestCase { private IAdministratorLocal administrator; public void setUp() throws Exception { Properties properties = new Properties(); (1) properties.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); properties.put("java.naming.factory.url.pkgs", "org.jboss.naming rg.jnp.interfaces"); InitialContext ctx = new InitialContext(properties); administrator = (IAdministratorLocal) ctx.lookup("ch14-cactus-ear- cactified/"+AdministratorBean.class.getSimpleName()+"/local"); (2) } public void testExecute() throws Exception { String sql = "SELECT * FROM CUSTOMER"; Collection result = administrator.execute(sql); Iterator beans = result.iterator(); assertTrue(beans.hasNext()); (3) DynaBean bean1 = (DynaBean) beans.next(); assertEquals(bean1.get("Id"), new Integer(1)); (3) assertEquals(bean1.get("name"), "John Doe"); (3) assertTrue(!beans.hasNext()); } } At (1) we start by initializing the context in the setUp method (remember this method is called before each test method), and get hold of a valid IAdministratorBeanLocal instance from the JNDI (2). Then in the each test method we invoke the methods on the EJB with different parameters and, of course, assert the validity of the result (3). So far we have covered what you need in order to write Cactus test-cases. Before we rush into the section that deals with execution of our Cactus tests, it is essential to get familiar with a project called Cargo. The tight integration between Cargo and Cactus is one of the new features that extremely facilitate you in running your tests. 13.6 What is Cargo? What we’ve seen so far is pretty much the most of how to write Cactus tests cases. At the beginning of the chapter we mentioned that Cactus test cases are executed inside the Licensed to Alison Tyler Download at Boykma.Com 22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 container. In this section and in the next ones we will focus on how to integrate execution of Cactus tests in your build lifecycle. In order to keep the extreme programming principles you need to execute the tests every time you make a build. This requires a tight integration between Cactus and the build system – Ant or Maven. But before we jump straight into that integration we need to get familiar with a project they both rely on – Cargo. Cargo (http://cargo.codehaus.org/) is an open-source project started, again, by Vincent Massol in 2004. The aim of the project is to automate container management in a generic way so that you could use the same mechanism to start and deploy a war file with Tomcat as you could with WebLogic or almost any other. Otherwise said it provides an API around most of the existent J2EE containers, for managing those containers (starting, stopping and deploying). But Cargo is a little bit more than just an API, and that’s where its strength comes from – it provides Ant tasks and Maven (1x and 2x) plugins for facilitating the management of those containers. Now after this brief introduction to the secrets of Cargo you are probably asking yourself, what is the connection between Cargo and Cactus? The Cactus team realizes that the idea behind the project is really great, but seems like there’s too much of a burden regarding the process of executing the tests – once written the tests need to get packaged in a war or ear archive, then the application descriptors need to get patched with the appropriate redirectors. After that, before the execution gets started, the archive needs to get deployed in a container that is already started. I bet you already noticed the three italicized words in the previous sentence. I also bet that you already guess that the main idea of the Cactus development team was to hide all the complexity regarding the management of the container by means of Cargo. This gives us full automation – if we use Cargo’s Ant tasks (or Maven plugins) to start the container, then deploy the war/ear in it and then stop the container, then we have achieved the extreme programming principles of continuous integration. Our build is fully automated, isn’t it? That is all true, however deploying the archive with the tests by itself does not do anything magical - we still need to figure out a way to trigger the execution of the tests when the archive is deployed. We also need a way to prepare the archive for deployment. This is all part of the tight integration between Cactus and the different build systems. We will deal with this integration in the subsequent chapters. 13.7 Executing Cactus tests with Ant The first way to fire up Cactus tests that we are going to show seems to be the most common one. Using Ant is easy and straightforward. If you are new to Ant, I would like to recommend you the “Ant in Action” by Steve Loughran and Erik Hatcher – a really marvelous book. Also, before you read this section, please make sure you have already read Chapter 8. 13.7.1 Cactus tasks to prepare the archive Cactus comes bundled with two kinds of Ant tasks – the first one would facilitate you in preparing the archive (WAR or EAR) to hold the test-cases, and the second will actually Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 invoke the tests. We will go through these tasks one after the other and will show you how to use these tasks. The process of preparing the archive for executing the tests is called cactification. This is a term initially introduced by the cactus team. Imagine you have at some point an archive (a WAR or EAR file) which is your application that you want to deploy. The cactification process includes, adding the required jars in the lib folder of the archive and also patching the web.xml to include desired cactus redirectors. According to the type of archive that you want to cactify, there are two different tasks that you may want to use – cactifywar and cactifyear tasks. Before we rush into describing these tasks, let us first take a minute to focus on the build.xml skeleton that we are going to use for our presentation purposes. Listing 13.13. Build.xml skeleton to execute the Cactus tests. […] (1) (1) […] (2) (2) (3) (4) […] (4) (5) (6) (7) Licensed to Alison Tyler Download at Boykma.Com 24 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 (8) (9) […] This Ant descriptor is pretty simple. As you can see one of the first things to do is declare some properties in the init target (1). We will use these properties later in the script. In this target we also resolve the additional dependencies with Ivy (2) and construct a classpath refid (3). The prepare target prepares the folder structure for the build (4), and after that we use the taskdef task (5) to define the external tasks (remember that cactifyXXX and cargo tasks are external tasks that come with Cactus/Cargo – they are not part of the official Ant tasks). Then we compile our code (6) and produce either JAR file containing the EJBs (7) or WAR file (8) containing the Servlets (depending on what part of the code we will test). We might use also a separate target to produce the EAR file (9) that we will need. Now that we are aware with the structure of the build.xml it’s time to focus on the first set of tasks that Cactus provides. THE CACTIFYWAR TASK This task is used when your application is a WAR file and you want to cactify it. The cactifywar task extends the built-in war Ant task so it also supports all attributes and nested elements that the war task supports. Nothing explains better than an example, so let’s try to start the test cases from this chapter using Ant. We will walk through the build.xml and discuss it over. Listing 13.14 Build.xml to present cactifywar task. In the cactifywar target we call the cactifywar task, which we imported in the first steps (in the load.tasks target). As you can see the cactifywar tasks takes the following parameters: srcfile, destfile and a list of redirectors we want to define. Of course there is a bunch of other, non-required parameters, all of which are perfectly documented on Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 25 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 the web-site of Cactus (http://jakarta.apache.org/cactus), where you can refer for additional help. Also, once again, since the cactifywar task extends the war task, you can pass all the parameters for the war task to it – they are all valid. In the srcfile attribute you specify the archive file of your application that you want to cactify. The important thing to notice here is that you may need to specify not only the name of the file, but also the destination path to it. In the destfile parameter you specify the name of the cactified file to produce. You also may want to describe a list of redirectors in the cactifywar task. This list of redirectors describe URL-patterns to map the Cactus test redirectors to which the nested elements filterredirector, jspredirector and servletredirector. If you don't specify those elements, it’s pretty normal - the test redirectors will be mapped to the default URL-pattern. After executing the target with ant cactifywar we should get the desired cactified archive, that you can examine. THE CACTIFYEAR TASK The cactifyear task is the analogue of the cactifywar task, but instead is used to cactify EAR applications. It is a bit different than the previous one, because in most of the cases EAR applications contain inside them a WAR archive that needs to be cactified itself. The cactifyear task is, again, an external task that comes from the Cactus team, and extends the Ant ear task. This way it accepts all of the parameters that are valid for the ear task. Let us now execute our tests from an EAR archive and walk through the example application and discuss the different aspects. Listing 13.15 Build.xml to present cactifyear task. (1) (2) Licensed to Alison Tyler Download at Boykma.Com 26 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Once again we use the build.xml skeleton from listing 13.13, and here we just list the target that is responsible for the cactification of the already packaged EAR file. As you can see the cactifyear task accepts the srcfile and destfile parameters again (1). Their meaning here is exactly the same as for the cactifywar task. The new component here is a cactuswar nested element. This element has all the parameters of the cactifywar task except the destfile parameter. The web application will always be named cactus.war and be placed in the root of the EAR. We also add the commons-beanutils.jar to the web application (2), because our servlet and filter test cases need it. Once executed with ant cactifyear we get cactified archive which can be examined. THE CACTUS TASK As the other Cactus-related tasks are external Ant tasks that extend some of the internal Ant tasks, this concept is also valid for the cactus task. The cactus task is used to execute the Cactus tests, and since every Cactus test is a pure JUnit test, I guess you already figured out what is the task that cactus task extend. That’s right – the cactus task extends the junit task. This way all the parameters that the junit task accepts, are also valid for the cactus task, too. Here is a listing that extends the 13.13 listing with the cactus task that executes the tests from the ear archive. Listing 13.16 The runtests target to demonstrate the cactus task. […] (2) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 […] As you can see the cactus task is used in connection with the Cargo tasks (this is actually the reason that we introduced Cargo awhile ago). With the one declaration in the given listing we have ensured all the necessary information to start, execute tests and stop the container, defined. The warfile/earfile parameter (1) is used to specify the name of an archive file that we are going to deploy. This has to be the name of our cactified war/ear file that holds our test-cases as well as the classes that you want to test (i.e. it is the result of the corresponding cactify task). It is also obligatory to add a containerset nested element (2). In this nested element we specify a number of cargo tasks (3), which will define the containers to execute our tests in. When we say that we specify cargo tasks, we really mean it – these are pure cargo tasks and they can take any parameter that a normal cargo task can take – proxy, timeout, server port, etc. In the given cargo tasks we have to specify the id of the containers, the configuration of the containers, and the deployable (the cactified archive that contains our test-cases). Cargo tasks can work with installed local or remote instance of a container – you only have to specify the home directory of that container. But there’s more - these tasks also let you specify the so-called “zipurlinstaller”. In this installer you specify a URL to a given container archive and Cargo will download the given container from there, extract the container from the archive, start the container, deploy the given deployable, then cactus will execute the tests inside the container, and then cargo will stop the container. That is a fully automated cycle, and in fact the cactus project comes with several sample applications that use this automation. Our application is done – not only this, but it is also well tested using Cactus. You already saw how to execute your tests with Ant, so it is time to show one final way to execute the given tests this time using another build tool – Maven. 13.8 Executing Cactus tests with Maven2x Another common approach for executing your Cactus tests is including it in your Maven1 build. 1 From now on whenever we discuss the term Maven we are going to consider the Maven2 project, since the authors of this book consider Maven1 a dead technology. Licensed to Alison Tyler Download at Boykma.Com 28 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Lots of people use Maven as their build system, and until version 1.8.1 of Cactus the only way of executing Cactus tests with Maven was calling the Ant plugin for Maven and executing the tests via Ant. The latest version of Cactus, however, contains a cactus-maven2 plugin that significantly facilitate the cactification process. The Cactus Maven plugin consists of two MOJOs (Maven Old Java Object), you can use respectfully for cactfication of a WAR or EAR. Let's walk through the examples and see how to use them. 13.8.1 Maven2 cactifywar mojo Here is a common pom.xml that we will enhance later. Listing 13.17 Sample pom.xml for running the cactus tests. 4.0.0 com.manning.junitbook2 ch14-cactus jar 2.0-SNAPSHOT This is a very basic pom.xml, and we can use it for all our purposes - compile our source code and package our application in a WAR archive. There is nothing fancy here – just using the corresponding plugins of maven. After executing the build with mvn package we should see the resulting archive and examine its correctness. Now we can add the cactus plugins to prepare the archive for deployment. The cactus plugin is actually nothing more, but a declaration of several other plugins, in a correct order. We are going to show you now, how to declare these plugins. The next three listings (13.18, 13.19 and 13.20) should be considered one big listing, but for the sake of readability we have split them into three. Listing 13.18 Build section of the pom.xml to enable cactus tests execution org.apache.cactus cactus.integration.maven2 (1) 1.8.1 ${project.build.directory}/${pom.artifactId}- ${pom.version}.war (2) ${project.build.directory}/${pom.artifactId}- cactified.war (2) target/test-classes (3) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 29 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 **/**Test*.class (3) commons-beanutils (4) commons-beanutils (4) cactus-cactifywar pre-integration-test (5) cactifywar As we mentioned already all we do is define three plugins one after another in a correct order. The first one is the cactus.integration.maven2 plugin (1). We use it for cactification of the WAR file we got from the previous listing. Again, as in the cactifywar task we specify a srcfile and destfile parameters (2). We also specify which test-classes to include (3) and also which libraries to include in the WEB-INF/lib folder (4). In this case we want only the commons-beanutils, because our tests use it. As I mentioned earlier it is really important to specify the execution order of the plugins. In Maven you specify the execution order by attaching every plugin goal to a single phase. For instance in the listing we have attached our plugin’s cactifywar goal to the pre- integration-test phase (5). This phase, as its name imposes, gets executed by Maven just before the integration-test phase (in which we are going to execute our tests). This is perfect, because we want our package cactified before we execute our tests. Next listing displays the second plugin. Listing 13.19 Continue with the plugin declarations from listing 13.18 org.codehaus.cargo cargo-maven2-plugin (6) 1.0-beta-2 start-container pre-integration-test (7) start Licensed to Alison Tyler Download at Boykma.Com 30 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 stop-container post-integration-test (8) stop false 20000 tomcat5x http://apache.speedbone.de/tomcat/tomcat- 5/v5.5.25/bin/apache-tomcat-5.5.27.zip ${basedir}/install cactifiedByMaven2.war http://localhost:8080/test/ /test The next plugin declaration is for the cargo-maven2-plugin which we use to declare the containers to execute our tests in (6). As you can see we attach its start goal to the pre- integration-test to start the container before we execute the tests (7). But wait a second; didn’t we just attach the cactifywar goal to the same phase? What will be the execution order here? In this case we need two different goals attached to the same phase, because we have more than one thing to do before the integration phase of Maven. In this situation the cactus plugin will execute first, because it is declared right before the cargo plugin. And here you can see why we insisted that the order of declaration of the plugins in the section is so important. This way we first prepare the cactified archive for deployment, and then start the container with this archive as a deployable. We also attach this plugin’s stop goal with the post-integration-test phase (8). This is pretty normal – we need to stop the container once the tests are executed. The last listing displays the third plugin – the maven-surefire-plugin. Listing 13.20 Continue the plugin declarations from listing 13.19 Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 31 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 org.apache.maven.plugins maven-surefire-plugin (9) true (10) surefire-it integration-test (11) test false (12) cactus.contextURL (13) http://localhost:8080/test/ The last plugin declaration is for the maven-surefire-plugin (9). As we have already seen in chapter 9 this maven plugin is responsible for executing junit tests. Because of the fact that every Cactus test is also a JUnit test, we can use this plugin to execute our Cactus tests. There are a couple of things to notice in its declaration. As you can see we have declare the skip parameter with true (10). That is because the surefire plugin is by default attached to the test phase. We surely don’t want it attached to this phase, so we declare the skip parameter to true, which will cause the plugin to skip the execution. Further in the declaration we attach the test goal with the integration-test phase (11) (where we actually want it to be), and declare the skip parameter with false (12). This will cause the plugin to execute the tests in the integration-test phase, just as we want it to happen. There’s also one thing to remember – whenever you execute cactus tests, you always have to specify cactus.contextURL (13). The cactus ant task does it for you, but the surefire plugin does not, so that is what we do in the last part. 13.8.2 Maven2 cactifyear mojo To execute cactus tests from an EAR file, you have to follow up the same procedure as for the war file, except for the cactification of the archive. That’s why we cover the cactifyear cactus plugin in this section. This listing that will cactify our example EAR package. Listing 13.21 Cactifyear cactus plugin declaration Licensed to Alison Tyler Download at Boykma.Com 32 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 org.apache.cactus cactus.integration.maven2 1.8.1 target/${pom.artifactId}-${pom.version}.ear(1) ${project.build.directory}/${pom.artifactId}- cactified.ear (1) (2) / (3) target/test-classes **/*Test*.* (4) 2.3 (5) cactus-cactifyear pre-integration-test (6) cactifyear This declaration seems pretty simple. All you have to take care is provide the srcfile and destfile parameters (1). In the section we describe parameters related to the WAR application inside our EAR file – like what will be the context of the application (3), which test-classes to include (4), and what version of the web.xml to be used (5). In (6) we attach the plugin to the pre-integration-phase of maven. The rest of the pom.xml is the same as the one in the previous section so we won’t focus on it any more. Now we will move on and show you one other way of executing your Cactus tests. This time we will use Jetty as an embedded container. Jetty is not only a servlet-container, but it also provides you with an API for manipulating the container. This API is used by Cactus to fire up the container for you, execute the tests and then stop the container all with a single command. 13.9 Execute the cactus tests from the browser We know a bunch of different ways to execute JUnit tests. We saw executions through JUnit’s own test-runner, also with the Ant and Maven test-runners, and also through Jetty. As we already know Cactus tests are also JUnit tests, so the question “What is the analogue of the JUnit’s text test-runner for Cactus?” seems valid and reasonable. The JUnit’s test-runner communicates straight with the JVM the execution takes place in and gets the result from Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 33 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 there. Cactus tests get executed in the server JVM so we need to find a way to communicate with the server (tell him to invoke the tests and get the results). The easiest way to communicate with the server is via a browser. In order to do this there are a couple of things that need to be made. First you need to declare the ServletTestRunner servlet in the application’s web.xml. The declaration is listed below: Listing 13.22 ServlettestRunner declaration [...] ServletTestRunner org.apache.cactus.server.runner.ServletTestRunner [...] ServletTestRunner /ServletTestRunner [...] Once declared we are going to use this servlet in our URL in the browser to tell the server to invoke the tests. You actually need to call the server with the following request in the browser: http://server:port/mywebapp/ServletTestRunner?suite=mytestcase Here you need to replace the server, port, mywebapp and mytestcase with the correct values of your server address, port number, context and the the fully qualified name (i.e. with packages) of your TestCase class containing a suite() method. After executing the given URL in the browser the server should respond with the following result: Licensed to Alison Tyler Download at Boykma.Com 34 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Fig 13.5. Xml result in the browser from cactus tests. If you see a blank page, click on the View source option of your browser. It means your browser doesn't know how to display XML data. Okay, that's nice ... but what if I want HTML instead of XML? Don't worry, there is a solution. Grab the XSLT stylesheet that comes with cactus (cactus-report.xsl, based on the stylesheet used by the Ant task), drop it in your webapp (in the root directory for example). Then, open a browser and type http://server:port/mywebapp/ServletTestRunner?suite=mytestcase&xsl=cactus-report.xsl. The xsl stylesheet will generate the HTML report you are familiar with, so you can view it from within your browser. 13.10 Summary When it comes to unit-testing container applications, pure JUnit unit tests come up short. A mock-objects approach (see chapter 6) works fine and should be used. However, it misses a certain number of tests—specifically integration tests, which verify that components can talk to each other, that the components work when run inside the container, and that the components interact properly with the container. In order to perform these tests, an in- container testing strategy is required. In the realm of JavaEE components, the de facto standard framework for in-container unit-testing is Jakarta Cactus. In this chapter, we ran through some simple tests using Cactus, in order to get a flavor for how it’s done. We also discussed how Cactus works, so we are now ready to in-container test our JavaEE applications. Testing the components from the JavaEE spec is nice, but is not the whole picture. Most of our applications are heavily framework-based. In the next chapters we will explore one of the most widely used MVC frameworks – JSF. We will also introduce the JSFUnit project that will let you test your JSF application inside the container. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 14 Testing JSF applications with JSFUnit This chapter covers ƒ What are the problems of testing JSF application ƒ Mock solution of the problems ƒ JSF unit/integration testing using JSFUnit ƒ JSF performance testing with JSFUnit Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The first 90% of the code accounts for the first 90% of the development time. The remaining 10% of the code accounts for the other 90% of the development time. - Tom Cargill The Cactus framework we just introduced in chapter 13 is great for testing most of the JavaEE spec. After all, on a daily basis what the majority of Java developers do is write JavaEE applications which are all based on the JavaEE specifications. In this chapter we take a closer look to the newest member of the JavaEE spec – the Java Server Faces (JSF) technology. It is a standard specification developed through the Java Community Process (JSC), for building Java web-based user interfaces. We will start the chapter explaining what JSF is, what the problems of testing JSF application are, and how to use the JSFUnit project to solve them. We will implement a sample MusicStore application and we will demonstrate the power of JSFunit on it. 14.1 JSF introduction Apart from the specification that we already mentioned, people refer JSF 1as the implementation of this specification - a server-side, user-interface, component framework for Java technology based applications. This means that different organization can implement the specification producing different frameworks, all compatible with the specification. Some of the most popular implementations out there are Apache MyFaces (http://myfaces.apache.org/), Sun’s Mojarra JSF Reference Implementation, etc. In this book we are using the Apache MyFaces implementation as we consider it the most robust. Figure 14.1 shows an overall architectural view of a sample JSF application. The JSF framework was intended to simplify development of web application. For this reason it is designed in a way to provoke the developers to think in terms of components, managed beans, page navigations, etc. and not in terms of technical details such as request, response, session etc. JSF handles most of the complexity that you might encounter during development time of a sample web application. What is JSF responsible is when you click a button in your browser a request is being sent to the server. This request needs to be translated in a way that your application can understand it. The JSF framework is also responsible to translate and visualize the response from the application in a way that the browser can display it. The framework provides a large number of tag-libraries which the developers can use to visualize absolutely anything. 1 If you are new to the Java Server Faces technology we would highly recommend the “Java Server Faces in Action”, ISBN: 1932394125, by Kito Mann Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 14.1 How JSF works – different communication layers. Let’s move further and introduce a sample JSF application. In the course of the implementation we will explain what the different parts of the application are, and further in the chapter we will test our sample application; in the end that’s what the book is about – testing. 14.2 Introducing the MusicStore application In the previous section we described the parts that a typical JSF application consists of. Now it is time to examine those parts by introducing a real application. We will demonstrate the MusicStore application which we will refine and will test till the end of the chapter. The MusicStore is a very simple JSF application that serves to present different kinds of music albums, which the user can navigate through and purchase. We start the implementation with a very simple POJO (Plain Old Java Object) representing an Album. The implementation is shown on the following Listing 14.1 Album.java POJO object […] public class Album { (1) Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 private String name = null; private String author = null; private double price = 0; private int year = 0; private String style = null; private String imageURL = null; public Album(String name, (2) String author, double price, int year, String style, String imageURL) { this.name = name; this.author = author; this.price = price; this.year = year; this.style = style; this.imageURL = imageURL; } //Getters and setter go here… } As you can see the implementation is very simple – the Album is a simple POJO that contains several properties (1) – like name of the album, author of the album … We have also added getters/setters for those properties and a constructor (2) to make convenient instantiation of different albums. Moving forward the next listing shows a manager that we use to manipulate the data. Listing 14.2 AlbumManager that we use to perform different operations on Albums […] public class AlbumManager { final static List albums = new ArrayList(); (1) static { | albums.add( new Album( … ) ); | albums.add( new Album( … ) ); | albums.add( new Album( … ) ); | albums.add( new Album( … ) ); | albums.add( new Album( … ) ); (1) } public static List getAvailableAlbums() { (2) return albums; } public static Album getAlbumByTitle( String title ) { (3) for ( Album album : albums ) { if ( album.getName().equals( title ) ) { return album; } Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } return null; } } As you will notice with the MusicStore application we want to focus only on JSF. So we will try to avoid any other layers like interaction with a database and so forth. That’s why we start the implementation with a declaration and initialization of a list with five hard-coded Albums (1). Normally we would invoke a database layer to get these albums, but for the sake of simplicity we will use the hard-coded values. Also to keep the code-listing simple and readable we have removed the Album declarations. Next we implement a number of methods around those hard-coded albums; we have one method for retrieving all the available albums (2), and another one for finding an album by a given title (3). Our next step would be to start implementing the bean that the front-end would communicate with. Listing 14.3 shows that bean. Listing 14.3 ListAvailableAlbumsBean implementation […] public class ListAvailableAlbumsBean { private List albums = new ArrayList(); (1) public List getAlbums() { (2) this.albums = AlbumManager.getAvailableAlbums(); return albums; } public void setAlbums( List albums ) { (3) this.albums = albums; } } This listing describes the baking bean for our application. For every attribute we want to display in the frontend we specify a corresponding property of the bean (1). Every property itself needs to have a getter (2) and a setter method (3). As you can see our baking bean communicates with both - on one hand our JSPs will interact with this baking bean, on the other hand the bean itself interacts with the AlbumManager from the previous listing. Before this bean is able to expose itself for the JSPs we need to configure the bean in a configuration file called faces-config.xml. Listing 14.4 faces-config.xml to configure our beans. […] (1) Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 com.sun.facelets.FaceletViewHandler (2) listAlbumsBean (3) com.manning.junitbook.ch14.beans.ListAvailableAlbumsBean request […] As you can see the faces-config.xml is very simple. All you need is an application declaration (1) which contains some information that is global for the whole application (like the view handlers, the locale parameters, etc). The managed-bean declaration (2) is the one that we use to expose our beans to the JSPs. Notice the managed-bean-name declaration (3) – this name will be used in the JSP to reference the bean with a class defined in managed- bean-class declaration. The only thing that we are really missing to have the full picture of the MusicStore application is the JSP to display all the albums available. Listing 14.5 The JSP to display all the available beans. (1) […] (2) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502
Name Author Year Price
(3) ${album.author} ${album.year} $ ${album.price}
We need to import the taglibraries that we want to use in this JSP. Everything that our JSP is doing is simply iterating over all the albums (2) and displaying the information for every album that we want. As you can see we use the standard JSTL taglibraries (2), as well core JSF taglibraries (3). As you can see from the previous listing, the JSP contains a link that to a managed bean we haven’t defined yet: […] What we want to implement is the following: the list_albums.jsp presents all the albums to the user, and on clicking the name of a certain album, you need to be redirected to a page that displays the details for the album that you have clicked. On that new page you would have the possibilities to purchase the album, or navigate back to the listing page. Here comes the listing that gives the code for the bean that displays the details for the selected album: Listing 14.6 AlbumDetailsBean implementation […] public class AlbumDetailsBean { private String status = null; private HttpServletRequest request = null; (1) private Album album = null; (2) public String showAlbumDetails() { (3) HttpServletRequest request = getRequest(); String name = request.getParameter( "albumName" ); (4) if ( name == null ) { return ""; } setAlbum( AlbumManager.getAlbumByTitle( name ) ); (5) return "showAlbumDetails"; } Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 protected HttpServletRequest getRequest() { if ( this.request == null ) { (6) return (HttpServletRequest) | FacesContext.getCurrentInstance() | .getExternalContext().getRequest(); (6) } else { return this.request; } } public String cancel() { return "cancel"; } public void purchase() throws InterruptedException { Thread.sleep( 1500 ); (7) // empty implementation System.out.println( "Here we must implement the purchase logic." ); } //Additional getters and setters follow. } We need to keep track of several things in the bean, among which are the request the user is sending (1) and the album that the user has selected to present (2). We need the request that because the name of the selected album is passed as a parameter in the request and we want to know it. The method that is called when you click on the name of a given album is showAlbumDetails (3). In this method we get the request and extract a parameter with the name “albumName” from it. After that we use the AlbumManager to extract the Album object containing all the details for the album with this name (5). Pay attention to the way we extract the request (at 6) – if the attribute of the bean is null we get it from the FacesContext object. After a minute, in the next section, when we implement the Mock tests we will mock the request object and set it as an attribute to the bean. The last method is the purchase method (7) – we call this method when a user wants to purchase a given album. Normally you would have your purchasing logic in here, but for the sake of simplicity we provide a blank implementation. You may notice that we stop the execution exactly for a second and a half, because we want to simulate that this method is time-consuming, when we write some performance tests in the last section of this chapter. Now let’s move to the JSP that provides the details for the bean: Listing 14.7 album_details.jsp presenting the details for a given product. Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 xmlns:a4j="http://richfaces.org/a4j" xmlns:c="http://java.sun.com/jstl/core"> Album details Details for album '#{albumDetailsBean.album.name}':
(1) | | | (1)
Title: #{albumDetailsBean.album.name}
Year: #{albumDetailsBean.album.year}
Style: #{albumDetailsBean.album.style}
Author: #{albumDetailsBean.album.author}
Price: $ #{albumDetailsBean.album.price}
| | | (2) (3) (4)
As you can see this is a pretty simple JSP that lists all the details for the selected album (1). The only thing interesting are the two button declarations: the purchase button (2) and the cancel button (3). We use Richfaces components to declare these buttons and to implement AJAX behavior. Notice the reRender attribute in the definition: this attribute holds the id of another component that needs to be re-rendered when we get the response Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 from the server. In our case the re-rendered component is a text-component that displays the status of the purchase (4). We are done – the MusicStore application is now completed. In order to see it in action you have to check the source code of the book. There we have provided a maven script with a jetty plugin configured, so all you have to do is go to the command line, navigate to the folder that contains the pom.xml for the project and invoke the jetty plugin from there: > mvn jetty:run After that the Jetty servlet container should be started and if you navigate to the http://localhost:8080/ch15-jsfunit/ you should be able to see the application in action (fig 14.2) Fig 14.2 The two screens of the MusicStore application The first image displays all the available albums, and if you click on any given album you should see the second part of the figure, where you can see the details for the album and eventually purchase it. To move on, in the next sections we will describe how to test the different parts of this application. 14.3 Typical problems when testing JSF applications As we have seen so far JSF applications typically consist of POJOs (called managed beans), and some frontend JSPs. As we know the managed beans are very simple and therefore easy to unit-test. So why are JSF applications hard to test? Indeed the managed beans are easy to unit test, but the hard part comes when you want to include interaction with a container in your tests. Normally tests give you the security you need to refactor mercilessly your application. They provide you with the confidence that you have not introduced a new bug in the system by trying to refactor it, or by trying to fix an issue. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 So what could possibly break in a normal JSF application? Here comes a list of typical problems that might occur in a JSF application: ƒ Managed-bean problems ƒ Managed-bean method problems ƒ faces-config.xml typos ƒ Proper interface implementation ƒ Missing getter/setter methods ƒ Duplicate managed bean declarations ƒ Different JSF tag problems ƒ Wrong navigation problems All of these problems can be solved by using the JSFUnit static analysis. The JSF static analysis is an API provided by the JSFUnit project, which will help you finding small problems in your project (like typo errors). A typo error in your faces-config.xml or a method name can be very annoying. Let’s see for instance the next listing. Listing 14.8 Static analysis for your faces-config.xml […] public class FacesConfigTestCase extends AbstractFacesConfigTestCase { (1) private static Set paths = new HashSet() (2) { { add( "src/main/webapp/WEB-INF/faces-config.xml" ); } }; public FacesConfigTestCase() { super( paths ); (3) } } All you have to do is first extend the special class AbstractFacesConfigTestCase that comes with JSFUnit (1). Then create a Set of Strings to hold the paths to your faces- config.xml files (2), and call the constructor of the base class with the just created Set (3). What JSFUnit will do is simply parse the config files you have specified, and it will make sure: ƒ All of your session and application scope beans are serializable ƒ All of your managed bean are in a valid scope Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 ƒ Check whether you have missing managed beans ƒ Make sure you are using the right faces config class inheritance ƒ Check for missing setter methods and for duplicate managed beans Cool, isn’t it? Enough with the static analysis, let’s do some real testing. 14.4 Different strategies for testing JSF applications The problems that might occur when developing JSF applications always have different approaches for solving them. Let’s try and categorize the different ways to test you JSF application. 14.4.1 Black-box approach This2 is probably the most straight-forward approach. With JSF applications a form of black- box testing would be to open up a web-browser and start clicking on your pages, to verify that it all works as expected. One way to automate the black-box testing would be to use tools such as Selenium, HttpUnit or HTMLUnit (See chapter 11 for those tools). The point is that using plain Selenium or HTMLUnit to test your JSF application in most of the cases comes short in several ways. First of all it is hard to validate expected HTML, and in the case that you succeed to validate it, your tests will fail on every minor HTML changes. Not to mention the hard-time comes when you have some AJAX parts of your application that need to be tested. We already covered the Selenium/HTMLUnit approach in chapter 11, so we will not focus on this here. 14.4.2 Mock objects to the rescue Another approach you might take, in order to test the MusicStore application would be the white-box approach. In this case you test only the server-side classes using our old friends the mocks3, and without running the whole application in a container. Actually the managed-beans are so simple that to test them you don’t need any kind of mocking. The problems appear when you want to involve objects like FacesContext which you normally don’t have access to. You can mock those objects, as well as the HttpServletRequest, HttpServletResponse and some other objects using the techniques we described in chapter 6. For instance let’s test the showAlbumDetails method of the AlbumDetailsBean from listing 14.6 using the JMock mocking library. This method extracts a parameter from the request, so we need to mock the request and pass different values for the parameter. The following listing demonstrates this. Listing 14.9 Testing the AlbumDetailsBean using JMock 2 We highly recommend you to first read chapter 5 before continuing with this section. 3 More about mocks you can learn from chapter 7 Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 […] public class TestAlbumDetailsBeanMock { private Mockery context = new JUnit4Mockery(); (1) private HttpServletRequest mockRequest; (2) @Before public void setUp() { mockRequest = context.mock( HttpServletRequest.class ); (3) } @Test public void testShowAlbumDetailsRealAlbum() { context.checking( new Expectations() { (4) { | oneOf( mockRequest ).getParameter( "albumName" ); | will( returnValue( "Achtung Baby" ) ); | } | } ); (4) AlbumDetailsBean albumDetailsBean = new AlbumDetailsBean(); (5) albumDetailsBean.setRequest( mockRequest ); (6) String forwardString = albumDetailsBean.showAlbumDetails(); (7) assertEquals( "The return string must match 'showAlbumDetails'",(8) forwardString, "showAlbumDetails" ); | | assertNotNull( "The album must not be null", | albumDetailsBean.getAlbum() ); | assertEquals( "The author must be U2", | albumDetailsBean.getAlbum().getAuthor(), Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 "U2" ); | assertEquals( "The year of the album must be 1991", | albumDetailsBean.getAlbum().getYear(), 1991 ); (8) } @Test public void testShowAlbumDetailsNoParameterAlbum() { context.checking( new Expectations() { { oneOf( mockRequest ).getParameter( "albumName" ); will( returnValue( null ) ); } } ); AlbumDetailsBean albumDetailsBean = new AlbumDetailsBean(); albumDetailsBean.setRequest( mockRequest ); String forwardString = albumDetailsBean.showAlbumDetails(); assertEquals( forwardString, "" ); assertNull( "The album must be null", albumDetailsBean.getAlbum() ); } @Test public void testShowAlbumDetailsNoRealAlbum() { context.checking( new Expectations() { { oneOf( mockRequest ).getParameter( "albumName" ); will( returnValue( "No-real-album" ) ); } } ); AlbumDetailsBean albumDetailsBean = new AlbumDetailsBean(); albumDetailsBean.setRequest( mockRequest ); String forwardString = albumDetailsBean.showAlbumDetails(); assertEquals( "The return string must match 'showAlbumDetails'", forwardString, "showAlbumDetails" ); assertNull( "The album must be null", albumDetailsBean.getAlbum() ); } } Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 tests. This test should be very simple, after you have already read chapter 6. We start by defining the Mockery context (1) and the object that we want to mock (2). After that, in the @Before method we initialize the request object to be ready for usage (3). The next step is to define several test-methods, each one of which will test the showAlbumDetails method by specifying different parameter in the request. For every one of those methods we define the expectations (4), initialize the AlbumDetailsBean (5), set the mock request to the bean (6), and execute the method under test (7). The final test is to perform the assertions that we want (8). As you can see this kind of testing is very easy, but the drawback is that it tests only the server-side logic. It doesn’t test the interaction with the HTML pages. That’s why we would like to focus our attention now on the not-so-trivial case, when you want to execute your tests inside the container and test all the layers. 14.5 Testing the MusicStore application with JSFUnit The last approach that we will discussed is in-container testing4 using the JSFUnit framework. The JSFUnit framework steps on the Cactus5 projects that we already discussed in the previous chapter, so the lifecycle of a sample test is the same. What JSFUnit does is provide an API to gain access to all of the JSF-realted objects in your As we strongly believe that an example is worth several pages of explanation, let’s give a short example of a JSFUnit test-case. Listing 14.10 First JSFUnit test public class TestListAvailableAlbumsBean extends org.apache.cactus.ServletTestCase { (1) public void testInitialPage() throws IOException, SAXException { (2) JSFSession jsfSession = new JSFSession( "/" ); (3) JSFServerSession server = jsfSession.getJSFServerSession(); (4) assertEquals( "/list_albums.jsp", server.getCurrentViewID() ); (5) UIComponent label = server.findComponent( "list_albums_label" ); assertEquals( label.getParent().getId(), "list_albums" ); assertEquals( 5, ((List) server.getManagedBeanValue( "#{listAlbumsBean.albums}" )).size()); (6) } } 4 More about in-container testing philosophy you can see in chapter 8. 5 We strongly encourage you to go back and read chapter 14 if you have skipped it and then come back to this chapter. Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Before diving into the example we need to mention one golden-rule for writing JSFUnit tests: We create a test-case by extending the ServletTestCase class (1) that comes from the Cactus API (we already mentioned that JSFUnit steps on Cactus and extends its API). As Cactus extends JUnit 3, our test-case class needs to contain one or more testXXX methods (2). In these test-methods we will perform the actual testing, using the JSFUnit API. In the current example what we do is create a JSFSession object (3), which points to the root of our application. From the JSFSession object we can hold of two important objects – the one is JSFServerSession (4) and the other one is JSFClientSession. We use the JSFClientSession to emulate the browser and test the HTML that is produced; the JSFServerSession is used to gain access to the JSF state and invoke operations on the managed beans. Going further with the listing we perform several assertions to test that the viewID is correct (5) and also that the managed-bean returns always a list of Albums of size 5 (at 6). As you can see it is pretty easy to test any JSF application with JSFUnit. It looks very much like testing with Cactus, but having the right API for testing the JSF components. That is because, as we already mention, the JSFUnit framework steps on top of Cactus. This means two things – first you have the exact same features as with Cactus (including the beginXXX and endXXX methods which we already covered in chapter 13), and second your JSFUnit tests get executed the same way as your Cactus tests. To use JSFUnit you require JDK 1.5+, and one of the following JSF implementations: MyFaces 1.1.x MyFaces 1.2.x Sun JSF RI 1.2.x Sun JSF RI 1.1.x Sun JSF RI 2.0.0-PR (public review release) To execute the tests you will can use integration with popular tools like Ant and Maven (we will not describe how to do it here, because we already did so in chapter 13), or you can execute your tests from a browser. 14.5.1 Executing your JSFUnit test from a browser To execute your tests from a browser all need is to place the cactus-report.xsl that comes with the Cactus distribution in the root of your web application. After you package the application, deploy it to any Servlet container and reach for a URL of the following kind: http://localhost:8080/ch15- jfsfunit/ServletTestRunner?suite=com.manning.junitbook.ch14.beans.TestListA vailableAlbumsBean&xsl=cactus-report.xsl and you should be able to see the results of the test execution as shown on figure 14.3 Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Figure 14.3 Results from the execution of our JSFUnit tests in a browser. 14.5.2 Testing AJAX using JSFUnit Now let’s talk a bit about AJAX. AJAX elements of your application are pretty scary to test for most of the developers. However, as you have seen in chapter 12, there it is not that hard to test AJAX. In this subsection we will focus on testing the AJAX layer of your application, this time using JSFUnit. Here’s the album_details.jsp page of our MusicStore application that would allow us to purchase a given album. Listing 14.11 album_details.jsp ... (1) (4) (5) Licensed to Alison Tyler Download at Boykma.Com 18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 ... We already covered this page in listing 14.7 but let’s refresh it. In our JSP we use Richfaces and Ajax4JSF taglibraries to implement the desired AJAX behavior. We start by defining two rich:panel components (1 and 4) to hold the rest of our components. In the first panel we put a commandButton that would submit the form to the bean specified in the action parameter (2). The command button also submits a status parameter to the bean (3). Notice the reRender parameter of the commandButton. This attribute specifies an id of another component that needs to be re-rendered. In our case we specify an outputText (at 5). You can see the screen for this JSP in figure 14.2. Now let’s implement some tests for this AJAX scenario. Testing AJAX with JSFUnit is as easy as testing any other component. This is because JSFUnit relies heavily on HTMLUnit which is a headless browser. So you can use the JSFClientSession and click on anything, regardless of the fact that the request to bean will be submitted via JavaScript. Listing 14.12 Testing the Ajax components from the album_details.jsp […] public class TestPurchaseAlbum extends ServletTestCase { (1) public static Test suite() { return new TestSuite( TestPurchaseAlbum.class ); } public void testCommandButton() throws IOException, SAXException { (2) JSFSession jsfSession = new JSFSession( "/album_details.jsp" ); (3) JSFServerSession server = jsfSession.getJSFServerSession(); (3) JSFClientSession client = jsfSession.getJSFClientSession(); (3) client.click( "PurchaseButton" ); (4) Object userBeanValue = server.getManagedBeanValue( "#{albumDetailsBean.status}"); (5) assertEquals( "Successfully purchased: ", userBeanValue ); (6) String spanContent =((HtmlPage) client.getContentPage()) (7) .getElementsByTagName( "span" ).item( 0 ).getTextContent(); assertEquals(spanContent, "Successfully purchased:"); (8) } } Again, as usual, we start the implementation by declaring a test-case by extending the ServletTestCase (1). In the test-method (2) we get hold of a JSFServerSession object, and extract the JSFServerSession/JSFClientSession out of it (3). Then we click the button with an id “PurchaseButton” (at 4), which is a RichFaces component. At (5) we extract the value of the status parameter of the managed-bean and we assert its value (6). On the Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 client-side we also get the value of the span element on the page (7) and assert its content (8). The JSFUnit project provides a tight integration with the RichFaces project, so if you find the described method short, you can use the RichFacesClient from the JSFUnit API. In this method you will find some very useful methods for testing drag-and-drop behavior, sliders, calendars and other JSF widgets. 14.6 Using HTMLUnit together with JSFUnit In chapter 11 we discussed the presentation layer testing using HTMLUnit, and we also showed how easily HTMLUnit can be integrated with Cactus. Well, this chapter deals with JSFUnit which steps on top of Cactus, so it seems logical the possibility to integrate JSFUnit tests with HTMLUnit. Not only it seems possible, but in fact is quite easy, and that’s what we are going to show in this section. If6 you remember correctly from chapter 11 to use HTMLUnit in your JUnit tests you basically need to get hold of a valid HtmlPage object. Well if you use plain HTMLUnit you do this by getting first a WebClient and then getting the HtmlPage out of it: WebClient webClient = new WebClient(); webClient.setThrowExceptionOnScriptError(false); HtmlPage searchPage = (HtmlPage) webClient.getPage("http://www.google.com"); Well, with JSFUnit it is much simpler. Listing 14.10 displays how to use HTMLUnit in a JSFUnit test: Listing 14.13 Using HTMLUnit in a JSFUnit test […] public class TestListAvailableAlbumsWithHTMLUnit extends ServletTestCase { (1) public void testIntialPage() throws IOException { (2) JSFSession jsfSession = new JSFSession("/"); (3) JSFClientSession client = jsfSession.getJSFClientSession(); (3) HtmlPage page = (HtmlPage)client.getContentPage(); HtmlTable table = (HtmlTable) page.getFirstByXPath("/html/body/form/table");(4) assertNotNull("table should not be null",table); (4) assertEquals( 6, table.getRowCount() ); (4) HtmlAnchor link = table.getRow(1).getCell(0) .getFirstByXPath( "a" ); (5) 6 In case you skipped reading chapter 12, we strongly recommend going back and reading it now before continuing. Licensed to Alison Tyler Download at Boykma.Com 20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 assertNotNull("link should not be null", link); (6) HtmlPage newPage = link.click(); (7) assertEquals(newPage.getTitleText(), "Album details"); (8) } } We start our test-case, again, by extending the ServletTestCase class (1) from the Apache Cactus API. In the one test that we have (2), we create a new JSFSession object with the request URL that we want to invoke. From the jsfSession object, just as in all the previous examples before, we get hold of a JSFClientSession object (3). Consecutively from the client object we take a HtmlTable object using the given XPath expression (4). The XPath expression we have provided represents the DOM tree structure of our document. After we get the table object we perform several assertions on it – we assert the table is not null (4), as well as the number of rows is 6 (we have fine rows for the hard-coded albums and one for the header). Again from the table we retrieve the second row, the first cell out of it, and an HTMLAnchor out from the cell (5). Again, we assert that the link is not null (6), and we click it. The last assertion (8) compares the title of the page that we got from clicking the link object – we want to make sure that we are taken to the new page. It’s that simple! 14.7 Performance testing for your JSF application JSFUnit provides also the option to test the performance of your application. For instance, going back to our MusicStore application, we would like to ensure that the purchase method of our AlbumDetailsBeam is always executed for less than a second and a half. Otherwise our application is working too slowly so the clients that use this might get bored waiting for a response, or even worse – “Get a connection timeout error”. So how do we ensure that a certain method of any managed bean is executed for less than a given time- barrier? The new JUnit 4.5 five has this nice timeout parameter to the @Test annotation. When it comes to testing web-applications this parameter is not sufficient. In most of the cases we want to time the time for which the different phases of the application execute. We can achieve this using JSFUnit API which provides a JSFTimer class to time the execution of a given phase of the application. The next listing demonstrates how to enable the JSFTimer for your application. Listing 14.14 Enabling the JSFTimer for your application //web.xml javax.faces.CONFIG_FILES Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 /WEB-INF/timer-config.xml //timer-config.xml org.jboss.jsfunit.framework.JSFTimerPhaseListener Not much here – we just declare a new context parameter with a value the timer- config.xml file, which holds the declaration for the timer. The next listing shows how to use the timer. Listing 14.15 Performance test for purchasing an album. […] public class TestPerformanceOfPurchaseBean extends ServletTestCase { (1) public void testPerformance() throws IOException { (2) JSFSession jsfSession = new JSFSession( "/album_details.jsp" ); (3) JSFClientSession client = jsfSession.getJSFClientSession(); (4) client.click( "PurchaseButton" ); (5) JSFTimer timer = JSFTimer.getTimer(); (6) assertTrue( "Total time to get the response should not be (7) more than 1600 ms.", timer.getTotalTime() < 1600 ); PhaseId appPhase = PhaseId.INVOKE_APPLICATION; (8) assertTrue( "Execution should not be more than 1600 ms.", (8) timer.getPhaseTime( appPhase ) < 1600 ); } } We start by creating a new test-case (1) and a new test inside it (2). After that, as always we get hold of a valid JSFSession object (3), and get the JSFClientSession object out of it (4). After that we click the PurchaseButton to initiate a request to the managed-bean (5). Next we create a JSFTimer object to measure the execution time (6), and we assert that the totalTime of the execution is less than 1600 milliseconds (7). We can also create measure the execution time against some specific Phase (8). The JSFUnit API provides timing the execution interval upon any of the standard JSF phases: Licensed to Alison Tyler Download at Boykma.Com 22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 APPLY_REQUEST_VALUES INVOKE_APPLIKCATION PROCESS_VALIDATIONS RENDER_RESPONSE RESTORE_VIEW UPDATE_MODEL_VALUES As we explained in chapter 3 and chapter 4 the main benefit from the test-cases is, that they serve as a shield. Once you have your test-cases written you can proceed and refactor your application mercilessly – and you are sure that as long as the tests are passing and the bar is green, everything is OK. In this sense the performance testing is very important. You can write your tests and assert that the execution of a given method takes always less than a given time-barrier; now you are free to improve the logic behind that method, and you will be always sure that the invocation of the given URL will take no more than the time barrier. 14.8 Summary As we just saw in this chapter testing JSF applications is not a tedious task, however it requires some preparation. Using black-box style of testing is brittle, limited and hard to perform. The white-box approach (mocking approach) also has its disadvantages – mock tests are always fine-grained, which means that the interaction between the different components is not tested well. Also once written the tests need to be rewritten again in case of small cosmetic changes on the application. JSFUnit on the other hand, steps on the Cactus project and uses in-container testing strategy. The project provides us with static and performance analyzing mechanisms to fully inspect our applications. In this chapter we also showed how to test Richfaces AJAX components using the API that JSFUnit provides. So far, starting with chapter 11 till now we discussed the different challenges on testing the front-end layer of a sample JavaEE application. Now it’s time to move to another layer and some other technologies. In the next chapter we will talk about one of the most recent booms in the Java world – the OSGi and the modularity that it provides. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 15 Testing OSGi components This chapter covers ƒ OSGi dynamic module system ƒ Mock testing of your modules ƒ In-container testing of your modules Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Theory is when you know something, but it doesn't work. Practice is when something works, but you don't know why. Programmers combine theory and practice: Nothing works and they don't know why. - Anonymous So far we have been testing everything from the J2EE spec. All the J2EE components that we dealt with (JSPs, Taglibraries, Servlets, Filters, EJBs, etc.) have been for a long time out there. In this chapter we discuss a technology that became popular relatively recent, and is getting more and more popular every day. In this chapter we talk about modules; we talk about modularity and the way we test different OSGi modules. We will start the chapter by introducing OSGi1. We walk over the basic concepts in OSGi and provide easy to grasp examples by means of the Calculator application from chapter 1. After that, in the second part of the book, we will see how we test our OSGi calculator bundle by introducing the JUnit4OSGi framework. 15.1 Introduction to OSGi So what is actually OSGi2? The term OSGi usually refers to two things – the OSGi alliance (http://osgi.org/) or the OSGi service platform. The first one – the OSGi alliance is an organization of companies started in late March 1999. The initial companies involved in the alliance included Sun Microsystems, Ericsson, IBM and others. The idea was to create a standards organization for defining specifications for a Java-based service platform, which can be remotely managed. This platform would consists of multiple modules (in the OSGi terms are called bundles) which can be installed, started, stopped, updated and uninstalled remotely and dynamically. The word dynamically in the previous sentence denotes those operations can be achieved without a reboot, at runtime. The specifications, this alliance deals with is the OSGi service platform to define a component and service model. All of the implementations of the OSGi framework need to provide environment for the applications to run in. The applications are modulated into smaller components called bundles. A bundle is the smallest unit of modularization in OSGi - a collection of classes, resources and configuration files, in which the bundle declares its dependencies. The key mission of a bundle is to declare and use services. The OSGi service platform provides a context where all the running services are registered. This bundle- context gets injected into every bundle during its startup. 1 OSGi is a registered trademark of the OSGi Alliance 2 For a complete reference to OSGi we recommend the “OSGi in Action”, ISBN: 1933988916, by Richard S. Hall, Karl Paulus and Stuart McCulloch, Manning Publications 2009. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The lifecycle of a given OSGi service is shown on figure 15.1. As we mentioned a bundle can be in different states. The life cycle layer defines the following 6 states: • INSTALLED: The bundle has been installed to the OSGi container. • RESOLVED: All package requirements are fulfilled and the bundle is ready to be started. • STARTING: The bundle has been started and is still in the process of starting. • ACTIVE: The bundle has started. • STOPPING: The bundle has been initiated to stop and is still in this process. After this the bundle will be in the state RESOLVED. • UNINSTALLED: The bundle has been removed from the OSGi container. It may sound a little bit confusing now, but I am sure that after looking at the example we provide everything will be much clear - let’s move on and implement our first OSGi service. 15.2 Our first OSGi service – the Calculator service In the first chapter of the book we implemented a simple Calculator application. That application back there was simple enough to demonstrate the basic concepts of unit testing. We take the exact same approach in this chapter, too. We will implement the Calculator application as an OSGi service. We will also implement a sample client for that service, and also a test-bundle for the client. Finally we will install the three bundles in the Apache Felix environment. Figure 15.1 Lifecycle of an OSGi application Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Apache Felix – Open source implementation of the service platform As we mentioned in the beginning of the chapter the OSGi alliance defines a number of specifications for the OSGi service platform. The implementation of these specifications, however, can be implemented by absolutely anyone. In this chapter we use the Apache Software Foundation implementation – the Apache Felix (http://felix.apache.org/) project. The installation of the Felix project is very easy – you need to go to the project’s website and download the distribution in the form of an archive. By the time this book is written the 1.4.1 version of the project has just been released. After you extract the archive in a place that you find suitable, create an environment variable called FELIX_HOME, and take a look into that folder. You should see four other folders: • bin – this folder contains only one jar file, called felix.jar. This jar is used as you will see later on to instantiate the Felix console to remotely operate with the different services. • bundle – this folder contains the various bundles that will be installed in Felix. By default Felix comes with only four bundles. • conf – this folder contains the Felix configuration file. • doc – as the name of this folder opposes it contains the documentation of the project. The implementation of our Calculator service starts with the interface shown in listing 15.1. Every OSGi service is defined by a POJI (Plain Old Java Interface). Listing 15.1 CalculatorService interface. […] public interface CalculatorService { [] parseUserInput(String str) throws NumberFormatException; (1) public double public double add(double... numbers); (2) public double multiply(double... numbers); (3) public void printResult(double result); (4) } Nothing big here – just define the methods we want to implement in our service. In our case we have four methods. The first one (1) is used to parse a line of user input. The user is supposed to input several numbers separated by space and this method parses the input numbers. The (2) method is the add method, which given a vararg of numbers sums all the numbers and returns the result. There is also a multiply method (3), which on given vararg of double numbers multiplies them and returns the result. The last method (4) we use simply to print a fancy result. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 As we already discussed every OSGi service exposes a certain interface to the other services so that they can use it. This is a must – your services need to provide an interface and implement it. Moving on listing 15.2 lists the implementation of the interface in listing 15.1. Listing 15.2. Implementation of CalculatorService interface. […] public class CalculatorImpl implements CalculatorService { public double add(double... numbers) { (1) double result = 0; | for(double number:numbers) | result+=number; | return result; | } (1) public double multiply(double... numbers) { (2) double result = 1; | for(double number:numbers) | result*=number; | return result; | } (2) public double[] parseUserInput(String str) throws NumberFormatException { (3) String[] numbers = str.split(" "); | double[] result = new double[numbers.length]; | for(int i=0;i Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 method (2). The parseUserInput method (3) splits the String parameter it gets by spaces and again iterates and populates an array, which later returns. The (4) printResult simply prints the message in the console. So what is the big difference in testing OSGi services than testing normal Java code? The answer is – there is no big difference in the unit testing. You can use plain JUnit to test this service and that would be fine. In fact, you can reuse the testcase we wrote in chapter one and incorporate it in your build the way we showed in third part of the book. But this chapter does not cover unit testing alone, it covers integration testing of OSGi services. But before we continue discussing the integration testing of the service that we just implemented, let’s move on, create a bundle to hold our service and install the service with the Apache Felix command-line tool. To create the bundle we need an implementation of the BundleActivator interface to register and unregister the service that we just made. Listing 15.3 CalculatorBundleActivator […] import org.osgi.framework.BundleActivator; (1) import org.osgi.framework.BundleContext; (1) public class CalculatorBundleActivator implements BundleActivator { (2) public void start(BundleContext bundleContext) throws Exception { (3) System.out.println(“Starting calculator service …”); bundleContext.registerService( (4) CalculatorService.class.getName(), new CalculatorImpl(), null);(4) } public void stop(BundleContext bundleContext) throws Exception { (5) System.out.println(“Stopping calculator service …”); //BLANK (6) } } We start by importing the required classes (1). These classes reside in the felix.jar file from the bin/ directory in the FELIX_HOME folder. You need to include that jar in your classpath. Next we declare our class to implement the BundleActivator interface (2). In (3) and (5) we implement the required start and stop methods, which define how the bundle will behave once it is started or stopped. In the start method all we do is register the CalculatorService interface we just implemented to the BundleContext (4). By doing so we notify the framework for the service and our next steps would be to expose the interface to other services. As you can see in the stop method we give no implementation body of the method (6). The reason behind that is that the Felix framework automatically unregisters the service once it is being stopped. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 To successfully expose our Calculator service so that it can be used by other services we need to include it in a bundle. In order to make our bundle we need to put all the classes in a jar file, along with the following MANIFEST.MF file. Listing 15.4 Manifest file for the calculator bundle. Bundle-Name: Calculator (1) Bundle-Description: A sample calculator bundle that registers the | calculator service | Bundle-Vendor: Apache Felix | Bundle-Version: 1.0.0 (1) Bundle-Activator: com.manning.junitbook.ch15.CalculatorBundleActivator (2) Export-Package: com.manning.junitbook.ch15.service (3) Import-Package: org.osgi.framework (4) Every OSGi bundle is a jar file actually; and what distinguishes the jar as an OSGi bundle is the MANIFEST.MF configuration file. In the MANIFEST.MF file we specify different kinds of metadata for our bundle. The first few lines give a name, description, vendor and version of the bundle (1). The Bundle-Activator (2) specifies the full name of the Activator class (the one that implements BundleActivator). As we already mentioned a bundle can export services to the other world. Which package is exported to the other world is specified in the Export-Package clause (3). A bundle can also use and demand services from the other world – which services we use we specify with the last clause. There we define the packages that are needed by our service (4). You can of course specify multiple values separated by comma. The final step is to compile the source code and produce the bundle (the jar file). If you have the source code of the book, you can use the appropriate Maven or Ant script. Open a shell, navigate to the ch16-osgi\calculator-service folder and type in the command: mvn clean install or ant depending what tool you use. The result should be the same – a jar file containing the classes and the given MANIFEST.MF file in the META-INF folder. Now that we have the bundle (the jar) that contains our service, let’s try and play a little bit with the Felix console and try to install the bundle. Navigate to the FELIX_HOME directory and execute the following command: java –jar bin/felix.jar You should be able to see this message: Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Welcome to Felix. ================= -> This means that Felix console is up and running and you are now able to manage your services through it. The first thing you can do from here is to list all the services that are installed. Type in the command: -> ps and you get the list. For a complete list of all the commands you can use, type in the help command: -> help Let’s now move on and install our calculator-service bundle. We first copy the jar file in the FELIX_HOME/bundle folder. We after that use the install command like this: -> install file:bundle/calculator-service.jar A BundleID should be assigned to your bundle. You can use that BunndleID later on to manage your bundle. Start the service with the following command: -> start [BundleID] where you specify your bundleID instead of [BundleID]. At this point there should be no errors or exceptions – we installed the bundle and started it, but this gives us nothing. The bundle is running and exports the service as an API but no one is currently using it. That’s why we need to create a sample client application for our CalculatorService. 15.2.1 The Calculator client application The idea behind the client application is to demonstrate how to make another bundle that uses the first one we just made – the CalculatorService. We start by implementing the BundleActivator to overwrite the behavior on start and on stop. The following listing demonstrates exactly this: Listing 15.5 ClientBundleActivator implementation […] import com.manning.junitbook.ch15.service.CalculatorService; (1) public class ClientBundleActivator implements BundleActivator { (2) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 public void start( BundleContext context ) throws Exception { (3) ServiceReference reference = context.getServiceReference( CalculatorService.class.getName() ); (4) if ( reference != null ) { CalculatorService calculator = (CalculatorService) context.getService( reference ); (5) BufferedReader in = new ufferedReader( B new InputStreamReader( System.in ) ); (6) | System.out.println( "Enter operation (add or multiply):" ); | String operation = in.readLine(); | | System.out.println( "Enter several numbers | separated with a space:" ); | String line = in.readLine(); (6) double[] numbers = calculator.parseUserInput( line ); (7) if ( operation.equals( "add" ) ) { calculator.printResult( calculator.add( numbers ) ); (8) } else if ( operation.equals( "multiply" ) ) { calculator.printResult( calculator.multiply( numbers ) ); (9) } else { throw new UnsupportedOperationException( "Unknown command: " + operation ); (10) } context.ungetService( reference ); (11) } else { System.out.println( "Calculator service is not installed or not started ..." ); } } public void stop( BundleContext context ) {} (12) } This client simply reads several lines of user input. The first line must contain the operation the user wants to perform, and the next line should be in the form of several numbers separated by a space. After that the client first invokes parseUserInput method and with the given result calls the corresponding add/multiply and print methods of the service we have already installed, and prints the result on the screen. The client starts by importing the necessary classes (1). Notice that this time we also import the service from the previous listings. Keep in mind that when it comes to describing that in the MANIFEST file. In (2) we start the class by implementing the BundleActivator and implement the two required methods at (3) and (12). In (4) we gain a ServiceReference from the context by specifying our service’s class-name. In case this reference is not null we get the CalculatorService interface from the context (at 5), by Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 providing the reference to the service we already have. After that (at 6) we read two lines of user input from the command-line. The first line contains the operation we want to perform (add or multiply), and the second line contains a set of numbers separated by a space. In (7) we call the parseUserInput method of the object reference we already got, and depending on the operation we call either the add method (8), or the multiply method (9), or we throw an UnsupportedOperationException if the command is different than any of those (10). The last thing would be to unget the service (at 11). Let’s see this client application in action. For that purpose we need to package it in a bundle with a corresponding manifest file. The next listing lists the MANIFEST.MF file for the client application. Listing 15.6 MANIFEST.MF for the client application Bundle-Name: Calculator client Bundle-Description: A bundle that uses the calculator service if it finds it at startup Bundle-Vendor: Apache Felix Bundle-Version: 1.0.0 Bundle-Activator: com.manning.junitbook.ch15.client.ClientBundleActivator Import-Package: junit.framework, org.osgi.framework, com.manning.junitbook.ch15.service The only thing different from the previous manifest file is the name of the bundle we have given, and the activator class. Notice that we have also specified in the Import-Package directive a few more packages: • junit.framework – we need this package because we have some tests written that we want to execute in the Felix service platform. More on this later in the chapter. • com.manning.junitbook.ch15.service – we use the service that is already installed so we need to specify its package here. The one thing that is left is to navigate on the command-line to the ch16- osgi/calculator-client folder from the source code of the book and invoke the appropriate Ant or Maven script there. You do this the exact same way we already showed – no matter what script you use the result should be identical. Get the jar file that is generated from the build and copy it to the FELIX_HOME/bundle folder. Go to the Felix command-line tool and invoke the two commands for installing and starting the bundle: -> install file:bundle/calculator-client.jar -> start [BundleID] Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 You need again to remember the assigned BundleID for this bundle and specify it when starting the bundle. In case you don’t remember it you can issue the ps command. Now this bundle is started you will be asked to enter operation and numbers separated by space. Enter some and press enter. The result should be the addition of both. As you can see our client application implementation is very simple. Actually it is so simple that we even missed data validation and exception handling. What happens if we enter a blank line, or even worse – what happens if we enter a random string? An exception would be raised – something we do not want to happen. How do we test that the client application behaves in the expected way? We need to have some kind of an integration tests that we execute inside the service platform (just like in chapter 13 we executed Cactus tests inside the servlet container). That is exactly what we are going to do. In the next subsection we will introduce an OSGi testing framework called JUnit4OSGi, we will also write some tests with that framework and we will include them in a separate test-bundle. 15.3 Testing OSGi services When it comes to testing OSGi services, the same categorization that we made in the second part of the book can be applied. The normal JUnit tests that you already know how to write would exercise each of your services on its own. Our attention is mainly focused on the integration tests that test actually the interaction between the different services. So for implementing integration tests there are different approaches that you might take; those different approaches we already covered in the second part of the book, and they include black-box testing, mock-objects or in-container testing. As to the black-box testing there is not much that you can do. A form of black-box testing would be simply to get an OSGi container, install your services there and hand it to someone to start playing around and test it “in the black”. We take a closer look in the other two approaches in the next sections. 15.3.1 Mock Objects Now that we have our services written, let’s try and examine them. The Calculator service exposes some API which is used by the CalculatorClient service to compute some sums or multiplications. The CalculatorClient service on the other hand reads some data input from the command line. This means that if we write a test for this service and invoke it we will have to enter some data in the command line, and since there is absolutely no restriction on the data that we can enter, there is absolutely no way to specify our assertions. There is also no way to automate those tests, since you always have to have someone entering data in the command line when the tests are executed. The solution for this problem is simple – refactoring. We need to refactor the ClientBundleActivator in a way that it allows it to be easily testable. The following listing shows the refactored class, and the different lines are marked with bold. Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Listing 15.7 Refactored ClientBundleActivator to enable testability. […] public class ClientBundleActivator implements BundleActivator { private String operation = null; (1) private String userNumberInput = null; (1) private double result = 0; (2) public void start( BundleContext context ) throws Exception { if (operation==null || userNumberInput==null) { (3) initUserInput(); | } (3) ServiceReference reference = context.getServiceReference( CalculatorService.class.getName() ); if ( reference != null ) { CalculatorService calculator = (CalculatorService) context.getService( reference ); double[] numbers = calculator.parseUserInput( getUserNumberInput() ); (4) if ( getOperation().equals( "add" ) ) { (6) result = calculator.add( numbers ); | } else if ( getOperation().equals( "multiply" ) ) { | } else { | result = calculator.multiply( numbers ); | throw new UnsupportedOperationException( | "Unknown command: " + getOperation() ); (6) } calculator.printResult( result ); context.ungetService( reference ); } else { System.out.println( "Calculator service is not installed or not started ..." ); } } public void initUserInput() { (7) BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(System.in)); (8) System.out.println( "Enter operation (add or multiply):" ); | operation = in.readLine(); (8) System.out.println( "Enter several (9) numbers separated with a space:" ); | userNumberInput = in.readLine(); (9) } catch ( IOException ex ) { System.err.println( "Error reading from the reader." ); ex.printStackTrace(); Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } finally { try { in.close(); } catch ( IOException e ) { System.err.println( "Error closing the reader." ); e.printStackTrace(); } } } //GETTERS AND SETTERS FOLLOW } We start by extracting all the data the user normally enters on the command line (the operation and the numbers separated by space) as local variables to the class (1). We extract the result from the computation as a local variable (2), and we also provide getters and setters for both (1) and (2). This gives us the possibility to set those parameters before we start the ClientBundleActivator, as well as to assert the expected result. Next, in the start method we add a check to see if the user-data has been already setuped (3), and in case it is not we call the initUserInput method (7). As you can see in (4), everywhere in our code when we want to use the user data, we use the getter methods of the class. At this point we are absolutely sure that this data will be setuped – either by the setter methods, or by the initUserInput method, which reads the data from the command line. In (6) we check what the command we want to issue is, and call the corresponding method in the CalculatorService according. Notice that the result is this time kept in a local variable, to which we have access through the getter methods. The initUserInput method (7) is called when we have no user-data defined through the setter methods. This method has the responsibility to read the operation we want to issue (8), as well as the numbers on which we want to issue the command (9). That’s it! So now that we already refactored the ClientBundleActivator class we can move on and show how to test it. The class contains one entry-point method called start, which we want to unit-test. In order to do this we must get hold of a valid BundleContext object, because the methods accepts it as a parameter and uses it. The BundleContext itself is an interface so we have no way to instantiate it and pass it to the method. So what do we do? If you remember correctly from chapter 6 when we introduced the mock objects, there are mock-objects frameworks that can produce fake instances of interfaces that we can use in our tests. That is exactly what we are going to do. Take a look at the following listing: Listing 15.8 Mock test for the CalculatorService […] public class TestClientCalculatorServiceMock { (1) private Mockery context = new JUnit4Mockery(); (2) Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 private BundleContext mockBundleContext; (3) private ServiceReference mockServiceReference; (3) @Before public void setUp() { mockBundleContext = context.mock( BundleContext.class ); (4) mockServiceReference = context.mock( ServiceReference.class ); (4) final CalculatorImpl service = new CalculatorImpl(); context.checking( new Expectations() (5) { | { | oneOf(mockBundleContext).getServiceReference( | CalculatorService.class.getName()); | will(returnValue(mockServiceReference)); | | oneOf(mockBundleContext).getService(mockServiceReference); | will(returnValue(service)); | | oneOf(mockBundleContext).ungetService(mockServiceReference);| } | } ); (5) } @Test public void testAddMethod() throws Exception { (6) ClientBundleActivator activator = new ClientBundleActivator(); (7) activator.setOperation( "add" ); (8) activator.setUserNumberInput( "1 2 3 4 5 6 7 8 9" ); (8) activator.start( mockBundleContext ); (9) assertEquals( "The result is not the same as expected", activator.getResult(), 45, 0 ); (10) } @Test public void testMultiplyMethod() throws Exception { (11) ClientBundleActivator activator = new ClientBundleActivator(); activator.setOperation( "multiply" ); activator.setUserNumberInput( "1 2 3 4 5 6 7 8 9" ); activator.start( mockBundleContext ); assertEquals( "The result is not the same as expected", activator.getResult(), 362880, 0 ); } } We start by creating a new JUnit test-case (1) that will exercise our CalculatorService. Just as the way we did in chapter 6 we define the Mockery context (2) and the objects that we want to mock (3). As we know the @Before method is executed before every test, so that’s why we stick the instantiation of the mock-objects in there (4). After that, in (5), we define the expectation for the mock objects. We got lucky - these expectations turn out to be the same for both the tests, so that’s why we can extract them in Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 the @Before annotated method. According to them we will first call the getServiceReference on the mockBundleContext, after that we will call the getService on the same object, passing the ServiceReference we already have. We would finally call the ungetService method again on the mockBundleContext. In our test-case we have got two tests - the one to test the add method of the service (6), and the other one for testing the multiply method (11). Those tests have the same structure – we first get a new instance of the ClientBundleActivator class (7), and then set the parameters using the setter methods on the class (8). The last thing would be to invoke the start method with our fake object (9), and assert the expected results are good. As you can see using mock-objects to test our service requires quite a bit of preparation – we need to take care of the proper instantiation and configuration of the mock objects. The next section introduces the JUnit4OSGi project which implements in-container strategy for testing the CalculatorClient service. So let’s go. 15.4 Introduction to JUnit4OSGi The JUnit4OSGi framework is a very simple OSGi testing framework that comes from the iPOJO subcomponent of the Apache Felix project. By the time this book is version 1.0 of the project has just been released as a general availability. You can download the jars from the website of the project () or in case you are using Maven declare them as dependencies in your maven project. To use the framework you will need several jar files. The bare minimum you need are these four: • org.apache.felix.ipojo-1.0.0.jar • org.apache.felix.ipojo.handler.extender-1.0.0.jar • org.apache.felix.ipojo.junit4osgi-1.0.0.jar • org.apache.felix.ipojo.junit4osgi.felix-command-1.0.0.jar Take these four files and place them in the sample folder FELIX_HOME/bundles/junit4osgi. After that install them one-by one with Felix command-line tool we already did with our service. Once they are installed and started you will get the chance to use one extra command on the command-line: -> junit [BundleID] This command will invoke all the JUnit and JUnit4OSGi tests that are present in the bundle with the given [BundleID] and are listed in the manifest descriptor. Let’s start implementing our first test-cases. Listing 15.7 lists the JUnit4OSGi test-case that tests the calculator-client application. Listing 15.9 JUnit4OSGi test for our calculator-client application. Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 […] import org.apache.felix.ipojo.junit4osgi.OSGiTestCase; (1) import org.osgi.framework.ServiceReference; | import com.manning.junitbook.ch15.service.CalculatorService; (1) public class TestClientActivator extends OSGiTestCase { (2) public void testServiceAvailability() { (3) ServiceReference ref = context.getServiceReference(CalculatorService.class.getName());(4) assertNotNull("Assert Service Availability", ref); (5) } public void testParseUserCorrectInput() { ServiceReference ref = context.getServiceReference(CalculatorService.class.getName()); assertNotNull("Assert Availability", ref); CalculatorService cs = (CalculatorService) context.getService(ref); (6) double[] result = cs.parseUserInput("11.5 12.2 13.7"); (7) assertNotNull("Result must not be null", result); assertEquals("Result must be 11.5", 11.5, result[0]); (8) assertEquals("Result must be 12.2", 12.2, result[1]); assertEquals("Result must be 13.7", 13.7, result[2]); } public void testParseUserIncorrectInput() { ServiceReference ref = context.getServiceReference(CalculatorService.class.getName());(9) assertNotNull("Assert Availability", ref); CalculatorService cs = (CalculatorService) context.getService(ref); (10) try { double[] result = cs.parseUserInput("THIS IS A RANDOM STRING TO TEST EXCEPTION HANDLING");(11) fail("A NumberFormatException was (12) supposed to be thrown but was not"); } catch (NumberFormatException nex) { assertTrue(true); //this is the normal execution flow (13) } } public void testAddMethod() { (14) assertTrue("Check availability of the service", isServiceAvailable(CalculatorService.class.getName())); CalculatorService cs = (CalculatorService) getServiceObject(CalculatorService.class.getName(), null); double[] numbers = cs.parseUserInput("1.2 2.4"); assertNotNull("Result from parseUserInput must not be null", numbers); double result = cs.add(numbers); assertNotNull("Result from add must not be null", result); assertEquals("Result must be 3.6", 3.6, result); } } Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The listing starts with import of the necessary classes (1). Don’t forget that every external package you use needs to be declared in the MANIFEST.MF file of the bundle. Every JUnit4OSGi test-case needs to extend from the OSGiTestCase class (2). The JUnit4OSGi framework is a JUnit3.x extension so you need to follow the JUnit3.x rules when it comes to writing test-cases. We start our first test method at (3) and we get a ServiceReference of the service that we just wrote and deployed (4). We get the ServiceReference from the context object that JUnit4OSGi provides us. The framework gives us the access to the BundleContext to check the status of given service – something that we do in (5). By asserting the ServiceReference is not null we make sure that the service is installed and started correctly. The following test-methods test the service methods for real – by having the service reference we can get hold of the service itself (at 6) and invoke different methods on it (at 7) to see that it behaves the expected way (8). We also test the exceptional case to provide a random string (at 11) and we catch the NumberFormatException that we expect to be raised (13) – just to make sure that the application is working fine even in extreme conditions. Now it is time to execute this test. If you try to run it as a normal test-case it would probably fail with a NullPointerException, because the context object is not initialized properly. This is due to the fact that these tests are in-container tests, and just as the Cactus tests, or the JSFUnit tests they are meant to be executed inside the container. In this case the container is not a servlet container, but the OSGi environment. It is a best-practice to separate all you junit-osgi tests in a separate bundle, and that is what we are going to do. Go to calculator-test folder of the source code of the book, and use Maven or Ant to package the bundle that will contain the test from the previous listing. After you have done this copy the so-produced jar file and paste it in the [FELIX_HOME]/bundle folder for it to be easy to install. The last thing would be to install and start the service in the bundle the way we already covered in the beginning of the chapter. Our final step is to call the test inside the container. If you remember well in the beginning of this section we talked about the JUnit4OSGi project and how it relies on the IPOJO services. We also mentioned that the IPOJO provide the junit felix command that you can use to execute your tests. It is as simple as: -> junit [BundleId] where you need to replace the BundleId with the number assigned for your module. If you are using the source-code for the book and there are no errors the result should be the same as: Executing [Client activator tests] Licensed to Alison Tyler Download at Boykma.Com 18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 .... Time: 0 OK (4 tests) Nice, isn’t it? As we already mentioned the junit command is currently available only for Apache Felix implementation of OSGi. So how do we run our tests in case we use any of the other implementations of OSGi? In that case you have to use the GUI runner that comes with the JUnit4OSGi project. To use the GUI runner you need to install and start the org.apache.felix.ipoj o.junit4osgi.swing- gui.jar bundle. After the bundle is started you will see a Java pop-up that lets you select the bundles that contain JUnit tests. You can select as many as you want and after clicking the ‘Execute’ button you should see the result – green bar for passing tests, and red bar for failing tests (see figure 15.2). Fig 15.2 Executing the JUnit tests with the GUI runner. 15.5 Summary In this chapter we talked about testing OSGi bundles – a technology that is getting more and more popular every day. We set up a small introduction to that technology, so that now you Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 should know what a module, a bundle and a service is. You should be also able to test all your OSGi bundles using the JUnit4OSGi project that we introduced in the second part of the chapter. You should be also able to apply different techniques for testing your bundles, like testing with mock objects, integration testing, etc. In the following chapters we begin to study the backend layer of your applications. All the chapters till the end of the book concern the part of your project that communicates with a database. We will show you different techniques for testing Hibernate and JPA mapping, as well as integration testing of your data-access layer. Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 16 Database testing with DbUnit This chapter covers: ƒ Challenges of database testing ƒ Introduction to DbUnit ƒ Advanced DbUnit techniques ƒ DbUnit best practices Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Dependency is the key problem in software development at all scales…. Eliminating duplication in programs eliminates dependency. —Kent Beck, Test-Driven Development: By Example The persistence layer (or, roughly speaking, the database access code) is undoubtedly one of the most important parts of any enterprise project. Despite its importance, the persistence layer is hard to unit test, mainly because of the following three issues: ƒ Unit tests must exercise code in isolation; persistence layer requires interaction with an external entity, the database. ƒ Unit tests must be easy to write and run; code that accesses the database can be cumbersome. ƒ Unit tests must be fast to run; database access is relatively slow. We call these issues the Database Unit Testing Impedance Mismatch, in a reference to the object-relational impedance mismatch (which describes the difficulties of using a relational database to persist data when an application is written using an object-oriented language). The database testing mismatch can be minimized using specialized tools, one of them being DbUnit. In this chapter, we show how DbUnit can be used to test database code, not only describing its basic concepts, but also presenting techniques that make its usage more productive, and the resulting code easier to maintain. 16.1 The Database Unit Testing Impedance Mismatch Let's take a deeper look at the three issues that comprise the Database Unit Testing Impedance Mismatch. 16.1.1 Unit tests must exercise code in isolation From a purist point of view, tests that exercise database access code cannot be considered unit tests, as they depend on an external entity, the almighty database. What should they be called then? Integration Tests? Functional Tests? Non-Unit Unit Tests? Well, the answer is: there is no secret ingredient! In other words, database tests can fit in many categories, depending on the context. Pragmatically speaking, though, database access code can be exercised by both unit and integration tests: ƒ Unit tests are used to test classes that interact directly with the database (like DAOs). Such tests guarantee these classes execute the proper SQL statements, assemble the right objects, and so on. Although these tests depend on external entities (like the database and/or persistence frameworks), they exercise classes that are building blocks in a bigger application (and hence are units). Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 ƒ Similarly, unit tests can be written to test the upper layers (like Facades), without the need of accessing the database – in these tests, the persistence layer can be emulated by mocks or stubs. ƒ Even with both layers (persistence and upper) unit tested aside, it is still necessary to write integration tests that access the database, as some situations can only arise in end-to-end scenarios (like the dread lazy-initialization exception that frequently haunts JPA applications1). Despite the theoretical part of the issue (which could be summarized as “To unite or to integrate, that is the question!”), there is still a practical question: can't the data present in the database get in the way of the tests? Yes, they can, so before you run the tests, you must assure the database is in a known state. Fortunately, there are plenty of tools that can handle this task, and in this chapter we analyze one of them, DbUnit. 16.1.2 Unit tests must be easy to write and run It does not matter how much a company, project manager, or technical leader praises unit tests – if they are not easy to write and run, developers will resist writing them. And writing code that access the database is not the sexiest of the tasks – one would have to write boring SQL statements, mix many levels of try/catch/finally code, convert SQL types to and from Java, etc. So, in order to database unit tests to thrive, is necessary to alleviate the “database burden” on developers. Luckily again, there are tools that provide such alleviation, and DbUnit is also one of them. 16.1.3 Unit tests must be fast to run Let's say you overcame the first two issues and have a nicely set environment, with hundred of unit tests exercising the objects that access the database, and where a developer can easily add new ones. All seems nice, but when a developer runs the build (and they should do that many times a day, at least after updating their workspace and before submitting changes to the source control system), it takes ten minutes for the build to complete, nine of them spent in the database tests. What should you do then? This is the hardest issue, as not always it can be solved. Typically, the delay is caused by the database access per se, as the database is probably a remote server, accessed by dozens users. A possible solution then is to move the database closer to the developer, by either using an embedded database (if the application uses standard SQL that enables a database switch), or locally installing lite versions of the database. DEFINITION: EMBEDDED DATABASE 1 If you do not have a clue of what we are talking about, don't panic! JPA testing and its issues will be explained in details on next chapter. Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 An embedded database is a database which is bundled within an application, instead of being managed by external servers (which is the typical scenario). There is a broad range of embedded databases available for Java applications, most of them based on open- source projects - like HSQLDB (http://hsqldb.org), H2 (http://h2database.com), Derby (http://db.apache.org/derby), and Java DB (http://developers.sun.com/javadb). Notice that the fundamental characteristic of an embedded database is the fact that it is managed by the application, and not the language it is written in. For instance, both HSQLDB and Derby supports client/server mode (besides the embedded option), while SQLite (which is C-based product) could also be embedded in a Java application. In the next sections, we will see how DbUnit (and to a lesser degree, embedded databases) can be used to solve the Database Unit Testing Mismatch. 16.2 Here comes DbUnit DbUnit (http://www.dbunit.org) is a JUnit2 extension created by Manuel LaFlamme in 2002, when Java Unit testing was still in its infancy, and there was no framework focused on database testing. At the same time, Richard Dalalaway wrote an online article titled “Unit testing database code” (http://dallaway.com/acad/dbunit.html), which inspired the creation of DbUnit. Since them, DbUnit has became the “de facto” Java framework for database testing, and its development has had its up and downs. After a period of high activity, where most of its codebase was created, it faced a long drought. Fortunately, though, new developers jumped in and, during the time this book was written, several new versions have been cut, providing many improvements and bug fixes. DbUnit is made of hundreds of classes and interfaces. But despite the high number of classes, DbUnit usage roughly summarizes to moving data to and from the database, and that data is represented by datasets (more specifically, classes that implement the IDataSet interface). In the following subsections, we will see the basic usage of datasets and some other DbUnit artifacts. 16.2.1 The sample application Throughout this chapter, we will use DbUnit to unit test the persistence layer of a Java application. In order to simplify, this layer will be made of only the interface defined at listing 16.1. Listing 16.1 DAO interface used in the examples public interface UserDao { long addUser(User user) throws SQLException; User getUserById(long id) throws SQLException; 2 Although it can be used without JUnit. Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } The DAO implementation (using plain JDBC) is not shown here, but is available for download in the book's website. And the User object is a simple POJO3, described by listing 16.2. Listing 16.2 Domain model used in the examples public class User { private long id; private String username; private String firstName; private String lastName; // getters and setters omitted } The User object will be mapped in the database by the users table, which can be created using the SQL statement listed at 16.3. Listing 16.3 SQL script that creates the users table CREATE TABLE users (id INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1), username VARCHAR(10), first_name VARCHAR(10), last_name VARCHAR(10) ) Finally, the examples will use HSQLDB as the database, because it is Java-based and hence does not require any further configuration. HSQLDB is also very flexible: it can be run as client-server or embedded, using disk or memory. The simplest – and fastest – mode is as an in-memory embedded database, and that is the mode used in the examples. 16.2.2 Setting up DbUnit and running the sample application DbUnit itself is comprised of just one jar (dbunit.jar), and the only required external dependency is the logging framework, SLF4J (Simple Logging Facade for Java). SLF4J requires two jars: slf4j-api.jar (which only contains the framework interfaces) and an implementation, such as slf4j-nop.jar (which does not log anything; we will talk more about logging later on). Of course, as DbUnit will connect to a database, it is also necessary to add the JDBC driver to the classpath; in the sample application, it's the hsqldb.jar. The sample application is available in two 'flavors', Maven and Ant. To run the tests on Maven, simply type 'mvn clean test'. Similarly, to use Ant instead, just type 'ant clean test'. The application is also available as 2 Eclipse projects, one with the required libraries (under the lib directory) and another with the project itself. 3 Plain Old Java Object Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 16.3 Using datasets to populate the database Let's start by writing a unit test for the getUserById() method. How do we test it? First, we need to analyze what the method does, and the answer is that it fetches data from the relational database, creates a Java object, populates that object with the fetched data, then return the object. Consequently, our test case must prepare the database with the proper data, run the code being tested, and verify that the object returned contains the expected data. The latter two steps can be done with trivial Java code, while the former needs interaction with a database – that's where DbUnit can be a handy tool. Data in DbUnit is represented by a dataset (interface org.dbunit.dataset.IDataSet), and DbUnit provides dozens of different IDataSet implementations, the most common ones being XmlDataSet and FlatXmlDataset. In our example, we need to insert a row in the table users with the values id=1, username=ElDuderino, firstName=Jeffrey, lastName=Lebowsky. Let's see how this data could be represented on these two different dataset implementations, first in the XmlDataSet format (listing 16.4). Listing 16.4 XmlDataSet representation of users table idusernamefirst_namelast_name 1 ElDuderino Jeffrey Lebowsky
The XmlDataSet format is self-described, but it has two problems. First, it's very verbose – as you can see in the above example, a simple row in a table required 16 lines of XML code. The advantage of this format is that it follows a well defined DTD (available inside DbUnit's jar), which could avoid problems caused by bad XML syntax. But that brings out the second issue: DbUnit does not validate the DTD (that DOCTYPE line could be removed or even changed to any garbage, and the result would be the same). Although the lack of XML validation is a DbUnit bug, the verboseness of the format is a design option. A much simpler option is to use the FlatXmlDataSet, where each line describes a row in the database. Listing 16.5 shows the same dataset using the flat XML format. Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Listing 16.5 FlatXmlDataSet representation of users table (user.xml) The FlatXmlDataSet format is much clearer and easier to maintain4, so we'll use it on our examples. And by speaking of examples, listing 16.6 shows our first test case. Listing 16.6 Initial test case for UserDaoJdbcImpl (UserDaoJdbcImplTest) [...] public class UserDaoJdbcImplTest { (1) private static UserDaoJdbcImpl dao = new UserDaoJdbcImpl(); private static Connection connection; private static HsqldbConnection dbunitConnection; @BeforeClass (2) public static void setupDatabase() throws Exception { Class.forName("org.hsqldb.jdbcDriver"); connection = DriverManager.getConnection("jdbc:hsqldb:mem:my-project- test;shutdown=true"); dbunitConnection = new HsqldbConnection(connection,null); (3) dao.setConnection(connection); dao.createTables(); } @AfterClass (4) public static void closeDatabase() throws Exception { if ( connection != null ) { connection.close(); connection = null; } if ( dbunitConnection != null ) { dbunitConnection.close(); dbunitConnection = null; } } protected IDataSet getDataSet(String name) throws Exception { (5) InputStream inputStream = getClass().getResourceAsStream(name); assertNotNull(“file”+name+“ not found in classpath”, inputStream); (6) Reader reader = new InputStreamReader(inputStream); FlatXmlDataSet dataset = new FlatXmlDataSet(reader); return dataset; } 4 This format has its issues as well, but they will be covered later in the chapter. Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 @Test public void testGetUserById() throws Exception { IDataSet setupDataSet = getDataSet("/user.xml"); DatabaseOperation.CLEAN_INSERT.execute(dbunitConnection, setupDataSet);(7) User user = dao.getUserById(1); assertNotNull(user); assertEquals("Jeffrey", user.getFirstName()); assertEquals("Lebowsky", user.getLastName()); assertEquals("ElDuderino", user.getUsername()); } } First of all, our test case (1) does not need to extend any DbUnit or JUnit class. Although it does not sound like a big deal in this example, it could be a big limitation in real life, especially if you used different testing frameworks like TestNG5. Remember that test methods (such as (2) and (4)) are still code, and hence “real code” best practices should be applied to them as well. In particular, as opening a database connection can be an expensive operation (in terms of time and number of concurrent connections), it is a good practice to open it at the beginning of the tests, and close them just at the ending. Alternatively, if a connection pool was used instead, these methods could be defined at @Before/@After respectively. On (3), connection is a just a regular JDBC connection (java.sql.Connection), while dbunitConnection is a Dbunit IDatabaseConnection instance. DbUnit uses IDatabaseConnection to encapsulate access to the database, and it provides implementations for the most common databases. The advantage of using a specialized IDatabaseConnection implementation (HsqldbDatabaseConnection, in this case) other then the generic one (DatabaseConnection) is that it can handle nuances specific to that database, like conversion between non-standard SQL types and Java classes. Notice that in this example, both connection and dbunitConnection were created using hardcoded values; in real projects, a better practice is to define these settings externally, such as in a property files. That would allow the same tests to be run in different environments, like using alternate databases or getting the connection from a pooled datasource. Getting an IDataSet from a XML file is so common that it deserves its proper method (5). It is also a good practice to load these XML files as resources in the classpath, instead of physical files in the operating system. And if the file is not found in the classpath (which is a very common scenario when you are writing the test cases – you might have forgotten to create the file, or misspelled its name), getResourcesAsStream() simply returns null (instead of throwing a resource-not-found exception), which in turn would cause a NullPointerException in the caller method. As a NPE is a sure bet to cause a lot of 5 Such testing framework independence is a DbUnit 2.2+ feature; before that release, all classes should extends DatabaseTestCase, which would make it hard to use DbUnit with TestNG. Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 headache, adding an assertNotNull() (6) with a meaningful message is a one-liner that can avoid a lot of trouble. Finally, on (7) is where the DbUnit job is effectively done! We have a dataset (setupDataset) with the data we want to insert, and a connection to the database (dbunitConnection). All is left is the class responsible to do the dirty work, and that's the DatabaseOperation, or more precisely, one of its sub-classes. In this case, we used CLEAN_INSERT, which first deletes all rows from all tables defined in dataset, and then inserts the new ones. See next subsection for more details about DatabaseOperation and its implementations. A final note about transactions: in order to keep this example simple, we are not dealing with transactions at all, and every database operation is done in one transaction (using JDBC's auto-commit feature). Although this simplification is fine here, usually the test cases must be aware of the transaction semantics. For instance, a transaction could be started before the test (using an @Before method) and committed afterwards (using @After), the test cases could explicitly set the auto-commit property (particularly if the connections were obtained from a pool), and so on. The exactly approach depends on many factors, like the type of test (unit or integration) being written, and the underlying technologies used in the code tested (like pure JDBC or ORM frameworks6). BEST PRACTICE: DEFINE A SUPER-CLASS FOR YOUR DATABASE TESTS The UserDaoJdbcImplTest class listed on 16.6 defines four methods, but only one is effectively a test case – the other three are helpers used in the testing infrastructure. As you add more test cases, the proportion tends to revert, although it is common to add more helper methods as well. And these helpers can typically be re-used by other test classes. Consequently, it is a good practice to create a super-class that only defines these infrastructure methods, and then make the real test classes extending this super-class. In fact, this best practice applies not only for DbUnit-based tests, but for testing in general. Back to the listing 16.6 example, it could be refactored in two classes, an AbstractDbUnitTestCase super-class (with methods setupDatabase(), closeDatabase(), and getDataSet()), and the UserDaoJdbcImplTest properly speaking (which for now contains only testGetUserById()). The next example will use this technique. 16.3.1 DataBaseOperation dissected DatabaseOperation is the class used to send datasets to the database. Although DbUnit makes a good use of interfaces and implementations, DatabaseOperation is one of the few concepts which are not defined by interfaces. Instead, it is defined as an abstract class 6 Chapter 18 offers a more detailed insight on transactions in JPA-based test cases. Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 with an abstract method (execute(), which takes as parameters a dataset and a database connection). The reason for such different design is to facilitate its use, as the abstract class also define constants for its implementations (DbUnit was created in the JDK 1.3/1.4 times; there was no native enum at that time), so the operations can be executed with just one line of code, as we saw in the first example. The implementations provided by DatabaseOperation as static fields are: ƒ DatabaseOperation.UPDATE – update the database with the dataset content. It assumes the rows in the dataset already exist in the database (i.e., the database contains rows with the same primary keys as the rows in the dataset); if they don't exist, it will throw an exception. ƒ DatabaseOperation.INSERT – insert the dataset rows in the database. Similarly to UPDATE, it will thrown an exception if any row already exists. As rows are inserted in the order they appear in the dataset, care must be taken when tables have foreign keys: rows must be defined in the dataset using the right insertion order. ƒ DatabaseOperation.REFRESH – this is a mix of INSERT and UPDATE: rows that exist only in the dataset but not in the database are inserted, while rows that exist in both are updated. ƒ DatabaseOperation.DELETE – delete from the database only the rows present in the dataset, in the reverse order they appear in the dataset. ƒ DatabaseOperation.DELETE_ALL – delete from the database all rows from each table present in the dataset. Although it is an aggressive approach in many cases (like when the database is shared by many developers, or it contains data that should not be deleted), it is the simplest way to guarantee the state of the database (as sometimes the database might contain data which is not deleted by DELETE and hence can interfere in the test results). ƒ DatabaseOperation.TRUNCATE – same purpose as DELETE_ALL, but faster, as it uses the SQL's TRUNCATE TABLE. The only drawback is that not all databases support such SQL operation. ƒ DatabaseOperation.CLEAN_INSERT – composite operation, first calls DELETE_ALL, then INSERT, using the same dataset. ƒ DatabaseOperation.TRANSACTION(operation) – not exactly a field, but a method. It creates a DatabaseOperation that will wrap another operation inside a database transaction. It is particularly useful in cases where tables have circular dependency and rows cannot be inserted outside of a transaction with deferred constraints. ƒ DatabaseOperation.CLOSE_CONNECTION(operation) – another wrapper, executes the operation and then automatically closes the connection. Could be useful in some teardown methods. ƒ DatabaseOperation.NONE – as the documentation states it, “Empty operation that Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 does absolutely nothing.” Should be used only by the brave of heart. That's it, not only we wrote our first DbUnit test case, but we set the foundation for most of the remaining tests to come! 16.4 Asserting database state with datasets Another common use of datasets is to assert the database has the right data after an insert or update. Back to our DAO example, we need a test case for the addUser() method, and the workflow for this test is the opposite from getUserById()'s test: here we first create an User object, ask our DAO to persist it, then use a DbUnit dataset to assert the data was properly inserted. The code snippet on listing 16.7 is our first attempt for such test case. Listing 16.7 Test case for addUser() method [...] import org.dbunit.Assertion; [...] public class UserDaoJdbcImplTest extends AbstractDbUnitTestCase { (1) [...] @Test public void testAddUser() throws Exception { User user = new User(); user.setFirstName("Jeffrey"); user.setLastName("Lebowsky"); user.setUsername("ElDuderino"); (2) long id = dao.addUser(user); assertTrue(id>0); (3) assertEquals(id, user.getId()); IDataSet expectedDataSet = getDataSet("/user.xml"); (4) IDataSet actualDataSet = dbunitConnection.createDataSet(); (5) Assertion.assertEquals( expectedDataSet, actualDataSet ); (6) } } (1) This version of the test class employs the best-practice of extending a class with the testing infrastructure. (2) Up to this point, we just prepared the User object that will be persisted. (3) Not only we asked DAO to persist the User object, but we also checked that it generated a valid id (a positive value, in this case), and the returned id matches the object's. Such checking is very important in situations where the caller needs to use the new id (for instance, in a web page which generates a link to edit the newly created object); it may Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 sound trivial and redundant in this case, but you would be surprised on how often the id is not set correctly in more complex combinations (like multi-layered applications with Spring managing transactions, and Hibernate being used as the persistence layer). (4) Here we used the same dataset (users.xml) and same method (getDataSet()) as the previous example – it is always a good practice to reuse code and testing artifacts. (5) IDatabaseConnection.createDataSet() returns a dataset containing all tables (with all rows) in the database. (6) Finally, Assertion.assertEquals() compares both datasets, and if a discrepancy is found, fails with the proper message. Notice that we did not staticly import this method: as both JUnit's Assert and DbUnit's Assertion have assertEquals() methods, if you static import both, chances are a call to assertEquals() will reference the wrong one. BEST PRACTICE: USE A HELPER CLASS TO CREATE AND ASSERT OBJECT INSTANCES In the previous example, testGetUserById() fetched an object from the database and asserted its attributes, while testAddUser() did the opposite (instantiated a new object, filled its attributes, then inserted it in the database). As your test cases grow, more and more tests will need do the same. To avoid the DRY (Don't Repeat Yourself) syndrome, it is better to create a helper class containing methods and constants for these tasks. Doing so, not only you improve reuse in the Java classes, but you facilitate the maintenance of dataset files. And if you use Java 5 and static import, accessing this helper class member becomes very simple. Listing 16.8 shows a revised version of testAddUser() using this practice. Listing 16.8 Revised version of testAddUser(), using a helper class. [...] import static EntitiesHelper.*; (1) [...] public class UserDaoJdbcImplTest extends AbstractDbUnitTestCase { [...] @Test public void testAddUser() throws Exception { User user = newUser(); (2) long id = dao.addUser(user); [...] } } [...] public final class EntitiesHelper { (3) public static final String USER_FIRST_NAME = "Jeffrey"; (4) public static final String USER_LAST_NAME = "Lebowsky"; public static final String USER_USERNAME = "ElDuderino"; public static User newUser() { (5) User user = new User(); Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 user.setFirstName(USER_FIRST_NAME); user.setLastName(USER_LAST_NAME); user.setUsername(USER_USERNAME); return user; } public static void assertUser(User user) { (6) assertNotNull(user); assertEquals(USER_FIRST_NAME, user.getFirstName()); assertEquals(USER_LAST_NAME, user.getLastName()); assertEquals(USER_USERNAME, user.getUsername()); } The newUser() method is called (2) as if it belong to the class being test, but it is actually defined in the helper class (5), and staticly imported (1). The helper class itself (3) is also shown; notice that it provides (6) a complementary assertUser() method (which is used by testGetUserById()), so it uses constants (4) to define the User attributes used by these methods. 16.4.1 Narrowing down the field The method IDatatabaseConnection.createDataSet() returns a dataset representing the whole database. This is fine in our example, where the database has only one table, and the database access is fast (because it is an embedded database). In most other cases, though, it would be an overkill – either the test would fail because it return tables that we are not interested in, or it would be slow to run. The simplest way to narrow down the field is filtering the tables returned by createDataSet(), by passing an array containing the name of the tables that should be returned. Applying that change to the previous example, we would have: IDataSet actualDataSet = dbunitConnection.createDataSet( new String[] { "users" ); A very similar approach is to use a FilteredDataSet to wrap the dataset containing the full database: IDataSet actualDataSet = dbunitConnection.createDataSet(); FilteredDataSet filteredDataSet = new FilteredDataSet( new String[] {"users"}, actualDataSet ); A FilteredDataSet decorates a given dataset using an ITableFilter, which in turn is a DbUnit interface that defines which tables belongs to a dataset, and what order they should be retrieved. In the example above, the constructor implicitly creates a SequenceTableFilter, which returns the tables in the order defined by the array passed as parameter. Finally, a third option is using a QueryDataSet, where you explicitly indicate which table should be present in the dataset. Lines below would return a dataset that has the exact same contents of the previous example: Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 QueryDataSet actualDataSet = new QueryDataSet(dbunitConnection); actualDataSet.addTable("users"); Comparing the three options, the overloaded createDataSet() is obviously simpler in this case. But the other options have their usefulness in different scenarios: ƒ QueryDataSet is more flexible, as you can also provides the query that will be used to populate the table (if you don't provide one, it assumes SELECT * FROM table_name). Using a query, you can narrow the field even more, by selecting only the rows the test case is interested on, which is useful when the database contains more data. Back to our example, we could use the following lines: QueryDataSet actualDataSet = new QueryDataSet(dbunitConnection); actualDataSet.addTable("users", "select * from users where id = " + id); ƒ FilteredDataSet can be used with any ITableFilter, such as a filter that returns tables in the right foreign-key dependency order (as will be shown in section 16.6). 16.4.2 Ignoring columns If you run the previous test method alone, it pass. But if you append it to the existing UserDaoJdbcImplTest class, and run the whole class, it will fail: junit.framework.AssertionFailedError: row count (table=users) expected:<1> but was:<2> at junit.framework.Assert.fail(Assert.java:47) This is a common problem when using DbUnit, and one of the most annoying ones: a test case pass when it is run alone, but fails when ran as part of a suite. In this particular case, our user.xml dataset has only one row, and this is what we assume the database should contain after we inserted the User object. But when many tests are run, it fails, as the database has something else. So, where that extra row come from? It came from the previous test (testGetUserById()) execution, as that method also inserted a row. One could say the culprit was the previous test, which didn't clean up its mess7, but the reality is that the database is a wild world, so it is the test case sole responsibility to make sure the database is in a known state before the test is ran. In our example, that could be achieved by using the same dataset and a DELETE_ALL operation: IDataSet setupDataSet = getDataSet("/user.xml"); DatabaseOperation.DELETE_ALL.execute(dbunitConnection, setupDataSet); 7 In fact, the DbUnit documentation states that “Good setup don't need cleanup” and you “should not be afraid to leave your trace after a test”. This is not always true, though, as we will see on section 16.8. Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 If we add those lines and run the whole test again it should pass – or shouldn't it? Let's try and see the result: junit.framework.AssertionFailedError: value (table=users, row=0, col=id): expected:<1> but was:<2> at junit.framework.Assert.fail(Assert.java:47) at org.dbunit.Assertion.assertEquals(Assertion.java:147) at org.dbunit.Assertion.assertEquals(Assertion.java:80) Oops, another failure! Now the number of rows is correct (because we deleted all rows from the users table before running the test), but the id of the inserted row does not match what we expected. This is another common problem, and it happens frequently when the database generates the id – although we cleaned up the rows, we did not reset the primary key generation, so the next row inserted has an id of 2, and not 1 as we expected. There are many solutions for this issue, ranging from simple (like ignoring the id column in the comparison) to very sophisticated ones (like taking control of how ids are generated – we will see this approach in the next chapter). For now, let's just ignore the id column, using the method Assertion.assertEqualsIgnoreCols(), instead of Assertion.assertEquals(): Assertion.assertEqualsIgnoreCols( expectedDataSet, actualDataSet, "users", new String[] { "id" } ); Listing 16.8 shows the full method for our new test case. Listing 16.8 Second approach for testAddUser() [...] public class UserDaoJdbcImplTest extends AbstractDbUnitTestCase { @Test public void testAddUserIgnoringIds() throws Exception { IDataSet setupDataSet = getDataSet("/user.xml"); DatabaseOperation.DELETE_ALL.execute(dbunitConnection, setupDataSet); User user = newUser(); long id = dao.addUser(user); assertTrue(id>0); IDataSet expectedDataSet = getDataSet("/user.xml"); IDataSet actualDataSet = dbunitConnection.createDataSet(); Assertion.assertEqualsIgnoreCols( expectedDataSet, actualDataSet, "users", new String[] { "id" } ); } } Although ignoring the column is the simplest approach to the problem (in the sense that it does not require any advanced technique), it introduces a 'maintenance bug': now it is necessary to keep both the dataset file (user.xml) and the Java class (which has references to both the users table and id column) in sync. In the next section we will see a Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 better (although not optimal yet) approach, where the database information (table and column names) is contained just in the dataset file. 16.5 Transforming data using ReplacementDataSet DbUnit provides a simple yet powerful IDataSet implementation called ReplacementDataSet. In the following sections, we will see how it can be used to solve some common problems. 16.5.1 Using ReplacementDataSet to handle the different ids issue Let's try a different approach for the 'same dataset, different ids' issue. Instead of simply ignoring the id column, couldn't we dynamically change a dataset value before the test case uses it? Well, changing the data inside a dataset would be quite complicated. Fortunately, though, DbUnit provides the ReplacementDataSet class, which decorates an existing dataset by dynamically replacing tokens, according to the developer needs. Back to our problem, first we need to change the dataset XML file, by replacing the hard- coded ids by a token (we used [ID] in this case, but it could anything, as long as that string does not occurs somewhere else in the dataset). Listing 16.9 shows the new XML. Listing 16.9 user-token.xml, a dataset that uses a token for ids Next, we change the test case class, with the changed (and new) methods listed on 16.10. Listing 16.10 Changes on UserDaoJdbcImplTest to handle dynamic ids [...] public class AbstractDbUnitTestCase { [...] protected IDataSet getReplacedDataSet(IDataSet originalDataSet, int id) throws Exception { ReplacementDataSet replacementDataSet = new ReplacementDataSet(originalDataSet); (1) replacementDataSet.addReplacementObject("[ID]", id); (2) return replacementDataSet; } protected IDataSet getReplacedDataSet(String name, int id) throws Exception{ IDataSet originalDataSet = getDataSet(name); (3) return getReplacedDataSet(originalDataSet, id); (4) } Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } [...] public class UserDaoJdbcImplTest extends AbstractDbUnitTestCase { [...] @Test public void testGetUserByIdReplacingIds() throws Exception { long id = 42; (5) IDataSet setupDataset = getReplacedDataSet("/user-token.xml", id ); (6) DatabaseOperation.INSERT.execute(dbunitConnection, setupDataset); User user = dao.getUserById(id); assertUser(user); } @Test public void testAddUserReplacingIds() throws Exception { IDataSet setupDataSet = getDataSet("/user-token.xml"); DatabaseOperation.DELETE_ALL.execute(dbunitConnection, setupDataSet); (7) User user = newUser(); long id = dao.addUser(user); assertTrue(id>0); IDataSet expectedDataSet = getReplacedDataSet(setupDataSet, id ); (8) IDataSet actualDataSet = dbunitConnection.createDataSet(); Assertion.assertEquals( expectedDataSet, actualDataSet ); } } (1) The ReplacementDataSet constructor takes as parameter the dataset it is decorating. Notice that the original dataset remains intact, and the method returns a new dataset. (2) Here is where we define what must be replaced, using addReplacementObject() (the API also provides a addReplacementSubstring() method, but addReplacementObject() is the most common option). (3), (4) We added 2 more methods to get datasets, taking care of not copying-and- pasting code, but reusing the methods instead. (5) In this test case, the value of the id does not matter, as long as the same value is used in both places (getReplacedDataSet() and getUserById()). (6) Here we call the new method, which will read the original XML file and return a dataset with the [ID] dynamically replaced. (7) As we are using DELETE_ALL to clean up the database, the ids are irrelevant. But if we used another DatabaseOperation (like DELETE), we would need to use a decorated dataset here as well. (8) We also need a decorated dataset to be used in the assertion, and here we used the id returned by the DAO itself (hence, if the test still fails because of a wrong id, then something is really wrong with the DAO class, and it is time to offer a sacrifice to Murphy's so your code is not abide by His law anymore). BEST PRACTICE: THOU SHOULD NOT HARD-CODE VALUES IN METHOD CALLS Licensed to Alison Tyler Download at Boykma.Com 18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Take a look in the code snippet below: IDataSet setupDataset = getReplacedDataSet("/user-token.xml", 1); DatabaseOperation.INSERT.execute(dbunitConnection, setupDataset); User user = dao.getUserById(1); If you didn't write that code (or even if you wrote it a long time ago), the following questions might pop up in your mind: What does the 1 in the first line stands for? Is that method replacing just 1 line? Does it sound like that 1 is directly related to the 1 in the third line? Now take a look back at the testGetUserById() method from the previous listing – would you have the same doubts? This example illustrate how a subtle change (which “costs” just a few seconds of a developer's time) can make code much more understandable (and consequently, easier to maintain). So, fight your inner laziness, and create variables whenever appropriated, even if the variable will be used only once. 1 Another situation where a Replace 6.5.2 Handling NULL values mentDataSet is useful is to represent NULL values (the XML line, then the value of that col t line of XML – DbUnit uses the firs DTD! onfused? Good, we made our point! Seriously, it is really frustrating when you spend hou SQL's NULL, not Java's null) in a dataset. Actually, the way DbUnit handles NULL in FlatXmlDataSet files is tricky, and deserves clarification: 1. IF a column exists in the database but is missing in a umn (for that row in the dataset) is assumed to be NULL; 2. BUT that just apply if the column was present in the firs t line to define which columns a table is made of; 3. UNLESS the database columns are defined in a C rs trying to figure out why your test case is failing, just to realize you got caught by a DbUnit idiosyncrasy8. Let's try to make it clearer with a naive example, where we have two XML files with the exact same lines, but in different order (as show by listings 16.11 and 16.12). Listing 16.11 user-ok.xml, where the first line has all columns me="ElDuderino" first_name="Jeffrey" ="1" userna 8 This situation has improved in more recent versions of DbUnit. Although the idiosyncrasy still exists, at least now DbUnit is aware of the problems it can cause, and logs a warning message whenever it finds a line in the XML file with different columns than the first one. Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Listing 16.12 user-reverted.xml, where the first line is incomplete e="TheStranger"/> st_name="Jeffrey" e a test case that: user-ok.xml) to populate the database. Because the second line doe ok lines (but in different order), both tests should pass, but th ]]> but Now let's writ . Uses the first dataset (1 s not have the first_name and last_name attributes, DbUnit will insert NULL in the equivalent database columns. 2. Then compares the database contents with the contents of both datasets (user- .xml and user-reverted.xml). As both datasets contains the same e user-reverted.xml assertion fails, complaining the dataset expected two columns, but the database contained four: junit.framework.ComparisonFailure: column count (table=users, expectedColCount=2, actualColCount=4) expected:<[[id, username was:<[[FIRST_NAME, ID, LAST_NAME, USERNAME]]> at org.dbunit.Assertion.assertEquals(Assertion.java:244) at org.dbunit.Assertion.assertEquals(Assertion.java:204) at org.dbunit.Assertion.assertEquals(Assertion.java:186) Listing 16.13 shows the new test method. Lis e missing column issue unitConnection, okDataset); taSet(); re are many ways to solve this issue, the two most common ones are: ting 16.13 Test case that demonstrates th [...] public class NULLTest extends AbstractDbUnitTestCase { @Test public void testNULL() throws Exception { k.xml"); IDataSet okDataset = getDataSet("/user-o DatabaseOperation.CLEAN_INSERT.execute(db IDataSet actualDataSet = dbunitConnection.createDa assertEquals(okDataset, actualDataSet); IDataSet revertedDataSet = getDataSet("/user-reverted.xml"); ctualDataSet); Assertion.assertEquals(revertedDataSet, a } } The Licensed to Alison Tyler Download at Boykma.Com 20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 1. Using a ReplacementDataSet e already used a ReplacementDataSet to replace the [ID] token; we could reuse the sam replacementDataSet.addReplacementObject("[NULL]", null); isting 16.14 shows all relevant changes, with comments. W e dataset to also replace NULL values. All we need is to define a new token (say, [NULL]) and add another replacement role: L Listing 16.14 ReplacementDataSet approach to the missing column issue class AbstractDbUnitTestCase { (IDataSet originalDataSet, long id) ", id); ); (1) .] class NULLTest extends AbstractDbUnitTestCase { ion { DataSet); ment.xml", s the same method used before, we just added a new replacement role here. ssing a bog is the original XM [...] public [...] ected IDataSet getReplacedDataSet prot throws Exception { ReplacementDataSet replacementDataSet = new ReplacementDataSet(originalDataSet); replacementDataSet.addReplacementObject("[ID] replacementDataSet.addReplacementObject("[NULL]", null return replacementDataSet; } } ..[ public @Test void testNULLReplacementDataset() throws Except public IDataSet okDataSet = getDataSet("/user-ok.xml"); ection, ok DatabaseOperation.CLEAN_INSERT.execute(dbunitConn IDataSet actualDataSet = dbunitConnection.createDataSet(); Assertion.assertEquals(okDataSet, actualDataSet); er-replace IDataSet revertedDataSet = getReplacedDataSet("/us -1); (2) IDataSet sortedDataSet = new SortedDataSet(revertedDataSet); (3) Assertion.assertEquals(sortedDataSet, actualDataSet); (4) } } (1) i In (2), in order to simplify, we are using the method that expects an id, and pa us value (-1), as it won't be replaced anyway (because the dataset does not have any [ID] token). Ideally, though, getReplacedDataSet() should be overload to handle the situation where the id is not necessary. Or better yet, the tokens to be replaced should not be passed as parameters (we will see how to do that later on section 16.7.3). If in (4) we compared actualDataSet against revertedDataSet (which L file with the proper [NULL] tokens replaced), the assertion would still fail. Although this time the number of columns is right, the order would be reverted: the database query would return rows 1 and 2, while the dataset defines the order as 2 and 1. So, in order to solve this Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 issue without changing the order in the dataset (whose wrong order is the whole purpose of the example!), we wrapped the dataset in a SortedDataSet (3), which returns a new dataset with the tables sorted by the order its column were defined in the database. In this example, it would sort first by id, which is the first column in the CREATE TABLE statement. If any two or more lines had the same id (which is not the case here, as id is the primary key), then it would sort them by username (the second column), first_name (third column), and so on. 2.Using a DTD ou can explicitly define the database structure (instead of letting DbUnit implicitly “gu Y ess” it when it reads the XML first line) in a DTD, and add that DTD to the dataset header, as shown on listings 16.15 and 16.16. Listing 16.15 new version of user-reverted.xml, with DTD declaration M "target/test-classes/user.dtd"> t_name="Jeffrey" d="2" username="TheStranger"/> Listing 16.16 user.dtd (users*)> IRED ice the weird location (target/test-classes/) to the user.dtd file – that is the relative dire be run again (without any cha one should be used? ges. Using a DTD adds more val otN ctory where our test artifacts are compiled. Unfortunately, DbUnit only supports physical locations, so the DTD path must be relative to the project's root directory, or an absolute path in the file system. Ideally, DbUnit should support looking up the DTDs in the classpath, but that luxury is not available at the time this book was written. Once user-reverted.xml is changed, the testNULL() can nge), and it will succeed this time. Given these two approaches, which Both approaches have their advantages and disadvanta idation to the datasets (which can avoid other errors), at the cost of a more complicated initial setup (creating the DTD, making sure it is in the right place, etc...). On the other Licensed to Alison Tyler Download at Boykma.Com 22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 hand, using [NULL] makes the datasets clearer, as its presence explicitly indicates that a value is NULL. It also has a setup cost, but if you are already using a ReplacementDataSet, that cost is minimum (just one more line of code). Hence, the decision depends more on the project context and/or personal preferences, then in the technical merits of the approach per se. TURNING LOGGING ON at DbUnit would warn us about the missing column in the XML issue, but enables logging. Ins ady uses a logging framework (like Log4J or Java's ja t the afo 74 [main] WARN org.dbunit.dataset.xml.FlatXmlProducer - Extra columns on for more you are facing hairy problems that sound like a DbUnit bug or misuse, try ena Earlier on we said th if you run the testNULL() method, it simply fails, without any warning. If that happens, it means DbUnit logging is somehow disabled. How could it be enabled, then? Well, there is no API or DbUnit configuration file where you explicitly tead, you must configure SLF4J in your project. It is not the intent of this book to explain how SLF4J works, or why the DbUnit chose this tool (until release 2.2, DbUnit used no logging framework at all). But, in a nutshell, you need to add to the project's classpath a JAR containing a real SL4J implementation. In our cases we didn't see any log because the project's Ant script is explicitly using sl4j-nop.jar, which simply does not logging anything (this is also the default implementation included in your Maven project if you just add DbUnit as a project dependency). If your project alre va.util.logging), chances are there is a SLF4J provider for that framework, so you can just include its jar (like sl4j-log4j12.jar) in the classpath. If you don't use any logging framework, the easiest solution (i.e., the one that requires no extra configuration) is to add sl4j-simple.jar – this provider sends info messages to System.out, warnings and errors to System.err, and ignores all other logging levels (like debug and trace). Adding sl4j-simple.jar to the classpath and running the test case again, we ge rementioned warning: 4 line 2. Those columns will be ignored. 474 [main] WARN org.dbunit.dataset.xml.FlatXmlProducer - Please add the extra columns to line 1, or use a DTD to make sure the value of those columns are populated or specify 'columnSensing=true' for your FlatXmlProducer. 474 [main] WARN org.dbunit.dataset.xml.FlatXmlProducer - See FAQ details. henever W bling the lower logging levels9 (like trace) before pulling your hair out. DbUnit will spit out a lot of debugging info, and hopefully that will help you to narrow down the issue. 9 Notice that you will need a better SL4J implementation than just sl4j-simple in this case. Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 16.6 Creating datasets from existing database data So far in the examples, we created dataset XML files from scratch, in a bottom-up approach. This is the ideal situation when you are doing pure TDD, but often you need to create these files in a top-down manner, where the data already exist in the database. For instance, you might be working in a big project, where the database development is done by a separate team of DBAs, and a QA team maintains a database instance full of testing data. Typically in these situations, your Java code (and hence its test cases) will have to deal with complex scenarios, like tables with dozens of columns and many foreign key relationships. It would be unpractical and very error prone to create the datasets from scratch, so you can leverage the existing data to create the initial files, then prune the data your test cases do not need. Even if your project is simpler and you can create the XML files from scratch in your typical development cycle, sometimes you face bugs that are hard to reproduce in a test case (for instance, the user access a web application, executes a couple of inserts and updates, then a page that display a table with the modified values return incorrect data). In this case, instead of trying to reproduce all steps through code in your test case, you could just manually reproduce the steps, then export the relevant database contents to a dataset, and reproduce only the buggy method call in the test case. In its simplest form, exporting a dataset is a very straightforward task: all you need to do is create a dataset object containing the data you want to export (for instance, using DatabaseConnection.createDataset() to export the whole database, or a QueryDataSet to narrow the data), then call a static methods from the dataset format class (like FlatXmlDataSet): IDataSet fullDataSet = dbunitConnection.createDataSet(); FileOutputStream xmlStream = new FileOutputStream("full-database.xml"); FlatXmlDataSet.write(fullDataSet, xmlStream); Similarly, you could also generate the dataset's DTD: FileOutputStream dtdStream = new FileOutputStream("full-database.dtd"); FlatDtdDataSet.write(fullDataSet, dtdStream); This simple approach works fine most of the times, but it has a drawback: the tables in the dataset are created in no particular order. As such, if one table has a foreign key constraint with another table, and they are generated in the wrong order, attempts to insert the dataset into the database will mostly likely fail (due to constraint violations). Fortunately, the solution for this problem is also simple, all it takes is to wrap the dataset in a FilteredDataSet that uses a DatabaseSequenceFilter, which in turn will return the tables in the right order: IDataSet fullDataSet = dbunitConnection.createDataSet(); Licensed to Alison Tyler Download at Boykma.Com 24 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 ITableFilter filter = new DatabaseSequenceFilter(dbunitConnection); FilteredDataSet filteredDatSet = new FilteredDataSet(filter, fullDataSet); FileOutputStream xmlStream = new FileOutputStream("full-database.xml") FlatXmlDataSet.write(fullDataSet, xmlStream); 16.7 Advanced techniques In the next sub-sections, we will analyze some techniques that makes DbUnit usage easier to understand and maintain. Curiously, these techniques do not employ any particular DbUnit feature, just “advanced” Java and JUnit APIs. 16.7.1 DbUnit and the Template Design Pattern If you look at the previous examples from a higher level, you might realize they all follow the same workflow: 1. Prepare the database, using a dataset XML file 2. Develop some Java code for the test itself 3. (Optionally) Compare the state of the database with another dataset file Going further, only step 2 is specific for each test; steps 1 and 3 are basically the same (except by the XML files location) for all tests. In the examples so far, we achieved a level of reuse by delegating part of step 1 to helper methods (like getDataSet() and getReplacedDataSet()), but we can improve it even more if we use the Template Design Pattern. DESIGN PATTERNS IN ACTION: TEMPLATE METHOD The Template (or Template Method) is a behavioral design pattern described in the classic GoF10 book. In this pattern, a super-class defines the overall skeleton of an algorithm (i.e., the template), but leaves some details to be filled in by sub-classes. Back to our example, the template is the workflow we described above, where a super- class would define the skeleton and take care of steps 1 and 3, while the sub-classes would be responsible for step 2. The most common – and simpler – way to implement the Template pattern in Java is through an abstract super-class that implements the template method and define abstract methods for the steps the sub-classes must fill in. This is not a good approach in our case, because it would allow each sub-class to have only one test method, which in turn would require dozens or even hundreds of test classes in a typical project. 10 “Design Patterns: Elements of Reusable Object-Oriented Software”, by Eric Gamma and 3 other authors (the Gang of Four). Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E25 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 A second approach is to create an interface that defines the steps the template method is not responsible for, and receive an implementation (which is typically an anonymous class) of that interface as parameter. This is the approach Spring Framework uses in his templates (like JdbcTemplate, HibernateTemplate, etc), and is the approach used in listing 16.17. Listing 16.17 UserDaoJdbcImplTest using Template Design Pattern [...] public class UserDaoJdbcImplTemplatePatternTest extends AbstractDbUnitTestCase { protected interface TemplateWorker { (1) long getId(); void doIt() throws Exception; String getSetupDataSet(); String getAssertDataSet(); } protected void runTemplateTest(TemplateWorker worker) throws Exception { (2) IDataSet setupDataSet = getReplacedDataSet(worker.getSetupDataSet(), worker.getId() ); DatabaseOperation.CLEAN_INSERT.execute(dbunitConnection, setupDataSet);(3) worker.doIt(); (4) String comparisonDataSetName = worker.getAssertDataSet(); if ( comparisonDataSetName != null ) { (5) IDataSet expectedDataSet = getReplacedDataSet(comparisonDataSetName, worker.getId()); IDataSet actualDataSet = dbunitConnection.createDataSet(); Assertion.assertEquals( expectedDataSet, actualDataSet ); } } @Test public void testGetUserById() throws Exception { final long id = 42; // value here does not matter TemplateWorker worker = new TemplateWorker() { public void doIt() throws Exception { User user = dao.getUserById(id); assertUser( user ); } public String getSetupDataSet() { return "/user-token.xml"; } public String getAssertDataSet() { return null; } (6) public long getId() { return id; } }; runTemplateTest(worker); } @Test Licensed to Alison Tyler Download at Boykma.Com 26 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 public void testAddUser() throws Exception { TemplateWorker worker = new TemplateWorker() { private long id = -1; public void doIt() throws Exception { User user = newUser(); id = dao.addUser(user); assertTrue(id>0); } public String getSetupDataSet() { return "/empty.xml"; } (7) public String getAssertDataSet() { return "/user-token.xml"; } public long getId() { return id; } }; runTemplateTest(worker); } } (1) This interface will be implemented as inner class by the test cases that use the template method. (2) This is the template method itself. Notice that (3), (4), and (5) matches the 3 workflow steps described above. (6) It is not necessary to check the database state after this test case is run, so null is returned here. (7) As we are using CLEAN_INSERT to prepare the database, in this test case we opted for a total cleanup, hence the “/empty.xml”, a dataset that contains all tables used by the test cases (its content is listed at 16.18). Listing 16.18 empty.xml, dataset used to clean up the database The problem with this approach is that it is too verbose and unnatural, as you have to create an inner class on each test method, and do the work inside that class (instead of inside the method). In the next sub-section we will see a much cleaner approach. 16.7.2 Improving reuse through custom annotations Since their introduction to the Java language, annotations have grown in popularity, and are used by many development tools, like JUnit itself (we've been using JUnit annotations, such as @Test, throughout this whole book). What most developers do not realize, though, is that they do not need to limit themselves to simply use 3rd party's annotations – they could create their own project-specific annotations, when appropriated. Although Joshua Bloch Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 preaches the opposite on “Effective Java - 2nd Edition”11, we believe that custom annotations can boost a project's productivity, particularly in the test cases arena. That being said, let's use custom annotations as a third approach to the template pattern implementation. The idea is to clear the noise in the test method, letting it focus on step 2, and using annotations to pass the information necessary to complete steps 1 and 3. Listing 16.19 shows the custom annotation, while 16.20 shows the new test methods. Listing 16.19 Custom annotation @DataSets @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DataSets { String setUpDataSet() default "/empty.xml"; (1) String assertDataSet() default ""; (2) } Annotation attribute setUpDataSet (1) defines the dataset used to prepare the database. If not specified, the default value is “/empty.xml”, which will clean up the whole database. Similarly, assertDataSet defines the dataset which will be used to check the database state after the test is executed. As not all test cases must check that (typically, test cases for methods that load data don't), the default is “”(2), which in our case means no dataset (notice that the meaning of an annotation value is relevant to the classes that will use the annotation. As it is not possible to use null, we use the empty string to indicate no dataset). Listing 16.20 UserDaoJdbcImplTest using custom annotations [...] public class UserDaoJdbcImplAnnotationTest extends AbstractDbUnitTemplateTestCase { @Test @DataSets(setUpDataSet="/user-token.xml") public void testGetUserById() throws Exception { User user = dao.getUserById(id); assertUser(user); } @Test @DataSets(assertDataSet="/user-token.xml") public void testAddUser() throws Exception { User user = newUser(); id = dao.addUser(user); assertTrue(id>0); } } 11 item 35, page 175: “most programmers will have no need to define annotation types Licensed to Alison Tyler Download at Boykma.Com 28 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Compare this new test class with the previous example (listing 16.17) – isn't it much cleaner? You barely notice the template pattern being used! Now, wait a minute, what's the catch? The “magic” is done by the AbstractDbUnitTemplateTestCase, which extends AbstractDbUnitTestCase and uses a custom TestRunner; this TestRunner intercepts the test methods12, and plays the Template role. Listing 16.21 shows this new super-class. Listing 16.21 New super-class, AbstractDbUnitTemplateTestCase [...] @RunWith(AbstractDbUnitTemplateTestCase.DataSetsTemplateRunner.class) (1) public abstract class AbstractDbUnitTemplateTestCase extends AbstractDbUnitTestCase { protected static long id; (2) public static class DataSetsTemplateRunner extends JUnit4ClassRunner { public DataSetsTemplateRunner(Class klass) throws InitializationError { super(klass); } @Override protected void invokeTestMethod(Method method, RunNotifier notifier) { (3) setupDataSet(method); super.invokeTestMethod(method, notifier); assertDataSet(method); } private void setupDataSet(Method method) { DataSets dataSetAnnotation = method.getAnnotation(DataSets.class); if ( dataSetAnnotation == null ) { return; (4) } String dataSetName = dataSetAnnotation.setUpDataSet(); if ( ! dataSetName.equals("") ) { #5 try { IDataSet dataSet = getReplacedDataSet(dataSetName, id); DatabaseOperation.CLEAN_INSERT.execute(dbunitConnection, dataSet); } catch (Exception e) { throw new RuntimeException( "exception inserting dataset " + dataSetName, e ); } } 12 This technique is explained in more details on Appendix B. Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E29 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } private void assertDataSet(Method method) { DataSets dataSetAnnotation = method.getAnnotation(DataSets.class); if ( dataSetAnnotation == null ) { return; (4) } String dataSetName = dataSetAnnotation.assertDataSet(); if ( ! dataSetName.equals("") ) { #5 try { IDataSet expectedDataSet = getReplacedDataSet(dataSetName, id ); IDataSet actualDataSet = dbunitConnection.createDataSet(); Assertion.assertEquals( expectedDataSet, actualDataSet ); } catch (Exception e) { throw new RuntimeException( "exception asserting dataset " + dataSetName, e ); } } } } } (1) The dirty job is done by DataSetsTemplateRunner, a static inner class. AbstractDbUnitTemplateTestCase itself does almost nothing other than using @RunWith to show who's the boss. (2) id cannot be passed around in annotations, as it can have dynamic values (like in testAddUser()), and annotations can only receive literals (because they are defined at compile time), so it must be shared among test cases. Such approach might faint the most purist test practitioner, but the truth is, keeping state in tests is not only acceptable in some cases, but often it is the best approach for a problem. In fact, the state (id) in this case is passed around only to solve an issue caused by the way the tests are executed. (3) This is the Template method properly speaking. Its three lines perfectly match the three steps of the workflow described earlier. (4) Here is where the annotation is read. (5) As explained before, the dataset is only used if the annotation value is not an empty string. 16.7.3 Using Expression Language (EL) in datasets Our getReplacedDataSet() methods have two issues: it requires passing the tokens to be replaced (like id) as parameters, and then explicitly calls addReplacementObject() for each token. Let's say that later on we create a test case where it is necessary to replace 2 ids, and we know they will be generated in sequence. Now the method call would be: IDataSet dataSet = getReplacedDataSet(dataSetName, id, id+1); Licensed to Alison Tyler Download at Boykma.Com 30 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The method itself would be changed to: public static IDataSet getReplacedDataSet(IDataSet originalDataSet, long id, long id2) { [...] replacementDataSet.addReplacementObject("[ID2]", id2); [...] } And the dataset XML would have both [ID] and [ID2]: That sounds like an ugly hack, not to mention the changes on the DataSetsTemplateRunner. A much cleaner approach would be figuring out the tokens dynamically, in a way that: 1. Values to be replaced are not passed by parameter, but added to a context. 2. Tokens are expressed so that values in the context are replaced dynamically. Better yet if the syntax allows basic operations, like [ID+1]. Fortunately, there is a standard Java technology that fits perfect in this description, the Expression Language (EL). EL has been around in Java for many years, and have been made a steadily progress toward being an integral part of the technology: first as part of JSP tag attributes on JSTL 1.0 and JSF 1.0, then available anywhere inside a JSP 2.0 page, and finally as a standalone Java API (javax.el). Besides the standard Java EL, many open-source projects offers alternative EL libraries, like OGNL (http://ognl.org) and Marmalade (http://marmalade.codehaus.org). Using EL, the same dataset would be expressed as: And the getReplacedDataSet () would not require id parameters anymore; instead, the id would be bound to the EL context: getContext().bind( “id”, id ); IDataSet dataSet = getReplacedDataSet(dataSetName); Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E31 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Listings 16.21 shows the changes necessary to support EL. Notice that, despite EL being a standard API, it is still necessary to create an ELContext implementation (ELContextImpl, in our example), and that is not a trivial task (due to lack of documentation). It is out of the scope of this book to explain how that class was implemented (although the code is available for download), but a quick explanation can be found at the author's blog (http://weblogs.java.net/blog/felipeal/). Listing 16.21 New AbstractDbUnitELTemplateTestCase which supports EL [...] public abstract class AbstractDbUnitELTemplateTestCase [...] { [...] private static ELContextImpl context; @Override protected void invokeTestMethod(Method method, RunNotifier notifier) { context = new ELContextImpl(); (1) context.bind("id", id); (2) setupDataSet(method); super.invokeTestMethod(method, notifier); context.bind("id", id); (3) assertDataSet(method); } protected static ELContextImpl getContext() { (4) return context; } public static IDataSet getReplacedDataSet(String name) throws Exception { [...] final FlatXmlDataSet dataSet = new ELAwareFlatXmlDataSet( reader ); (5) [...] } private static class ELAwareFlatXmlDataSet extends FlatXmlDataSet { [...] @Override public void row(Object[] values) throws DataSetException { (6) final ELContextImpl context = getContext(); (7) if ( context != null ) { ExpressionFactory factory = context.getFactory(); int i = 0; for ( Object value : values ) { String stringValue = ""+value; Object newValue; if ( stringValue.startsWith("${") && (8) stringValue.endsWith("}") ) { ValueExpression converted = factory.createValueExpression( context, stringValue, Object.class ); (9) newValue = converted.getValue(context); } else { newValue = value; } values[i++] = newValue; } } else { Licensed to Alison Tyler Download at Boykma.Com 32 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 throw new IllegalStateException( "No context on thread" ); } super.row(values); } } (1), (4) A new context object is created before each test, and it is made available through the getContext() method, so the test cases could bind more objects to the context, when necessary. (2) id is bound before the setup dataset is read. (1) id is bound again before the assert dataset is read. This is necessary because the test case might have changed the id (like on testAddUser()), and id is represented by a primitive type (if it was a mutable object, this second bind would not be necessary). (5) Here is the only relevant change (the other is the absence of the id parameter) on getReplacedDataSet(): it now use a custom dataset (ELAwareFlatXmlDataSet). (6) ELAwareFlatXmlDataSet trick is done by overriding row(), so it passes each dataset value to the EL engine for evaluation. (7) There is a subtle trick in place here: instead of passing the EL context as a parameter to ELAwareFlatXmlDataSet constructor, it is fetched from the outside (through getContext()). Such hack is necessary because row() is used during the XML parsing, and FlatXmlDataSet parses the XML right in the constructor. This is a bad practice from DbUnit's part – a constructor should not call methods that can be overridden by sub-classes. (8) This if statement is just an optimizer – if the value is not enclosed in ${}, there is no need to evaluate it. (9) Here is where the EL engine does its job of evaluating the expression, according to the values bound in the context. Finally, listing 16.22 shows the new test case – notice that the only differences from the previous version (listing 16.20) are the super-class and the dataset being used (which is listed on 16.23) Listing 16.22 UserDaoJdbcImplELTest using custom annotations [...] public class UserDaoJdbcImplAnnotationTest extends AbstractDbUnitELTemplateTestCase { @Test @DataSets(setUpDataSet="/user-EL.xml") public void testGetUserById() throws Exception { User user = dao.getUserById(id); assertUser(user); } @Test @DataSets(assertDataSet="/user-EL.xml") public void testAddUser() throws Exception { User user = newUser(); Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E33 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 id = dao.addUser(user); assertTrue(id>0); } } Listing 16.23 user-EL.xml, dataset that uses EL syntax for tokens 16.8 DbUnit Best Practices Throughout this chapter, whenever a best practice applied to the example at hand, we described that practice in details. In this final section, we present a few more best practices that did not apply to the examples seem so far. 16.8.1 Use one database per developer When you are running database test cases, not only the test case can mess up the database, but other simultaneous database users can affect the result of the tests. Consequently, is paramount that each developer uses his own database. If you are fortunate enough to be developing an application that can be run in different databases flavors (for instance, if it only uses standard SQL statements, or if the SQL is managed by a ORM tool), then the best approach is to use an embedded database – not only each developer would have its own instance, but the database access would be very fast. If the embedded database approach is not possible, then you should try to install a matching database in each developer's machine (many database vendors – like Oracle – provides a lite version of their product, which would be a good option for this approach). If neither the embedded nor the lite databases are affordable, then try to allocate one database instance for each developer in the database server. In the worst case, if not even that is possible (too many instances could be costly in resources or even license fees), well, don't panic! Try to at least allocate a few instances to be used just for test cases, and another few for regular development. 16.8.2 Make sure the target database is tested If you can afford the embedded database approach, but the final application will be deployed in another database flavor, make sure the application is tested against the target database. A reasonable approach is to let the developers use the embedded database, but have a daily build that uses the target database. Do not make the mistake of assuming databases can be switched at will, there are always incompatibilities or issues, even if you use an ORM tool. And the sooner you catch these issues, the better. Licensed to Alison Tyler Download at Boykma.Com 34 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 16.8.3 Create complementary tests for loading and storing data As the old sayings goes “everything that goes up must come down”. If you write a test case that verifies an object is corrected stored in the database, chances are you should write the test that asserts it is loaded correctly. And if you keep that in mind when you write one of them, it makes easy to write the other one – you could reuse the same dataset, or even write special infrastructure to handle both. 16.8.4 When writing load test cases, cover all the basic scenarios All versions of testGetUserById() we saw in this chapter covered just one scenario: the database contained a row with the tested id, and only that row. That was enough for the purpose of describing the techniques, but in a real project, you should test other scenarios, such as: testing id that does not exist in the database, testing an empty database, testing when more than one row is available, testing joins when multiple rows are available. The last scenario deserves a special explanation, as it is a common issue when you use ORM and is testing a method that uses a complex query where one or more tables are selected using a join. Say the User object has a List relationship, the Telephone class is mapped to a telephones table with a foreign key to the users table, you are using JPA in the persistence layer, and the getUserById() must do a join fetch to get all telephones in just one query (if you are not familiar with these concepts, don't worry – they will be better explained on Chapter 17). You write just one test case, where the dataset contains only one row in both users and telephone rows, and you implement the JPA query as something like “from User user left join fetch user.telephones where user.id = ?”. Your test case pass, so you commit your code. But then once the application is on production, and a user happens to have more than one telephone, your code returns two users for the query! After half day of a frustrating debug session, you figured out the fix would be changing the query to be “select distinct(user) from User user left join fetch user.telephones where user.id = ?”. Had your initial test case covered more than the canonical scenario, you would not have had this bug. And believe us; this particular issue is more common than it appears. 16.8.4 Plan the dataset usage in advance As your application grows and you write more database test cases, your datasets becomes hard to manage. If you have 20 dataset XML files containing a particular table, and that table changes, then you have to change 20 XML files. This is probably the biggest DbUnit drawback, and unfortunately it is a problem without a clear solution. The best “practice” here is to be aware of this problem, and plan your solution in advance. Having that in mind, the techniques below can mitigate the problem: ƒ use the same dataset for loading / storing an object ƒ keep datasets small, restricting the tables and columns compared. ƒ if you always use the same values for the same objects (as described in “Best Licensed to Alison Tyler Download at Boykma.Com Last saved: Sebastian Stirling Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E35 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Practice: use a helper class to create and assert object instances”), at least you can apply the changes with just one search-and-replace command... ƒ ... or, in a more sophisticated approach, you could keep smaller XML files for group of objects, then use XML include or a CompositeDataSet to join them. Keep in mind, though, that any of these approaches brings more complexity to the test cases, and you might end up with something that is harder to maintain than a good old search- and-replace. 16.8.5 Typically, a good setup does not need cleanup. But... In all examples so far, the test cases setup the database, but did not bother to clean it up after they did their job. This is typically a good practice, and is indeed one of the best practices endorsed by DbUnit. Notice our emphasis in the typically, though. The problem of not cleaning up the database after the test is done is that a test case could make another test's life miserable, if it inserts data that is hard to be cleaned, like rows with foreign keys. Back to the users/telephones tables example, say a test case adds 1 row in each table, with the telephones row having a foreign key to users, and this test case does not clean up these rows after its ran. Then a second test case is going to use the same users row, but it does not care about the telephones table. If this test tries to remove the users row at set up, it would fail due to a foreign key violation. So, long story short, although typically a good setup does not need cleanup, it does not hurt to clean up, especially when the test case inserts rows with foreign key constraints. 16.9 Summary The persistence layer is undoubtedly one of the most important parts of any enterprise applications, although testing it can be a challenge: test cases must be agile, but database characteristics make them bureaucratic. Although JUnit itself does not have an answer to this problem, there are many tools that do, and one of them is DbUnit, whose only purpose in life is to make database testing easier. DbUnit is a stable and mature project, comprised of a few dozen interfaces, implementations, and helpers. Despite this high number of classes, DbUnit usage is relative simple, as it consists basically of setting up the database before a test is ran, and comparing its state afterwards. And although DbUnit is a great tool, it is still a low-level library, which provides the basic blocks for database testing. To use it efficiently, it is necessary to define infrastructure classes and methods that at the same time leverage DbUnit strengths and provides reuse throughout the project. With creativity, experience, and earlier planning, it is possible to write DbUnit tests in an efficient and enjoyable way, and in this chapter we showed techniques that will help you to achieve that goal. Licensed to Alison Tyler Download at Boykma.Com 36 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: Sebastian Stirling ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 In this chapter we demonstrated (through progressive examples) how to use DbUnit to populate the database before tests, assert the database contents after the test, create datasets from existing databases, and use advanced APIs to make tests easier to write and maintain. In the next chapter we will see how to extend these techniques to JPA-based applications, while on chapter 18 we will cover tools that enhance JUnit, including a tool (Unitils) that provides some of this chapter's techniques out of the box. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 17 Testing JPA-based applications This chapter covers ƒ Multi-layered application testing ƒ Using DbUnit to test JPA applications ƒ JPA mapping tests ƒ Testing JPA-based DAOs ƒ Verifying JPA-generated schema Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Unfortunately we need to deal with the object relational (O/R) impedance mismatch, and to do so you need to understand two things: the process of mapping objects to relational databases and how to implement those mappings. —Scott W. Amber, Mapping Objects to Relational Databases: O/R Mapping In Detail Most Java applications need to store data in a persistent storage, and typically this storage is a relational database. There are many approaches on how to persist the data, from simply executing low-level SQL statements directly, to more sophisticated modules that delegate the task to third-party tools. Although discussing the different approaches is almost a religious matter, we assume it is a good practice to abstract data persistence to specialized classes, like DAOs (Data Access Object design pattern). And the DAO themselves can be implemented in many different ways: ƒ writing brute-force SQL statements (such as the UserDaoJdbcImpl example in Chapter 16) ƒ using 3rd-party tools (like Apache Ibatis or Spring JDBC templates) that facilitates JDBC programming ƒ delegating the whole database access to tools that map objects to SQL (and vice- versa) Over the last years, the third approach has become widely adopted, through the use of ORM (Object-Relational Mapping) tools, such as Hibernate and Oracle Toplink. In fact, ORM became so popular that JCP (Java Community Process, the organization responsible for defining Java standards) created a specification1 to address it, the JPA (Java Persistence API). JPA, as the name states, is just an API; in order to use it, you need an implementation. Fortunately, most existing tools adhered to the standard, so if you already use an ORM tool (like Hibernate), you could use it in “JPA mode”, i.e., using the JPA APIs, instead of proprietary ones. This chapter is divided in two parts. First, we will see how to test layers that use DAOs in a multi-layered application. Then we will learn how to test the JPA-based DAO implementation, using DbUnit2. And although this chapter is focused on JPA and Hibernate, the ideas presented here apply to other ORM tools as well. 1 Some people may argue that two standards already existed before JPA, EJB entities and JDO. Well, EJBs entities were too complicated, and JDO never really took off. 2 And in this aspect, this chapter is an extension of Chapter 17; if you are not familiar with DbUnit, we recommend you read that chapter first. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 17.1 Testing multi-layered applications By multi-layered (or multi-tiered) application we mean an application whose structure has been divided in layers, with each layer responsible for one aspect of the application. A typical example is a 3-tiers web application comprised of presentation, business, and persistence layers. Ideally, all of these layers should be tested, both in isolation (through unit tests), and together (through integration tests). In this section, we will see how to unit test the business layer without depending on its lower tier, the persistence layer. But first, let's take a look at the sample application. 17.1.1 The sample application The examples in this chapter will test the business and persistence layers (presentation layer testing has been covered at Chapter 11) of an enterprise application. The persistence layer is comprised of an User object and an UserDao interface, similar to those defined on Chapter 16, but with a few differences: a new method – removeUser() – on UserDao, the User object now has a one-to-many relationship with a Telephone object, and both these classes are marked with JPA annotations, as shown by listing 17.1. Listing 17.1 User and Telephone class definitions @Entity @Table(name="users") public class User { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String username; @Column(name="first_name") private String firstName; @Column(name="last_name") private String lastName; @OneToMany(cascade=CascadeType.ALL) @JoinColumn(name="user_id") @ForeignKey(name="fk_telephones_users") private List telephones = new ArrayList(); // getters and setters omitted } @Entity @Table(name="phones") public class Telephone { public static enum Type { HOME, OFFICE, MOBILE; Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String number; private Type type; // getters and setters omitted } This chapter sample application also has a business layer interface (UserFacade, defined at listing 17.2), which in turn deals with DTOs (Data Transfer Objects), not the persistent objects directly. As such, we need an UserDTO class (also defined at listing 17.2). Listing 17.2 Business layer interface (UserFacade) and tranfer object (UserDto) public interface UserFacade { UserDto getUserById(long id); } public class UserDto { private long id; private String username; private String firstName; private String lastName; // getters and setters omitted } Finally, as the persistence layer will be developed using JPA, a new implementation (UserDaoJpaImpl) is necessary, and it its initial version is shown at listing 17.33. Listing 17.3 Initial implementation of UserDao using JPA public class UserDaoJpaImpl implements UserDao { private EntityManager entityManager; // getters and setters omitted public void addUser(User user) { entityManager.persist(user); } public User getUserById(long id) { return entityManager.find(User.class, id); } public void deleteUser(long id) { String jql = "delete User where id = ?"; Query query = entityManager.createQuery(jql); query.setParameter(1, id); 3 The astute reader might be wondering why we listed UserDaoJPAImpl in this chapter, but omitted UserDaoJdbcImpl in chapter 17. The reason is that the JPA implementation is much simpler, just a few lines of non-plumbing code. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 query.executeUpdate(); } } The sample application is available in two 'flavors', Maven and Ant. To run the tests on Maven, simply type 'mvn clean test'. Similarly, to use Ant instead, just type 'ant clean test'. The application is also available as 2 Eclipse projects, one with the required libraries (under the lib directory) and another with the project itself. 17.1.2 Multiple layers, multiple testing strategies As each application layer has different characteristics and dependencies, they require different testing strategies. If your application has been designed to use interfaces and implementations, it is possible to test each layer in isolation, using mocks or stubs to implement the other layer's interface. In our example, the business layer Facade (listing 17.4) will be tested using EasyMock as the “implementation” of the DAO interfaces, as shown in listing 17.5. Listing 17.4 Facade implementation (UserFacadeImpl ) [...] public class UserFacadeImpl implements UserFacade { private static final String TELEPHONE_STRING_FORMAT = "%s (%s)"; private UserDao userDao; // getters and setters omitted public UserDto getUserById(long id) { User user = userDao.getUserById(id); UserDto dto = new UserDto(); dto.setFirstName(user.getFirstName()); dto.setLastName(user.getLastName()); dto.setUsername(user.getUsername()); List telephoneDtos = dto.getTelephones(); for ( Telephone telephone : user.getTelephones() ) { String telephoneDto = String.format(TELEPHONE_STRING_FORMAT,telephone.getNumber(), telephone.getType()); telephoneDtos.add(telephoneDto); } return dto; } } Listing 17.5 Unit test for UserFacadeImpl [...] Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 import static org.easymock.EasyMock.*; import static EntitiesHelper.*; public class UserFacadeImplTest { private UserFacadeImpl facade; private UserDao dao; @Before (1) public void setFixtures() { facade = new UserFacadeImpl(); (2) dao = createMock(UserDao.class); (3) facade.setUserDao(dao); } @Test public void testGetUserById() { int id = 666; User user = newUserWithTelephones(); (4) expect(dao.getUserById(id)).andReturn(user); (5) replay(dao); (5) UserDto dto = facade.getUserById(id); (6) assertUser(dto); (7) } } Before the test is run, we prepare the fixtures (1) that will be used in the test methods, the object being tested (2), and a mock (3). Even if you just have one test method (as is the case in this example), eventually more will be added, so it worths to have such setup method from beginning. Then, on the test case we create an User object (4) and set the mock expectation (5) to return it when requested (see more about mock expectations on Chapter 6). Notice that newUser() belongs to EntitiesHelper, which simply provides methods to create new entities (like User) and assert the properties of existing ones (EntitiesHelper was introduced in Chapter 16, and new methods were added in this chapter; the full code is not listed here, but is available for download). Finally, on (6) the method being tested is called, and the result is checked on (7) (which is another method defined on EntitiesHelper). This test case seems pretty much complete, and it almost is, excepted that it only exercises the best case scenario. But what if the DAO didn't find the request user? To be complete, the test cases must also exercise negative scenarios. So, let's try to add a new test case that simulates the DAO method returning null4: 4 How do we know the DAO returns null in this case? And who defined what the Facade should return? What happens in these exceptional cases should be documented in the DAO and Facade methods javadoc, and the test cases should follow that contract. In our example, we did not document that on purpose, to show how serious that lack of documentation can be. Anyway, we are returning null on both cases, but another approach could be throwing an ObjectNotFoundException. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 @Test public void testGetUserByIdUnknownId() { int id = 666; expect(dao.getUserById(id)).andReturn(null); replay(dao); UserDto dto = facade.getUserById(id); assertNull(dto); } Running this test, we get the dreaded NPE (NullPointerException): java.lang.NullPointerException at com.manning.junitbook.ch17.business.UserFacadeImpl.getUserById(UserFacadeIm pl.java:49) at com.manning.junitbook.ch17.business.UserFacadeImplTest.testGetUserByIdUnkow nId(UserFacadeImplTest.java:51) Makes sense, as the Facade method is not checking if the object return by the DAO is null. It could be fixed by adding the following lines after the user is retrieved from DAO: if ( user == null ) { return null; } Once this test case is added and the method fixed, our Facade is complete, and fully tested, without needing a DAO implementation. Such separation of functionalities in interfaces and implementations greatly facilitates the development of multi-layered applications, as each layer can be developed in parallel, by different teams. Using this approach, the whole business layer could be developed and unit tested without any depending on the persistence layer, which would free the business developers from database worries. It is still necessary to test everything together though, but that could achieved through integration tests – a good compromise is to write many small (and fast) unit tests that extensively exercise individual components, then a few (and slower) integration test that cover the most important scenarios. Similarly, one could test the persistence layer using mocks for the JPA interfaces. However, this approach is not recommended, as mocks only emulate API calls, and that would not be enough for a few reasons. First, the APIs is just part of JPA-based development – it is still necessary to annotate classes and provide configuration files. Second, even if the JPA part is correctly configured, there are still 3rd party involved: the JPA vendor (like Hibernate), the vendor's “driver” (like HibernateDialect implementations) for the database being used, not to the mention the database itself. In other words, there are many things that could go wrong (like vendor or drivers bugs, usage of table names that are illegal for a given database, transaction issues, etc...) at runtime that would not be detected by using mocks for testing. Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 So, for the persistence layer, it is important to test real access to the database, as we will see in the next section. WHO LET THE TRANSACTIONS OUT? In a JPA-based application, it is paramount to start and commit transactions, and the methods that use an EntityManager have 2 options: either they handle the transactions themselves, or they rely on the upper layers for this dirty job. Typically, the former option is more appropriate, because it gives the caller the option to invoke more than one DAO method in the same transaction. Looking at our examples, UserDaoJpaImpl follows this approach, as it does not deal with transaction management. But if you look at its caller, UserFacadeImpl, it does not handle transactions neither! So, in our application example, who is responsible for transaction management? The answer is the container. In our examples, we are just showing pieces of an application. But in a real project, these pieces would be assembled by a container, like a Java EE application server or Spring, and this container would be responsible for wrapping the Facade methods inside a JPA transaction and propagating it to the DAO object. And in our DAO test cases, we will play the role of the container and explicitly manage the transactions. 17.2 Testing JPA-based persistence layers When you use JPA (or any other ORM software) in your application, you are delegating the task of persisting objects to/from the database to an external tool. But in the end, the results are basically the same as if you wrote the persistence code yourself. So, in its essence, JPA testing is not much different than testing regular database access code, and hence most of the techniques explained at Chapter 16 apply here. There are a few differences and caveats that worth mentioning though, and we will cover them in the next sub-sections. But first, let's consider some aspects of JPA testing. 17.2.1 Aspects of JPA testing WHAT SHOULD BE TESTED? JPA programming could be divided in two parts: entities mapping and API calls. Initially, you need to define how your objects will be mapped to the database tables, typically through the use of Java annotations. Then you use an EntityManager object to send these objects to/from the database: you can create objects, delete them, fetching them using JPA queries, etc. Consequently, it is a good practice to test these two aspects separately. For each persistent object, you write a few test cases that verify they are correctly mapped (there is a lot of caveats on JPA mapping, particularly when dealing with collections). Then you write Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 separate unit tests for the persistence code itself (like DAO objects). We will see practical examples for both tests in the next sub-sections. THE EMBEDDED DATABASE ADVANTAGE As we mentioned on section 17.1.3, unit tests must be fast to run, and database access is typically slow. A way to improve the access time is to use an in-memory embedded database, but the drawback is that this database might not be totally compatible with the database the application really uses. But when you use JPA, database compatibility is not an issue, quite the opposite: the JPA vendor is responsible for SQL code generation5, and they typically support all but the rarest databases. As such, it is perfectly fine to use a fast embedded database (like HSQLDB or its “successor,” H2) for unit tests. Better yet, the project should be set in a way that the embedded database is used by default, but databases could be easily switched. That would take advantage of the best of both worlds: developers would use the fast mode, while official builds (like nightly and release builds) would switch to the production database (thus guaranteeing the application works in the real scenario). TO COMMIT OR NOT TO COMMIT, THAT IS THE QUESTION JPA operations should happen inside a transaction, which typically also translates to a vendor-specific session. A lot of JPA features and performance depends on the transaction/session life-cycle management: objects are cached, new SQL commands are issued on demand to fetch lazy relationships, update commands are flushed to the database, etc. On the other hand, committing a transaction is not only an expensive operation, but it also makes the database changes permanent. Because of that, there is a tendency for test cases to rollback the transaction on teardown, and many frameworks (like Spring Testing) follow this approach. So, what is the better approach, to commit or not? Well, again, there is no secret ingredient, and each approach has its advantages. We, in particular, prefer the commit option, because of the following aspects: ƒ if you are using an embedded database, speed is not an issue. You could even re- create the whole database before each test case. ƒ as we mentioned on section 17.8.5, test cases can do clean up on teardown, when necessary. And again, when using an embedded database, the cleanup is a cheap operation. ƒ if you rollback the transaction, the JPA vendor might not send the real SQL to the database (for instance, Hibernate only issue the SQL at commit or if session.flush() is 5 This is the ideal scenario; some applications might still need to manually issue a few SQL commands due to JPA bugs or performance requirements. These cases are rare, though, and they could be handled separately in the test cases. Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 explicitly called). As such, there might be cases where your test case passes, but when the code is executed in “real life”, if fails. ƒ in some situations, you want to explicitly test how the persistent objects will behave once outside a JPA transaction/session. For these reasons, the test cases in this chapter will manually handle the transactions life cycle. Now, without further ado, let's start use-testing it. 17.2.2 Preparing the infrastructure Okay, we lied: before our first test, we need some more ado! More specifically, we need to define the infrastructure classes that our real test case classes will extend (remember section 17.3 best practice: “Define a super-class for your database tests”). Figure 17.1: Class diagram of the testing infrastructure Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The infrastructure class hierarchy depicted on figure 17.1 is quite similar to the one used on chapter 16: the only difference is the root class, AbstractJpaTestCase, which is shown on listing 17.6. Listing 17.6 Root class of testing infrastructure, AbstractJpaTestCase [...] public abstract class AbstractJpaTestCase { private static EntityManagerFactory entityManagerFactory; protected static Connection connection; protected EntityManager em; @BeforeClass public static void setupDatabase() throws Exception { entityManagerFactory = Persistence.createEntityManagerFactory("chapter-17"); (1) connection = getConnection(entityManagerFactory); (2) } @AfterClass public static void closeDatabase() throws Exception { (3) if ( connection != null ) { connection.close(); connection = null; } if ( entityManagerFactory != null ) { entityManagerFactory.close(); } } @Before public void setEntityManager() { (4) em = entityManagerFactory.createEntityManager(); } @After public void closeEntityManager() { (5) em.close(); } public static Connection getConnection(Object object) throws Exception { Connection connection = null; if ( object instanceof EntityManagerFactoryImpl ) { (6) EntityManagerFactoryImpl impl = (EntityManagerFactoryImpl) object; SessionFactory sessionFactory = impl.getSessionFactory(); if ( sessionFactory instanceof SessionFactoryImpl ) { SessionFactoryImpl sfi = (SessionFactoryImpl) sessionFactory; Settings settings = sfi.getSettings(); ConnectionProvider provider = settings.getConnectionProvider(); connection = provider.getConnection(); } Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } return connection; } protected void beginTransaction() { (7) em.getTransaction().begin(); } protected void commitTransaction() { (8) em.getTransaction().commit(); } protected void commitTransaction(boolean clearContext) { (9) commitTransaction(); if ( clearContext ) { em.clear(); } } } (1) First step is to create an EntityManagerFactory, and as this is an expensive operation, it's done only once, using the @BeforeClass anotation. Notice this initialization does not contain any information about the JPA provider (Hibernate) or database (HSQLDB) used in the test case – that info is defined in the persistence.xml and hibernate.properties files (shown on listings 17.7 and 17.8, respectively). (2) Although when you use JPA you don't need to deal with low-level JDBC interfaces directly, DbUnit needs a database connection. You could define the JDBC settings for this connection in a properties file, but that would be redundant; it's better to extract the JDBC connection from JPA's entity manager. Also notice that such reuse is the reason why this chapter's AbstractJpaDbUnitTestCase (which is not shown here neither, but is basically the same as chapter's 16 AbstractDbUnitTestCase) extends AbstractJpaTestCase, and not vice- versa. If @BeforeClass methods didn't have to be static, such reverse of fortune would not be necessary: AbstractJpaDbUnitTestCase could define a protected getConnection() method, which in turn would be overriden by AbstractJpaTestCase. (3) Don't forget to close at @AfterClass what you opened at @BeforeClass! (4), (5) The EntityManager – which is the object effectively used by the test cases – is created (and closed) before each test method. This is the way JPA is supposed to be used, and this object creation is cheap.(6) Unfortunately, there is no standard way to extract the JDBC Connection from the JPA EntityManagerFactory, so it is necessary to use proprietary APIs from the chosen JPA vendors. (7), (8) As our test cases will manually manage transactions, it is convenient to create helper methods for this task. (9) Similarly, some test cases will test how objects behaves outside the JPA context, so we defined an overloaded commitTransaction() which also clears the context. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 The JPA configuration is quite simple: persistence.xml (listing 17.7) only defines a persistence-unit name, and the vendor-specific configuration is defined on hibernate.properties (listing 17.8). Such split is a good practice, because if the test cases must change any configuration (as we will see later on), we could create a new hibernate.properties file in the test directories, without interfering with the artifacts used by the production code. Notice also that we set the property hibernate.hbm2ddl.auto to update, so the test cases do not need to create the database tables (Hibernate will automatically create or update the database schema as needed). Listing 17.7 JPA configuration (persistence.xml) Listing 17.8 Hibernate-specific configuration (hibernate.properties) hibernate.hbm2ddl.auto=update hibernate.show_sql=false hibernate.dialect=org.hibernate.dialect.HSQLDialect hibernate.connection.driver_class=org.hsqldb.jdbcDriver hibernate.connection.url=jdbc:hsqldb:mem:my-project-test;shutdown=true As a final note, there are some frameworks out there – like Spring and Unitils (which will be covered on next chapter) – that already provides a similar setup. So, even though this infrastructure is based in real projects (it was not just created for the book samples), provides flexibility (and can adapt it to your project needs), and is simple enough (just a few classes), you might prefer – or it might be more suitable for your project – to use such frameworks instead. Now that the infrastructure is set, let's move to our tests, starting with the JPA entities mapping tests. 17.3 JPA Entities mapping testing The first step on JPA development is mapping your object to tables. Although JPA does a good job of providing default values for most mappings, it is still necessary to tune it up, typically through the use of Java annotations. And as you annotate the persistent classes, Licensed to Alison Tyler Download at Boykma.Com 14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 you want to be sure they are correctly mapped, so you write test cases that exercise the mapping alone (i.e., without worrying on how your final code is going to call the JPA API). You might be wondering: are these tests really necessary? What could be wrong? Unfortunately, despite the fact that JPA is a powerful and helpful tool, many things could be wrong in the mappings, and the sooner you figure it out, the better. For instance: ƒ some table or column name (like user, role, or group) might be a reserved word in the chosen database ƒ if you don't set the relationship annotations (like @OneToMany) correctly, JPA will create many weird mappings, sometimes even unnecessary tables ƒ when dependent objects must be persisted automatically, it is important to verify the proper cascade options have been set Not to mention the mother of all reasons you write unit tests: you need to somehow verify that JPA is persisting/restoring your objects! Without the entity mapping tests, you would have to either rely on your IDE, or manually test it using the main() method of some hacky class. So, in order to avoid these issues, it is a good practice to write a couple (at least 2, one for loading, another for saving) of unit tests for each primary persistent entity in the system. You don't need to write tests for all of them, though – some entities are too simple, or can be indirectly tested (like the Telephone entity in our examples). Also, as the load and save tests are orthogonal, they should use the same dataset whenever possible, to facilitate maintenance; listing 17.9 shows the dataset that will be used in the user entity mapping tests (listing 17.10). Listing 17.9 Dataset file for User and Telephone tests, user-with-telephone.xml Notice that the ids are dynamically defined using EL expressions, so the test cases succeed regardless of the order they are executed (see sections 17.5.1 and 17.7.3 for more details). Listing 17.10 Entity mapping unit tests for User and Telephone classes Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 [...] public class EntitiesMappingTest extends AbstractJpaDbUnitELTemplateTestCase { @Test @DataSets(setUpDataSet="/user-with-telephone.xml") public void testLoadUserWithTelephone() { beginTransaction(); (1) User user = em.find(User.class, id); (2) commitTransaction(); (3) assertUserWithTelephone(user); (4) id++; phoneId+=2; (5) } @Test @DataSets(assertDataSet="/user-with-telephone.xml") public void testSaveUserWithTelephone() throws Exception { (6) User user = newUserWithTelephone(); beginTransaction(); em.persist(user); commitTransaction(); } (1), (3) As explained earlier, we opted for manual transaction control, so the test case explicitly starts and finishes the transaction. (2) Because we are testing the entity mappings – and not the real persistence layer code like DAO implementations, we deal with the persistence manager directly. (4) Once the object is read from the database, we assert it has the expected values, using our old friend EntitiesHelper(5) id and phoneId (which are defined in the super-class and has the initial value of 1) are updated to reflect the objects that were loaded from the database. If they are not updated, the next test case could fail (if it saves objects, which is the case in our example). (6) The test case for saving an entity is pretty much orthogonal to the load entity test case; nothing new here. Although these tests run fine and pass, the way the ids (id and phoneIds) are handled is far from elegant, and present many flaws. For instance, if a test case fails, the ids are not updated, and then other tests running after it would fail as well. Sure, a try/finally block would fix this particular issue, but that would make the test even uglier and more verbose. Another problem is that testSaveUserWithTelephone() does not update the ids (because its dataset assertion would fail), so a third test case would probably fail. What should we do to solve these id issues then? Well, don't throw the book away (yet) – next sub-section will present a solution for this problem. Licensed to Alison Tyler Download at Boykma.Com 16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 17.2.1 Integrating test cases with JPA id generators In JPA, every persistent entity needs an id, which is the equivalent of a primary key. When a new entity is persisted, the JPA engine must set its id, and how its value is determined depends on the mapping. For instance, our User class has the following mapping: @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; The @Id annotation indicates this attribute represents the entity's primary key, and the @GenerateValue defines how its value is obtained when the entity is persisted. AUTO in this case means you left the decision to the JPA vendor, which will pick the best strategies depending on what the target database supports. Another values are IDENTITY (for databases that support auto-generated primary keys), SEQUENCE (uses database sequences, where supported), or TABLES (uses one or more table only for the purpose of generating primary keys). A 5th option would be omitting the @GeneratedValue, which means the user (and not the JPA engine) is responsible for setting the ids. In most cases, AUTO is the best choice; in fact, it's the default option for the @GeneratedValue annotation. Back to our test cases, the dataset files contains references to the id columns (both as primary key in the users table, and foreign key on telephones), and because we are using the same dataset for both load and save, the id values must be defined dynamically at the time the test is run. If we used distinct datasets, the ids would not matter on the load test (because JPA would not be persisting entities, only loading them), and for the save tests, we could ignore them. The problem with this option is that it makes it harder to write and maintain the test cases – it would require two datasets, and also changes in the DataSetsTemplateRunner.assertDataSet() method in order to ignore the id columns. As our goal is always to facilitate long-term maintenance, we opted for using the same dataset, and hence we need a solution for the id synchronization problem. Listing 17.10 tried to solve the problem the simplest way, by letting the test case update the ids, but that approach had many issues. A better approach is to integrate the dataset ids maintenance with the JPA's entity ids generation, and there are two ways to achieve such integration: taking control of id generation, or being notified of the generated ids. Generating ids for persistent objects is not a simple task, as there are many complex aspects to be taken in account, like concurrent id generation in different transactions. Besides, when you use this approach, your test cases will not be reflecting the real application scenario (and hence they could hide potential problems). For these reasons, we will present just the second approach, being notified of the generated ids. Using pure JPA, it is possible to define listener for entity life-cycle events (like object creation, update, and deletion). But this approach does not work well in our case, because these events do not provide a clear way to obtain the id of the saved objects. A better solution is to use vendor-specific extensions. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Hibernate provide its own API for life-cycle events, with listeners for many pre and post events. In particular, it provides a PostInsertEventListener interface with a onPostInsert(PostInsertEvent event) method, and the event itself contains a reference to both the entity inserted, and the generated id. The PostInsertEventListener API solves part of our problem: our test cases now have the ability to be notified of the ids generated for each object. Good, but now what? Well, the answer relies on our good old friend EL (Expression Language, first introduced on Chapter 16, section 16.7.3). So far, we have been using just simple variable resolution (like ${id}) on our datasets. But EL also supports function resolution, so we could have a function that returns an id for a given class, and then use a PostInsertEventListener to set the values returned by the function. Let's start with the new dataset, on listing 17.11. Listing 17.11 New version of user-with-telephone.xml dataset, using EL functions Instead of using hardcoded ids for each class (like ${id} and ${phoneId}), this new version uses a generic ${db:id('ClassName')} function, which is much more flexible. The db:id() function then returns the respective id for a given class, which is either the value defined by the Hibernate listener, or 1 by default (which means no entity has been generated by JPA for that class. In fact, in this case the value does not matter, but 1 makes it easier to debug in case of errors). To add support for this function, first we need to change ELContextImpl to support function mapping, as shown in Listing 17.12. Listing 17.12 Changes on ELContextImpl to support the id funtion [...] public final class ELContextImpl extends ELContext { [...] private FunctionMapper functionMapper = new ELFunctionMapperImpl(); [...] @Override public FunctionMapper getFunctionMapper() { return functionMapper; } [...] } And the id function itself is defined at ELFunctionMapperImpl, shown in listing 17.13. Licensed to Alison Tyler Download at Boykma.Com 18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Listing 17.13 Custom EL functions (ELFunctionMapperImpl) [...] public class ELFunctionMapperImpl extends FunctionMapper { private static final Map METHODS = new HashMap(); private static final Map IDS = new HashMap(); private static final int INITIAL_ID = 1; (1) static { (2) for ( Method method : ELFunctionMapperImpl.class.getDeclaredMethods() ) { int modifiers = method.getModifiers(); String name = method.getName(); if ( Modifier.isStatic(modifiers) && name.startsWith("db_")) { METHODS.put(name.replace('_', ':'), method); } } } @Override public Method resolveFunction(String prefix, String localName) { (3) return METHODS.get(prefix+":"+localName); } public static long db_id(String className) { (4) long id; if ( IDS.containsKey(className) ) { id = IDS.get(className); } else { id = INITIAL_ID; } return id; } public static void setId(String className, long newId) { (5) if ( ! IDS.containsKey(className) ) { IDS.put(className, newId); } } public static void resetIds() { (6) IDS.clear(); } public static long getId(Class clazz) { (7) return db_id(clazz.getSimpleName()); } } (1) Defines the value returned by the id function when no id was set for a given class (2) Initializes the map of static methods that define EL functions. To facilitate the mapping, these methods starts with db_. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 (3) resolveFunction() is part of the EL API, and is used to map an EL function (like db:id) to a Java method (db_id(), in this case). It simply returns the method defined at (2). (4) This is the id function properly speaking, which returns either the initial id or the value present in the ids map. (5) Our custom Hibernate listener will call this method every time a new entity is generated, but only the first value will be saved in the ids map; the other values will be calculated by the dataset , using this base id when necessary (like ${db:id('Telephone')+1}, if the dataset had 2 telephone rows). Notice that this mechanism assumes ids are generated in sequence, which is true most of the time, but not always. If that does not apply in your case (for instance, if you are using a generation strategy based on UUIDs), you will need a different (and more complex) approach to represent multiple ids, like using a sequence parameter in the id function: ${db:id('Telephone',1)} would return the first generated id, ${db:id(´Telephone',2)} would return the second, and so on; then instead of keeping a single id in the map, you would keep a List of the generated ids for each class. (6) The id map must be reset before each method, so load methods always start with id=1 (or whatever value defined at (1)) (7) Helper method used by test cases that need to know the base id for a given class. Next step is the Hibernate integration; first we need a listener, as defined in listing 17.14. Listing 17.14 Custom Hibernate event listener (ELPostInsertEventListener) import org.hibernate.event.PostInsertEvent; import org.hibernate.event.PostInsertEventListener; public class ELPostInsertEventListener implements PostInsertEventListener { public void onPostInsert(PostInsertEvent event) { String className = event.getEntity().getClass().getSimpleName(); (1) Long id = (Long) event.getId(); ELFunctionMapperImpl.setId(className, id); } } In order to simplify the datasets, only the simple name (1) of the class is used, not the whole fully-qualified name (otherwise, the datasets would need something like ${db:id('com.manning.jia.chapter17.model.User')}). If your project has more than one class with the same name (but in different packages), then you would need some work-around here, but keeping the simple class name should be enough most of the times (and having multiple classes with the same name is not a good practice anyways). Then we change hibernate.properties to add the custom listener, by including the following property: Licensed to Alison Tyler Download at Boykma.Com 20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 hibernate.ejb.event.post-insert= org.hibernate.ejb.event.EJB3PostInsertEventListener, com.manning.jia.chapter17.hibernate.ELPostInsertEventListener Notice that when you set the listeners for a life-cycle event in the properties, you are defining all listeners, not only adding new ones. As such, it is recommended to keep the default Hibernate listeners. But Hibernate does not clearly documents what these listeners are, so you need to figure that out by yourself, and one of doing that is changing the setEntityManager() as shown by listing 17.15. Listing 17.15 Changes on setEntityManager() to show Hibernate listeners [...] public abstract class AbstractJpaTestCase { [...] @Before public void setEntityManager() { em = entityManagerFactory.createEntityManager(); // change if statement below to true to figure out the Hibernate listeners if ( false ) { Object delegate = em.getDelegate(); SessionImpl session = (SessionImpl) delegate; EventListeners listeners = session.getListeners(); PostInsertEventListener[] postInsertListeners = listeners.getPostInsertEventListeners(); for ( PostInsertEventListener listener : postInsertListeners ) { System.out.println("Listener: " + listener.getClass().getName() ); } } } } Finally, it's necessary to change the test case classes, first the infrastructure classes (as shown by listing 17.16), then the test methods themselves (listing 17.17). Listing 17.16 Changes on AbstractJpaDbUnitELTemplateTestCase [...] public abstract class AbstractJpaDbUnitELTemplateTestCaseJUnit extends AbstractJpaDbUnitTestCase { (1) @Override protected void invokeTestMethod(Method method, RunNotifier notifier) { context = new ELContextImpl(); ELFunctionMapperImpl.resetIds(); (2) setupDataSet(method); super.invokeTestMethod(method, notifier); assertDataSet(method); Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 21 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } } (1) Although not shown here, super class AbstractJpaDbUnitTestCase also changed: it does not need to keep track of the ids anymore, so id and phoneId were removed. (2) This method now is also simpler than before: instead of binding the id variable to the EL context before and after invoking the test, it simply resets the ids map before each test is run. Listing 17.17 New version of EntitiesMappingTest using JPA ids integration [...] public class EntitiesMappingTest extends AbstractJpaDbUnitELTemplateTestCase { @Test @DataSets(setUpDataSet="/user-with-telephone.xml") public void testLoadUserWithTelephone() { beginTransaction(); long id = ELFunctionMapperImpl.getId(User.class); (1) User user = em.find(User.class, id); commitTransaction(); assertUser(user,true); } @Test @DataSets(assertDataSet="/user-with-telephone.xml") public void testSaveUserWithTelephone() throws Exception { (2) User user = newUser(true); beginTransaction(); em.persist(user); commitTransaction(); } } Because ids are not tracked by a super class anymore, it is necessary to inquire ELFunctionMapperImpl (1) to get the id on testLoadUserWithTelephone(). Test case testSaveUserWithTelephone() (2) , on the other hand, was not changed, as it does not use the ids directly (only in the dataset). 17.4 Testing JPA-based DAOs Once you are assured the persistence entities are correctly mapped, it is time to test the application code that effectively uses JPA, like DAOs. And the test cases for JPA-based DAOs are very similar to the entity mapping tests we saw in the previous section; the main Licensed to Alison Tyler Download at Boykma.Com 22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 difference (besides the fact that you use DAO code instead of direct JPA calls) is that you have to cover more scenarios, paying attention to some tricky issues. Let's start with the simplest cases, the same cases for getUserById() and addUser() we implemented on Chapter 16 (where the DAOs were implemented using pure JDBC), plus a test case for testRemoveUser(). The test cases are shown in listing 17.18, while the initial DAO implementation was listed at 17.4 Listing 17.18 Initial version of UserDaoJpaImplTest [...] public class UserDaoJpaImplTest extends AbstractJpaDbUnitELTemplateTestCase { UserDaoJpaImpl dao; @Before public void prepareDao() { dao = new UserDaoJpaImpl(); dao.setEntityManager(em); } @Test @DataSets(setUpDataSet="/user.xml") public void testGetUserById() throws Exception { beginTransaction(); long id = ELFunctionMapperImpl.getId(User.class); User user = dao.getUserById(id); commitTransaction(); assertUser(user); } @Test @DataSets(assertDataSet="/user.xml") public void testAddUser() throws Exception { beginTransaction(); User user = newUser(); dao.addUser(user); commitTransaction(); long id = user.getId(); assertTrue(id>0); } @Test @DataSets(setUpDataSet="/user.xml",assertDataSet="/empty.xml") public void testDeleteUser() throws Exception { beginTransaction(); long id = ELFunctionMapperImpl.getId(User.class); dao.deleteUser(id); commitTransaction(); } } Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 23 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Compare this test case with chapter 16's latest version (listing 16.22) of the equivalent test – they are almost the same, the only differences being the pre-test method prepareDao() (which instantiates a DAO object and set its EntityManager), the local variable representing the user's id (because of the Hibernate id generation integration we discussed in the previous section), and the transaction management calls. But now the User object could also have a list of telephones; it's necessary then to add analogous test cases to handle this scenario, as shown in listing 17.19. Notice that if the User->Telephone relationship was mandatory (and not optional) the new tests would replace the old ones (instead of being added to the test class). Listing 17.19 New test cases on UserDaoJpaImplTest to handle user with telephone [...] public class UserDaoJpaImplTest extends AbstractJpaDbUnitELTemplateTestCase { [...] @Test @DataSets(setUpDataSet="/user-with-telephone.xml") public void testGetUserByIdWithTelephone() throws Exception { beginTransaction(); long id = ELFunctionMapperImpl.getId(User.class); User user = dao.getUserById(id); commitTransaction(); assertUserWithTelephone(user); } @Test @DataSets(assertDataSet="/user-with-telephone.xml") public void testAddUserWithTelephone() throws Exception { beginTransaction(); User user = newUserWithTelephone(); dao.addUser(user); commitTransaction(); long id = user.getId(); assertTrue(id>0); } @Test @DataSets(setUpDataSet="/user-with- telephone.xml",assertDataSet="/empty.xml") public void testDeleteUserWithTelephone() throws Exception { beginTransaction(); long id = ELFunctionMapperImpl.getId(User.class); dao.deleteUser(id); commitTransaction(); } } Running these tests would review the first bug in the DAO implementation, as testDeleteUserWithTelephone() fails with a constraint violation exception: although the user row was deleted, its telephone wasn't. The reason? Cascade-deletes are only taken in Licensed to Alison Tyler Download at Boykma.Com 24 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 account when the EntityManager's remove() method is called, and our DAO used a JPA query to delete the user. The fix is shown on listing 17.21. Regardless of this delete issue, the tests presented so far only cover the “easy” scenarios: saving, loading and deleting a simple user, with or without a telephone. It's a good start, but not enough: we need to test negative cases as well. For the addUser() method, the only negative case is receiving a null reference – it is always a good practice to check for method arguments and throw a IllegalAccessArgumentExeption if they do not comply. The getUserById() method have at least 2 negative cases: handling an id that does not exist in the database, and loading from an empty database. In our sample application, a null reference should be returned in these cases (but another valid option would be to throw an exception). Negative cases for removeUser() would be the same as getUserById(), and in our example they do not need to be tested because nothing happens when the user does not exist (if an exception should be thrown, we should exercise these scenarios). Listing 17.20 adds these new tests. Listing 17.20 New test cases for negative scenarios [...] public class UserDaoJpaImplTest extends AbstractJpaDbUnitELTemplateTestCase { [...] @Test(expected=IllegalArgumentException.class) public void testAddNullUser() throws Exception { dao.addUser(null); } @Test public void testGetUserByIdOnNullDatabase() throws Exception { getUserReturnsNullTest(0); } @Test @DataSets(setUpDataSet="/user.xml") public void testGetUserByIdUnknownId() throws Exception { getUserReturnsNullTest(666); } private void getUserReturnsNullTest(int deltaId) { beginTransaction(); long id = ELFunctionMapperImpl.getId(User.class)+deltaId; User user = dao.getUserById(id); commitTransaction(true); assertNull(user); } } Notice that the workflow for getUserById() negative cases is the same for both scenarios, so we used a private helper method (getUserReturnsNullTest()) on both. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 25 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Now we have a fully-tested DAO implementation, right? Well, not really. Once you put this code on production (or handle it to the QA team for functional testing), a few bugs are going to happen. To start with, the first time someone calls UserFacade.getUserById() (which in turns call the DAO method; see listing 17.4) on a user that has at least 1 telephone, and try to call user.getTelephones(), the following exception is going to happen: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.manning.jia.chapter17.model.User.telephones, no session or session was closed at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializati onException(AbstractPersistentCollection.java:358) at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializati onExceptionIfNotConnected(AbstractPersistentCollection.java:350) at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPers istentCollection.java:97) at org.hibernate.collection.PersistentBag.size(PersistentBag.java:225) at com.manning.jia.chapter17.model.EntitiesHelper.assertUserWithTelephones(Ent itiesHelper.java:39) at com.manning.jia.chapter17.model.EntitiesHelper.assertUserWithTelephone(Enti tiesHelper.java:29) at com.manning.jia.chapter17.dao.UserDaoJpaImplTest.testGetUserByIdWithTelepho ne(UserDaoJpaImplTest.java:62) When that happen, you, the developer, are going to curse Hibernate, JPA, ORM, and (with a higher intensity) this book authors: you followed our advices and created unit tests for both the facade and DAO classes in isolation, but once one called the other in real life, a JPA exception has arisen! Unfortunately, situations like these are common – just because you wrote test cases, it does not mean your code is bug free. But thinking on the bright side, because you have a test case, it is much easier to reproduce and fix the issue. In this case, the exception is caused because the User->Telephone relationship is defined as lazy, which means the telephones were not fetched by the JPA queries and are loaded on demanded when getTelephones() is called. But if that method is called outside the JPA context, an exception is thrown. So, to simulate the problem in the test case, we change the testGetUserByIdWithTelephone() to clear the context after committing the transaction: @Test @DataSets(setUpDataSet="/user-with-telephone.xml") public void testGetUserByIdWithTelephone() throws Exception { beginTransaction(); long id = ELFunctionMapperImpl.getId(User.class); User user = dao.getUserById(id); Licensed to Alison Tyler Download at Boykma.Com 26 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 commitTransaction(true); assertUserWithTelephone(user); } Running the test case after this change would cause it to fail due to the same exception our poor user faced in the real application. The solution then is to fix the JPA query to eagerly fetch the telephones, as shown below: public User getUserById(long id) { String jql = "select user from User user left join fetch user.telephones where id = ?"; Query query = entityManager.createQuery(jql); query.setParameter(1, id); return (User) query.getSingleResult(); } Such change makes the testGetUserByIdWithTelephone() to pass, but now testGetUserByIdOnNullDatabase() and testGetUserByIdUnknownId() fail: javax.persistence.NoResultException: No entity found for query at org.hibernate.ejb.QueryImpl.getSingleResult(QueryImpl.java:83) at com.manning.jia.chapter17.dao.UserDaoJpaImpl.getUserById(UserDaoJpaImpl.jav a:45) Let's change the method again, using getResultList() instead of getSingleResult() this time: public User getUserById(long id) { String jql = "select user from User user left join fetch user.telephones where id = ?"; Query query = entityManager.createQuery(jql); query.setParameter(1, id); @SuppressWarnings("unchecked") List users = query.getResultList(); // sanity check assert users.size() <= 1 : "returned " + users.size() + " users"; return users.isEmpty() ? null : (User) users.get(0); } Although the change itself is simple, this issue illustrates the importance of negative tests – if we didn't have these tests, the lazy initialization fix would have introduced a regression bug! Once these two fixes are in place, the application will run fine for a while, until an user has two or more telephones, which would cause the following exception: java.lang.AssertionError: returned 2 users at com.manning.jia.chapter17.dao.UserDaoJpaImpl.getUserById(UserDaoJpaImpl.jav a:49) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 27 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 at com.manning.jia.chapter17.dao.UserDaoJpaImplTest.testGetUserByIdWithTelepho nes(UserDaoJpaImplTest.java:114) What is happening now is that a query that was supposed to return just one user, is returning two – very weird! But again, because the testing infrastructure is already in place, it is easy to reproduce the problem. All you have to do is add a new testGetUserByIdWithTelephones() method6: @Test @DataSets(setUpDataSet="/user-with-telephones.xml") public void testGetUserByIdWithTelephones() throws Exception { beginTransaction(); long id = ELFunctionMapperImpl.getId(User.class); User user = dao.getUserById(id); commitTransaction(true); assertUserWithTelephones(user); } What about the issue itself? The solution is to use the distinct keyword in the query, as shown below: jql = "select distinct(user) from User user left join fetch user.telephones where id = ?"; Notice that having both a testGetUserByIdWithTelephone() and a testGetUserByIdWithTelephones() methods is redundant and would make the test cases harder to maintain; once you added testGetUserByIdWithTelephones(), you can safely remove testGetUserByIdWithTelephone() and its companion helpers (newUserWithTelephone() and assertUserWithTelephone(), which would be replaced by newUserWithTelephones() and assertUserWithTelephones() respectively). Listing 17.21 shows the final version of UserDaoJpaImpl, with all issues fixed. Listing 17.21 Final (and improved) version of UserDaoJpaImpl [...] public class UserDaoJpaImpl implements UserDao { public void addUser(User user) { if ( user == null ) { throw new IllegalArgumentException("user cannot be null"); } entityManager.persist(user); } 6 You also need a new assertUserWithTelephones() method on EntitiesHelper, and a new user-with- telephones.xml dataset, but as they are very similar to the existing method and dataset, they are not shown here. Licensed to Alison Tyler Download at Boykma.Com 28 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 public User getUserById(long id) { String jql = "select distinct(user) from User user left join fetch user.telephones where id = ?"; Query query = entityManager.createQuery(jql); query.setParameter(1, id); @SuppressWarnings("unchecked") List users = query.getResultList(); assert users.size() <= 1 : "returned " + users.size() + " users"; // sanity check return users.isEmpty() ? null : (User) users.get(0); } public void deleteUser(long id) { User user = entityManager.find(User.class, id); entityManager.remove(user); } } 17.5 Testing foreign key names When the first version of testDeleteUserWithTelephone() failed, the error message was: Integrity constraint violation FKC50C70C5B99FE3B2 table: PHONES in statement [delete from users where id=?] Because we are just starting development, it's easy to realize that the violated constraint is the telephones foreign key on the users table. But imagine down on the road you face a bug report with a similar problem – would you know what constraint FKC50C70C5B99FE3B2 refers to? By default, Hibernate does not generate useful names for constraints, so you get gibberish like FKC50C70C5B99FE3B2. Fortunately, you can use the @ForeignKey annotation to explicitly define the FK names, as we did in the User class (see listing 17.1). But as more entities are added to the application, it's very easy for a developer to forget to use this annotation, and such slip could stay undetected for months, until the application is hit by a foreign key violation bug (whose violated constraint would be a mystery). As you can imagine, there is a solution for this problem: writing a test case that verifies all generated foreign keys have meaningful names. And the skeleton for this test is relatively simple: you ask Hibernate to generate the schema for your application as SQL statements, then check for invalid foreign key names. The tricky part is how to generate the SQL code in a String, using only the project JPA settings; we present a solution on listing 17.2, and although it is not that pretty, it gets the work done7. Anyway, let's take a look on the test case by itself first, on listing 17.22. 7 We could do a further diligence and present a more elegant solution, as this is a book (and hence educational). But when you are writing unit tests in real life, many times you have to be pragmatic and use a quick-and-dirty solution for a given problem, so we decided to take this approach here as well. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 29 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Listing 17.22 Test case for Hibernate-generate database schema (SchemaTest.java) [...] public class SchemaTest extends AbstractJpaTestCase { (1) @Test public void testForeignKeysNames() { (2) SqlHandler handler = new SqlHandler() { public void handle(String sql) { assertForeignKeysDoesNotHaveFunnyNames(sql); } }; analyzeSchema(handler); } private static final String FK_LINE_REGEXP = "alter table (.*) add constraint (.*) foreign key .*"; (3) private static final Pattern FK_LINE_PATTERN = Pattern.compile(FK_LINE_REGEXP); private static final Matcher FK_LINE_MATCHER = FK_LINE_PATTERN.matcher(""); private static final String FK_REGEXP = "fk_[a-z]+_[a-z]+$"; (4) private static final Pattern FK_PATTERN = Pattern.compile(FK_REGEXP); private static final Matcher FK_MATCHER = FK_PATTERN.matcher(""); private void assertForeignKeysDoesNotHaveFunnyNames(String sql) { String[] lines = sql.split("\n"); (5) StringBuilder buffer = new StringBuilder(); for ( String line : lines ) { FK_LINE_MATCHER.reset(line); if( FK_LINE_MATCHER.find() ) { String table = FK_LINE_MATCHER.group(1); String fk = FK_LINE_MATCHER.group(2); if ( ! isValidFk(fk) ) { buffer.append(table).append("(").append(fk).append(") "); (6) } } } String violations = buffer.toString(); if ( violations.length() > 0 ) { (7) fail( "One or more tables have weird FK names: " + violations ); } } private boolean isValidFk(String fk) { (8) FK_MATCHER.reset(fk); return FK_MATCHER.find(); } } (1) Although we are only testing foreign key names, we could test other aspects of the generated schema, so we created a generic SchemaTest class. Licensed to Alison Tyler Download at Boykma.Com 30 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 (2) The test case method by itself is quite simple: it calls the super-class analyzeSchema() method (described below), passing as parameter an inner class that will do the job. (3) Regular expression used to identify if a line defines a foreign key. (4) Regular expression to extract the foreign key name (5) The SQL representing the schema generation is parsed in 2 levels: first split() is used to break the lines, then a regular expression (3) checks for lines that defined a foreign key. We could use only one regular expression to handle both, but the end result would be more complex. (6), (7) Once we find an invalid foreign key, we buffer it and fail later, which the failure message containing all violations. Although it would be simpler to fail right away, this approach is more helpful, as it would require just one run to spot all invalid names. (8) The meaning of a valid foreign key is up to the project. In our case, we defined it has to start with FK_ and have 2 names (which should represent the tables involved in the relationship) separated by an underscore (_). This test case relies on a new infra-structure method, analyzeSchema(), which is defined at AbstractJpaTestCase; listing 17.23 shows all changes required to implement it. Listing 17.23 Changes on AbstractJpaTestCase to support analyzeSchema() [...] public abstract class AbstractJpaTestCase { [...] protected void analyzeSchema( SqlHandler handler ) { (1) ConfigurationCreator cfgCreator = new ConfigurationCreator(); Configuration cfg = cfgCreator.createConfiguration(); SchemaExport export = new SchemaExport(cfg); (2) ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); PrintStream oldOut = System.out; PrintStream newOut = new PrintStream(outputStream); System.setOut(newOut); try { export.create(true, true); (3) String sql = outputStream.toString(); handler.handle(sql); } finally { System.setOut(oldOut); newOut.close(); } } protected interface SqlHandler { void handle( String sql ); } private class ConfigurationCreator extends JPAConfigurationTask { (4) Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 31 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 @Override protected Configuration createConfiguration() { return super.createConfiguration(); } } (1) analyzeSchema() is based in the Template Method Design Pattern (see section 17.7.1): it knows how to create a Java String containing the database schema, but does know what to do with it, so it passes this String to a SqlHandler, which was passed as parameter. (2) SchemaExport is the Hibernate class (part of the Hibernate Tools project) used to export the schema. It requires a Hibernate Properties object, which unfortunately cannot be obtained from EntityManager or EntityManagerFactory, the objects our test case has references to. To overcome this limitation, we needed our first “hack”: using an Ant task (JPAConfigurationTask), which is also part of Hibernate Tools. (3) This is where the schema is effectively generated. The problem is, SchemaExport either exports it to the system output, or to a file. Ideally, it should allow the schema to be exported to a Writer or OutputStream object, so we could pass a StringWriter as a parameter to this method. As this is not the case, we had to use a second hack: replace the System.out while this method is executed, then restoring it afterwards. Yes, we know this is ugly, but we warned you... (4) A final hack: because JPAConfigurationTask's createConfiguration() method is protected, our test case would not have access to it, so we created a sub-class and override that method, making it accessible to classes in the same package. 17.6 Summary Using ORM tools (like Hibernate and JPA) greatly simplifies the development of database access code in Java applications. But regardless of how great the technology is or how much it automates the work, it is still necessary to write test cases for code that uses it. A common practice among Java EE applications is to use Facades and DAOs in the business and persistence layers (respectively). In this chapter, we show how to test these layers in isolation, using mocks as DAO implementation. We also showed how to leverage DbUnit and advanced techniques to effectively test the JPA-based persistence layer, first testing the entity mappings, then the DAOs properly speaking. We also demonstrated how to use JPA-generated ids in your DbUnit datasets. And although JPA testing is an extension of database testing (which was covered in Chapter 16), it has its caveats, like lazy initialization exceptions, duplicated objects returned in queries, cascade deletes, and generation of weird constraint names – the examples in this chapter demonstrated how to deal with such issues. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/27/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 18 JUnit on Steroids This chapter covers tools that provide ƒ Transparent mocks utilization ƒ Out-of-the-box DbUnit integration ƒ Extended assertion capabilities ƒ Bypassing encapsulation through reflection Licensed to Alison Tyler Download at Boykma.Com 2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/27/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Make everything as simple as possible, but not simpler. —Albert Einstein Throughout this final part of the book, we analyzed tools focused on testing specific technologies, such as AJAX applications and database access. In this final chapter, we will evaluate tools that do not fit a particular niche, but rather facilitate overall test development by providing helper methods and plumbing infrastructure. By using such tools, the developer can focus on the real functionality being tested, which can greatly improve productivity. Functionally speaking, we will analyze tools that automate mock usage, provide a wider number of assertion methods, use reflection to access private members of tested objects, and make DbUnit usage easier. And as these tools provide generic testing support, many of these features will be provided by more than one tool. Such feature overlapping might sound redundant (the classic NIY1 syndrome), but this diversity allows you to choose the most appropriate tool for your needs. 18.1 Introduction Let's take a brief overview of the tools analyzed and how to run this chapter’s examples. All of these tools are open-source projects; some are very active and mature, others have been stalled in development for quite a while. 18.1.1 Tools overview Below is a list of all tools analyzed in this chapter. UNITILS Unitils (http://unitils.org) is a library that provides plumbing infrastructure for many types of testing needs, like database access, mocks usage, and Spring integration. Although it is a relative new framework (created in the end of 2006), it is a mature project, and it has been designed from the ground up with modern testing concepts in mind. It is framework-agnostic (works with both JUnit 3.x, JUnit 4.x, or TestNG), its features are offered as modules (which provides room for extensibility), and it makes heavy use of Java annotations. JUNIT-ADDONS Created in 2002, JUnit-addons is the oldest tool analyzed in this chapter. As the web site (http://sourceforge.net/projects/junit-addons) states, “JUnit-addons is a collection of helper classes for JUnit.” Sounds quite simple, and indeed it is. But despite its simplicity and the fact that its development has pretty much stalled (last version was released in 2003!), it is 1 Not Invented Here Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/27/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 still a useful tool, especially for projects based on JUnit 3.x, as many of the features it provides are already available on JUnit 4.x. FEST FEST (http://fest.easytesting.org) stands for Fixtures for Easy Software Testing and, as the name implies, it is another library providing useful testing infrastructure. Similarly to Unitils, FEST also works with JUnit or TestNG2, and is based on modules. Although most of the modules provides functionalities already offered by other tools, they do it in a different way, which might sound more natural for developers used to the JMock style of declarations, more specifically, to the “Fluent Interface” style, as defined in http://martinfowler.com/bliki/FluentInterface.html. But regardless of these overlapping features, it offers a module (FEST Swing) that is quite unique, as it provides support for GUI testing. MYCILA TESTING FRAMEWORK Mycila (http://code.google.com/p/mycila) is an umbrella for many sub-projects, each one focused in particular needs, such as testing. In other words, it is the latest offspring of this new breed of general-purpose testing libraries (which also includes Unitils and FEST), and at the time this chapter was written it was still in its infancy. Although all of its features we analyze in this chapter are provided by other tools, this project also offer unique features, like a module to test Guice3-based applications; if it fulfills its ambitious goal of providing “Powerful projects for everyday needs !”, it could be another valuable asset in the toolbox. 18.1.2 Running the examples The test cases for this sample application are available in two 'flavors', Maven and Ant. To run the tests on Maven, run 'mvn clean test'. Similarly, to run them using Ant, just type 'ant clean test'. Some of these tools might require esoteric dependencies at runtime (for instance, Unitils database support uses Spring for transaction management), but all such dependencies are commented in the build.xml file. The application is also available as 2 Eclipse projects, one with the required libraries and another with the project itself. Now that all introductions were made, let's get down to business. 18.2 Transparent mock usage When you use mocks in your test cases4, the test method is typically structured as described below: 2 In fact, the project was initialized called TestNG-Abbot 3 Guice (http://code.google.com/p/google-guice) is “a lightweight dependency injection framework for Java 5 and above, brought to you by Google.”. Putting in another words, it is the simplified Google-based version of Spring. 4 Mocks are explained in more details on Chapter 7. Licensed to Alison Tyler Download at Boykma.Com 4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/27/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 1.Create an instance of the object being tested 2.Create mock instances for dependent objects, and inject them in the tested object 3.Set mock expectations 4.Call method being tested 5.Optionally, verify mock expectations You could manually write these 5 steps in every test (in fact, that's the approach we took so far, in Chapters 6 and 16), but as we learned on Chapter 16 (Section 16.7.1), such repetitive workflow is a strong candidate for refactoring through the Template Design Pattern: only steps 3 and 4 are test-specific, all other steps are pretty much the same for all tests. In this section, we analyze three tools that provide infrastructure for transparent mock usage, and refactor the existing UserFacadeImplTest (originally defined in listing 17.5) to use each of them. 18.2.1 Unitils EasyMock support Before we dig into Unitils mock support, let's first learn how Unitils works so we can configure it properly. Unitils is configured through standard Java properties (i.e, those defined by a pair of Strings in the form name=value), and these properties can be defined in 3 distinct files. The first file is called unitils-default.properties, and it is provided on Unitil's JAR5. As the name implies, it provides default values for most of the properties, so Unitils could be used out-of- the-box without custom configuration. But of course, default properties are not always enough – if they were, there would be no need for properties at all – so Unitils allow project- specific properties to be overridden in a unitils.properties file, which should be available either in the classpath or in the user's home directory6. Finally, Unitils also allows each developer to override the properties in a user-specific file called unitils-locals.properties7, which should also be present in the classpath or user's home directory. This configuration mechanism allows a high degree of flexibility, which can be dangerous – if tests rely too many on the user-specific properties, they might be hard to reproduce. Ideally, the whole test suite should be runnable using only the project-specific properties, and the user-specific ones should be used only in some particular cases, like when each user has its own testing database. That being said, each module has its own properties, and even which modules are available are defined by a property, unitils.modules, which by default includes all modules. For our mock example (Listing 18.1), it is not necessary to change any property, although 5 The contents of this file is also documented online, at http://unitils.org/unitils-default.properties 6 The home directory is defined by the Java system property user.home 7 Actually, the name of this file is defined itself by a property, so it could have any name, as long as the name property is changed on unitils.properties. Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/27/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 the mock configuration is quite extensive (in fact, there are two modules involved, as we will see below). Listing 18.1 UserFacadeImpl test refactored to use Unitils mock support package com.manning.junitbook.ch18.business; import static com.manning.junitbook.ch18.model.EntitiesHelper.*; import static org.unitils.easymock.EasyMockUnitils.replay; (1) import static org.easymock.EasyMock.expect; (1) import org.junit.Test; import org.junit.runner.RunWith; import org.unitils.UnitilsJUnit4TestClassRunner; import org.unitils.easymock.annotation.Mock; import org.unitils.inject.annotation.InjectIntoByType; import org.unitils.inject.annotation.TestedObject; import com.manning.junitbook.ch18.dao.UserDao; import com.manning.junitbook.ch18.model.User; import com.manning.junitbook.ch18.model.UserDto; @RunWith(UnitilsJUnit4TestClassRunner.class) (2) public class UserFacadeImplUnitilsTest { @TestedObject private UserFacadeImpl facade; (3) @Mock @InjectIntoByType private UserDao dao; (3) @Test public void testGetUserById() { (4) int id = 666; User user = newUserWithTelephones(); expect(dao.getUserById(id)).andReturn(user); replay(); UserDto dto = facade.getUserById(id); assertUser(dto); // verify(); } } In order to use Unitils, first the test class must either extend the superclass that provides support for the testing framework being used (like UnitilsJUnit4), or annotate it with the proper JUnit runner; in this example, we opted for the latter (2). Next step is to declare fields representing the object being tested and the mocks; Unitils provides annotations for both, as shown in (3). Notice the @Mock annotation will create an EasyMock mock – although Unitils mock support is provided through modules, currently only EasyMock is available. Then you have the test method itself (4), whose content is pretty much the same as before; the only differences are that it uses Unitil's replay() method instead of Easymock's Licensed to Alison Tyler Download at Boykma.Com 6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/27/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 (that's why in (1) we static imported any methods explicitly, instead of using *), and is not necessary to call verify() (Unitils will automatically do that after the test is run, although such behavior could also be changed by modifying a property). Compare this example with Chapter 17's (Listing 17.5): although the core of the class (i.e., the test method itself) is the same, this new example required much less setup. It might not sound like a big difference in these 2 simple examples, but in a real project, with dozens or even hundreds of such test cases, such small 'local' improvement results in a big gain in the 'global' productivity. Behind the scenes, Unitils used 2 modules, inject and easymock. Because we didn't configure anything, Unitils loaded all modules, as the default value for the modules property is: unitils.modules=database,dbunit,hibernate,mock,easymock,inject,spring,jpa If this property is not overridden, Unitils will load all modules, which means more implicit setup methods to be called before and after each test. So, if you don't need all modules, set this property with just the necessary ones. In our example, it would be: unitils.modules=easymock,inject If you forget to include a module, Unitils will not instantiate the attributes that use annotations from that module, and the test case will eventually throw an exception. For instance, if the easymock module was not included, the dao reference would not be set and the expect(dao.getUserById(id)) statement would throw a NullPointerException. Besides configuring which modules are used, you can also change some module behavior through module-specific properties. For instance, to disable calls to Easymock's verify() after the test cases are run, you set the EasyMockModule.autoVerifyAfterTest.enabled to false. It is also possible to set the mock behavior mode (lenient or strict), and even if the order of calls should be taken in account. Another interesting Unitils feature is the @Dummy concept. Attributes annotated with @Dummy behaves similar to those annotated with @Mock, except that you don't need to set expectations: all method calls will return default values (such as 0 for methods that return an int). These dummies are very convenient for the cases where your tested objects need a valid reference to another object, but the behavior of that object is irrelevant for the test case where it is used. 18.2.2 FEST Mocks Listing 18.2 shows the same example using FEST Mocks. Listing 18.2 UserFacadeImpl test refactored to use FEST Mocks Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/27/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 package com.manning.junitbook.ch18.business; import static org.easymock.EasyMock.*; import org.fest.mocks.EasyMockTemplate; import org.junit.Before; import org.junit.Test; import com.manning.junitbook.ch18.dao.UserDao; import com.manning.junitbook.ch18.model.User; import com.manning.junitbook.ch18.model.UserDto; public class UserFacadeImplFESTTest { (1) private UserFacadeImpl facade; private UserDao dao; @Before public void setFixtures() { (2) facade = new UserFacadeImpl(); dao = createMock(UserDao.class); facade.setUserDao(dao); } @Test public void testGetUserById() { final int id = 666; final User user = newUserWithTelephones(); new EasyMockTemplate(dao) { (3) @Override protected void expectations() throws Throwable { expect(dao.getUserById(id)).andReturn(user); } @Override protected void codeToTest() throws Throwable { UserDto dto = facade.getUserById(id); assertUser(dto); } }.run(); (4) } FEST Mock doesn't require the test class (1) to extend any class neither use any special runner; as a drawback, it is necessary to manually instantiate the mocks and objects being tested (2). In fact, all it does is to provide an abstract template class that must be extended on each test case (3), which in turn must explicitly set the expectations and run the code to be tested. Then when the method run() is called (4), it executes a workflow similar to that described at the beginning of this section (the main difference is that the verify step is not optional, and verify() is always called). Overall, FEST Mock is a little bit convoluted, as it explicitly uses the Template Design Pattern, but in a complex way. They claim that separating the mock’s expectation and code being tested make the test case clear; although we agree that the end result is clear to read, it seems less natural and more verbose to develop. Licensed to Alison Tyler Download at Boykma.Com 8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/27/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 18.2.3 Mycila Mycila mock support is similar to Unitils in the way that you mark your mock attributes with annotations. Unlike Unitils, however, you still need to do some manual setup in a @Before method, such as creating the objects being tested and calling Mycila initialization method. Listing 18.3 shows our example converted to Mycilla. Listing 18.3 UserFacadeImpl test refactored to use Mycilla EasyMock plugin package com.manning.junitbook.ch18.business; import static com.manning.junitbook.ch18.model.EntitiesHelper.*; import static org.easymock.EasyMock.*; import org.junit.Before; import org.junit.Test; import com.manning.junitbook.ch18.dao.UserDao; import com.manning.junitbook.ch18.model.User; import com.manning.junitbook.ch18.model.UserDto; import com.mycila.testing.core.TestSetup; import com.mycila.testing.plugin.easymock.Mock; public class UserFacadeImplMycilaEasyMockTest { (1) private UserFacadeImpl facade; @Mock private UserDao dao; (2) @Before public void setFixtures() { facade = new UserFacadeImpl(); TestSetup.setup(this); (3) facade.setUserDao(dao); (4) } @Test public void testGetUserById() { (5) int id = 666; User user = newUserWithTelephones(); expect(dao.getUserById(id)).andReturn(user); replay(dao); UserDto dto = facade.getUserById(id); assertUser(dto); verify(dao); } } As you can see on (1), Mycila does not require any special inheritance or custom runner, which forces the test case to explicitly call its TestSetup.setup() in a @Before method (3). That method will then scan the test class looking for @Mock annotations, and do the proper Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/27/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 EasyMock setup when they are found (such as in (2)). Notice that each test case statement that require a reference to these mocks (like the dependency injection defined in (4)) must be executed after TestSeup.setup() is called. In fact, mock injection is the only mock support Mycilla provides – the test method itself ((5)) is responsible for calling replay() and verify() in the mocks. Mycilla also supports other mock frameworks, like JMock (also analyzed at Chapter 6) and Mockito (http://mockito.org). Listing 18.4 shows the same example using the JMock plugin. Listing 18.4 UserFacadeImpl test refactored to use Mycilla JMock plugin package com.manning.junitbook.ch18.business; import static com.manning.junitbook.ch18.model.EntitiesHelper.*; import org.jmock.Expectations; import org.jmock.Mockery; import org.junit.Before; import org.junit.Test; import com.manning.junitbook.ch18.dao.UserDao; import com.manning.junitbook.ch18.model.User; import com.manning.junitbook.ch18.model.UserDto; import com.mycila.testing.core.TestSetup; import com.mycila.testing.plugin.jmock.Mock; (1) import com.mycila.testing.plugin.jmock.MockContext; public class UserFacadeImplMycilaJMockTest { private UserFacadeImpl facade; @MockContext (2) private Mockery context; @Mock (3) private UserDao dao; @Before public void setFixtures() { (4) facade = new UserFacadeImpl(); TestSetup.setup(this); facade.setUserDao(dao); } @Test public void testGetUserById() { final int id = 666; final User user = newUserWithTelephones(); context.checking(new Expectations() {{ (5) one(dao).getUserById(id); will(returnValue(user)); }}); UserDto dto = facade.getUserById(id); assertUser(dto); context.assertIsSatisfied(); (6) Licensed to Alison Tyler Download at Boykma.Com 10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/27/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 } } This new example is very similar to the previous one, the test setup (4) is even exactly the same. The only differences are the dao reference (3) being marked with a @Mock annotation defined in another package (1), the need of a Mockery object (2), and the way expectations are set and verified ((5) and (6) respectively). So, giving the mock support offered by these three tools, which one should you use in your project? If you are looking for transparent EasyMock usage, Unitils is clearly the best option, as it requires less effort in the test cases (no setup or calls to verify), and is highly configurable. But if you need to use JMock or prefer a clear separation between expectations and tested code, then Mycilla or FEST are respectively the better suitable options. 18.3 DbUnit integration On Chapter 16 we presented an in-house framework which uses Java annotations to facilitate usage of DbUnit datasets in test cases. Wouldn't it be nice if such a framework was offered out-of-the-box? Well, guess what: Unitils dbunit module provides exactly that! Actually, Unitils provides four modules related to database testing: database, dbunit, hibernate, and jpa. The database module is mainly responsible for providing a database connection that will be used by tests and managing transactions, although it offers other features such as a database maintainer that can be used to synchronize the developers databases. Then the dbunit module scans the test class for annotations that defines which DbUnit datasets should be used on each tests. Finally, the hibernate and jpa can be used to inject the necessary ORM classes (such as Hibernate's Session or JPA's EntityManager) in the test cases, also throughout the usage of annotations. The dbunit module works in a way very similar to the infrastructure provided at Chapter 16: you mark the test methods with annotations (@DataSet and/or @ExpectedDataSet), and Unitils takes care of preparing the database or asserting its content with the dataset defined by these annotations. The main difference is where the datasets are located (relative to the class' package directory in the classpath), and also the fact that the annotations could be defined at class or method levels (class level is useful when many methods use the same dataset; individual methods could then override it by using the annotation again with different values). In Chapters 16 we used DbUnit to test a JDBC-based DAO, while on Chapter 17 we used it to test a JPA-based DAO that used Hibernate as the JPA implementation. Let's rewrite these two test cases using Unitiles, starting with the JDBC version at listing 18.5. Listing 18.5 Refactored UserDaoJdbcImplTest using Unitils package com.manning.junitbook.ch18.business; Licensed to Alison Tyler Download at Boykma.Com Last saved: 7/27/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 import static com.manning.junitbook.ch18.model.EntitiesHelper.*; import static org.junit.Assert.*; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import org.junit.Test; import org.unitils.UnitilsJUnit4; import org.unitils.database.annotations.TestDataSource; import org.unitils.dbunit.annotation.DataSet; import org.unitils.dbunit.annotation.ExpectedDataSet; public class UserDaoJdbcImplTest extends UnitilsJUnit4 { (1) private UserDaoJdbcImpl dao = new UserDaoJdbcImpl(); @TestDataSource void setDataSource(DataSource ds) throws SQLException { (2) Connection connection = ds.getConnection(); dao.setConnection(connection); dao.createTables(); } @Test @DataSet("user.xml") (3) public void testGetUserById() throws Exception { long id = 1; User user = dao.getUserById(id); assertUser(user); } @Test @DataSet("user.xml") (3) public void testGetUserByIdUnknowId() throws Exception { long id = 2; User user = dao.getUserById(id); assertNull(user); } @Test @ExpectedDataSet("user.xml") (4) public void testAddUser() throws Exception { User user = newUser(); dao.addUser(user); long id = user.getId(); assertTrue(id>0); } } In the mock example we used a custom runner to provide Unitils integration; this time we opted to extend in (1) the proper Unitils superclass. Next step is to provide a hook for the Licensed to Alison Tyler Download at Boykma.Com 12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/27/2009 ©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 database module inject a datasource which will in turn be used to configure the DAO; that hook is defined through the @TestDataSource, which could be used in an attribute or a method. In our case, we used it in a method ((2)), as it is necessary to pass the database connection to the DAO, and call the DAO to create the database tables8, otherwise the dbunit module will fail when it tries to load the datasets. Then in (3) we have tests that load data from the database, so we use the @DataSet annotation to define a dataset that will be used to prepare the database before the test. Notice that the name “user.xml” refers to a dataset file located in the classpath within the same directory structure as the test class package (in our example, com/manning/junitbook/ch18/dao/user.xml). The content of this file is the same as the one listed on Chapter 16, listing 16.5. Finally, on (4) we have a test case where data is inserted in the database, and DbUnit is used to compare the results; we use the @ExpectedDataSet in this case. The JPA example is pretty much the same, the main difference is the code to setup the EntityManager and the DAO; listing 18.6 shows the new test case, focusing on test setup and showing only one test method. Listing 18.6 Relevant changes on UserDaoJpaImplTest package com.manning.junitbook.ch18.dao; import static com.manning.junitbook.ch18.model.EntitiesHelper.*; import static org.junit.Assert.*; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import org.junit.Test; import org.unitils.UnitilsJUnit4; import org.unitils.database.annotations.TestDataSource; import org.unitils.dbunit.annotation.DataSet; import org.unitils.dbunit.annotation.ExpectedDataSet; import com.manning.junitbook.ch18.model.User; pulic class UserDaoJpaImplTest extends UnitilsJUnit4 { @JpaEntityManagerFactory(persistenceUnit="chapter-18") (1) @PersistenceContext EntityManager em; private final UserDaoJpaImpl dao = new UserDaoJpaImpl(); 8 If we were not using an embedded database but rather a developer database with the tables already created, then we could use @TestDataSource in a Datasourcec attribute, then use a @Before method to pass the connection to the DAO. Licensed to Alison Tyler Download