• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
J.unit.action.2
 

J.unit.action.2

on

  • 19,348 views

junit examples

junit examples

Statistics

Views

Total Views
19,348
Views on SlideShare
19,348
Embed Views
0

Actions

Likes
0
Downloads
40
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    J.unit.action.2 J.unit.action.2 Document Transcript

    • 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
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 MEAP Edition Manning Early Access Program Copyright 2009 Manning Publications For more information on this and other Manning titles go to www.manning.com©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 Part I JUnitWelcome to JUnit in Action. JUnit is a framework that was started by Kent Beck and ErichGamma in the late 1995. Ever since then the popularity of the framework has been growingand 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 byVincent 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 featuresthat were included after the first edition of the book. At the same time we will try to focus onsome 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 andtechniques further in the book. The first chapter will give you a very quick introduction to the concepts of testing. Youneed this knowledge to get you jumpstarted. You will get straight to the code and see how towrite 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 walkingover the code we will let you know not only of the JUnit concepts, widgets and guts, but alsowe will show you the best-practices in writing a test-case and will demonstrate them with theproject 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 differentplatforms (development, production, etc.) and will show you which tests and which scenariosare best to execute there. The last chapter in this part of the book is dedicated on improving your testing skills. Wewill show you how to measure your test-coverage and how to improve it. How to producetestable code before you write your tests and how to write the tests before you write a singleline of code.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 01 JUnit jump-startThis chapter covers What JUnit is Installing JUnit Writing your first test Running tests©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Never in the field of software development was so much owed by so many to so fewlines of code. —Martin FowlerAll code is tested. During development, the first thing we do is run our own programmer’s “acceptancetest.” We code, compile, and run. When we run, we test. The “test” may just be clicking abutton to see if it brings up the expected menu. Nevertheless, every day, we code, wecompile, 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 arecord, edit a record, and delete a record. Running a little test suite like this by hand is easyenough to do; so we do it. Over and over again. Some programmers like doing this type of repetitive testing. It can be a pleasant breakfrom deep thought and hard coding. When our little click-through tests finally succeed, thereis 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 isone thing; running automated tests is another. If you are a “play-test” developer, this book is for you. We will show you how creatingautomated 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 developmentprocess: You cannot prove a component works until it passes a comprehensive series oftests. In fact, two developers felt that this type of “unit testing” was so important that itdeserved its own framework. In 1997, Erich Gamma and Kent Beck created a simple buteffective unit testing framework for Java, called JUnit. The work followed the design of anearlier framework Kent Beck created for Smalltalk, called SUnit. DEFINITION: framework — A framework is a semi-complete application 1 . A framework provides a reusable, common structure to share between applications. Developers incorporate the1 Ralph Johnson and Brian Foote, “Designing Reusable Classes,” Journal of Object-OrientedProgramming 1.5 (June/July 1988): 22–35; http://www.laputan.org/drc/drc.html.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 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 ofFour” who gave us the now classic Design Patterns book 2 . We know Kent Beck equally wellfor 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 CommonPublic License Version 1.0 and hosted on SourceForge. The Common Public License isbusiness-friendly: People can distribute JUnit with commercial products without a lot of redtape 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 standardframework 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 unittesting 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 asingle unit in isolation from other units. We focus on the type of small, incremental test thatprogrammers apply to their own code. Sometimes we call these programmer tests todifferentiate 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 themethod accepts the expected range of input, and that the method returns the expectedvalue for each input.” This description asks us to test the behavior of a method through its interface. If we giveit value x, will it return value y? If we give it value z instead, will it throw the properexception?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).©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 underspecific conditions, an API contract is a formal agreement made by the signature of amethod. A method requires its callers to provide specific object references or primitive valuesand 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. Wewill start by writing a test and its minimal runtime framework, so you can see how we usedto do things. Then, we will roll out JUnit to show you how the right tools can make life muchsimpler.1.2 Starting from scratchLet 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’sadd(double, double) method is to take two doubles and return the sum as a double. Thecompiler can tell you that it compiles, but you should also make sure it works at runtime. Acore tenet of unit testing is “Any program feature without an automated test simply doesn’t©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5exist.” 4 The add method represents a core feature of the calculator. You have some codethat allegedly implements the feature. What is missing is an automated test that proves yourimplementation 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 userinterface with which to enter a pair of doubles. You could write a small command lineprogram that waited for you to type in two double values and then displayed the result. Ofcourse, then you would also be testing your own ability to type a number and add the resultourselves. This is much more than what you want to do. You just want to know if this “unit ofwork” will actually add two doubles and return the correct sum. You do not want to testwhether programmers can type numbers! Meanwhile, if you are going to go to the effort of testing your work, you should also try topreserve that effort. It is good to know that the add(double,double) method workedwhen you wrote it. However, what you really want to know is whether the method workswhen 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 testprogram for the add method. The test program could pass known values to the method and see if the result matchesour expectations. You could also run the program again later to be sure the methodcontinues to work as the application grows. So what is the simplest possible test programyou 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).©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 if (result != 60) { System.out.println("Bad result: " + result); } } }The first CalculatorTest is simple indeed. It creates an instance of Calculator, passesit two numbers, and checks the result. If the result does not meet your expectations, youprint 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 watchcarefully the screen for the error message. You may not have to supply the input, but youare 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. Letus throw an exception instead to indicate a test failure. Meanwhile, you may also want to run tests for other Calculator methods that you havenot written yet, like subtract or multiply. Moving to a modular design would make iteasier 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) { CalculatorTest test = new CalculatorTest(); try { (2) test.testAdd(); | } | catch (Throwable e) { | test.nbErrors++; | e.printStackTrace(); | } | if (test.nbErrors > 0) { (2) throw new IllegalStateException("There were " + test.nbErrors + " error(s)"); } } }©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7Working from listing 1.3, at (1) you move the test into its own testAdd method. It is noweasier to focus on what the test does. You can also add more methods with more unit testslater, without making the main method harder to maintain. At (2), you change the mainmethod to print a stack trace when an error occurs and then, if there are any errors, end bythrowing a summary exception.1.3 Understanding unit-testing frameworksThere are several best practices that unit testing frameworks should follow. These seeminglyminor improvements in the CalculatorTest program highlight three rules that (in ourexperience) 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. Forexample, in order for each unit test to be truly independent, each should run in a differentclass instance and ideally in a different class loader instance. You can now add new unit tests by adding a new method and then adding acorresponding try/catch block to main. This is a step up, but still short of what you would want in a real unit test suite. Ourexperience tells us that large try-catch blocks cause maintenance problems. You couldeasily 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 atleast inventory which tests are running. Another approach would be to use Java’s reflection and introspection capabilities. Aprogram could look at itself and decide to run whatever methods follow a certain namingconvention —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 rulefor a unit testing framework. The support code to realize this rule (via registration or introspection) would not betrivial, but it would be worthwhile. There would be a lot of work up front, but that effortwould pay off each time you added a new test. Happily, the JUnit team has saved you the trouble. The JUnit framework already supportsintrospecting methods. It also supports using a different class instance and class loaderinstance 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 upJUnit and see it in action.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/20091.4 Setting up JUnit In order to use JUnit to write your application tests, you will simply need to add the JUnitjar file to your project’s compilation classpath and to your execution classpath. Follow thesesteps: Download the JUnit distribution (junit-4.6 or newer) from http://www.junit.org. JUnitcontains 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 youdownloaded (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 comescomplete 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.AllTestsUNIX: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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9Fig 1.1 Execution of the JUnit distribution sample tests using the text test runnerNotice that the JUnit text test runner displays passing tests with a dot. Had there beenerrors, 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 theMaven build tool.1.5 Testing with JUnitJUnit has many features that make it easy to write and run tests. You will see these featuresat 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 testlooks like when written with JUnitListing 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)©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 } }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 bepublic, you can name it whatever you like though. It is standard practice to end the classname with "Test". Notice also that in contrast to JUnit 3 where you needed to extend theTestCase class, this requirement has been removed in JUnit 4. At (2), you mark the method as a unit test method by adding the @Test annotation 5 . Abest-practice is to name your test methods following the testXXX pattern. JUnit does nothave method name restrictions, you can name your methods as you like, as long as theyhave the @Test annotation they will be executed by JUnit. At (3), you start the test by creating an instance of the Calculator class (the “objectunder 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 anassertEquals method, which you imported with a static import on the first line of theclass. 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 = 0Since you passed the calculator the values 10 and 50, you tell assertEquals to expect thesum to be 60. (You pass 0 as the delta since you are adding integers.) When you called thecalculator object, you tucked the return value into a local double named result.Therefore, you pass that variable to assertEquals to compare against the expected valueof 60. If the actual value is not equal to the expected value, JUnit throws an uncheckedexception, which causes the test to fail. Most often, the delta parameter can be zero, and you can safely ignore it. It comes intoplay with calculations that are not always precise, which includes many floating-point5 Annotations were first introduced in JDK 1.5. so in order to use them you need to have the 1.5 or later version ofthe JDK.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11calculations. The delta provides a range factor. If the actual value is within the rangeexpected - delta and expected + delta the test will pass. You may find it usefulwhen doing mathematical computations with rounding or truncating errors or when assertinga condition about the modification date of a file, as the precision of these dates depend onthe 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 theC:junitbookjumpstart directory (/opt/junitbook/jumpstart on UNIX). Let usfirst 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.6junit-4.6.jar *.javaUNIX: javac -cp /junit4.6/junit-4.6.jar *.javaYou are now ready to start the console test runner, by typing the following:Windows: java -cp .;junit4.6junit-4.6.jar ➔ org.junit.runner.JUnitCore CalculatorTestUNIX: java -cp .:/junit4.6/junit-4.6.jar ➔org.junit.runner.JUnitCore CalculatorTestFigure 1.2 shows the test result .The remarkable thing about the JUnit CalculatorTest class in listing 1.4 is that the codeis easier to write than the first CalculatorTest program in listing 1.2. In addition, you canrun the test automatically through the JUnit framework.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Figure 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 SummaryEvery developer should perform some type of test to see if code actually works. Developerswho use automatic unit tests can repeat these tests on demand to ensure that new codeworks and does not break existing tests. Simple unit tests are not difficult to create without JUnit, but as tests are added andbecome more complex, writing and maintaining tests becomes more difficult. JUnit is a unittesting 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. Ofcourse, JUnit has much more to offer. In chapter 2, we take a closer look at the JUnit framework classes (different annotationsand assertion mechanisms) and how they work together to make unit testing efficient andeffective. We will also walk through the differences between the old-style JUnit 3 and thenew features in JUnit 4.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 02 Exploring JUnitThis chapter covers Using the core JUnit classes Understanding JUnit mechanisms Understanding the JUnit lifecycle©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 programsAPI. As our program grows with new classes and new methods to existing classes, we needto grow our test code as well. As experience has taught us that sometimes classes interact inunexpected ways, we need to make sure that we can run all of our tests at any time, nomatter what code changes took place. The question becomes, how do we run multiple testclasses? 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 thosequestions. We will begin with an overview of the core JUnit concepts – the test class, testsuite 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 classeswork together. In the second part of this chapter, we will use an example application to show you how touse these core JUnit concepts. We will demonstrate best practices for writing and organizingtest code.2.1 Exploring core JUnit The CalculatorTest program from chapter 1, shown in listing 2.1, defines a test classwith a single test method testAdd. The requirements to define a test class are that the class must be public and contain azero-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, bepublic, 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. Thishelps provide independence between test methods and avoids unintentional side effects in©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3the test code. Since each test methods runs on a new test class instance, you cannot reuseinstance variables values across test methods. To perform test validation, you use the assert methods provided by the JUnit Assertclass. As you can see from the previous example, you statically import these methods inyour test class. Alternatively, you can import JUnit Assert class itself, depending on yourtaste 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 forassertArrayEquals(“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 firstparameter (A above) is the expected value and the second parameter (B above) is the actualvalue. 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 yourassert method calls. Recall Murphys Law and apply it here, when an assertion fails, describewhat went wrong in a human readable message. When you need to run several test classes at once, you create another object calleda test suite (or Suite.) Your test suite is actually a special test runner (or Runner), so youcan run it as you would a test class. Once you understand how a test class, Suite, andRunner work, you will be able to write whatever tests you need. These three objects formthe backbone of the JUnit framework. On a daily basis, you only need to write test classes and test suites. The other classeswork behind the scenes to bring your tests to life.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 upJUnit.Table 2.2 Core objects that make up JUnit.JUnit concept Responsibilities Introduced in … Section 2.1Assert 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.1Test 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.1Test class A test class is the container for @Test methods. The Suite allows you to group test classes together. Section 2.3Suite The Runner class runs tests. JUnit 4 is backward Section 2.2Runner compatible and will run JUnit 3 tests. We can move on now on explaining in details those objects from the table above that westill haven’t talked about – that are the Runner and Suite objects. We will start byintroducing the Runner object in the next section and later on in section 2.3 we will talkabout Suites.2.2 Running tests with the JUnit test runnerWriting tests can be fun, but what about the grunt work of running them? When you are firstwriting tests, you want them to run as quickly and easily as possible. You should be able to©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5make 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 andrunning applications, but what can you use to run the tests?2.2.1 JUnit’s test runnersJUnit4 is built around the notion of backward compatibility. Because of the fact that the 4.xversion of JUnit is completely different from the 3.x ones, it should be possible to executenot only JUnit4 test, but also to execute “older” 3.x tests. That is the reason why in its latestversions JUnit provides a couple of runners – for running JUnit3x tests, JUnit4 tests and forrunning different sets of tests.Table 2.3 Different test runners that come with JUnit4Runner Purpose This runner is included in the current releaseorg.junit.internal.runners.JUnit38ClassRunner of JUnit only for backward compatibility. It will start the test case as a JUnit38 test case. This runner is inclined to force JUnit to startorg.junit.runners.JUnit4 the test case as a JUnit4 test case. Parameterized test-runner is supposed to runorg.junit.runners.Parameterized same sets of tests with different parameters. The Suite is actually a container that can holdorg.junit.runners.Suite 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 thetest-case you provide. If you want to force it to use any particular runner you need to usethe @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 touse any of them. But in order to choose the right test-runner we need to look in more detailsinto those objects©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/20092.2.2 Selecting a test runnerThe two first runners listed in table 2.3 force JUnit to run the test respectfully as JUnit3.x orJUnit4.x test. JUnit does not need to be instructed which test-runner to use and these twotest-runners are only for special occasions. The third listed runner (Parameterized) is an interesting creature that allows you torun your single tests many times with different sets of parameters. As we believe anexample is worth several pages of explanation, the following listing demonstrates theParameterized 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 haveseveral obligatory elements. You need to denote the @RunWith annotation to use theParameterized test-runner (1). Then we declare some local instance variables we aregoing to use in our tests (at (2)) and we provide the obligatory @Parameters-annotatedmethod (3). This method is used to provide the input and output data for the parameterizedtests. This method needs to be public, static and return a Collection instance bysignature. As we want to test the add method of our Calculator program we provide threeparameters – expected value, and two values that we will add together. At (4) we specify©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7the 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 ofthe Collection returned by the @Parameters method – that is the number of the parameterentries. The execution of this single test-case has actually the same result as the executionof 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 acloser 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. Thisfacade is designed to execute your tests and provide you with statistics regarding theoutcome. Because it is specifically designed for this purpose, the facade can be very easy touse. You can see the JUnitCore class in action in figure 1.3 in the previous chapter. Design patterns in action: Facade A 1 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 dont have to be concerned with exactly which object in a subsystem theyre 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 touse for running your tests. It supports running JUnit38 tests, JUnit4 tests or a mixture ofboth. 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 ofthe framework. Those graphical test runners usually had a progress indicator running acrossthe screen, which was known as the famous JUnit green bar. JUnit testers tend to refer to1 The definition is taken from the Portland Pattern Repository here: http://c2.com/cgi/wiki?FacadePattern©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009passing tests as green-bar and failing tests as red-bar. “Keep the bar green to keep thecode clean” is the JUnit motto. So don’t get curious what its origin is, the next time you readit. As I already mentioned, in the current version of JUnit there is no graphical test runnersincluded, 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 pluginexecuting 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 withJUnit all extend Runner. If you needed to write yourown test runner for any reason, you could alsoextend this class yourself. Please refer to Appendix Bwhere we cover this topic in details.2.3 Composing tests with TestSuiteSimple things should be simple … and complex thingsshould be possible. Suppose you compile the simplecalculator test program from listing 2.1 and hand it tothe console façade runner, like this: >java org.junit.runner.JUnitCore CalculatorTest Fig. 2.1 JUnit’s green bar, shown in Eclipse.It should run just fine, assuming you have the correctclasspath. Altogether, this seems simple - at least asfar as running a single test case is concerned. But what happens when you want to run multiple test cases? Or just some of your testcases? How can you group test cases? Between the notions of test-case and test-runner, it would seem that you need some typeof container that can collect several tests together and run them as a set. But, by making iteasier 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 testcases. The test runner launches the Suite; which test-cases to run is up to the Suite.2.3.1 Running the suiteYou might wonder how you managed to run the example at the end of chapter 1, when youdidn’t define a Suite. To keep simple things simple, the test runner automatically creates aSuite if you don’t provide one of your own. (Sweet!)©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 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 youannotate 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 @Testannotated methods in the test-class. But is it possible to combine test-methods fromdifferent test-classes? Of course it is - that’s the real purpose of the Suite object! The nextcode-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 representedin 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 theJUnit framework. We looked over TestClass, Runner, and Suite to explore them indetails. What we want next is to see how to use those classes in a real-world example. Andthat is exactly what we are going to do! In the next section we introduce the Controller design pattern and build a sampleController component application that we will test with JUnit. This way we will not only showyou how to use the JUnit components we have been dealing so far, but we will also introducea lot of JUnit best-practices with the means of the example application we have.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/20092.4 Introducing the controller componentCore 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 bothpresentation-tier and business-tier patterns. 2In 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, ina presentation-tier pattern, a web controller accepts HTTP requests and extracts HTTPparameters, cookies, and HTTP headers, perhaps making the HTTP elements easilyaccessible to the rest of the application. A web controller determines the appropriatebusiness logic component to call based on elements in the request, perhaps with the help ofpersistent data in the HTTP session, a database, or some other resource. The Apache Strutsframework 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 behandled 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 itsown controller, which connects to different presentation tiers through a business façade ordelegate. Given the many uses for a controller, it’s no surprise that controllers crop up in a numberof enterprise architecture patterns, including Page Controller, Front Controller, andApplication Controller 3 . The controller you will design here could be the first step inimplementing any of these classic patterns. Let’s work through the code for the simplecontroller, to see how it works, and then try a few tests. If you would like to follow along andrun 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 interfacesLooking over the description of a controller, four objects pop out: the Request, theResponse, the RequestHandler, and the Controller. The Controller accepts a2 Deepak Alur, John Crupi, and Dan Malks, Core J2EE Patterns: Best Practices and Design Strategies(Upper SaddleRiver, NJ: Prentice Hall, 2001).3 Martin Fowler, Patterns of Enterprise Application Architecture (Boston: Addison-Wesley, 2003).©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11Request, dispatches a RequestHandler, and returns a Response object. With adescription in hand, you can code some simple starter interfaces, like those shown in listing2.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’sunique name, just so you can differentiate one request from another. As you develop thecomponent 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 aResponse object. What the Response encloses is something you can deal with later. Fornow, 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 callupon classes that throw any type of exception. So, Exception is what you have theprocess method throw.(4) Define a top-level method for processing an incoming request. After accepting therequest, the controller dispatches it to the appropriate RequestHandler. Notice thatprocessRequest does not declare any exceptions. This method is at the top of the controlstack and should catch and cope with any and all errors internally. If it did throw anexception, the error would usually go up to the Java Virtual Machine (JVM) or servletcontainer. The JVM or container would then present the user with one of those nasty whitescreens. Better you handle it yourself.(5) This is a very important design element. The addHandler method allows you to extendthe Controller without modifying the Java source.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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. 42.4.2 Implementing the base classFollowing up on the interfaces in listing 2.5, listing 2.6 shows a first draft of the simplecontroller 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 " + "[" + request.getName() + "]"; throw new RuntimeException(message); (3) } 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 { 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 requesthandlers.(2) Add a protected method, getHandler, to fetch the RequestHandler for a givenrequest.(3) If a RequestHandler has not been registered, you throw a RuntimeException(java.lang.RuntimeException), because this happenstance represents a programmingmistake rather than an issue raised by a user or external system. Java does not require youto declare the RuntimeException in the method’s signature, but you can still catch it as anexception. 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 methoddispatches 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 anexception if it has. Looking at the implementation, note that the signature passes therequest object, but you only use its name. This sort of thing often occurs when an interfaceis defined before the code is written. One way to avoid over-designing an interface is topractice 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; }©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 public Exception getOriginalException() { return this.originalException; } }At this point, you have a crude but effective skeleton for the controller. Table 2.4 shows howthe requirements at the top of this section relate to the source code.Table 2.4 Resolving the base requirements for the componentRequirement Resolution public Response processRequest(Request request)Accept requests this.requestHandlers.get(request.getName())Select handler response = getRequestHandler(request).process(request);Route requests Subclass ErrorResponseError-handlingThe next step for many developers would be to cobble up a stub application to go with theskeleton controller. But not us! As “test-infected” developers, we can write a test suite forthe controller without fussing with a stub application. That’s the beauty of unit testing! Youcan 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 twostarter classes shown in listings 2.6 and 2.7. If we don’t write an automatic test now, theBureau 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 thesame with the new set of unit tests. What’s the simplest possible test case we can explore?2.5.1 Testing the DefaultControllerHow about a test-case that instantiates the DefaultController class? The first step indoing anything useful with the controller is to construct it, so let’s start there. Listing 2.8shows the bootstrap test code. It constructs the DefaultController object and sets up aframework 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;©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 @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 notrequired, but doing so we mark the class as a test case so that you can easily recognize testclasses 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 asyou are sure the test infrastructure is working, you can begin adding real test methods. Ofcourse, 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 beenimplemented. This prevents the test from passing and reminds you that you must implementthis 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/20092.5.2 Adding a handlerNow that you have a bootstrap test, the next step is to decide what to test first. We startedthe test case with the DefaultController object, because that’s the point of thisexercise: to create a controller. You wrote some code and made sure it compiled. But howcan you test to see if it works? The purpose of the controller is to process a request and return a response. But beforeyou process a request, the design calls for adding a RequestHandler to do the actualprocessing. 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, youcompared 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 ifadding it worked, you can use the getHandler method from DefaultController, whichuses this signature: RequestHandler getHandler(Request request)This is possible because the getHandler method is protected, and the test classes arelocated 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 comefrom?” 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 environmentlike Java, objects are designed to interact with other objects. To create a unit test, it followsthat you need two flavors of objects: the domain object you are testing and test objects tointeract 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17If you used another domain object, like a logon request, and a test failed, it would be hard toidentify the culprit. You might not be able to tell if the problem was with the controller or therequest. So, in the first series of tests, the only class you will use in production isDefaultController. 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 coulddo one of the following: Make them public classes in your package Make them inner classes within your test case classIf the classes are simple and likely to stay that way, then it is easiest to code them as innerclasses. The classes in this example are pretty simple. Listing 2.9 shows the inner classesyou 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 } [...]©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009(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 haveto code that, too. You’re not testing the process method right now, so you have it return aSampleResponse object to satisfy the signature.(3) Go ahead and define an empty SampleResponse just so you have something toinstantiate.With the scaffolding from listing 2.9 in place, let’s look at listing 2.10, which shows the testfor 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 @Testannotation.(2) Instantiate your test objects.(3) This code gets to the point of the test: controller (the object under test) adds the testhandler. 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 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 forstoring and retrieving RequestHandler is alive and well. If addHandler or getRequestfails 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, acquireresources). 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 requestLet’s look at testing the core purpose of the controller, processing a request. Because youknow 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, uniformname.(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 youcall the getClass method on the Response object. It will fail with a dreadedNullPointerException if the Response object is null. You use theassertNotNull(String, Object) signature so that if the test fails, the error displayed©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009is meaningful and easy to understand. If you had used the assertNotNull(Object)signature, the JUnit runner would have displayed a stack trace showing ajava.lang.AssertionError exception with no message, which would be more difficultto 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 thusyou 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 @Beforemethods. For now, eliminating duplicate code as soon as possible helps you write more testsmore 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 controller; private Request request; 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 }©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 21 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 toinitialize (1). This saves you repeating the same code in testAddHandler (2) andtestProcessRequest (3). Also, we make a new @Before annotated method for addingthe 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 book 5Note that you do not try to share the setup code by testing more than one operation in a testmethod, 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).©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 youwith the @Before and @BeforeClass methods: so you can share fixtures between testswithout combining test methods.2.5.4 Improving testProcessRequestWhen we wrote the testProcessRequest method in listing 2.11, we wanted to confirmthat the response returned is the expected response. The implementation confirms that theobject returned is the object that we expected. But what we would really like to know iswhether the response returned equals the expected response. The response could be adifferent 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. TheassertEquals method utilizes the equals method, inherited from the base Object class.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 23To see if two different objects have the same identity, you need to provide your owndefinition of identity. For an object like a response, you can assign each response its owncommand token (or name). The empty implementation of SampleResponse didn’t have a name property you cantest. To get the test you want, you have to implement a little more of the Response classfirst. 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 ownequals 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 ofthe test. However, the tests are really telling you that this should have existed in the properResponse class. Thus you need to modify the Response interface as follows: public interface Response©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com24 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 { String getName(); } As you see tests can sometimes “talk” and guide you to a better design of yourapplication. But this is not the real purpose of the tests. Don’t forget that the tests are usedto protect us from introducing errors in our code. To do this we need to test absolutely everycondition under which our application might be executed. We start investigating theexceptional conditions in the next chapter.2.6 Testing exception handlingSo far, your tests have followed the main path of execution. If the behavior of one of yourobjects under test changes in an unexpected way, this type of test points to the root of theproblem. In essence, you have been writing diagnostic tests that monitor the application’shealth. But sometimes, bad things happen to healthy programs. Say an application needs toconnect to a database. Your diagnostics may test whether you are following the database’sAPI. If you open a connection but don’t close it, a diagnostic can note that you have failed tomeet 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 youhave all the resources you need, this may never happen. But all resources are finite, and someday, instead of a connection, you may be handed anexception. “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 turnoff the database while the application is running. Forcing actual error conditions is anexcellent way to test your disaster-recovery capability. Creating error conditions is also verytime-consuming. Most of us cannot afford to do this several times a day—or even once aday. 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 testingexception-handling can be even more important. If the main path does not work, yourapplication 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 25 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, manyotherwise great programs are not error-proofed before they go into production. If properlytested, an application should not expose a screen of death but should trap, log, and explainall errors gracefully.2.6.1 Simulating exceptional conditionsThe exceptional test case is where unit tests really shine. Unit tests can simulate exceptionalconditions as easily as normal conditions. Other types of tests, like functional and acceptancetests, work at the production level. Whether these tests encounter systemic errors is often amatter 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 handlerinto the base classes. As you saw back in listing 2.6, the processRequest method traps allexceptions 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 testhandling a normal request, you created a SampleRequestHandler that returned aSampleRequest (see listing 2.9). To test the handling of error conditions, you can create aSampleExceptionHandler 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");©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com26 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 } } }This just leaves creating a test method that registers the handler and tries processing arequest—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 twothings. First, you need to use a different name for the test request, because there is alreadya request named Test in the fixture. Second, you may need to add more exception-handlingto 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 yourown, but that fails with the same error. (Moral: Once you have a test, use it to explorealternative coding strategies.) You consider changing the fixture. If you remove from thefixture the code that registers a default SampleRequest and SampleHandler, youintroduce duplication into the other test methods. Not good. Better to fix theSampleRequest so it can be instantiated under different names. Listing 2.17 is therefactored 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)©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 27 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’sdefault.(2) Introduce a new constructor that lets you pass a name to the request, to override thedefault.(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 withthe fixture.Of course, if you added another test method that also used the exception handler, you mightmove 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com28 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009But 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 exceptionsDuring testing, you found that addHandler throws an undocumented RuntimeExceptionif you try to register a request with a duplicate name. (By undocumented, we mean that itdoesn’t appear in the signature.) Looking at the code, you see that getHandler throws aRuntimeException if the request hasn’t been registered. Whether you should throw undocumented RuntimeException exceptions is a largerdesign issue. (You can make that a to-do for later study.) For now, let’s write some teststhat prove the methods will behave as designed. Listing 2.18 shows two test methods that prove addHandler and getHandler willthrow 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(expected=RuntimeException.class) (5) public void testAddRequestDuplicateName() (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 willproduce an exception of some kind, we need also to specify what kind of an exception weexpect to be raised. We do this by specifying the expected parameter of the @Testannotation.(2) Give the test an obvious name. Because this test represents an exceptional case, appendNotDefined to the standard testGetHandler prefix. Doing so keeps all the getHandlertests together and documents the purpose of each derivation.(3) You create the request object for the test, also giving it an obvious name.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 29(4) Pass the (unregistered) request to the default getHandler method. Since this requesthas 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 testsuite 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 methods 6 . Or, you may need to modify the class hierarchy to better represent the problem domain 7 . 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com30 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/20092.7 Timeout testingSo far we have tested our application for working properly – when supplied with the rightdata, not only behave in the expected manner, but also produce the expected result. Now wewant to take a look at another aspect of testing our application – scalability. How scalableour DefaultController class is? We are going to write some tests and expect that they run below a given time-barrier. Todo 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 thetest takes more time to execute JUnit will mark the test as failed. For example let’s take alook at the following code-listing:Listing 2.19 Timeout tests […] public class TestDefaultController { [...] @Test(timeout=130) (1) public void testProcessMultipleRequestsTimeout() { 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 ourtime-barrier (1). Then, at (2), we declare the Request, Response and RequestHandlerobjects we are going to use in the test. At (3) we start a for-loop to create a 99999SampleRequest objects to and to attach them to a handler in the controller. After thatinvoke the processRequest() method of the controller and assert (at (4)) that we get anon-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 areright. This time barrier was the lowest possible on my machine. But of course the executiontime depends on the hardware it runs on (processor speed, memory available, etc.), and alsoon the software it runs on (mainly on the operating system, but also Java version, etc.). Sofor different developers this test would be failing or passing. Even more – when adding morefunctionality in the processRequest() method the time-barrier we have chosen will getinsufficient for our needs.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 31 So we get to the point where a few timeout tests might fail the whole build for some ofthe developers. Sometimes it is good to skip some of the tests. In the previous version 3.x ofJUnit to do this you had to change the name of the test-method (not to start with the testprefix). In version 4.x of JUnit, however, we have a very nice way to skip a test. The onlything we need to do is annotate the @Test method with a @Ignore annotation. Let’s have alook 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 specifysome 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 testwas to rename it, or comment it out. This way you get no information whatsoever, howmany tests were failing and how many were skipped. In the current version of JUnit onceannotated the method with the @Ignore annotation we get a detailed statistics of how manyof the tests passed, how many failed, and how many were skipped. Pretty cool, isn’t it?2.8 Introducing Hamcrest matchersThe statistics show that people get easily “infected” with the unit-testing philosophy. Onceyou get accustomed to writing tests and see how good the feeling of someone protecting youfrom possible mistakes is, you will wonder how was possible to live without unit-testingbefore. But as you write more and more unit tests and assertions you will inevitably get to theproblem that some of the assertions are big and hard to read. For example consider thefollowing code-listing:Listing 2.19 Cumbersome JUnit assert method […]©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com32 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 public class HamcrestTest { private List<String> values; @Before (1) public void setUpList() { values = new ArrayList<String>(); 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 wehave been constructing so far. We have a @Before fixture at (1), which will initialize somedata for our test and then we have a single test-method at (2). In this test-method you cansee we make a long and hard-to read assertion (maybe it’s not that hard to read, but it isdefinitely not obvious what it does on a first glance). Our goal is to simplify the assertion wemake in the test-method. To solve this problem we are going to present a library of matchers for building testexpressions – Hamcrest. Hamcrest (http://code.google.com/p/hamcrest/) is a library thatcontains a lot of helpful matcher objects (known also as constraints or predicates), ported inseveral languages (Java, C++, Objective-C, Python and PHP). Note that Hamcrest is not atesting 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 particularlyhelpful 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")))); } […]©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 33 Here we reuse the 2.19 listing and simply add another test-method to it. This time weimport the needed matchers and the assertThat method (1), and after that construct atest-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 readableassert statement. And if that is not enough reason to include the Hamcrest matchers in your testingarsenal, how about this – the Hamcrest matchers provide you with a much more throughoutput when the test fails. If you followed the examples in the two previous listings you haveprobably noticed that in both the cases we construct a List with the “x”, “y” and “z” aselements in it. After that we assert the presence of either “one”, “two” or “three”, whichmeans that the test, that written will fail. So let’s really execute that test. The result fromthe 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 theHamcrest, 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? Thefollowing table tries to list some of the most-common used Hamcrest matchers.Table 2.4 Some of the most-common used Hamcrest matchers.Core LogicalAnything 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).©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com34 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009anyOf A matcher that checks if any of the contained matchers match (like the operator ||).not A matcher that traverses the meaning of the contained matchers (just like the ! operator in Java).instanceOf, isCompatibleType A matchers that matches whether objects are of compatible type (are 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, Test given numbers are close to, greater than, greater than or equal,greaterThanOrEqual, less than or less than or equal to a given value.lessThan, lessThanOrEqualequalToIgnoringCase Tests a given string equals another one, ignoring the case.equalToIgnoringWhiteSpace Tests a given string equals another one, by ignoring the white spaces.containsString, endsWith, Tests whether the given string contains, starts with or ends with astartWith certain string. All of them seem pretty straight-forward to read and use, and remember you cancompose them into each other. Last but not least Hamcrest is extremely extensible. It is very easy to write your ownmatchers that check a certain condition. The only thing you need to do is implement theMatcher interface and an appropriately-named factory method. More on how to writecustom matchers you can find in Appendix D of the book – that is where we provide acomplete overview how to write your own matchers.2.9 Setting up a project for testingBecause this chapter covers testing a fairly realistic component, let’s finish up by looking athow you set up the controller package as part of a larger project. In chapter 1, you kept allthe Java domain code and test code in the same folder. They were introductory tests on an example class, so this approach seemed simplest foreveryone. In this chapter, you’ve begun to build real classes with real tests, as you would forone of your own projects. Accordingly, you’ve set up the source code repository just like youwould for a real project.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 35Figure 2.3 A “separate but equal” filing system keepstests in the same package but in different directories. So far, you have only one test case. Mixing this in with the domain classes would nothave been a big deal. But, experience tells us that soon you will have at least as many testclasses as you have domain classes. Placing all of them in the same directory will begin tocreate 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 youwant to keep everything in the same Java package. The solution? One package, two folders.Figure 2.3 shows a snapshot of how the directory structure looks in a popular integrateddevelopment environment (IDE). This is the code for the second chapter, so we used ch2-internals for the toplevel projectdirectory name (see appendix C). Under the root directory we created separatesrc/main/java and src/main/test folders. Under each of these, the actual packagestructure begins. In this case, all of the code falls under thecom.manning.junitbook.ch02.internals package. The working interfaces and classesgo under src/main/java; the classes we write for testing only go under thesrc/main/test directory. Beyond eliminating clutter, a “separate but equal” directory structure yields several otherbenefits. Right now, the only test class has the convenient Test prefix. Later you may need©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com36 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009other helper classes to create more sophisticated tests. These might include stubs, mockobjects, and other helpers. It may not be convenient to prefix all of these classes with Test,and it becomes harder to tell the domain classes from the test classes. Using a separate test folder also makes it easy to deliver a runtime jar with only thedomain classes. And, it simplifies running all the tests automatically. 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.2.10 SummaryAs you saw in chapter 1, it’s not hard to jump in and begin writing JUnit tests for yourapplications. In this chapter, we zoomed in and took a closer look at how JUnit works underthe 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 into JUnit are several convenient assertmethods that make tests quick and easy to build. In this chapter, we also created a test case for a simple but complete applicationcontroller. Rather than test a single component, the test case examined how severalcomponents worked together. We started with a bootstrap test case that could be used withany class. Then we added new tests to TestCase one by one until all of the originalcomponents were under test. As the assertions were getting more and more complicated wefound a way to simplify them by the means of the Hamcrest matchers. We expect thispackage to grow, so we created a second source code directory for the test classes. Becausethe test and domain source directories are part of the same package, we can still testprotected and package default members. In the next chapter, we will put unit testing in perspective with other types of tests thatyou need to perform to fully test your applications. We will also talk about how unit testingfits in the development life cycle.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 03 Software Testing PrinciplesThis chapter covers The need for software tests Types of software tests Types of unit tests©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 KawasakiEarlier chapters in this book took a very pragmatic approach to designing and deploying unittests. This chapter steps back and looks at the various types of software tests and the rolesthey play in the application’s life cycle. Why would you need to know all this? Because unit testing is not just something you doout of the blue. In order to become a well-rounded developer, you need to understand unittests compared to functional, integration, and other types of tests. Once you understand whyunit tests are necessary, then you need to know how far to take your tests. Testing in and ofitself is not the goal.3.1 The need for unit testsThe main goal of unit testing is to verify that your application works as expected and to catchbugs early. While functional testing accomplishes the same goal, unit tests are extremelypowerful 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-coverageUnit tests are the first type of tests any application should have. If you had to choosebetween writing unit tests and functional tests, you should choose the latter. In ourexperience, functional tests are able to cover about 70% of the application code. If you wishto 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 withfunctional tests (it is impossible in some instances). Unit tests provide much more than justtesting, as explained in the following sections.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 33.1.2 Increasing team productivityImagine you are on a team working on a large application. Unit tests allow you to deliverquality 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 (ora good part of it) to be ready before you can test it.3.1.3 Detecting regressions and limiting debuggingA passing unit test suite confirms your code works and gives you the confidence to modifyyour 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 warnyou if you break something. A suite of unit tests reduces the need to debug an application to find out why somethingis failing. Whereas a functional test will tell you that a bug exists somewhere in theimplementation of a use case, a unit test will tell you that a specific method is failing for aspecific 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/20093.1.4 Refactoring with confidenceWithout unit tests, it is difficult to justify refactoring, because there is always a relativelyhigh chance that you may break something. Why would you risk spending hours ofdebugging time (and putting the delivery at risk) only to improve the implementation orchange a method name? Unit tests provide the safety net that gives you the confidence torefactor.3.1.5 Improving implementationUnit tests are a first-rate client of the code they test. They force the API under test to beflexible and to be unit-testable in isolation. You usually have to refactor your code under testto make it unit-testable (or use the TDD approach, which by definition spawns code that canbe unit-tested; see next chapter). It is important to monitor your unit tests as you create and modify them. If a unit test istoo long and unwieldy, it usually means the code under test has a design smell and youshould refactor it. You may also be testing too many features in one test method. If a testcannot verify a feature in isolation, it usually means the code is not flexible enough and youshould refactor it. Modifying code to test it is normal.3.1.6 Documenting expected behaviorImagine you need to learn a new API. On one side is a 300-page document describing theAPI, 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 showhow to use the API. As such, they make excellent developer documentation. Because unittests match the production code, they must always be up to date, unlike other forms ofdocumentation, Listing 3.1 illustrates how unit tests help provide documentation. ThetestTransferWithoutEnoughFunds() method shows that anAccountInsufficientFundsException is thrown when an account transfer is performedwithout 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) } }©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 At (1) we declare the method as a test-method by annotating it with @Test, and declarethat it must throw the AccountInsufficientFundsException (with the expectedparameter). Next, we create a new account with a balance of 1000 (2), and request atransfer of 2000 (4). As expected, the transfer method throws anAccountInsufficientFundsException, if it did not, JUnit would fail the test.3.1.7 Enabling code coverage and other metricsUnit tests tell you, at the push of a button, if everything still works. Furthemore, unit testsenable you to gather code coverage metrics (see the next chapter) showing, statement bystatement, what code the tests caused to execute and what code the tests did not touch. Youcan also use tools to track the progress of passing vs. failing tests from one build to thenext. You can also monitor performance and cause a test to fail if its performance hasdegraded compared to a previous build.3.2 Test typesFigure 3.1 outlines our five categories of software tests. There are other ways of categorizingsoftware tests, but we find these most useful for the purposes of this book. Please note thatthis section is discussing software tests in general, not just the automated unit tests coveredelsewhere in the book. In figure 3.1, the outermost tests are broadest in scope. The innermost tests arenarrowest in scope. As you move from the inner boxes to the outer boxes, the software testsget more and more functional and require that more and more of the application be present.Figure 3.1 The five types of tests©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 Next, we will first look at the general test types. Then, we will focus on the types of unittests.3.2.1 The four types of software testsWe have mentioned that unit tests each focus on a distinct unit of work. What about testingdifferent units of work combined into a workflow. Will the result of the workflow do what youexpect? How well will the application work when many people are using it at once? Differentkinds of tests answer these questions; we categorize them into four varieties: Integration tests Functional tests Stress and load tests Acceptance testsLet us look at each of the test types, starting with the innermost after unit testing andworking our way out.INTEGRATION SOFTWARE TESTINGIndividual unit tests are an essential quality control, but what happens when different unitsof 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 theinteraction between components, possibly running in their target environment, is the job ofintegration testing. Table 3.1 describes the various cases under which components interact.Table 3.1 Testing how object, services and subsystems interactInteraction Test DescriptionObjects 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 aremajor contributors of bugs. Ideally, you should define integration tests before you write©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7application code. Being able to code to the test dramatically increases a programmer’s abilityto write well-behaved objects.FUNCTIONAL SOFTWARE TESTINGFunctional tests examine the code at the boundary of its public API. In general, thiscorresponds to testing application use cases. Developers often combine functional tests with integration tests. For example, a webapplication contains a secure web page that only authorized clients can access. If the clientdoes not log in, then trying to access the page should result in a redirect to the login page. Afunctional unit test can examine this case by sending an HTTP request to the page to verifythat a redirect (HTTP status code 302) response code comes back. Depending on the application, you can use several types of functional tests, as shown intable 3.2.Table 3.2 Testing frameworks, GUIs and subsystemsApplication Type Functional Test DescriptionThe application uses a Functional testing within a framework focuses on testing theframework. 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 A layered system tries to separate systems by roles. There may be asubsystems. 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 TESTINGHow well will the application perform when many people are using it at once? Most stresstests examine whether the application can process a large number of requests within a givenperiod. Usually, you implement this with software like JMeter 1 , which automatically sendspre-programmed requests and track how quickly the application responds. These testsusually do not verify the validity of responses; which is why we have the other tests. Figure3.2 shows a JMeter throughput graph1 http://jakarta.apache.org/jmeter©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Figure 3.2 A JMeter throughput graph You normally perform stress tests in a separate environment, typically more controlledthan a development environment. The stress test environment should be as close as possibleto 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 onerule of optimization: "dont do it". The point is that before you spend valuable timeoptimizing code, you must do it to address a specific problem. That said; let us proceed on toperformance testing. Aside from stress tests, you can perform other types of performance tests within thedevelopment environment. A profiler can look for bottlenecks in an application, which thedeveloper 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. WithJUnit, you can create a performance test to match your unit test.. You might want to assertthat 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 {©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 @Test(timeout=5000) (1) public void someVeryLongTest() { […] } } The example uses the timeout parameter (1) on the @Test annotation to set a timeout inmilliseconds on the method. If this method takes more than 5,000 milliseconds to run, thetest will fail. An issue with this kind of test is that you may need to update the timeout value when theunderlying hardware changes, the OS changes, or when the test environment changes to orfrom running under virtualization.ACCEPTANCE SOFTWARE TESTINGIt is important that an application perform well; but the application must also meet thecustomers needs. Acceptance tests are our final level of testing. The customer or a proxyusually conducts acceptance tests to ensure that the application has met whatever goals thecustomer or stakeholder defined. Acceptance tests are a superset of all other tests. Usually they start as functional andperformance tests, but they may include subjective criteria like “ease of use” and “look andfeel.” Sometimes, the acceptance suite may include a subset of the tests run by thedevelopers, 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 Wikisite regarding Ward Cunningham’s fit framework (http://fit.c2.com/).3.2.2 The three types of unit testsWriting unit test and production code take place in tandem, ensuring that your application isunder test from the very beginning. We encourage this process and urge for programmers touse their knowledge of implementation details to create and maintain unit tests that can berun automatically in builds. Use your knowledge of implementation details to write tests isalso 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 youwrite code, your first tests will probably be logic unit tests. As you write more tests and morecode, you will add integration and functional unit tests. At any one time, you may be workingon a logic unit test, an integration unit test, or a functional unit test. Table 3.3 summarizesthese unit test types.Table 3.3 Three flavors of unit tests: logic, integration, and functionalTest type Description©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Logic 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 threetypes of tests to ensure your code works. Using this type of testing will increase your testcode coverage, which will increase your confidence in making changes to the existing codebase while minimizing the risk of introducing regression bugs. Strictly speaking, functional unit tests are not pure unit tests, but nor are they purefunctional tests. They are more dependent on an external environment than pure unit tests©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11are, but they do not test a complete workflow, as expected by pure functional tests. We putfunctional unit tests in our scope because they are often useful as part of the battery of testsrun in development. A typical example is the StrutsTestCase framework(http://strutstestcase.sourceforge.net/), which provides functional unit testing of the runtimeStruts configuration. These tests tell a developer that the controller is invoking theappropriate software action and forwarding to the expected presentation page, but they donot confirm that the page is actually present and renders correctly. Having described the various types of unit tests, we now have a complete picture ofapplication testing. We develop with confidence because we are creating tests as we go andwe are running existing tests as we go to find regression bugs. When a test fails, we knowexactly what failed and where, we can then focus on fixing each problem directly.3.2.3. Black box vs. white box testingBefore we close this chapter let us focus on one other categorization of software tests - blackbox and white box testing. This categorization is intuitive and easy to grasp but developersoften 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, imagineit with buttons and LEDs. You do not know what is inside, or how the system operates - allyou know is that by providing correct input, the system produces the desired ouput. All weneed to know in order to test the system properly is the systems functional specification.The early stages of a project typically produces this kind of specification, which means wecan start testing early. Anyone can take part of testing the system - a QA engineer, adeveloper, or even a customer. The simplest form of black box testing would try to mimic manually actions on the userinterface. Another, more sophisticated approach would be to use a tool for this task - such asHTTPUnit, HTMLUnit, or Selenium. We will discuss most of these tools in the last part of thebook. 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 createtests and drive the testing process. Not only is a components implementation knowledgerequired but also how it interacts with other components. For these reasons, theimplementers are the best candidates to create white box tests. Which one of the two approaches should you use? Unfortunately, there is no correctanswer and we suggest that you use both approaches. In some situations, you will need©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009user-centric tests, and in others you will need to test the implementation details of thesystem. 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 fromextreme programming is to "release early and release often." However, we are unlikely toget useful feedback if we just tell the customer, “Here it is. Let me know what you think.” Itis far better to get customer involved by providing a manual test script to run through. Bymaking the customer think about the application, he can also clarify what the system shouldreally do.TESTING DIFFICULTIES Black-box tests are more difficult to write and run 2 because they usually deal with agraphical front-end, whether a web browser or desktop application. Another issue is that avalid result on the screen does not always mean the application is correct. White box testsare 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 otherhand, black box tests can bring more value than white box tests. We will focus on testcoverage in the next chapter. While these test distinctions can seem academic, recall that divide and conquer does nothave to apply only to writing production software, it can also apply to testing. We encourageyou 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 SummaryThe pace of change is increasing. Product release cycles are getting shorter, and we need toreact to change quickly. In addition, the development process is shifting—development asthe art of writing code is not enough. Development must be the art of writing complete andtested solutions. To accommodate rapid change, we must break with the waterfall model where testingfollows development. Late stage testing does not scale when change and swiftness areparamount. 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 andcomplement each other. They also complement the other software tests that performed byquality assurance personnel and by the customer. In the next chapter, we will continue to explore the world of testing. We will present bestpractices, like measuring test coverage, writing testable code, and practicing test-drivendevelopment (TDD).2 Black box testing is getting easier with tools like Selenium and HtmlUnit, which we describe in chapter 11.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 04 Test coverage and developmentThis chapter covers Measuring test coverage Writing testable code Practicing Test-Driven Development©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 I dont think anybody tests enough of anything. —James Gosling In the previous chapters, we introduced testing software and started exploring testingwith JUnit, we also presented different test methodologies. Now that we are writing test cases, it is time to measure how good these tests are byusing 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 coverageWriting unit tests gives you the confidence to change and refactor an application. As youmake changes, you run tests, which give you immediate feedback on new features undertest and whether or not changes break existing tests. The issue is that these changes maystill break existing untested functionality. In order to resolve this issue, we need to know precisely what code runs when you or thebuild invokes tests. Ideally, our tests should cover 100% of our application code. Let us lookinto more detail at what help test coverage provides.4.1.1 Introduction to test coverageUsing black box testing, we can create tests that cover the public API of an application. Sincewe are using documentation as our guide and not knowledge of the implementation, we donot create tests, for example, that use special parameter values to exercise specialconditions in the code. One metric of test coverage would be to track which methods the tests call. This does nottell 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3Figure 4.1 Partial test coverage with black box testsYou can write a unit test with intimate knowledge a methods implementation. If a methodcontains a conditional branch, you can write two unit tests: one for each branch. Becauseyou 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 testsYou can achieve higher test coverage using white box unit tests because you have access tomore methods and because you can control both the inputs to each method and the behaviorof secondary objects (using stubs or mock objects, as you will see in later chapters). Sinceyou can write white box unit tests against package protected, default access and publicmethods, you get more code coverage.4.1.2 Introduction to CoberturaCobertura 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=502Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 filesyou specify. This process, called byte-code instrumentation adds byte-codes to existingcompiled code to enable logging of what byte-codes are executed. Instead of, or in additionto, 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 PATHenvironment variable. The COBERTURA_HOME folder contains several command-line scriptswe 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 ➔uninstrumentedCalculator.class The –-destination parameter specifies where to place the instrumented classes. Theapplication argument specifies the path to the pre-compiled classes, in our case,uninstrumentedCalculator.class. Next, we run the unit tests against the instrumented code. Cobertura integrates withJUnit and Ant, but is also tool-agnostic and can work with any other testing framework. Torun 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 TestCalculatorThe net.sourceforge.cobertura.datafile property points to a file where Coberturawill store the code coverage results. If you do not specify this property, Cobertura will createa 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 theinstrumented folder, and a code coverage file for a given test run. To produce an HTMLreport, use the cobertura-report script. >cobertura-report -–format html --datafile cobertura.ser ➔--destination reports srcThe destination parameter specifies the output directory for the report. The reportsfolder contains the HTML report shown in figure 4.3.Figure 4.3 A Cobertura code-coverage report. Cobertura shows not only code coverage by package, but also by class. You can click onany of the classes in the report to see to which extent that particular class was tested. Figure4.4 shows the report for one class.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Figure 4.4 Class code coverage with Cobertura. The report shows good test coverage of the squareRoot method in the Calculatorclass. 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, wehave zero executions of the sum method. Overall, we have 67% code coverage of theCalculator class, indicating that developers need to create more tests. Depending on how you application is composed, it might not be possible to reach all codein the test environment. You may consider refactoring your code to allow for better coveragein combination with the use of mock objects or stubs. Whether or not you choose thisapproach to reach 100% code coverage is a policy decision your team can review throughthe development cycle.4.1.4 Combining black box and white box testingIf we can achieve higher test coverage with white box unit tests, and we can generatereports 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 goingon than how many methods the tests execute. The black box tests in figure 4.1 are verifyinginteractions 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 JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7object interactions. If a white box test does interact with another object, that object isusually a stub or a mock object designed to produce specific test behavior (see chapters 5and 6.), If you want to thourouly test your application, including how runtime objects interact witheach 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 whichpart of an application unit tests exercise. Let us now move on to how differentimplementation techniques affect how to write tests for an application.4.2 Writing testable code This chapter is dedicated to best practices in software testing. We introduced JUnit (inchapter 1 and 2) and discussed different types of tests (in chapter 3). We are now ready toget to the next level: writing code that is easy to test. Sometimes writing a single test caseis 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 andtestable. In this section, we will discuss some best practices to improve your architectureand code. Remember that it is always easier to write easily testable code than it is torefactor existing code to make it easily testable.4.2.1 Benefit from access modifiers One of the principles in the open source world says that you “never change the signatureof a public method.” An application code review will show that most calls are made to publicAPIs. If you change the signature of a public method, then you need to change every call sitein the application and unit tests. Even with refactoring wizards in tools like Eclipse, you mustalways perform this task with care. In the open source world, and for any API made public by a commercial product, life canget even more complicated –many people use your code and you should be very careful ofthe changes you make to stay backwards compatible. Public methods become the articulation points of an application between components,open source projects and commercial products that usually do not even know of each other’sexistence. Imagine a public method that takes a distance as a double parameter and a black boxtest to verify a computation. At some point, the meaning of the parameter changes frommiles to kilometers. Your code still compiles, but the runtime breaks. Without a unit test tofail and tell you what is wrong, you may spend a lot of time debugging and talking to angrycustomers. This example illustrates that you must test all public methods. For non-publicmethods, you need to go to a deeper level and use white box tests.4.2.2 Don’t mix object instantiation in your application logicRemember that unit tests verify your code in isolation. What you want to do in your unittests is to instantiate the class that you want to test and assert for correctness against it.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Your test-cases should be simple. What happens when your class instantiates a whole newset of objects with the new keyword, somewhere in the logic? That makes your classdependent to those classes. In order to achieve a testable code you want to reducedependencies as much as possible. Because, if your classes depend on a lot of other classesthat need to be instantiated and set up to some state, then your tests will be verycomplicated – you will need to use some complicated mock-objects solution (see chapter 6for mock objects). The solution to reducing the dependencies is to separate your code in two parts – the onethat can instantiate new objects (factories), and the one that has only pure business logic init. Consider the following listingListing 4.1 Reducing the dependencies in your code class Vehicle { Driver d = new Driver(); boolean hasDriver = true; private void setHasDriver(boolean hasDriver) { this.hasDriver = hasDriver; } } Every time we instantiate the Vehicle object, we also instantiate the Driver object. Inthat case we say that we have mixed the concepts. The solution would be to have theDriver interface passed to the Vehicle class, like in the following listing:Listing 4.2 Passing the Driver to the Vehicle class class Vehicle { Driver d; boolean hasDriver = true; Vehicle(Driver d) { this.d = d; } private void setHasDriver(boolean hasDriver) { this.hasDriver = hasDriver; } } This way we can produce a mock Driver object (see chapter 6) and pass it to theVehicle class at instantiation. Furthermore, at any other moment in the future we canmock any other type of Driver implementation – JuniorDriver, SeniorDriver, etc.and we can pass it to the Vehicle class.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 94.2.3 Don’t work in constructorWe always strive for a better test coverage. This leads us to adding more and more testcases. 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), youmix the first and the second point in our list. It is a bad practice not only from architecturalpoint of view (you will do the same work every time you instantiate your class), but alsobecause you always get your class in a pre-defined state. This code is hard to maintain, anduntestable.4.2.4 Follow “The Principle of Least Knowledge”The “Law of Demeter,” or “Principle of Least Knowledge,” is a design guideline that statesthat one class should know only as much as it needs to know. For example consider thefollowing: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 theDemeter law, because the Car class needs to know that the Context object has agetDriver() method. If we want to test this constructor we need to get hold of a validContext object before calling the constructor. Bad things will happen when the Contextobject has a lot of variables and methods. Then, we are forced to use mock objects (seechapter 6) to simulate the context, but since the Context object is big enough this will bloatout code. The proper solution is to apply the principle of the least knowledge and pass references tomethods and constructors only when you need to do so. In our example you should pass theDriver 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!©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/20094.2.5 Avoid hidden dependencies and global stateYou, as a developer, should be very careful with global state. And this is simply becauseglobal state makes it possible for some objects to get hold of other global objects withoutdeclaring 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 DBManagersecretly to access the database, because the DBManager is in a global state. I say it usesthe DBManager secretly, because it never defines that usage in the API, and thus leads to ahidden 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 insidethe “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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 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 singletonsWhat 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 andglobal instance variables. Design patterns in action: Singleton Singleton 1 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 anyother way, but only by calling the getInstance method. So what we do is hide the1 More on the Singleton pattern you can find in“Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, RalphJohnson, John M. Vlissides©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009constructor by making it private. However, this code would be impossible to test, and sowe hide the constructor by making it protected, which is hidden enough and at the sametime 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 seethe SINGLETON_INSTANCE variable in the previous example is a global variable. Well, youwould probably respond to this one by stating that the global variable is an immutable oneand 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 needsome “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 appearas another object) the method you are calling is not determined at compile time. So you caneasily use polymorphism to substitute your code with the test code to force certain codepatterns to be tested. The opposite situation occurs when you use nothing but static methods. Then youpractice 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 chosesome method that ends the execution graph, like Math.sqrt(). On the other side, you canchoose a method that lies in the heart of your application logic. In that case every methodthat 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 iswrong, but in hell a lot of the situations you can use composition which is easier to test. Atruntime there is no chance to change the inheritance, but you can always choose a differentcomposition. What we strive for is to make our code as flexible as possible at run-time. Thisway we can be sure that it is easy to switch from one state of our objects to another andthat makes out code easily testable. For example consider it bad practice to have all of your servlets extendAuthenticatedServlet, simply because you will always need to instantiate thecredentials for the user in your tests. On the other hand you could add an instance variableCredentials to those servlets that really need it, and make your classes easier to test byinstantiating 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:©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 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 toinstantiate your class if it is too complex. That said, you need to avoid complexity in yourcode, otherwise it would be untestable. One of the main ways to decrease complexity is to try to avoid long switch and ifconstructions. 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 topoor performance, this code is also hard to test and maintain. Every time you want to add anew document-type you will add additional switch statements and if that happens often inyour 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 ofpolymorphism! Polymorphism is a natural way to avoid long conditionals, by breaking yourcode into several smaller components or classes, and each one of it clearly relate to eachother. And we all have to agree that several smaller components are easier to test than onelarge complex component. In the given example the conditional can be avoided by simply calling creating differentdocument-types like a WordDocument, PDFDocument or an XMLDocument, each one ofwhich having a printDocument() method. This will decrease the complexity of your codeand will make it easier to read.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/20094.3 Test-Driven DevelopmentIn chapter 3, you designed an application controller and quickly wrote some tests to proveyour design. As you wrote the tests, the tests helped improve the initial design. As you writemore unit tests, positive reinforcement encourages you to write them earlier. Pretty soon, asyou design the implementation, it becomes natural to wonder about how you will test aclass. 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 cycleWhen you develop code, you design an application programming interface (API) and thenimplement the behavior promised by the interface. When you unit-test code, you verify thepromised behavior through a method’s API. The test is a client of the method’s API, just asyour 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 effectiveadjustment: [test, code, (repeat), commit]. (More on this later.) The test drives the designand becomes the method’s first client. Listing 4.7 illustrates how unit tests can help design the implementation. ThegetBalanceOk method shows that the getBalance method of Account returns theaccount balance as a long and that this balance can be set in the Account constructor. At thispoint, the implementation of Account is purely hypothetical, but writing the unit tests allowsyou to focus on the design of the code. As soon as you implement the class, you can run thetest to prove that the implementation works. If the test fails, then you can continue workingon the implementation until it passes the test. When the test passes, you know that yourcontract 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);©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 } }When you use the test as the method’s first client, it becomes easier to focus purely on theAPI. Writing the tests first provides the following: Means to design the code Documentation as to how the code works Unit tests for the codeSomeone 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 portionof the application in detail, someone can drill down into individual unit tests.4.3.2 The TDD two-stepEarlier, 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 gomore 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 duplicationThe eliminate duplication step ensures that you write code that is not only testable but alsomaintainable. When you eliminate duplication, you tend to increase cohesion and decreasedependency. These are hallmarks of code that is easier to maintain over time. Other coding practices have encouraged us to write maintainable code by anticipatingchange. In contrast, TDD encourages us to write maintainable code by eliminatingduplication. Developers following this practice have found that testbacked, well-factored codeis, by its very nature, easy and safe to change. TDD gives us the confidence to solve today’sproblems 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 cycleTesting occurs at different places and times during the development cycle. Let’s firstintroduce a development life cycle and then use it as a base for deciding what types of testsare executed when. Figure 4.5 shows a typical development cycle we have used effectively insmall 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).©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17Figure 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 typesof 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com18 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 perfectworld we would have all the four platforms to run our tests on. In real world, however, mostof the software companies try to skip some of the platforms we listed, or the concept oftesting as a whole. As a developer who bought this book, you already made the rightdecision – more tests, less debugging! Now, again, it is up to you – are you going to strive for perfection, stick to everythingthat 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 JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 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 SummaryThis chapter was dedicated mainly on some advanced techniques in unit testing – checkingyour test coverage and improving it, designing your code from architectural point of view tobe easily testable, and practicing Test Driven Development (TDD). These advancedtechniques 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 softwaretests as a whole (chapter 3). The next chapter starts the second part of the book, which will take you to the next levelof 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!©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 Part II Different testing strategies This part of the book reveals the different strategies and techniques in testing. Here wetake a more scientific and theoretical approach to explain what the differences are. Wedescribe incorporating mock objects, stubs and dive into details of what in-container testingmeans. The fifth chapter of the book is stubs-dedicated. We take a look into another solution toisolate the environment and make our tests seamless. The sixth chapter starts explaining what mock-objects are. We give a thorough overviewof how to construct and use mock-objects. We also give a real-world example showing notonly where do mock-objects fit best, but also how to benefit by using them integrated withyour JUnit tests. The last chapter describes a totally different technique - executing tests inside acontainer. This solution is very different from the previous ones and, just like them it has itspros and cons. We start by making an overview of what in-container means, how it isachieved, and at the end of the chapter we compare the mocks-stubs approach versus thein-container one. Along with the theoretical benefit, this chapter will serve as a very goodstarting point to understand chapters 13 and 16.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 05 Coarse-grained testing with stubsThis chapter covers Introducing stubs Using an embedded server in place of a real web-server Unit-testing an HTTP connection with stubs©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 And yet it moves. —GalileoAs you develop your applications, you will find that the code you want to test depends onother classes, which themselves depend on other classes, which then depend on theenvironment. For example, you might be developing an application that uses JDBC to accessa 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 thatconnects to some resource using HTTP, SOAP, or another protocol. In the previous chapters, we introduced the JUnit framework. Starting in this chapter, wewill 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 achallenge. Your tests need to be stable, and when run repeatedly, need to yield the sameresults. You need a way to control the environment in which the tests run. One solution is toset up the real required environment as part of the tests and run the tests from within thatenvironment. In some cases, this approach is practical and brings real benefits (see chapter7, which discusses in-container testing). However, it works well only if you can set up thereal 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 byanother company, you usually will not have that server application available in yourdevelopment environment. Therefore, you need a way to simulate that server so you can stillwrite and run tests for your code. Alternatively, suppose you are working with other developers on a project. What if youwant to test your part of the application, but the other part is not ready? One solution is tosimulate 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 mockobjects. Stubs, the original solution, are still very popular, mostly because they allow you totest code without changing it to make it testable. This is not the case with mock objects. Thischapter is dedicated to stubbing, while chapter 6 covers mock objects.5.1 Introducing stubsStubs are a mechanism for faking the behavior of real code or code that is not ready yet. Inother words, stubs allow you to test a portion of a system without the other part beingavailable. Stubs usually do not change the code you are testing but instead adapt to provideseamless 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3Here 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 subsystemsStubs usually provide very good confidence in the tested system. With stubs, you are notmodifying the objects under test, and what you are testing is the same as what will executein production. A build or developer usually executes tests involving stubs in their runningenvironment, providing additional confidence. On the downside, stubs are usually hard to write, especially when the system to fake iscomplex. The stub needs to implement the same logic as the code it is replacing, and that isdifficult 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, aconnection to a server, a database, and so forth. Stubs can replace a method call to a singleclass, but it is more difficult. (We will demonstrate how to do this with mock objects inchapter 6.)5.2 Stubbing an HTTP connectionTo demonstrate what stubs can do, let us build some stubs for a simple application thatopens an HTTP connection to a URL and reads its content. Figure 5.1 shows the sampleapplication (limited to a WebClient.getContent method) opening an HTTP connection toa remote web resource. The remote web resource is a servlet, which generates an HTMLresponse. The web resource in Figure 5.1 is what we called the "real code" in the stubdefinition.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Figure 5.1 The sample application opens an HTTP connection to a remote web resource. The webresource is the "real code" in the stub definition. Our goal in this chapter is to unit-test the getContent method by stubbing the remoteweb resource, as demonstrated in figure 5.2. You replace the servlet web resource with thestub, a simple HTML page returning whatever you need for the TestWebClient test case.This approach allows you to test the getContent method independently of theimplementation of the web resource (which in turn could call several other objects down theexecution 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 toaccept the stub. The change is transparent to the application under test. In order to allow©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5stubbing, the target code to needs to have a well-defined interface and allow plugging in ofdifferent implementations (a stub, in our case). In the figure 5.1 example, the interface isactually the public abstract class java.net.URLConnection, which cleanly isolates theimplementation of the page from its caller. Let us see a stub in action using the simple HTTP connection example. Listing 5.1 fromthe example application demonstrates a code snippet opening an HTTP connection to a givenURL and reading the content found at that URL. Imagine the method is one part of a biggerapplication 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 thenread the stream content until there is nothing more to read (2). If an error occurs, we returnnull (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 solutionThere are two possible scenarios in the example application: the remote web server (seefigure 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009server and drop some test web pages in its document root. This is a typical, widely usedstubbing 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 inJava, 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, wewill use Jetty to set up our stubs. For more information about Jetty, visithttp://www.eclipse.org/jetty/. We use Jetty because it is fast (important when running tests), it is lightweight, and itcan 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 formost 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 casestarts the server, you write the tests in Java in one location, and automating the test suite isa nonissue. Thanks to Jetty’s modularity, the real point of the exercise is only to stub theJetty handlers and not the whole server from the ground up.5.2.2 Using Jetty as an embedded serverIn order to understand how to set up and control Jetty from your tests, let us implement asimple example. Listing 5.2 shows how to start Jetty from Java and how to define adocument 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 {©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 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 portto listen to for HTTP requests (port 8080). Next, we create a Context object (2) thatprocesses the HTTP requests and passes them to various handlers. You map the context tothe already created server instance, and to the root (/) URL. The setResourceBasemethod sets the document root from which to serve resources. On the next line, we attach aResourceHandler handler to the root to serve files from the file system. Because thishandler will return an HTTP 403-Forbidden error if we try list the content of a directory, wespecify the resource-base to be a file. In this example, we specify the file pom.xml in theproject’s directory. Finally, we start the server (3). If you start the program from listing 5.2 and navigate your browser tohttp://localhost:8080, you should be able to see the content of the pom.xml file (see figure5.3).Figure 5.3 Testing the JettySample in a browser. These are the results when you run listing 5.2 andopen a browser on http://localhost:8080.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/20095.3 Stubbing the web server’s resourcesNow that you know how to easily start and configure Jetty, let us focus on the HTTPconnection unit test. You will write a first test that verifies you can call a valid URL and getits content.5.3.1 Setting up the first stub testTo verify that the WebClient works with a valid URL, you need to start the Jetty serverbefore the test, which you can implement in a test case setUp method. You can also stopthe 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 { @Before 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 canprepare 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 toyour WebClient client application.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9CREATING A JETTY HANDLERListing 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, andimplementing a single method, handle. Jetty calls the handle method to forward anincoming request to our handler. After that, we use the Jetty ByteArrayISO8859Writerclass (2) to send back the string "It works" which we write in the HTTP response (3). Thelast step is to set the response content length to be the length of the string written to theoutput 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 callingcontext.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 becauseJUnit 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 thetests By using the JUnit annotations we described in the second chapter of the book:@BeforeClass and @AfterClass. These annotations let you execute code before andafter 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 CLASSWe can now easily write the test class using the @BeforeClass annotation, asdemonstrated in listing 5.5.Listing 5.5 Putting it all together […] import java.net.URL; […] public class TestWebClient { @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()); 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 theServer object the same way as in listing 5.2. Then we have our @Test methods and we©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11leave our @AfterClass method empty intentionally because we programmed the server tostop 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 andthe server shuts itself down after the last test.5.3.2 Testing for failure conditionsNow 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, yousimply need to create a new Jetty Handler class that returns an error code and register it inthe @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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009for that purpose. You only need to modify the TestWebClientSetup1 setUp method in asfollows (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. Returninga 5XX HTTP response code indicates this problem. To do so, you will need to write a JettyHandler class, using HttpServletResponse.SC_SERVICE_UNAVAILABLE, and registerit in the @Before method of TestWebClientSetup1 class. A test like this would be very difficult to perform if you did not choose an embedded webserver like Jetty.5.3.3 Reviewing the first stub testYou have now been able to fully unit-test the getContent method in isolation by stubbingthe web resource. What have you really tested? What kind of test have you achieved? Youhave done something quite powerful: You have unit-tested the method, but at the sametime, you have executed an integration test. In addition, not only have you tested the code©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13logic, you have also tested the connection part that is outside the code (through the JavaHttpURLConnection class). The drawback to this approach is that it is complex. It can take a Jetty novice half a dayto learn enough about Jetty to set it up correctly. In some instances, you will have to debugstubs to get them to work properly. Keep in mind that the stub must stay simple and notbecome a full-fledged application that requires tests and maintenance. If you spend toomuch 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 bedifferent and will need a different setup. Experience helps, but different cases usually requiredifferent stubbing solutions. The example tests are nice because you can both unit-test the code and perform someintegration tests at the same time. However, this functionality comes at the cost ofcomplexity. More solutions that are lightweight focus on unit testing the code withoutperforming integration tests. The rationale is that while you need integration tests, theycould 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 bringsyou one step closer to the mock-object strategy, which is described in the following chapter.5.4 Stubbing the connectionSo far, you have stubbed the web server’s resources. Next, we will stub the HTTP connectioninstead. Doing so will prevent you from effectively testing the connection, but that is finebecause 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 fromJava’s URL and HttpURLConnection classes, which let you plug in custom protocolhandlers to process any kind of communication protocol. You can have any call to theHttpURLConnection class redirected to your own class, which will return whatever youneed for the test.5.4.1 Producing a custom URL protocol handlerTo implement a custom URL protocol handler, you need to call the URL methodsetURLStreamHandlerFactory and pass it a custom URLStreamHandlerFactory.Whenever the URL openConnection method is called, the URLStreamHandlerFactoryclass is called to return a URLStreamHandler. Listing 5.6 shows the code to perform thisfeat. The idea is to call the URL static method setURLStreamHandlerFactory in the JUnitsetUp method. (A better implementation would use a TestSetup class, such that this isperformed only once during the whole test suite execution.)Listing 5.6 Providing custom stream handler classes for testing […]©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 theStubHttpURLConnection class. We start by calling setURLStreamHandlerFactory (1)with our first stub class, StubStreamHandlerFactory. InStubStreamHandlerFactory, we override the createURLStreamHandler method (2),in which we return a new instance of our second private stub class,StubHttpURLStreamHandler. In StubHttpURLStreamHandler, we override onemethod, openConnection, to open a connection to the given URL (3). You could also use anonymous inner classes for conciseness, but that approach wouldmake the code more difficult to read. Note that you have not written theStubHttpURLConnection class yet, which is the topic of the next section.5.4.2 Creating a JDK HttpURLConnection stubThe last step is to create a stub implementation of the HttpURLConnection class so youcan return any value you want for the test. Listing 5.7 shows a simple implementation thatreturns the string "It works" as a stream to the caller.Listing 5.7 Stubbed HttpURLConnection class©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 […] 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, soyou extend it and override the methods wanted by the stub. In this stub, you provide animplementation for the getInputStream method as it is the only method used by yourcode under test. Should the code to test use more APIs from HttpURLConnection, youwould need to stub these additional methods. This is where the code would become morecomplex—you would need to reproduce completely the same behavior as the realHttpURLConnection. For example, at (1), you test that if setDoInput(false) has beencalled in the code under test, then a call to the getInputStream method returns aProtocolException. (This is the behavior of HttpURLConnection.) Fortunately, in mostcases, you only need to stub a few methods and not the whole API.5.4.3 Running the testLet us run the TestWebClient1 test, which uses the StubHttpURLConnection. Figure5.5 shows the result of the execution of the test in Eclipse.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Figure 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 integrationtests), but it enables you to more easily write a focused unit test for the WebClient logic.5.5 SummaryIn this chapter, we have demonstrated how using a stub has helped us unit-test codeaccessing a remote web server using the Java HttpURLConnection API. In particular, wehave 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 requesthandler, instead of having to stub the whole container. We also demonstrated a morelightweight 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 goodcode. 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 modifyexisting code. The new mock objects strategy not only allows modification of code, it favorsit. Using mock objects is more than a unit-testing strategy: it is a completely new way ofwriting code.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 06 Mock objectsThis chapter covers Introducing and demonstrating mock objects Performing different refactorings Practicing on an HTTP connection sample application Introducing the EasyMock and the JMock libraries©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 iscertainly a nice goal. But how do you perform this feat? You saw in chapter 5 how thestubbing technique lets you unit-test portions of code by isolating them from theenvironment (for example, by stubbing a web server, the file system, a database, and soon). But what about fine-grained isolation like being able to isolate a method call to anotherclass? Is that possible? Can you achieve this without deploying huge amounts of energy thatwould 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. Themock-objects strategy allows you to unit-test at the finest possible level and develop methodby method, while providing you with unit tests for each method.6.1 Introducing mock objectsTesting in isolation offers strong benefits, such as the ability to test code that has not yetbeen written (as long as you at least have an interface to work with). In addition, testing inisolation 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 asingle method, without side effects resulting from other objects being called from the methodunder test. Small is beautiful. Writing small, focused tests is a tremendous help; small testsare 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 youto refactor mercilessly—the unit tests act as a safeguard against regression. If you havelarge tests and your refactoring introduces a bug, several tests will fail; that result will tellyou 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 pinpointthe exact cause of the breakage. Mock objects (or mocks for short) are perfectly suited for testing a portion of code logic inisolation from the rest of the code. Mocks replace the objects with which your methods undertest 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: Theyare empty shells that provide methods to let the tests control the behavior of all the businessmethods of the faked class. We will discuss when to use mock objects in section 6.6 at the end of this chapter, afterwe show them in action on some examples.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 36.2 Mock tasting: a sample example Let’s taste our first mock! Imagine a very simple use case where you want to be able tomake 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 transfermethod. The AccountService class offers services related to Accounts and uses theAccountManager to persist data to the database (using JDBC, for example). The servicethat interests us is materialized by the AccountService.transfer method, which makesthe transfer. Without mocks, testing the AccountService.transfer behavior wouldimply setting up a database, presetting it with test data, deploying the code inside thecontainer (J2EE application server, for example), and so forth. Although this process isrequired to ensure the application works end to end, it is too much work when you want tounit-test only your code logic. Listing 6.1 presents a very simple Account object with two properties: an account IDand 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() {©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 return this.balance; } }Listing 6.2 introduces the AccountManager interface, whose goal is to manage the lifecycle and persistence of Account objects. (You are limited to finding accounts by ID andupdating 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. Ituses the AccountManager interface you previously defined to find the debit and creditaccounts 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 thatpurpose, 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 inisolation.Listing 6.4 MockAccountManager.java […] import java.util.HashMap; public class MockAccountManager implements AccountManager { private Map<String, Account> accounts = new HashMap<String, Account>(); public void addAccount(String userId, Account account) { (1)©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 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 theAccount objects to return in a HashMap. This makes the mock generic and able to supportdifferent test cases: One test could set up the mock with one account, another test could setit up with two accounts or more, and so forth. In (2) we implement a method to retrieve the account from the accounts map – we canonly retrieve accounts that have been added before that. The updateAccount methodupdates an account but does not return any value (3). Thus you simply do nothing. When itis called by the transfer method, it will do nothing, as if the account had been correctlyupdated. 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 atypical 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); |©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 theverification of the result (3). During the test setup, you create the MockAccountManagerobject and define what it should return when called for the two accounts you manipulate (thesender and beneficiary accounts). You have succeeded in testing the AccountService codein 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 amock is. In the next section, we will show you that writing unit tests with mocks leads torefactoring your code under test—and that this process is a good thing!6.3 Using Mock objects as a refactoring techniqueSome people used to say that unit tests should be totally transparent to your code undertest, 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 asany other user. If your code is too inflexible for the tests to use, then you should correct thecode. 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 DefaultAccountManager implements AccountManager { private static final Log LOGGER = (1) LogFactory.getLog(AccountManager.class); (1) public Account findAccountForUser(String userId) { LOGGER.debug("Getting account for user [" + userId + "]");©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 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 codeflexibility and the ability to resist change. The first problem is that it is not possible to decideto use a different Log object, as it is created inside the class. For testing, for example, youprobably 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 sameremark applies to the use of PropertyResourceBundle. It may sound OK right now, butwhat happens if you decide to use XML to store the configuration? Again, it should not be thegoal 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 itsimmediate business logic. The choice of peripheral objects can be controlled by someonehigher in the calling chain. Ultimately, as you move up in the calling layers, the decision touse a given logger or configuration should be pushed to the top level. This strategy providesthe best possible code flexibility and ability to cope with changes. And, as we all know,change is the only constant.6.3.1 Easy refactoringRefactoring all code so that domain objects are passed around can be time-consuming. Youmay 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 foryour 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 areshown 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;©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 listingin favor of a new Configuration interface. This makes the code more flexible because itintroduces an interface (which will be easy to mock), and the implementation of theConfiguration interface can be anything you want (including using resource bundles).The design is better now because you can use and reuse the DefaultAccountManagerclass with any implementation of the Log and Configuration interfaces (if you use theconstructor that takes two parameters). The class can be controlled from the outside (by itscaller). Meanwhile, you have not broken the existing interface, because you have only addeda new constructor. You kept the original default constructor that still initializes the loggerand configuration field members with default implementations. With this refactoring, you have provided a trapdoor for controlling the domain objectsfrom your tests. You retain backward compatibility and pave an easy refactoring path for thefuture. 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’show 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 myfurnace and on the front of my oven. My pen cartridges are transparent so I can see if thereis 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 happensonce 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 itwants 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©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 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 class 2 .IOC makes unit testing a breeze. To prove the point, let’s see how easily you can now writea 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 fromoutside the code to test, in the test code. As a result, your code is more flexible and allowsfor any logging and configuration implementation to be used. You will see more of these coderefactorings in this chapter and later ones. One last point to note is that if you write your test first, you will automatically designyour 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 sampleTo see how mock objects work in a practical example, let’s use a simple application thatopens an HTTP connection to a remote server and reads the content of a page. In chapter 5we tested that application using stubs. Let’s now unit-test it using a mock-object approach tosimulate 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 startwith an initial testing implementation, improve the implementation as you go, and modify2 See the Jakarta Avalon framework for a component framework implementing the IOC pattern(http://avalon.apache.org)©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009the original code to make it more flexible. We will also show how to test for error conditionsusing 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 theprocess, you will learn how to reach a simple and elegant testing solution while making yourapplication 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 simpleWebClient.getContent method performing an HTTP connection to a web resourceexecuting on a web server. You want to be able to unit-test the getContent method inisolation from the web resource.6.4.1 Defining the mock objectsFigure 6.3 illustrates the definition of a mock object. The MockURL class stands in for thereal URL class, and all calls to the URL class in getContent are directed to the MockURLclass. As you can see, the test is the controller: It creates and configures the behavior themock must have for this test, it (somehow) replaces the real URL class with the MockURLclass, and it runs the 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 JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11Figure 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 ableto swap-in the mock in the production code. The perceptive reader will have noticed thatbecause the URL class is final, it is actually not possible to create a MockURL class thatextends it. In the coming sections, we will demonstrate how this feat can be performed in a differentway (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 anegative point for mock objects, because you usually need to modify your code to provide atrapdoor. Ironically, modifying code to encourage flexibility is one of the strongestadvantages 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 toa given URL and reads the content found at that URL. Let’s imagine that it’s one method of abigger 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 {©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 public String getContent(URL url) { StringBuffer content = new StringBuffer(); try { HttpURLConnection connection = (1) (HttpURLConnection) url.openConnection(); (1) connection.setDoInput(true); InputStream is = connection.getInputStream(); (1) int count; (1) while (-1 != (count = is.read())) { (2) content.append( new String( Character.toChars( count ) ) ); (2) } (2) } catch (IOException e) { 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-handlingsolution, but it is good enough for the moment. (And your tests will give you the courage torefactor later.)6.4.3 Try #1: easy method refactoring techniqueThe idea is to be able to test the getContent method independently of a real HTTPconnection to a web server. If you map the knowledge you acquired in section 6.2, it meanswriting a mock URL in which the url.openConnection method returns a mockHttpURLConnection. The MockHttpURLConnection class would provide animplementation 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 containingIt 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 (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 noURL interface is available. So much for extensibility… You need to find another solution and, potentially, another object to mock. One solutionis 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. Ifyou think about it, this method does two things: It gets an HttpURLConnection objectand 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 theHttpURLConnection 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 auseful trick, which consists of writing a test helper class that extends the WebClient classand overrides its createHttpURLConnection method, as follows: private class TestableWebClient extends WebClient { private HttpURLConnection connection; public void setHttpURLConnection(HttpURLConnection connection) { this.connection = connection; } public HttpURLConnection createHttpURLConnection(URL url)©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 throws IOException { return this.connection; } }In the test, you can call the setHttpURLConnection method, passing it the mockHttpURLConnection object. The test now becomes the following (differences are shown inbold): @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 especiallyuseful when the class to mock has no interface. The strategy is to extend that class, addsome setter methods to control it, and override some of its getter methods to return whatyou want for the test. In the case at hand, this approach is OK, but it isn’t perfect. It’s a bitlike the Heisenberg Uncertainty Principle: The act of subclassing the class under test changesits 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, butstopping here means testing something that is similar to (but not exactly) the class you wantto 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 useneeds to be passed to the getContent method or WebClient class. The only resourceyou use is the HttpURLConnection object. You could change theWebClient.getContent signature to public String getContent(URL url, HttpURLConnection connection)©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 This means you are pushing the creation of the HttpURLConnection object to thecaller 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 thatinvolves creating a ConnectionFactory interface, as shown in listings 6.8 and 6.9. Therole ofclasses implementing the ConnectionFactory interface is to return anInputStream from a connection, whatever the connection might be (HTTP, TCP/IP, and soon). This refactoring technique is sometimes called a Class Factory refactoring 3 .Listing 6.8 ConnectionFactory.java […] import java.io.InputStream; public interface ConnectionFactory { InputStream getData() throws Exception; } The WebClient code then becomes as shown in listing 6.9. (Changes from the initialimplementation 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 independentof the way you get the connection. The first implementation worked only with URLs using theHTTP protocol. The new implementation can work with any standard protocol (file://,http://, ftp://, jar://, and so forth), or even your own custom protocol. For3 J. B. Rainsberger calls it Replace Subclasses with Collaborators:http://www.diasparsoftware.com/template.php?content=replaceSubclassWithCollaborator©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009example, listing 6.10 shows the ConnectionFactory implementation for the HTTPprotocol.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 = (HttpURLConnection) this.url.openConnection(); return connection.getInputStream(); } }Now you can easily test the getContent method by writing a mock forConnectionFactory (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 useMockConnectionFactory 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 {©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 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 theWebClient.getContent method. In the process you had to refactor it for the test, whichled to a more extensible implementation that is better able to cope with change.6.5 Using mocks as Trojan horsesMock objects are Trojan horses, but they are not malicious. Mocks replace real objects fromthe inside, without the calling classes being aware of it. Mocks have access to internalinformation about the class, making them quite powerful. In the examples so far, you haveonly used them to emulate real behaviors, but you haven’t mined all the information theycan provide. It is possible to use mocks as probes by letting them monitor the method calls the objectunder test makes. Let’s take the HTTP connection example. One of the interesting calls youcould monitor is the close method on the InputStream. You have not been using a mockobject for InputStream so far, but you can easily create one and provide a verifymethod to ensure that close has been called. Then, you can call the verify method atthe end of the test to verify that all methods that should have been called, were called (seelisting 6.13). You may also want to verify that close has been called exactly once, andraise an exception if it was called more than once or not at all. These kinds of verificationsare 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;©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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: Youalways want it to be called once. However, most of the time, the expectation forcloseCount depends on the code under test. A mock usually has a method likesetExpectedCloseCalls so that the test can tell the mock what to expect. Let’s modify the TestWebClient.testGetContentOk test method to use the newMockInputStream: […] public class TestWebClient { @Test public void testGetContentOk() throws Exception { MockConnectionFactory mockConnectionFactory = new 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©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19MockInputStream. Note that you call the verify method of MockInputStream at theend of the test to ensure that all expectations are met. The result of running the test isshown 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.getContentmethod. The same error would be raised if you were closing it twice or more, because thetest verifies that it is called once and only once.Figure 6.4 Running TestWebClient with the new close expectationLet’s correct the code under test (see listing 6.14). You now get a nice green bar (figure6.5).Figure 6.5 Working WebClient that closes the input streamListing 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();©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 componentmanager calling different methods of your component life cycle, you might expect them to becalled in a given order. Or, you might expect a given value to be passed as a parameter tothe 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-sourcemocking frameworks – they are powerful enough for our needs, and we don’t needimplement 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 seeit’s not a tedious task, but rather a very recurring one. You might guess that we don’t needto reinvent the wheel every time we need a mock. And you are right – there are a lot of goodprojects already written that are out there to help us facilitate the usage of mocks in ourprojects. In this section we will take a closer look on two of the most widely-used mockframeworks – the EasyMock and the JMock. We will try to rework the example HTTPconnection 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 classesfor mocking objects. To use the framework you need to download the zip archive from the©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 21web-site of the project, unpack it somewhere, and include the contained easymock.jar inyour 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 startwith a very simple one – reworking the AccountService test from listing 6.5Listing 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() {©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 verify( mockAccountManager ); (10) } } As you see the given listing is pretty much the same size, as listing 6.5, but we spare thewriting of any additional mock classes. We start the listing defining the imports from theEasyMock library that we need (1). As you see EasyMock relies heavily on static-importfeature of Java5+. In (2) we declare the object that we would like to mock. Notice that ourAccountManager is an interface; the reason behind this is simple – the core EasyMockframework can mock only interface objects. In (3) we call the createMock method to createa mock of the class that we want. In (4), just like in listing 6.5, we create two accountobjects 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 isvoid, you simply call it on the mock-object (as in (5)), or when the method returns any kindof 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 toannounce it (7). In (8) we call the transfer method to transfer some money, between thetwo accounts, and in (9) we assert the expected result. The @After method which getsexecuted after every @Test method, holds the verification of the expectations. WithEasyMock you can call the verify method with any mock object (10), to verify that themethod-call expectations we declared were triggered. That was pretty easy, wasn’t it? So how about moving a step forward and revising a bitmore complicated example. No problem, the next listing shows the reworked WebClient testfrom listing 6.12. What we would like is to test the getContent method of the WebClient. For thispurpose we need to mock all the dependencies to that method. In this example we have twodependencies – one is the ConnectionFactory and one is the InputStream. It looks likethere is a problem because EasyMock can mock only interfaces and the InputStream is aclass. To be able to mock the InputStream class we are going to use the classextensionsextension of EasyMock. The classextensions is an extension project of EasyMock that lets yougenerate mock objects 4 for classes and interfaces. You can download it separately from theEasyMock 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 TestWebClientEasyMock4 final and private methods cannot be mocked.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 23 { 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 ); } }©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com24 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 We start the listing by importing the objects that we need (1). Notice that since we usethe classextensions extension of EasyMock, we now need to import theorg.easymock.classextension.EasyMock object instead oforg.easymock.EasyMock. That’s it! Now you are ready to create mock objects of classesand interfaces using the statically imported methods of the classextensions. In (2), just asthe previous listings, we declare the objects that we want to mock, and in (3) we call thecreateMock 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 readmethod 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 todenote that we have done declaring our expectations – we do this by calling the replaymethod (6). The rest is just invoking the method under test (7), and assert the expectedresult (8). We also add another test to simulate a condition when we cannot close theInputStream. We define an expectation where we expect the close method of the stream©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 25to be invoked (9), and right on the next line we declare that an IOException should beraised if this call occurs (10). As the name of the framework suggests, using EasyMock is very easy, and you shoulduse it whenever is possible. But to make you aware of the whole mocking picture we wouldlike to introduce another framework, so you have a better taste on what the mocking is.6.6.2 Reworking some of the examples using JMockSo far we saw how to implement our own mock-objects and how to use the EasyMockframework. In this section we introduce the JMock framework (http://jmock.org/), so thatwe can have the full view on the different mocking techniques. Just like in the previoussection 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 ); } } );©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com26 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 youcan see unlike EasyMock, the JMock framework does not rely on any static import features.Luckily enough the JMock framework provides a JUnit4 runner 5 , that will facilitate us a lot. In(2) we instruct JUnit to use the JMock runner that comes with the framework. In (3) wedeclare the context Mockery object that will serve us to create mocks and to defineexpectations. In (4) we declare the AccountManager that we would like to mock. Just likeEasyMock, the core JMock framework provides mocking only of interfaces. In the @Beforemethod, that gets executed before each of the @Test methods, we create the mock bymeans of the context object (5). Just like in any of the previous listings, we declare twoaccounts that we are going to use to transfer money inbetween (6). Notice that this time theaccounts are declared final. This is because we will use them in an inner class defined in adifferent method. In (7) we start declaring the expectations, by constructing a newExpectations object. In (8) we declare the first expectation, each expectation having theform: 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 casethe method returns some object we can declare the return object by using thewill(returnValue()) construction. In (9) we start the transfer from the one account to the other, and after that we assertthe expected results (10). It’s just as simple as that! But wait, what happened with theverification of the invocation count? In all of the previous examples we needed to verify thatthe invocations of the expectations really happened the expected number of times. Well, withJMock you don’t have to do that – the JMock JUnit runner takes care of this, and in case anyof the expected calls were not made the test will fail. Following the pattern from the previous section about EasyMock, let’s try and reworklisting 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 27Listing 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 );©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com28 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 propertyof 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 startdeclaration of the expectations. Notice the fine way we declare the consecutive execution ofthe read() method of the stream (5), and also the returned values. In (6) we call themethod 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 @Testmethod, which tests our WebClient under exceptional conditions. In (8) we declare theexpectation of the close() method being triggered and in (9) we instruct JMock to raise anIOException when this trigger happens. As you can see the JMock library is as easy to use as the EasyMock one. Whichever youprefer to use is up to you, as long as you remember that what increases your softwarequality is not the framework you use, but rather how much you use it.6.7 SummaryThis chapter has described a technique called mock objects that lets you unit-test code inisolation from other domain objects and from the environment. When it comes to writingfine-grained unit tests, one of the main obstacles is to abstract yourself from the executingenvironment. We have often heard the following remark: “I haven’t tested this methodbecause 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 rewritesome of the code under test. In practice, code is often not written well. You hard-codeunnecessary couplings between the classes and the environment. It’s easy to write code thatis hard to reuse in a different context, and a little nudge can have a big effect on other©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 29classes in the system (similar to the domino effect). With mock objects, you must thinkdifferently about the code and apply better design patterns, like Interfaces and Inversion ofControl (IOC). Mock objects should be viewed not only as a unit-testing technique but also as a designtechnique. A new rising star among methodologies called Test-Driven Developmentadvocates writing tests before writing code. With TDD, you don’t have to refactor your codeto enable unit testing: The code is already under test! (For a full treatment of the TDDapproach, see Kent Beck’s book Test Driven Development 6 . For a brief introduction, seechapter 4.) Although writing mock objects is easy, it can become tiresome when you need to mockhundreds of objects. In the following chapters we will present several open sourceframeworks that automatically generate ready-to-use mocks for your classes, making it apleasure to use the mock-objects strategy.Kent Beck, Test Driven Development: By Example (Boston: Addison-Wesley, 2003).6©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 07 In-container testingThis chapter covers The drawbacks of mock objects In-container testing Comparing stubs, mock objects, and in-container testing©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 applicationcontainer: in-container unit testing, or integration unit testing. We will discuss in-containertesting pros and cons and show what can be achieved using the mock objects approachintroduced in chapter 6, where mock objects fall short, and how in-container testing enablesyou 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 HttpServletmethod 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 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(); } } This servlet, while simple enough, will allow us to show the limitation of standard unittesting. In order to test the method isAuthenticated, you need a validHttpServletRequest. Since HttpServletRequest is an interface, you cannot just callnew HttpServletRequest. The HttpServletRequest life cycle and implementation isprovided by the container (in this case, a servlet container.) The same is true for otherserver-side objects like HttpSession. JUnit alone is not enough to write a test for theisAuthenticated 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 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 standardJUnit techniques to test those objects.7.2. The Mock objects solutionWe will look at several solutions for in-container testing. The first solution for unit testing theisAuthenticated method (listing 7.1) is to mock the HttpServletRequest class usingthe approach described in chapter 6. While mocking works, you need to write a lot of code tocreate a test. You can achieve the same result more easily using the open source EasyMock 1framework (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/©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 @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 Hamcrestmatchers. 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 mockobjects. 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 EasyMockverify 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 ateasing this task.7.3 In-container testingAnother approach to testing the SampleServlet is to run the test cases where theHttpsServetRequest and HttpSession objects live, in the container itself. This avoidsthe need to mock any objects, we simply access the objects and methods we need in the realcontainer.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 For our example, we need the HttpServletRequest and HttpSession to be realobjects managed by the container. Using a mechanism to deploy and execute our tests in acontainer, we have in-container testing. Next we will see what options are available toimplement 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 containerand the unit tests. Alternatively, we can drive the tests from the client-side as shown infigure 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 JUnittest runner executes the test classes on the client (1). A test class opens a connection via aprotocol like HTTP(S) and calls the same test case on the server-side (2). The server-sidetest case operates on server-side objects, which are normally available (likeHttpServletRequest, HttpServletResponse, HttpSession, BundleContext, etc…),and tests our domain objects (3). The server returns the result from the tests back to theclient (4), which an IDE or Ant can gather.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/20097.3.2 In-container testing frameworksAs we just saw, in-container testing is applicable when code interacts with a container, andtests cannot create valid container objects (HttpServletRequest in the previousexample). 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 canbe applied. In the third part of the book, we will present different open source projects thatuse this strategy. Table 7.1 lists the type of containers and the testing frameworks we will beusing 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 Chapter 13 libraries, Filters, and EJBs)HTTP container JSFUnit (for testing JSF components) Chapter 14OSGi container JUnit4OSGi (For testing OSGi modules) Chapter 15Database Container DBUnit Chapter 16While the open source world offers other projects, we cover the more mature projects listedin the table above. Next, we will compare stubs, mock objects, and in-container testing.7.4 Comparing stubs, mock objects, and in-container testingIn this section we will compare 2 the different approaches we presented to test components.This section draws from the many questions in forums and mailing lists asking about the prosand cons of stubs, mock-objects, and in-container testing.7.4.1 Stubs pros and consWe introduced stubs as our first out-of-container testing technique in chapter 5. Stubs workwell 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 thestate of the server is, or what URLs where requested. Using mocks, however, we can test the state of the server and its behavior. When usingmock objects, we code and verify expectations; we check at every step that tests executedomain 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 One of the biggest advantages of stubs over mocks is that stubs are easier tounderstand. Stubs isolate a class with little extra code compared to mock objects, whichrequire an entire framework to function. The drawback of stubs is that they rely on externaltools 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 withmock objects would be much harder since we would need to fake container objects with stateand 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 prosThe biggest advantage of mock-objects 3 over in-container testing is that mocks do notrequire a running container in order to execute tests. Tests can be set up quickly and runfast. The main drawback is that the tested components do not run in the container in whichyou will deploy them. The tests cannot verify the interaction between components andcontainer. The tests also do not test the interaction between the components themselves asthey run in the container. You still need a way to perform integration tests. Writing and running functional testscould achieve this. The problem with functional tests is that they are coarse-grained and testonly a full use case—you lose the benefits of fine-grained unit testing. You will not be able totest 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 mockobjects to set up, which may prove to be non-negligible overhead. Obviously, the cleaner thecode (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 usuallymust know exactly how the mocked API behaves. It is easy to know the behavior of yourown 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 Even though containers of a given type all implement the same API, not all containersbehave 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 differentbehaviors. 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 prettymuch 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 aspecification 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. Whileunfortunate, you will have to deal with bugs, tricks and hacks for various third party librariesin any project. To wrap up this section, let us summarize the drawbacks of unit testing with mockobjects. 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 consSo far, we have described the advantages of in-container unit testing. However, there arealso a few disadvantages, which will now discuss.SPECIFIC TOOLS REQUIREDA 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 JavaEEtesting. If you wish to write integration unit tests for another component model, chances arethat such a framework exists. With mock-objects, since the concept is generic you can testalmost any API.NO GOOD IDE SUPPORTA significant drawback of most of the in-container testing frameworks is the lack of good IDEintegration. In most cases, you can use Ant or Maven to execute tests, which also provides©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9the 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 unittests and will most likely run them in a CIS, alleviating the need for IDE integration.LONGER EXECUTION TIMEAnother issue is performance. For a test to run in a container, you need to start and managethe container, which can be time consuming. The overhead in time (and memory) dependson the container: Jetty can start in less than 1 second, Tomcat in 5 seconds, and WebLogicin 30 seconds. The startup overhead is not limited to the container. For example, if a unittest hits a database, the database must be in an expected state before the test starts (seedatabase application testing in chapter 17). In terms of execution time, integration unit testscost more than mock objects. Consequently, you may not run them as often as logic unittests.COMPLEX CONFIGURATIONThe biggest drawback to in-container testing is that tests are complex to configure. Since theapplication and its tests run in a container, your application must be packaged (usually as awar or ear file) and deployed to the container. You must then start the container and run thetests. On the other hand, since you must perform these exact same tasks for production, it is abest 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 packagingand deployment becomes a win-win. The need to provide in-container testing will drive thecreation of this automated process at the beginning of the project, which will in additionfacilitate continuous integration. To further this goal, most in-container testing frameworks include support for build toolslike Ant and Maven. This will help hide the complexity involved in building various runtimeartifacts as well as with actually running tests and gathering reports. 7.4.4 In-container versus out-of-container testingA standard design goal is to separate presentation from business layers. For example, youshould implement the code for a tag that retrieves a list of customers from a database withtwo classes. One class implements the tag and depends on the Taglib API while the otherimplements the database access and does not depend on the Taglib API but on databaseclasses like JDBC. The separation of concerns strategy not only permits the reuse of classes in more thanone context, it also simplifies testing. You can test the business logic class with JUnit andmock 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 willwork in the target environment. While you may be tempted to skip testing a component, like©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009a taglib -- reasoning that functional tests would eventually test the tag as a side effect -- werecommend that you fight this temptation. All components benefit from unit testing. Weoutline 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 SummaryWhen it comes to unit-testing an application in a container, we have seen that standard JUnittests come up short. While testing with mock objects work, they miss some scenarios likeintegration tests to verity component interactions in and with a container. In order to verifybehavior in a container, we need a technique that addresses testing from an architecturalpoint of view: in-container testing. While complex, in-container testing addresses these issues and provides developers withthe confidence necessary to change and evolve their applications. This chapter provides thefoundation for the last part of this book where we will continue to explore such testingframeworks. Next, we start the third part of this book by integrating JUnit into the build process, atenet of Test Driven Development.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 Part III JUnit and the build process This part of the book deals with a very important aspect in the development cycle ofevery project - the build. The importance of the build is reconsidered more and more thesedays - especially in large projects. That is the reason why we dedicate a whole part of thebook to integration between JUnit and two of the most important build tools nowadays - Antand 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. Thischapter will let you serve as a basis for most of the rest of the chapters, since you need agood knowledge of Ant to be able to grasp all of the Ant integration sections in the furtherchapters. The ninth chapter will guide you through the same concepts, but this time by the meansof another popular tool called Maven. We will show you how to include the execution of yourtests in the Maven build lifecycle and how to produce nice HTML reports by means of some ofthe 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 acode repository and automate the build upon it. This is really helpful in building largeprojects that depend on a lot of other projects that change very often (like any open-sourceproject).©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 08 Running JUnit tests from AntThis chapter covers Introduction to Ant and Ivy Ant JUnit tasks Producing fancy reports with the JUnitReport task©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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. Antis a build tool that can be used with any Java programming environment. We willdemonstrate how you can be productive with JUnit and these environments and how toautomate running JUnit tests. At the end of this chapter, you will know how to set up yourenvironment 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. Mostdevelopment cycles begin by checking out a module from the project’s source-coderepository. 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. Beforestarting any development of your own, you should see for yourself that no one has brokenthe all-green rule. You should always be sure that your work progresses from a knownbaseline. The next step is to write the code for a new use case (or modify an existing one). If youare a Test-Driven Development (TDD) practitioner, you’ll start by writing new tests for theuse case. (For more about TDD, see chapter 3.) Generally, the test will show that your usecase isn’t supported and either will not compile or will display a red bar when executed. Onceyou write the code to implement the use case, the bar turns green, and you can check inyour code. Non-TDD practitioners will implement the use case and then write the tests toprove 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 toprove the new feature works. After you code the next feature, you can run the tests for theprior feature too. In this way, you can ensure that new development does not break olddevelopment. If the old feature needs to change to accommodate the new feature, then youupdate its test and make the change. If you test rigorously, both to help you design new code (TDD) and to ensure that oldcode works with new (regression testing 1 ), you must continually run the unit tests as anormal 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 thetests automatically and effortlessly throughout the day. In chapter 1, section 1.4, we discussed running JUnit from the command line. Running asingle JUnit test case against a single class is not difficult. But it is not a practical approachfor running continuous tests against a project with hundreds or even thousands of classes.1 About regression testing see chapter 4.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 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 mosteffective way possible. Using JUnit should be seamless, like calling a build tool or plugging ina code highlighter. Ant is the de facto standard tool for building Java applications; it is an excellent tool formanaging and automating JUnit tests. Ant will happily build your applications, but it doesn’thelp 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 ifyour only tool is the stock javac compiler. Increasing numbers of classes refer to eachother, 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 classesto 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 anessential tool for building applications, but also a great way to run your JUnit regressiontests.8.3 Ant, indispensable Ant Apache’s Ant product (http://ant.apache.org/) is a build tool that lets you easily compileand test applications (among other things). It is the de facto standard for building Javaapplications. One reason for Ant’s popularity is that it is more than a tool: Ant is a frameworkfor running tools. In addition to using Ant to configure and launch a Java compiler, you canuse 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 documentis referred to as the buildfile and is named build.xml by default. The Ant buildfile describeseach task that you want to apply on your project. A task might be compiling Java sourcecode, generating Javadocs, transferring files, querying databases, or running tests. Abuildfile can have several targets, or entry points, so that you can run a single task or chainseveral together. Let’s look at using Ant to automatically run tests as part of the buildprocess. 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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:Antapache-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:Antapache-ant-1.7.0 3 Edit your system’s PATH environment variable to include the ANT_HOMEbin 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_HOMElib 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 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_HOMElib 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 binarycode. For a final distribution, you may want to generate Javadocs along with the binaryclasses. For an interim compile during development, you may skip that step. Sometimes, youwant to run a clean build from scratch. Other times, you want to build the classes that havechanged. To help you manage the build process, Ant lets you create a buildfile for each ofyour projects. The buildfile may have several targets, encapsulating the different tasksneeded to create your application and related resources. To make the buildfiles easier to configure and reuse, Ant lets you define dynamicproperty 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 useproperty elements to set the parameters a tool needs and a task to run the tool. A great©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009number of tasks come bundled with Ant, and you can also write your own. For more aboutdeveloping with Ant, we highly recommend Ant in Action 2 . Listing 8.1 shows the top of the buildfile for the sampling project from chapter 3. Thissegment of the buildfile sets the default target and the properties your tasks will use.Listing 8.1 The ant buildfile project and property elements. <project name="sampling" default="test"> (1) <property file="build.properties"/> (2) <property name="src.dir" location="src"/> (3) <property name="src.java.dir" location="${src.dir}/java"/> (3) <property name="src.test.dir" location="${src.dir}/test"/> (3) <property name="target.dir" location="target"/> (4) <property name="target.classes.java.dir" location="${target.dir}/classes/java"/> (4) <property name="target.classes.test.dir" location="${target.dir}/classes/test"/> (4) […] (1) Give the project the name sampling and set the default target to test. (Thetest target appears in listing 8.3.) (2) You include a build.properties file. This file contains Ant properties that mayneed 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. Becauseprogrammers may store jars in different locations, it is good practice to use abuild.properties file to define them. Many open source projects provide abuild.properties.sample file you can copy as build.properties and then edit tomatch 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 testsource code. You use the Ant property task to define these values so that they can bereused 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). Noticethat you use different properties to define where the compiled production and tests classeswill be put. Putting them in different directories is a good practice because it allows you toeasily 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 thebuild.properties file is loaded, the new value is ignored. The first definition always wins.8.4.1 The javac taskFor 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 a2 The “Ant In Action” is a title by Steve Loughran and Erik Hatcher published by Manning Publications (ISBN: 1-932394-80-X)©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7Sisyphean task. Ant’s javac task tames the compiler and its classpath, making buildingprojects 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 aspart of the target. The javac task lets you set any of the standard options, including thedestination directory. You can also supply a list of paths for your source files. The latter ishandy for projects with tests, because you may tend to keep production classes in one folderand 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 <target name="compile.java"> (1) <mkdir dir="${target.classes.java.dir}"/> (2) <javac destdir="${target.classes.java.dir}"> (3) <src path="${src.java.dir}"/> (4) </javac> </target> <target name="compile.test" depends="compile.java"> (5) <mkdir dir="${target.classes.test.dir}"/> (5) <javac destdir="${target.classes.test.dir}"> (5) <src path="${src.test.dir}"/> (5) <classpath> (6) <pathelement location="${target.classes.java.dir}"/> (6) </classpath> (6) </javac> </target> <target name="compile" depends="compile.java,compile.test"/> (7)First, declare the target (1) to compile the java production sources, naming itcompile.java. Ensure that the directory where you will generate your production classfiles 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 thedirectory already exists, Ant quietly continues. Call the Java Compiler (javac) and pass it thedestination 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 youmust add depends element to that compile.test target definition a(depends="compile.java"). You may have noticed that you don’t explicitly add the JUnitjar to the classpath. Remember that when you installed Ant, you put the JUnit jar inANT_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 taskto properly compile your tests.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 You need to add a nested classpath element (6) in order to add the productionclasses you just compiled to the classpath. This is because test classes call productionclasses. Create a compile target (7) that automatically calls the compile.java andcompile.test targets.8.4.2 The JUnit taskIn chapter 3, you ran the DefaultController tests by hand. That meant between anychanges, you had to Compile the source code Run the TestDefaultController test case against the compiled classesYou can get Ant to perform both these steps as part of the same build target. Listing 8.3shows the test target for the sampling buildfile.Listing 8.3 The buildfile test target. <target name="test" depends="compile"> (1) <junit printsummary="yes" haltonerror="yes" haltonfailure="yes" (2) fork="yes"> (2) <formatter type="plain" usefile="false"/> (3) <test name="junitbook.sampling.TestDefaultController"/> <classpath> <pathelement location="${target.classes.java.dir}"/> (4) <pathelement location="${target.classes.test.dir}"/> (5) </classpath> </junit> </target>Give the target a name and declare that it relies on the compile target (1). If you ask Antto run the test target, it will run the compile target before running test. At (2) you getinto the JUnit-specific attributes. The printsummary attribute says to render a one-linesummary at the end of the test. By setting fork to yes, you force Ant to use a separate JavaVirtual Machine (JVM) for each test. This is always a good practice as it avoids interferencesbetween test cases. The haltonfailure and haltonerror attributes say that the buildshould stop if any test returns a failure or an error (an error is an unexpected error, whereasa failure happens if one of the test asserts does not pass). Configure the junit taskformatter to use plain text and output the test result to the console (3). Provide the classname of the test you want to run (4) . Finally, extend the classpath to use for this task toinclude the classes you just compiled (5). Thats it; youve 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 changingto your project directory and entering ant. Figure 8.1 shows what Ant renders in response.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9Fig. 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 testsfail, the haltonfailure/haltonerror settings will stop the build, bringing the failure toyour 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 onone other aspect of the build process – the dependency management. Every build needs away 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/20098.6 Dependency management with Ivy When your build is pretty small it is relatively easy to cope with the jars your projectdepends on. For instance in the buildfile from the previous listings, the only jar we needed tocompile our classes was the junit.jar. In the open-source world, however, it is a commonproblem that your project depends on, sometimes dozens of other open-source projects. Inmost of the cases, it is also inconvenient to redistribute those dependencies with yourproject, and a great burden for the users of your project to tell them before compiling yourproject to get the other several dependencies from somewhere in the internet. You need todeal 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 lotsof different jars that you might need in your buildfile. What you, as a programmer, do issimply list all the dependencies you need in the configuration file and the dependencymanager will download them for you in a local repository on you hard disk. From there youcan add them to the classpath of your application, and you can easily change the version youwant 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. Whileavailable as a standalone tool, Ivy works particularly well with Apache Ant providing anumber of powerful Ant tasks ranging from dependency resolution to dependency reportingand publication. It is out of the scope for this book to cover Ivy in depth, but I would like todedicate a few pages on this tool and rework our buildfile with the proper dependencymanagement. The installation of Ivy is pretty straightforward – just download the zip from the mainwebsite, extract it somewhere and copy the ivy-xxx.jar(where xxx stands for the versionof Ivy you are using) in the ANT_HOME/lib folder. Ivy works the way we described in a few paragraphs paragraphs earlier. Ivy uses theMaven central repository for resolving and downloading your dependencies. Thedependencies to your project need to be specified in a separate file, named by defaultivy.xml. From there the tool, once started, will download all the dependencies listed in theivy.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 <project name="sampling" default="test" xmlns:ivy=”antlib:org.apache.ivy.ant”> (1) <property file="build.properties"/> […] <ivy:retrieve file="./ivy.xml" sync="true"/> (2) <property name="junit.jar" location="lib/junit-4.5.jar"/> (3) […]©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 <target name="compile.test" depends="compile.java"> <mkdir dir="${target.classes.test.dir}"/> <javac destdir="${target.classes.test.dir}"> <src path="${src.test.dir}"/> <classpath> <pathelement location="${target.classes.java.dir}"/> <pathelement location=”${junit.jar}”/> (4) </classpath> </javac> </target> […] <target name="test" depends="compile"> <junit printsummary="yes" haltonerror="yes" haltonfailure="yes" fork="yes"> <formatter type="plain" usefile="false"/> <test name="junitbook.sampling.TestDefaultController"/> <classpath> <pathelement location="${target.classes.java.dir}"/> <pathelement location="${target.classes.test.dir}"/> <pathelement location=”${junit.jar}”/> (5) </classpath> </junit> </target> […] What we actually do here is simply declare the ivy namespaces at (1), and then call theIvy Ant task to retrieve the dependencies listed in the ivy.xml file from the publicrepository (2). After we retrieve the junit.jar with Ivy, the archive is placed in the lib folderin our project. In (3) we define the junit.jar property that points to the jar we justdownloaded. After that in (4) and (5) we include it in the classpath of the javac task (sothat the compilation of the tests can be done) and in the classpath of the junit task (sothat the execution of the tests can be done). We no longer need the junit.jar in theANT_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. <ivy-module version="2.0"> (1) <info organisation="junitbook" module="sampling-ivy"/> (2) <dependencies> (3) <dependency org="junit" name="junit" rev="4.5"/> </dependencies> </ivy-module> 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 definingdependencies. Finally, the dependencies nested element (3) is where we specify ourdependencies. Our module has only one dependency listed – the junit module.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 After invoking the ant file again by changing to the project directory and entering Ant weshould see the same result (Figure 8.2) as we did before, when the junit.jar was in theANT_HOME/lib folder, but this time we should see Ivy being invoked and resolve theappropriate dependencies.Fig 8.2 Output of Ivy resolving the JUnit dependency.8.7 Pretty printing with JUnitReportA report like the one in figure 8.1 is fine when you are running tests interactively.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13But what if you want to run a test suite and review the results later? For example, the testsmight be run automatically every day by a cron job (whether you liked it or not!) or aContinuous integration server (we discuss continuous integration servers in chapter 10). Another optional Ant task, junitreport, is designed to output the result of the tests asXML. 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 anybrowser. 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. <project name="sampling" default="test"> […] <property name="target.report.dir" location="${target.dir}/report"/>(1) […] <target name="test" depends="compile"> <mkdir dir="${target.report.dir}"/> (2) <junit printsummary="yes" haltonerror="yes" haltonfailure="yes" fork="yes"> <formatter type="plain" usefile="false"/>©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 <formatter type="xml"/> (3) <test name="junitbook.sampling.TestDefaultController" todir="${target.report.dir}"/> (4) <classpath> <pathelement location="${target.classes.java.dir}"/> <pathelement location="${target.classes.test.dir}"/> </classpath> </junit> </target> <target name="report" depends="test"> (5) <mkdir dir="${target.report.dir}/html"/> (6) <junitreport todir="${target.report.dir}"> (7) <fileset dir="${target.report.dir}"> <include name="TEST-*.xml"/> (8) </fileset> <report todir="${target.report.dir}/html"/> (9) </junitreport> </target> </project>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 testresults as XML (3). The junitreport task works by transforming the XML test result intoan 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 thejunitreport task to create the report (7). The junitreport task works by scanning thelist of XML test results you specify as an Ant fileset (8). Last, tell the junitreport taskwhere 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 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 runThe buildfile you have written is using the test element of the junit task to tell JUnit whattest to execute. Although this is fine when there are only a few test cases, it becomestiresome when your test suite grows. The biggest issue then becomes ensuring that youhaven’t forgotten to include a test in the buildfile. Fortunately, the junit task has aconvenient batchtest element that lets you specify test cases using wildcards. Listing 8.5shows how to use it (changes from listing 8.4 are shown in bold).Listing 8.5 A better buildfile using batchtest <project name="sampling" default="test"> [...] <target name="test" depends="compile"> <mkdir dir="${target.report.dir}"/> <property name="tests" value="Test*"/> (1) <junit printsummary="yes" haltonerror="yes" haltonfailure="yes" fork="yes"> <formatter type="plain" usefile="false"/> <formatter type="xml"/> <batchtest todir="${target.report.dir}"> (2) <fileset dir="${src.test.dir}"> (2) <include name="**/${tests}.java"/> (2) <exclude name="**/Test*All.java"/> (2) </fileset> (2) </batchtest> <classpath> <pathelement location="${target.classes.java.dir}"/> <pathelement location="${target.classes.test.dir}"/> </classpath> </junit> </target> […] <target name="clean"> <delete dir="${target.dir}"/> </target> </project>(1) You may wonder why you define a property here when you could have put the wildcardsdirectly into the fileset element at (2). Using this trick, you can define the testsproperty on the command line and run a single test (or a specific set of tests) instead. This isan easy way to run a test against the class you are working on right now. Of course, once it’sworking, you still run the full test suite to be sure everyone is on the same page. Here is anexample that only executes the TestDefaultController test case: ant –Dtests=TestDefaultController 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 JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009(2) You improve the buildfile by making the test target more flexible. Whereas before youhad to explicitly name the different tests you wanted to execute, here you leverage thejunit task’s nested batchtest element. With batchtest, you can specify the test to runas a fileset, thus allowing the use of wildcards. Add the always-useful clean target toremove all build-generated files. Doing so lets you start with a fresh build with no sideeffects from obsolete classes. Typically, a dist target that generates the projectdistributable depends on the clean target. It is a common good practice to name your test-cases in some way that they can hint tothe other developers that this is actually a test-case. As you saw in the first chapter you cangive your test-cases any name you want. Despite this, most of the people tend to nametheirs 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 batchtestnested 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 SummaryIn 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 veryimportant tasks – the javac task and the junit task. We also introduced Ivy and thejunitreport task. Now you should be able to execute you tests with Ant, and also producenice reports of the test execution. In the following chapters, we will continue to explore the continuous integration paradigmby getting familiar with another tool for building software – Maven. We will start by gettingfamiliar with the core concepts that biuld Maven and we are going to explore the two mostimportant 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 willcompare it to the Ivy way that we just saw. So let’s go on!©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 09 Running JUnit tests from Maven2This chapter covers Introduction to Maven Dependency management the Maven way Maven Surefire Plugin Maven Surefire-Report Plugin©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 calledMaven. We will show you how Maven differs from Ant, also we will make a brief introductionto this build system, which will be very useful if you are new to it, or just need a way to startyour tests continuously. Very often people come to Maven thinking that it would be something like Ant. And oncethey discover that it is a totally different, they get frustrated. Don’t worry, maybe you arenot alone in the boat. This is the reason why we will spend the first few pages of this chapteron explaining what is the most essential in order to understand Maven, how it is differentfrom Ant. Right after that we are going to show you some real-world examples of compilingyour 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 yourmachine 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 almostalways need the same Ant scripts (or at least a good percentage). These scripts are easyenough to reuse through cut and paste, but each new project requires a bit of fussing to getthe Ant buildfiles working just right. In addition, each project usually ends up having severalsubprojects, 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 manyteams. Like Ant, Maven is a tool for running other tools, but Maven is designed to take toolreuse to the next level. If Ant is a source-building framework, Maven is a source-buildingenvironment. 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 thenext level, beyond Ant. So you need to get familiar what are the things that the Mavencommunity did not like in Ant and how it tried to escape them designing Maven, i.e. what isthe 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 thewhole software architecture of the project. These rules aim to simplify the development withMaven and to make it easier for us as developers to implement the build system. One of thefundamental ideas of Maven is that the build system should be as simple as possible – thesoftware engineers should not spend a lot of time implementing the build system. It shouldbe easy enough to start a new project from scratch and then rapidly begin developing thesoftware, 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 weexplain what they really mean for us, from a developer’s point of view.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 39.1.1 Convention over configurationThis feature is actually software design principle which aims to decrease the number ofconfigurations a software engineer needs to make, in favour of introducing a number ofconventional rules that need to be strictly followed by the developers. This way you, as adeveloper 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 everysingle Ant buildfile the person who writes the buildfile has to design the folder structure – hehas to declare the source folders, the build folders and a bunch of other folders you need forthe build itself. And although the Ant community tries to imply some folder names and folderstructure, there is still no official specification (convention) of how the folder should benamed. For instance lots of people declare the target.dir property to note the folderwhich holds your compiled classes, others may have accustomed to the build.dirproperty and it seems very unnatural to them to use the target.dir. Also lots of peopleplace 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 everytime the build structure themselves, they would introduce a convention for this. So at theend 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 foryou – like the src/main/java/ is the Maven convention where your java code for theproject resides, in src/main/test/ the unit-tests for the project reside, target is thebuild folder, and so on…. And it is not just the folder structure, later on when we see theplugins themselves, we will see how every plugin has a default state already defined, so youdon’t need to define it again. That sounds great but aren’t we losing from the flexibility of the project? What if I wantto use Maven and my source code resides in another folder? Maven is really great at this - itprovides the convention, but you still can, at any point of time, override the convention anduse 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 startedde facto build system for Java projects was Ant. And with Ant you have to distribute thedependencies of your project, with the project itself. Maven introduced the notion of a centralrepository – a location in the internet where all kinds of artifacts (dependencies) are storedand 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 classpathof your application. This way you only need to list your dependencies in the dependenciessection of your build descriptor©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.5</version> </dependency> <dependency> <groupId>jmock</groupId> <artifactId>jmock</artifactId> <version>1.0.1</version> </dependency> </dependencies> after which you are free to build the software on any other machine – no need to bundlethe dependencies with your project and so forth. But Maven introduced also the concept of the local repository. This is a folder on yourhard disk (~/.m2/repository/ on UNIX and C:Documents andSettings<UserName>.m2repository on Windows) on your hard disk where Mavenkeeps the artifacts that it has just downloaded from the central repository. Also, after youhave built your project your artifacts get installed in the local repository for later usage, bysome other projects – simple and neat.9.1.3 Maven build lifecyclesAnother very strong principle in Maven is the build lifecycle. The Maven project is buildaround this idea of defining the process of building, testing and distributing a particularartifact. Maven projects can produce only one artifact. This way we can use Maven forbuilding the project’s artifact, or cleaning the project’s folder structure or generating theproject’s documentation. The activities we use Maven for, define the three built-in lifecyclesof 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 acertain 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 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 Antyou 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 alreadydefined for you in the order we listed them. And not only this, but also Maven invokes thesephases in a very strict order – they get executed sequentially one after another in the orderwe have listed them, to complete the lifecycle. This means that if you invoke any of them –for example if you type: mvn compileon the command line in your project’s home directory Maven will first validate the projectand 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 anymoment you can attach additional Maven plugins to the phases and orchestrate the orderand the way these plugins get executed.9.1.4 Plugin based architectureThe last feature of Maven that we will mention is its plugin-based architecture. At thebeginning of this chapter we mentioned that Ant is a source-building framework and Mavenis 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 theproject allows multiple different plugins to get attached to the core and so Maven builds anenvironment where different plugins can get executed. Each one of the phases in a given lifecycle has a number of plugins attached to thatphase and Maven invokes them when passing through the given phase in the order theplugins 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 forevery kind of situation you may need – maven war plugin, maven javadoc plugin, mavenantrun plugin… you name it. Plugins are declared in the plugins section of your build configuration file. For instance: <build> <plugins> <plugin> <groupId>org.apache.cactus</groupId> <artifactId>cactus.integration.maven2</artifactId> <version>1.8.1-SNAPSHOT</version> </plugin> </plugins> </build> 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 arehandled the same way – they get downloaded in your local repository the same thedependencies do. When specifying a plugin the groupId and version are optionalparameters – if you don’t declare them Maven will look for a plugin with the specifiedartidfactId, and one of the following groupIds – org.apache.maven.plugins ororg.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 version2.0.9 the versions of most plugins are locked down in the super pom, so it wont downloadthe latest version anymore. Locking down plugin versions is highly recommended to avoidauto-updating and non-reproducible builds. There are tons of additional plugins that are outside the Maven project, but can be usedwith 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 theinformation for our build. In that buildfile we specify all the things that we want to getaccomplished in the form of tasks and targets. So what is the analogue in Maven of Ant’s build.xml? Maven also has a build descriptorthat is by default called pom.xml (shortened for Project Object Model). In contrast to Ant inMaven’s project descriptor we don’t specify the things we want to do – we specify generalinformation for the project itself. Like in the following listing:Listing 9.1 Very simple pom.xml©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 <project> <modelVersion>4.0.0</modelVersion> <groupId>com.manning.junitbook</groupId> <artifactId>example-pom</artifactId> <packaging>jar</packaging> <version>2.0-SNAPSHOT</version> </project> It looks really simple, doesn’t it? I believe one big question rises at this moment – “Howis 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.xmlinherits most of its functionality from a Super POM. Just like in Java every Object inheritscertain methods from the java.lang.Object object, the Super POM empowers each oneof 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 asparents for others. For instance if we want to use the pom from listing 9.1 for our parent allwe 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 ourparent what the children modules are:Listing 9.2 Children modules for parent pom.xml <project> <modelVersion>4.0.0</modelVersion> <groupId>com.manning.junitbook</groupId> <artifactId>example-pom</artifactId> <packaging>pom</packaging> (1) <version>2.0-SNAPSHOT</version> <modules> <module>example-module</module> (2) </modules> </project> The listing is an extension of listing 9.1. We declare that this pom is an aggregationmodule 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 therelative 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 <project> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.manning.junitbook</groupId> <artifactId>example-pom</artifactId> <version>2.0-SNAPSHOT</version>©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 </parent> <artifactId>example-child</artifactId> </project> 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 pomthere’s no need to specify groupId and version for the child pom, maven expects theyare the same as the parent’s. Going with the analogue of Java it seems like a reasonable question to ask “what kind ofobjects can poms inherit from their parents?” Here is a list of all the elements that a pom caninherit 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 automaticallyspecified in the child pom. We will go on and discuss the poms further in the upcoming sections.9.2 Setting up a Maven projectNow that we have seen what the differences between Ant and Maven are, it’s time to moveon 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_HOMEbin (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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 Let’s navigate to the c:junitbook folder. This is our work directory and here we willset 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 seea folder named maven-sampling being created. If you look inside that folder you shouldsee 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 thecommand line and told it to create a new project from scratch with the given parameters. Asa result this maven plugin created a new project with a new folder structure, following theconvention of the folder structure. And even more it created a sample App.java class with amain 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 insrc/main/java and what files stay in src/test/java. But it gets even more automated – if you noticed the Maven plugin also generated apom.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 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.manning.junitbook</groupId> <artifactId>maven-sampling</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>maven-sampling</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project> This is the build descriptor for our project. It starts with a global <project> tag withthe appropriate namespaces. Inside which we place all of our components. modelVersion – this is the version of the model of the pom being used. Currently 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 groups together different projects from one organization, company, group of people… artifactId – The artifactid of our project. Again the value here is the one we specified on the command line. The artifactId represents the name that the project is known by. packaging – What kind of artifact-packaging our project will use? We specify here 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 denotes that this artifact is still in development mode – we have not released it yet. dependencies – This section is used to enlist your dependencies. Now that we have our project descriptor let’s start and improve it a little bit. First weneed to change the version of the junit dependency, since we are using 4.5 and the one thatthe plugin generated is 3.8.1. After that we can add some additional info to make thepom.xml more descriptive, like a developers section. This information not only makes thepom.xml more descriptive, but it will also be included later on when we build the web-site.Listing 9.5 Additional metadata to the pom.xml©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 <developers> <developer> <name>Petar Tahchiev</name> <id>ptahchiev</id> <organization>Apache Software Foundation</organization> <roles> <role>Java Developer</role> </roles> </developer> <developer> <name>Stan Silvert</name> <id>ssilvert</id> <organization>JBoss Foundation</organization> <roles> <role>Java Developer</role> </roles> </developer> <developer> <name>Felipe Leme</name> <id>felipeal</id> <organization>Apache Software Foundation</organization> <roles> <role>Java Developer</role> </roles> </developer> </developers>and also organization, description and inceptionYear:Listing 9.6 Description elements to the pom.xml <description> “JUnit in Action II” book, the sample project for the “Running Junit tests from Maven” chapter. </description> <organization> <name>Manning Publications</name> <url>http://manning.com/</url> </organization> <inceptionYear>2008</inceptionYear> But let’s move on and start developing our software. We want to use our favourite JavaIDE – Eclipse or IntelliJIDEA. No problem – Maven offers additional plugins to import theproject in your favourite IDE. For instance we will use Eclipse to show you how this importhappens. Again, open a terminal and navigate to the directory that contains your projectdescriptor (pom.xml). Once there type the following and hit Enter: mvn eclipse:eclipse -DdownloadSources=true©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 This will invoke the maven-eclipse-plugin which on its turn, after downloading thenecessary artifacts will produce the two files (.project and .classpath) that Eclipseneeds in order to recognize your project as an Eclipse project. The downloadSourcesparameter that we specify in the command line is optional. By using it we instruct the pluginto download also source attachments. There is also ability to download the JavaDOCattachments by setting the optional downloadJavadocs parameter with a true value onthe command line. Now you can go on and import your project in Eclipse and have a look atit, you will notice that all of the dependencies that are listed in the pom.xml are now addedto your buildpath. Amazing, isn’t it? To continue let’s generate some documentation for the project. But wait a second, howare 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 thatwe have, we can produce a full functional web-site skeleton. Just type in mvn siteon the command line where your pom.xml is. Maven should start downloading its pluginsand after their successful installation it will produce the nice web-site you can see atfig 9.2.Figure 9.2 Maven produces nice web-site documentation for the project.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 This web-site is generated in Maven’s build directory – another convention. Maven usesthe target/ folder for all the needs of the build itself. The convention goes even under thisfolder - source code is compiled in the target/classes/ folder and the documentation isgenerated in target/site/. After you examine the project you will probably notice that this web-site is more like askeleton of a web-site. That’s absolutely true, still remember that we entered a small amountof data in first place. We could enter more data and web-pages in the src/site and Mavenwill include it in the web-site, thus generating a full-blown documentation.9.3 Introduction to Maven pluginsSo far so good – we have seen what Maven is and how to use it to start a project fromscratch. We have also seen how to generate the project’s documentation and how to importour project in Eclipse. To continue, we can get the source code from the first part of the book and just place itin the src/main/java folder, where Maven expects it to be. Also we can get the tests forthe 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 thetests, and also invoke the tests execution. But first of all we need to clean the project from our previous activities: mvn cleanThis will cause Maven to go through the clean phase and invoke all of the plugins that areattached 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 pluginAs any other build system Maven is supposed to build your projects – compile your softwareand package in an archive. As we mentioned in the beginning of the chapter, every task inMaven in done by an appropriate plugin, the configuration of which happens in the<plugins> section of our project descriptor. To compile your source code all you need to dois invoke the compile phase on the command line: mvn compilewhich 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 compilephase, as already discussed maven will go through the validate phase and will download allof the dependencies that are listed in the pom.xml and will include them in the classpath ofthe project. So after the compilation process is completed you can go to thetarget/classes/ folder and you should see the compiled classes there.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 Let’s move on and try to configure the compiler plugin. Notice the italics in the previoussentence? Yeah, that’s right – we will escape from the “convention over configurationprinciple” and will try to configure the compiler plugin. So far we have used the conventional compiler plugin and it all worked well. But what ifwe need to include the –source and –target attributes in the compiler invocation togenerate class files for specific version of the JVM. We should add the following lines in the<build> section of your buildfile:Listing 9.7 Configuring the maven-compiler-plugin <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.4</source> <target>1.4</target> </configuration> </plugin> </plugins> </build>This is a general way to configure each one of your Maven plugins – you enter a <plugins>section in your <build> section. There you enlist each of the plugins that you want toconfigure – in our case it’s the maven-compiler-plugin. You need to enter the configurationparameters in the plugin’s configuration section. You can get a list of parameters forevery plugin from the Maven web-site. As you see in the declaration of the maven-compiler-plugin in the previous listing wehave not set the groupId parameter. That is because the maven-compiler-plugin is one ofthe core Maven plugins that has a org.apache.maven.plugins groupId, and as wementioned 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 Mavenuses the maven-compiler-plugin to compile all of the source code that is insrc/main/java/. The exact same thing happens with the process of unit testing yourproject – ant uses the junit task and executes the test-cases that we have selected, whileMaven uses ... guess what – a plugin of course. The Maven plugin executing the unit tests iscalled maven-surefire-plugin. Notice the italics in the previous sentence – the surefire pluginis used to execute the unit tests for your code, but these unit tests are not necessary JUnittests. There are also other frameworks for unit testing and the surefire plugin can executetheir tests, too. Now the conventional way to start the maven-surefire-plugin is very simple – all youneed to do is invoke the test phase of Maven. This way Maven will first invoke all of thephases that are supposed to come before the test phase (validate and compile phases)©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15and then invoke all of the plugins that are attached to the test phase, and this wayinvoking the maven-surefire-plugin. So by calling mvn clean testMaven first cleans the target/ directory, then compiles the source code and the tests andfinally executes all of the tests that are in the src/test/java directory (remember theconvention). The output should be similar to the one shown in figure 9.3Fig 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 toexecute only a single test-case? Well, this is something unconventional so we need toconfigure the maven-surefire-plugin to do it. Hopefully there is a parameter for the pluginthat allows us to specify a pattern of test-cases that we want to get executed. Theconfiguration of the surefire plugin is done in absolutely the same way as the configuration ofthe compiler plugin and it is listed in the following©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Listing 9.8 Configuration of the maven-surefire-plugin <build> <plugins> […] <plugin> <artifactId>maven-surefire-plugin</artifactId> <configuration> <includes>**/Test*.java</includes> </configuration> </plugin> […] </plugins> </build>As you can see we have specified the includes parameter to denote that we want only thetest-cases matching the given pattern to get executed. Yes, but how do we know whatparameters does the maven-surefire-plugin accept? Of course, no one knows all theparameters 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 theJUnit’s XML output. The same thing applies for Maven, too. And since Maven, by defaultproduces plain and XML formatted output (by convention they go in thetarget/surefire-reports folder) we don’t need any other configuration to produceHTML surefire-reports for the JUnit tests. As you already guess, the job for producing these reports is done by a Maven plugin. Thename of the plugin is maven-surefire-report-plugin, and is, by default, not attachedto any of the core phases that we already know (a lot of people don’t need HTML reportsevery time they build their software). This means that we can’t invoke it by running a certainphase (like we did with both the compiler plugin and the surefire plugin), but instead we willhave to call it directly from the command line: mvn surefire-report:reportBy doing so Maven will try to compile the source files and the test-cases, and then invokethe surefire plugin to produce the plain text and XML formatted output of the tests. After thatthe 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 generateddocumentation 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 shownin figure 9.4:©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17Figure 9.4. HTML report from the maven-surefire-report plugin.9.4 The bad side of MavenSo far we have discussed Maven as the tool that is going to take the place of Ant, as asubsequent Ant. But as all the other things in life – it’s not all a bed of roses. Maven has itsbad sides, too. Before you get too excited, just think about it – Maven has been out since2002, 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. Andthe idea behind the project is amazing. However, things seem to break when you need to dosome of the unconventional things. What is great about Maven is that it will set up a frame for you and will constrain you tothink inside that frame – to think the Maven way, and do things the Maven way. When youwork with Maven for a certain period of time, you will inevitably need to copy a file from oneplace to another. Well, I think you will be surprised to see that there is no copy plugin inMaven, on contrast with Ant where we have the copy task. So you have to deal with thesituation and if you investigate further and think “the Maven way”, it may turn out that younever really needed to copy that file. In most of the cases Maven won’t let you do any nonsense. It will restrict you and showyou the way things need to be done. Ant works on the other way – it is a very, very powerfultool, and you can do whatever you need. However it can be dangerous in inappropriate©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009hands. And, again, it’s up to you to decide which one of these tools you want to use. Somecompanies hire build engineers, that are considered to have the appropriate knowledge, sofor 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 ajob 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 adevelopment environment to build your source code. We discussed in details all of thefeatures of Maven, that make it unique compared to any other build system. We also sawtwo of the Maven’s plugins in details – the compiler plugin and the surefire plugin. Youshould be able not only to start and execute your tests but also to produce, nice HTMLreports of the test results. In the next chapter we close the automation part of the book, by introducing differentcontinuous integration build tools, like CruiseControl and Hudson. We will show you thebenefit of using such a tool, and also how to install and configure each one of these tools.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 10 Continuous integration toolsThis chapter covers Practicing continuous integration Introduction to CruiseControl Introduction to Hudson©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 “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 byusing tools such as Ant and Maven. Our tests were then triggered by the build. Now it’s timeto go to the next level – automatically executing the build and the tests on a regular intervalof time by using some other popular tools. In this chapter we will get to know the paradigmof continuous integration and will show you how to schedule your project to be builtautomatically on a certain period of time.10.1 A taste of continuous integrationIntegrating 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 conceptin the sense that JUnit tests are unit tests – i.e. they test a single component of your projectin 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. Eachdeveloper takes care developing his own module and his own unit-tests to make sure hismodule is well-tested. Different modules interact with each other, so we need to have all the different modulesget assembled to see how they work together. In order for the application to be test-provenwe need another sort of tests – integration or functional tests. As we already saw in chapter3 these tests test the interaction between different modules. But almost always integration tests are time-consuming, and also as a single developeryou may not have all the different modules built on your machine. Therefore it makes nosense to run all the integration tests during development time. That is because atdevelopment time we are only focused on our module and all we want to know is that itworks as a single unit. During development time we care we care mostly that providing theright input data, the module not only behaves as expected, but also produces the expectedresult. Test-driven-development taught us to test early and to test often. Executing all of ourunit, integration, and functional tests every time we make a small change will slow usimmensely. To avoid this we execute at development time only the unit tests – as early andas 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. Thebest is to have them executed in a regular interval of time (say 15 minutes). This way ifsomething gets broken you will hear about it in the next 15 minutes and there is a betterchance for you to fix it.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 DEFINITION 1 : 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 alsothe modules of the system, be prepared and built. After the modules are built and theintegration tests are executed, we would like to see the results of the execution as quickly aspossible. This way we get to the point that we need a software tool to do all of the following stepsautomatically: 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 ahuman executing all these steps and a tool doing so? The answer is: there is no differenceand there shouldn’t be! Apart from the fact that no one can bear such a job, if you take aclose look at the first thing we do in the list above, you see that we simply checkout theproject from the source-control system. We do that as if we were a new member of the teamand we just started with the project – with a clean checkout in an empty folder. Then, beforemoving on we want to make sure that all of the modules work proper in isolation, because ifthey 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 testresults. The notification could be done with an email, or an ICQ message, or simply bypublishing 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 SourceControl System to get the project (1). After that it uses the build tool, that your project isusing, 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Fig 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 thesource-control system before we start building. Otherwise we would waste the CPU power ofour 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’sstart and see what are the open-source solutions we might want to use (there makes nosense of reinventing the wheel from scratch, when there are good tools already made forus).10.2 CruiseControl (CC) to the rescueThe first open-source 2 project we are going to look at is called CruiseControl(http://cruisecontrol.sourceforge.net/) and is currently the de-facto standard when it comesto continuous build process. This project was created inside a company called ThoughtWorksand was actually the first continuous integration server available.10.2.1 Getting started with CruiseControlThe first thing to do before starting to build our code continuously is managing ourresources. 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 separatemachine just for continuous integration. You may not need this at the beginning, but withthe 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 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 thebuilds. Once you find the host for the CruiseControl server, it’s time to install it. The installationprocedure is pretty simple - you go to the CruiseControl web-site(http://cruisecontrol.sourceforge.net/) and download the zip distribution of your choice. Bythe time this book is written the latest version of CruiseControl is 2.7.3 and we are going touse the binary distribution. Once you download the distribution, you need to extract it in a folder (probably the bestplace to extract the zip is in the home folder of the CC user you just created), which fromnow on we will refer as $CC_HOME. In the $CC_HOME folder you should see the content wehave explained in table 10.1.Table 10.1 Default content of the $CC_HOME folderFile/folder Used forapache-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 projectlib Different third-party libraries to the projectlogs 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 youcan see the CruiseControl package comes with an Apache Ant distribution. However, this©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009does not mean that you can’t use Maven as a build system. You can use whichever one youlike more. We will talk further on this topic later on. Another thing that is worth mentioning is the projects folder. This folder is the placeyou store your projects you want to build continuously. If you look in that folder you will seea sample project called connectfour. This is a checked out project and before moving on itis good to take a brief look at it. So now that we have found a host for our CriuseControl installation and we have seenhow 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 projectSetting up a project in CruiseControl is pretty straightforward – all you need to do is threethings: 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, andthat system has the responsibility of managing the version of your software. If you are usingSubversion as your revision control system, you can easily checkout your project by firstnavigating to the $CC_HOMEprojects folder and then executing the following commandthere: svn co http://url.of.your.repository.here/ theNameOfMyProjectYou need to specify the URL of your source repository and also the name of the folder inwhich to checkout the project. After that Subversion will check out a local copy of yourproject in the theNameOfMyProject folder.10.2.3 The CruiseControl config file explainedNow that we have the project checked out in the projects folder it is time to configureCruiseControl to build it the way we want. The configuration itself is done with a file calledconfig.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 <cruisecontrol> (1) <project name="connectfour"> (2) <listeners> (3) <currentbuildstatuslistener file="logs/${project.name}/status.txt"/>©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 </listeners> <bootstrappers> (4) <antbootstrapper (5) anthome="apache-ant-1.7.0" buildfile="projects/${project.name}/build.xml" target="clean" /> </bootstrappers> <modificationset quietperiod="30"> (6) <!-- touch any file in connectfour project to trigger a build --> <filesystem folder="projects/${project.name}"/> </modificationset> <schedule interval="300"> (7) <ant anthome="apache-ant-1.7.0" buildfile="projects/${project.name}/build.xml"/> </schedule> <log> (8) <merge dir="projects/${project.name}/target/test-results"/> </log> <publishers> (9) <onsuccess> <artifactspublisher dest="artifacts/${project.name}" file="projects/${project.name}/target/${project.name}.jar"/> </onsuccess> </publishers> </project> </cruisecontrol>This config file describes the build for the connectfour project. We will walk over it andexplain what the different parts mean, so then later you can modify the script with yourproject’s configurations. You start with a global cruisecontrol tag (1), which is required to be there, andinside that global tag you enlist all the projects you want to build (2). You can have multipleprojects 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 listenerinstances. 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 acurrentbuildstatuslistener 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 fewlines on them. As you have probably noticed building the project on a regular interval of timewastes a lot of resources. CruiseControl is very smart in that way that it does not fire a new©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009build, unless it detects some changes in the source-control system. Otherwise the buildwould be pointless, because we would get the same results. But how do we distinguish ofwhether CruiseControl server was up, and skipped the build because there were no changesin the source-control system, or on the other hand the server was shut? The answer to this isthe 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 listenersare triggered regardless of the state of the source-control system. This way you can keeptrack of whether a build was attempted or not. The next thing in our config.xml is the bootstrappers element (4). This element is acontainer 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 ourconfig file we have specified an antbootstrapper (5) that will simply invoke the cleantarget of our project’s build descriptor, and thus will clean all the resources we have usedduring the previous build. The modificationset element (6) is there to define all the sets of files and foldersthat CruiseControl will monitor for changes. One thing to remember here is that build isattempted only when change is detected on any of the sets listed in modificationsetelement. The schedule element (7) is actually the one that schedules the build. The intervalparameter specifies the build time interval (in seconds), and inside the schedule element welist what is the type of build-system we are using. We need to specify the home-folder of thebuild-system, and also the buildfile to execute. In our config file we have specified ant, butyou 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 executionshould be stored. The merge element inside it, tells CruiseControl what logs of the buildexecution are valuable and should be stored in the log directory of the CruiseControlexecution. In our example we only care for the xml files from the JUnit execution, so we aregoing to store them in the log folder. The last section is the publishers section (9) and it is used to make the final steps ofthe scheduled build. The same ways as bootstrappers are executed every time before thebuild, the publishers are executed every time after the build, regardless of the result of thebuild. In publishers we can specify what whistles to be blown as the build finishes. It couldbe sending an email, publishing the produced artifacts somewhere on the internet, or simplyposting a message on the Jabber Messenger. In the example shown above we publish theresult from the build (the jar file from the target folder) into the artifacts folder. We dothat only in case the result from the execution was successful (see the onsuccesselement). So far so good - we are able to start CruiseControl and according to the configuration inthe 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 theCruiseControl server by issuing the following command:©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 cruisecontrol.bator in case of UNIX 3 : cruisecontrol.shAfter that CruiseControl will be executed and will look for the config.xml file to read. Incase you have done all the things right, after execution you should see something similar tofigure 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 youshould see the build being executed again. Here is an interesting question: how often should I build my projects? This is a toughquestion and it all depends on you and on the time you need to make a new build. Theredoesn’t make much sense to make a build every minute, when you need more than a minuteto execute the build itself, does it? Keep in mind that this is a book about software testingand we all propagate a lot of testing – not only using JUnit, but also using all kinds ofintegration and functional testing tools. This means that usually production builds tend to betime-consuming. CruiseControl also provides you with a way to control your scheduled builds through anice GUI, by starting a Jetty instance. You can check the GUI if you visit thehttp://localhost:8080/ URL in a browser. You should be able to see something like the one3 You need to check that the .sh script has executable modifiers set.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009displayed in figure 10.3. You can see there some nice details on how many builds wereiterated, 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 usmuch data. If we look in the console or the GUI we can see the build is going well, but it is atedious task to look there all the time, isn’t it? What if we had some way to get the resultsfrom the execution straight into our email, or even better - get the email from CruiseControlonly in case things go bad. This is absolutely possible, and all we have to do is add another publisher in ourconfig.xml. The section we want to add is shown in the following listing:Listing 10.2 CruiseControl’s HtmlEmail notification <htmlemail mailhost="your.company.smtp.host" (1) returnaddress=”cruisecontrol@yourcompany.com” (2) buildresultsurl=”http://localhost:8080/buildresults/connectfour/” css="/home/peter/my-very-own-css/cruisecontrol.css" (3) logdir="logs/connectfour"> (4) <map alias="developers" address="connectfour-team@mycompany.com" /> <map alias="manager" address="boss@mycompany.com" /> <always address="manager" /> <failure address="developers" reportWhenFixed="true" /> </htmlemail>The htmlemail publisher defines what notification emails to be sent. We start by definingthe mailhost to use for sending the emails (1), and also from what address the emails arecoming (2). These two, along with the buildresultsurl parameter (the location at which©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11our build results reside) are required and must be present. All the rest parameters areoptional – 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 whatoccasion you want those guys to receive email notifications. By default CruiseControl deliversnotification on both success and failure. But that’s too much for the development team, andthat’s why we have listed them to receive only emails on failure and when the things getfixed. Now it is time to restart the CruiseControl server, and break the build on purpose. Simplycommit 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, butalso who was the last guy to make last commit in the source-control system. So it’s prettyeasy 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 differentlisteners, bootstrappers, to do stuff before the build execution. You can also specify differentpublishers for different ways of notifying the results from the build execution – along withthe htmlemail way we already covered; there is also a publisher to send an instantmessage on the Yahoo! Messenger, or on a Jabber Messenger, or posting the results on ablog and collecting them through an RSS feed.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 Let us now move on and take a look on another continuous integration server calledHudson. After we have covered both of them you can compare them and choose whicheveryou want to use.10.3 Another Neat Tool – HudsonAs we mentioned in the beginning of the chapter CruiseControl was probably one of the firstcontinuous integration servers ever available. But there are a whole bunch of other softwaretools out there, trying to compete with CruiseControl by introducing some interesting new 4features. Some of those tools are not even free (like AntHill Pro, BeetleJuice or Cruise 5 ),and those include not only the product you purchase, but also different trainings andsupport. For the sake of completeness we need to cover another tool. This way you can choosewhichever you like. Just remember – your software quality will not improve from the tool youchose to use, but rather from the fact that you decided to practice continuous integration!10.3.1 Introducing HudsonHudson (http://hudson.dev.java.net/) is an open source project for continuous build. Likeany other software for continuous build it lies on the idea of being able to continuously pollthe source code from the Source Control System and in case changes were detected fire up abuild. Why do we cover it in this chapter? First of all because it grew a lot popular, andsecond of all it is very different from CruiseControl we already saw.10.3.2 InstallationBefore installing Hudson make sure you have J2SE version 1.5 or higher already installed.Also make sure your JAVA_HOME environment variable points to the place you have installedJava. The installation procedure itself is very easy. You go to the project’s web-site anddownload the latest version of Hudson. By the time of writing this book the latest version is1.262. Hudson distribution comes as a single war file, as opposed to CruiseControl where thedistribution is a zip. You don’t need to extract the war file, because Hudson comes with aWinstone servlet-container, so you can simply start the server from the command line withthe following command: java –jar hudson.war 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 Now in order to start using the server, you need to navigate to the http://localhost:8080/URL. In case no errors occurred you should be able to see something similar to what isshown 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 redefinethe port on which the server is started or the root under which the application is started. Also - if you don’t want to use the Winstone servlet container, you can use any otherservlet container you want. If you stick with that solution you will be forced to follow theinstallation procedures specific to the Servlet container you use.10.3.3 Configuring HudsonHudson’s feature over CruiseControl is easier configuration. Configuration is all done throughthe web interface. And now that you have already installed Hudson it is time to start theconfiguration. Open a browser and navigate to the http://localhost:8080/ URL. You shouldsee 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 ofthe 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Figure 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. Allthis 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 ofHudson is an interesting creature so we will spend a subsection on it.HUDSON HOME DIRECTORYThe home directory of Hudson is used to maintain the source, perform builds and keep somearchives there. By default it is located in $USER_HOME/.hudson ($USER_HOME isinterpreted as /home/<username> in the UNIX systems and as C:Documents andSettings<username> 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 structuresimilar to this one:Listing 10.3 Hudson home directory folder structure [HUDSON_HOME]©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 +- 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 thatyou 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 thepath to your Ant installation (in case the project you want to build uses Ant) or your Maveninstallation (in case your project is being built by Maven). You can also specify a JDKinstallation, 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 yourbuild.xml files, but instead you point to the place where Ant was installed, so that later onHudson 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 restyou can move on and configure a new job. To configure a new job with Hudson, firstnavigate to the main screen and select the “New job” link from the list on the left side. Afterthat you will be presented with a sample form to fill-in. You need to specify a name for thejob, 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Figure 10.6 Job-configuration screen in Hudson Here you are given the ability to configure the way you want to build your job. The firstlines you use to specify/change the name and the description of the job. After that there arealso 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 totrigger your build. You are presented with several options – poll the SCM system to check ifbuild is needed, or build the project periodically, or build it after some dependent projectswere built, etc. Let us select the “Poll the SCM” trigger – a field opens where we need tospecify on what interval of time we want the poll to happen. This field uses a very nicesyntax that follows the syntax of the UNIX cron tool. We would like to have our project getexecuted every hour so we simply specify @hourly in the field. You can learn more aboutthe 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 ashell script, or Windows batch file, or Ant build file, or Maven build file. On any of these youcan 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 allthese steps by dragging and dropping. The last section is to configure the post-build triggers. The options listed here will helpyou to publish the artifact, or publish the javadoc, or build some other project, or send anemail with the build results, or anything else you need. There is also an option of selectingmultiple triggers.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 After all this you can go on and save the job-configuration. This will lead you to theproject’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 themenu on the left side you can select to see the changes one has made on the job, inspectthe 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 rightnow. 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. Thereyou can see not only when the last build was run, but also when the last successful buildhappened. 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).©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Figure 10.8 Hudson build results After spending some time using Hudson you will probably find it a lot easier to use – itsentire configuration is done through a nice web-interface, and it is relatively easy. But thenicest thing is that the web-interface is very intuitive. That is why we don’t cover Hudson indetails. Another reason for this is that Hudson currently undergoes a very rapiddevelopment. It is a very mature project with a large community of developers thatconstantly improve the codebase – from that point of view it is very interesting to see howthe project will evolve in time.10.4 Benefits of continuous integrationAs a general benefit the software tests are there to help you find your own errors. Youexecute the unit tests every time you have made any changes on the code you develop. Thisway they cover your back and will alert you whenever you introduce a new bug into thesystem. The continuous integration servers serve exactly the same reason – they cover your backand 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 thesystem, they run on a separate host and try to continuously integrate your software. Butagain – 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 shouldnt use CI tools,and looks like the winner is this one – “I don’t see the profit in using it.” And usually thiscomes 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 nomatter how good you are, you will sometimes make a mistake and introduce a bug into thesystem. Knowing this it seems reasonable to have something to prevent us from errors by©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19notifying us when those errors occur. The software tests and CI tools are exactly this. Onceyou 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 emptymailbox 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 SummaryIn this chapter we looked at two of the most popular CI tools out there – CruiseControl andHudson. As you probably saw, they are totally different. So why do we cover exactly thosetools? To make you understand that continuous integration is a very important concept in themodern software development lifecycle, and that there is absolutely no difference whichevertool 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 thebuild process. You should be now ready to run your build and execute your tests with Ant orMaven, and not only that, but also to set up a continuous integration build and execute yourbuilds and tests on a scheduled basis. This way you have JUnit tests that keep your modules from new bugs, and you have alsoa workspace where you execute continuously your build and run your tests to see if anythinggot broken during the integration time. You are now fully automated and ready to move onwith the next chapters. The next part of the book deals with testing different layers of your application. We willlook at some examples on how to execute tests against the presentation layer and also onthe database and persistence layer. We will see a way to test your GUI components, and ofcourse, you can include those tests and run them in the continuous integration environmentyou just learned about. Let’s move on!©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 Part IV JUnit extensions This last part of the book deals with different kinds of JUnit extensions. We cover all kindsof external projects that try to extend JUnit to the point where the testing framework comesshort. 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 detailsof not only how to setup your projects but also some best-practices in testing yourpresentation layer. Chapter twelve is dedicated, again, on testing your frontend. In thatchapter we discuss on how to test the AJAX part of your application. We also discuss on howto test GWT applications with tools like Mozilla MCP. On testing your server-side Java code is dedicated chapter thirteen and fourteen. Thefirst one introduces the Cactus project which is dedicated on testing your J2EE corecomponents (JSPs, Servlets, EJBs, …). Chapter fourteen describes the JSFUnit project, whichtests your JSF-based applications. Chapter fifteen is a small chapter that scatters around testing component-orientedapplications. We describe different techniques for testing your OSGi services. Chapter sixteen and seventeen are all dedicated on testing the persistence layer. Thesechapters talk about the DBUnit project and give recipes of how to test JPA part of anapplication. The last chapter describes on how to create your own extensions by means of the Unitilsand JUnit-Addons projects.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 11 Presentation Layer TestingThis chapter covers: • Introducing presentation layer testing • Testing with HtmlUnit • Testing with Selenium©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009If debugging is the process of removing software bugs, then programming must be theprocess 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 otherapplication tiers. A bad user experience can lose a customer or discourage a web surfer fromvisiting your site again. Furthermore, bugs in the user-interface may cause other parts of theapplication to malfunction. Due to its nature and interaction with a person, GUI testing presents unique challengesand requires its own set of tools and techniques. This chapter will cover testing webapplication user interfaces. We address here what can be objectively, that is programmatically, asserted about theGUI. Outside the scope of this discussion are whether the choice of subjective elements likefonts, 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 includespelling), the application structure or navigation (following links to their expected destinationfor example), and the ability to verify user-stories with acceptance tests 1 . We can also verifythat 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 withinJUnit: HtmlUnit and Selenium. HtmlUnit is a 100% Java headless browser framework that runs in the same virtualmachine as your tests. Use HtmlUnit when your application is independent of operatingsystem features and browser specific implementations of JavaScript, DOM, CSS, etc, notaccounted for by HtmlUnit. Selenium drives various web browsers programmatically and checks the results fromJUnit. Selenium also provides a simple IDE to record and playback tests and can generatetest code. Use Selenium when you require validation of specific browsers and operatingsystems, especially if the application takes advantage of or depends on a browser’s specificimplementation 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 imitateprogrammatically the user of a browser-based web application. HtmlUnit tests do not displaya user interface. The framework lets you test all aspects of a web application, we will1 Extreme programming acceptance tests http://www.extremeprogramming.org/rules/functionaltests.html©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3describe here the most common tasks, for the rest, you will find the API quite intuitive andeasy to use. In the remainder of this HtmlUnit section when we talk about “testing with a webbrowser,” it is with the understanding that we are really “testing by emulating a specific webbrowser.” 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 theinternet. We will go to the HtmlUnit web site, navigate the Javadoc, and make sure a classhas 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 fromthe 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). Thisgives us a new page for the link we clicked, which we then query for the first paragraphelement (5). Finally, we check that the paragraph starts with the text we expect should bethere (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 thatnavigates the HTML model, if HtmlUnit does not find an element or encounters a problem; itwill throw an exception on our behalf.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/200911.3 Writing HtmlUnit tests When you write an HtmlUnit test, you write code that simulates the action of a usersitting in front of a web browser: you get a web page, enter data, read text, and click onbuttons and links. Instead of manually manipulating the browser, you programmaticallycontrol an emulated browser. At each step, you can query the HTML object model and assertthat values are what you expect. The framework will throw exceptions if it encounters aproblem, 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 failwhen 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 standardassertions for HTML like assertElementPresent, assertLinkPresent andassertTextPresent. HtmlUnit itself uses WebAssert notNull extensively to guardagainst null parameters. Make sure to check the WebAssert class before you write code thatmay duplicates its functionality. If a method you need is absent, you should consider creating your own assert class foradditional HTML assertions. You should also consider creating an application specificassertion 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 ConstantFirefox 2 BrowserVersion.FIREFOX_2Firefox 3 BrowserVersion.FIREFOX_3Internet Explorer 6 BrowserVersion.INTERNET_EXPLORER_6Internet Explorer 7 BrowserVersion.INTERNET_EXPLORER_7Table 11.1 HTMLUnit supported browsers. By default, WebClient emulates Internet Explorer 7. In order to specify which browserto 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 InternetExplorer and Firefox. For our purposes, we define our test matrix to be all HtmlUnitsupported web browsers.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 The following example uses the JUnit Parameterized feature to drive the same test withall 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) @Parameters public static Collection<BrowserVersion[]> 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 theParamerized JUnit test runner (1). We have added the method getBrowserVersions (2) toreturn a list of BrowserVersion objects corresponding to the browsers we want to test. Thesignature of this method must be @Parameters public static java.util.Collection,without parameters. The Collection elements must be arrays of identical lengths. Thisarray length must match the number of arguments of the only public constructor. In ourcase, 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). JUnitloops for each array in the getBrowserVersions collection (3). JUnit calls the only publicconstructor (4). If there is more than one public constructor, JUnit will throw an assertionerror. JUnit calls the constructor (4) with an argument list built from the array elements. Inour 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 inthe getBrowserVersions collection (2). When you compare the test results with the previous example, you see that instead ofrunning one test, the parameterized JUnit test runner ran the same method four times, oncefor each value in our @Parameters collection.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/200911.3.4 Creating stand-alone tests You may not always want to use actual URL addressed pages as test fixtures, http, file, orotherwise. 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 mock 2 HTTP connection into a web client. In thenext example, we set up a mock connection with a default HTML response String. The testcan 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 = "<html><head><title>" + expectedTitle + "</title></head></html>"; 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 andHTML test fixture. Then we create the web client, a MockWebConnection (1), and install theHTML fixture as the default response for the mock connection (2). We can then set the webclient’s connection to our mock connection (3). We are now ready to go, and we get the testpage. 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 MockWebConnectionsetResponse methods for each page. The next example sets up three web pages in a mockconnection.Listing 11.4 Configuring a test with multiple page fixtures @Test public void testInLineHtmlFixtures() throws Exception { WebClient webClient = new WebClient(); 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©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7 "<html><head><title>Hello 1!</title></head></html>"); (1) conn.setResponse(page2Url, "<html><head><title>Hello 2!</title></head></html>"); conn.setResponse(page3Url, "<html><head><title>Hello 3!</title></head></html>"); 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 tonavigate 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 apage, 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 insections below.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/200911.3.7 Accessing elements by name vs. index This discussion applies to all methods that return a List: getAnchors, getForms, andgetFrames. You should consider the implication of addressing these lists with indices. Forexample, 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 testwill always be the first form in the list. If the page changes and the search form changesposition, your test will fail, even though the page’s functionality may not have changed. Byaddressing the form by index, you are explicitly testing the form order on the page. Onlyaddress an element through a list index if you want to test the order of an element in thatlist. 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 onform position 0. The benefit is that when you change the form order on a page, the formname 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 ananchor 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 alsolets you get to any element by name, id or access key with any the methods starting withgetElementBy like getElementById, getElementsByName and others. These methods allowyou 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. Notetwo changes in the code: First, we cast the result to the desired type unless you can workwith 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 If you can address the desired element by ID, you can use getElementById and do awaywith the get call. Of course, calling get introduces some brittleness to this test since we are introducing adependency on the list order. If we wanted a more resilient test, and the element did notcontain 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 willallow 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 HTMLelement “a”, the class is HtmlAnchor, for “h1”, the class is HtmlHeading1.11.3.9 Using XPath Use XPath 3 for complex searches to reduce test code complexity. XPath is a languagespecified by the W3C for querying nodes in an XML document. We will not cover the XPathlanguage 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 elementsand getFirstByXPath returns a single element. Since DomNode implements both methods, itis accessible not only to HtmlPage but to all DomNode subclasses, which includes the HTMLclasses. Knowing what XPath expression to use can involve a lot of trial and error. Fortunately,you can inspect any web site with XPath Checker 4 or Firebug 5 , free open source Firefox add-ons, and create an XPath from the current selection. For example, to access the text inputfield 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 thesame 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 resilientto changes in the page: //input[@name=q]3 XPath 1.0: http://www.w3.org/TR/xpath4 XPath Checker: http://slesinsky.org/brian/code/xpath_checker.html5 Firebug: http://getfirebug.com/©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009We all know there is no such thing as a free lunch 6 , and this expression’s gain in pagechange resilience and brevity comes with a small performance price: XPath must find allinput elements on the page that match the criteria [@name=q] and then give us the firstone. This is in contrast to the first expression which just drills down to a know spot in thepage 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, theability to collect data.DATA GATHERING WITH XPATH An extremely powerful feature of XPath is its ability to return a set of nodes. This featureallows us to perform, with one expression, a query that returns a data set. This is a greatway to gather values on a page, whether or not the values are formally present in a list-likestructure like an HTML table, list, or form. For example, this expression returns an anchor list from the Java 6 Javadoc page for allpackage 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<DomNode> anchors = (List<DomNode>) packageListPage.getByXPath("//a[contains(@href, package-frame.html) and @target=packageFrame]"); Beware that there is an XPath version 1.0 7 and 2.0 8 specification. HtmlUnit includesApache Xalan XPath implementation, which only supports 1.0. If you want to use XPath 2.0features, you need to get an XPath 2.0 engine, which usually means an XSL 2.0 engine, likeSaxon. You will also need to write some code, an advanced endeavor.6 TANSTAAFL: http://en.wikipedia.org/wiki/TANSTAAFL7 XPath 1.0: http://www.w3.org/TR/1999/REC-xpath-199911168 XPath 2.0: http://www.w3.org/TR/xpath20/©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1111.3.10 Test failures and exceptions Tests check for error condition with the JUnit Assert class, the HtmlUnit WebAssert classand by letting the HtmlUnit API throw unchecked exceptions. We have already covered theWebAssert class in the section HTML Assertions. For example, if you query for a form with aninvalid 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 packagecom.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 yourmethods but you will need to remember to catch them if you want to examine the particularstate of a failure. For example, the exception ElementNotFoundException contains specificinformation as to what exactly caused the failure: the name of the element, the name ofattribute, and the attribute value. While not explicitly documented in the WebAssert Javadoc, WebAssert methods willthrow exceptions for unexpected conditions. In fact, many WebAssert methods throwElementNotFoundException.JAVASCRIPT AND SCRIPTEXCEPTION By default, all JavaScript errors will throw a ScriptException and cause your unit test tofail. This may not be acceptable, especially if you are testing integration with third party sitesor if the exception is due to a shortcoming in the Mozilla JavaScript library or in HtmlUnititself. You can avoid aborting your unit test on a JavaScript error by callingsetThrowExceptionOnScriptError 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 canperform all forms of navigation. Let us look at various types of navigation.PAGE NAVIGATIONGetting a page is performed with the WebClient getPage() methods. You can get a page byURL or URL String. For example:©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 Testfailures 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 clickon 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 fromClickableElement. Click methods simulate clicking on an element (remember HtmlUnit is anemulator) and return the page in the window that has the focus after the element has beenclicked. The HTML 4.01 specification 9 defines "clickable" HTML elements. ClickableElement isthe 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 Javadoc 10 or select ClickableElement in Eclipse and hit F4 todisplay the class hierarchy.KEYBOARD NAVIGATION To simulate the user hitting the Enter key instead of clicking the Search button, replacegetting 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 tabbingaround the page with the HtmlPage methods tabToNextElement andtabToPreviousElement. Hitting the Enter key or any key may not be enough or the rightprocess to test. You can set the focus to any element with the HtmlPage method9 http://www.w3.org/TR/html401/10 http://htmlunit.sourceforge.net/apidocs/com/gargoylesoftware/htmlunit/html/ClickableElement.html©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13setFocusedElement. Be aware that this will trigger any onfocus and onblur eventhandlers. 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 accessedwith getForms (returns List<HtmlForm>) to get all form elements and getFormByName to getthe first HtmlForm with a given name. You can call one of the HtmlForm getInput methodsto 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, wecreate a simple page to display a form with an input field and submit button. We includeform validation via JavaScript alerts in the example as a second path to test, which isdescribed in the section Testing JavaScript Alerts.Listing 11.5 Example form page <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <script type="text/javascript"> function validate_form(form) { if (form.in_text.value=="") { alert("Please enter a value."); form.in_text.focus(); return false; } } </script> <title>Form</title></head> <body> <form name="validated_form" action="submit.html" onsubmit="return validate_form(this);" method="post"> Value: <input type="text" name="in_text" id="in_text" size="30"/> <input type="submit" value="Submit" id="submit"/> </form> </body> </html> This form looks like the following when you click the button without input.Figure 11.1 Sample form and alert©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009We 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 theform, and get the form. Next, we get the input text field from the form, emulate the usertyping in a value (1), then get and click the submit button (2). We get a page back fromclicking 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 andyour test automatically fails. This allows you to focus on the test and let the frameworkhandle failing your test if the page or form is not as expected. The section Testing JavaScriptAlerts completes this example.11.3.13 Testing frames HTML frame support is built into the HtmlPage API where frames can be accessed withgetFrames (returns List<FrameWindow>) to get all iframes and frames andgetFrameByName to get the first iframe or frame with a given name. You then callFrameWindow getEnclosedPage to get the HTML page in that frame. The following examplenavigates 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();©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 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. Testcan use the list API getFrames as well but we point you to the issues discussed in Accessingelements 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 APIsto 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 generatedwith Document.write(), you follow the usual pattern: call getPage, find an element, callclick on it, and check the result. You can toggle JavaScript support on and off in a web client by calling setJavaScriptEnabled. HtmlUnit enables JavaScript support by default. You can also set how a long ascript is allowed to run before being terminated with setJavaScriptTimeout and passing it atimeout in milliseconds. To deal with JavaScript alert and confirm calls, you can provide the framework withcallbacks routines. We will explore these next.TESTING JAVASCRIPT ALERTS Your tests can check which JavaScript alerts have taken place. We will re-use our formexample from Testing forms, which includes JavaScript validation code to alert the user ofempty input values. The following test loads our form page and checks calling the alert when the form detectsan error condition. In a second example, we will enhance our existing test from Testingforms to ensure that normal operation of the form does not raise any alerts. Our test willinstall an alert handler that gathers all alerts and will check the result after the page hasbeen loaded. The stock class CollectingAlertHandler saves alert messages for laterinspection.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)©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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<String> collectedAlerts = alertHandler.getCollectedAlerts(); (4) List<String> 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, getthe submit button and click on it (3). This invokes the JavaScript, which calls alert. Clickingthe button returns a page object, which we use to check that the page has not changed bycomparing current and previous page titles. We also check that the page has not changed bycomparing 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 afuture version of the framework implements equals in an unexpected manner. Finally, weget 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 noalerts.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");©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 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 aCollectingAlertHandler in the web client; we simulate a user entering a value (2) and atthe 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. Thefollowing 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 confirmhandler 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. Bydefault, HtmlUnit enables CSS support. When calling APIs, the standard HtmlUnit behavior is to throw an exception whenencountering a problem. In contrast, when HtmlUnit detects a CSS problem, it does notthrow an exception; instead, it reports problems to the log through the Apache CommonsLogging 11 library. You can customize this behavior in a WebClient with aorg.w3c.css.sac.ErrorHandler. There are two ErrorHandler implementation providedwith 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. Forexample, 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 ofyour testing, you can call WebClient.setUseInsecureSSL(true) to allow the test to11 Apache Commons Logging: http://commons.apache.org/logging/©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009proceed. Using this API causes HtmlUnit to use an insecure SSL handler, which trustseveryone. 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 Cactusframework.11.4 Integrating HtmlUnit with Cactus Cactus 12 is a free open-source test framework for unit testing server-side Java codeincluding Servlets, EJBs, and much more. Chapter 13 Server-side Java testing with Cactusdiscusses Cactus in detail. Where does HtmlUnit fit in? Let us look at the various opportunities to test an applicationfrom 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 aweb browser to test your application. Cactus unit testing manages a different interaction; Cactus calls your HtmlUnit unit testsat just the right time to verify the web pages returned to the client. The main difference hereis that HtmlUnit unit testing takes place in-container instead of through an emulated webclient. To install HtmlUnit in Cactus, see Appendix E: Installing Software. For details onmanaging 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 theCactus test execution at the point where a page is about to be returned to the client. Cactustests for Java-based code like Servlets are subclasses oforg.apache.cactus.ServletTestCase. If the test class contains a method whose namestarts with end, Cactus will call this method with a WebResponse, which contains thecontents of the server’s response. Take great care to import the appropriate WebResponseclass 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 itselfYour boilerplate test class should look as follows.Listing 11.11 Boilerplate HtmlUnit ServletTestCase subclass12 Cactus site: http://jakarta.apache.org/cactus/©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 […] 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("<html><head><title>Hello World</title></head><body><p>Hello World</p></body></html>"); } } This servlet returns an HTML document with a title and a single paragraph. The next stepis to flesh out our end method; we need get an HtmlPage from the WebResponse argument©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009and validate its contents. Getting an HtmlPage from a WebResponse requires parsing theHTML. 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, whichrequires 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 straightforward, so we invite you to explore the rest on your own. Let us now look at Selenium, atesting framework that differs from HtmlUnit in a fundamental way: instead of emulating aweb browser, Selenium drives real a web browser process.11.5 Introducing Selenium Selenium 13 is a free open source tool suite used to testweb applications. Selenium’s strength lies in its ability to run tests against a real browser ona specific operating system, this is unlike HtmlUnit, which emulates the browser in the sameVM as your tests. This strength comes at a cost: the complexity of setting up and managingthe Selenium runtime. While Selenium provides many components, we will consider thefollowing 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 inmany languages including Java. The Selenium client driver API is what tests call to drive theapplication, it communicates to the Remote Control Server, which in turns drives the webbrowser.13 Selenium site: http://seleniumhq.org/©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 21Figure 11.2 The main Selenium IDE window. The client driver connects to the Server over TCP/IP; the Server does not need to run inthe JVM, or even on the same physical machine. Selenium recommends 14 running the Serveron many different machines, with different operating systems and browser installations. Atest connects to a server by specifying a host name and port number to theDefaultSelenium class. Figure 11.2 shows the main Selenium IDE (1.0 Beta 2) window. The IDE generates codeagainst the client driver API but does not support change management. You should considerthe IDE a one-way, use-once tool you to get yourself started for any given test case. Youmust 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 IDErecords 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 IDEto generate an assertion from the browser’s context menu; the current web page selectiondetermines the choices. Figure 11.3 shows an example context menu.14 Selenium Server set up: http://seleniumhq.org/documentation/remote-control/languages/java.html©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Figure 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 andJava 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: Goto 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 setUpmethod with the base URL for the tests and the browser launcher to use, in this case,©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 23Internet Explorer (see Testing for a specific web browser for other settings.) This initializesthe selenium instance variable to a DefaultSelenium instance. In the test method, we start by opening the home page (2) and check that we landed onthe right page by verifying the page title. Next, we set the value of the input field (3), as if auser typed it in and we click on the Search button (4). The click argument is a Seleniumlocator, which here is simply the button name (more on the locator concept later.) We waitfor 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 openedpage’s title. Selenium tests subclass SeleneseTestCase, which in turn subclasses JUnit’s TestCaseclass. You will note that methods are not annotated; Selenium generated tests are JUnit 3tests. The immediate issue raised by running within the JUnit 3 framework is the performanceof a test class. Each time JUnit calls a test method, JUnit also calls the setUp and tearDownmethods, this means starting, and stopping a web browser, which is very slow. We remedythis performance problem in the section Selenium and JUnit 4. Another issue to consider when using the Selenium IDE is that you are recording tests inFirefox 3. If your browser requirements are different from Firefox 3, what you recorded maynot 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 containscripts to customize behavior based on the host browser. Server-side code can customizereplies based on the agent making the request. Consider these issues before generating codefrom Firefox with the Selenium IDE; you may need to write the tests from scratch, a laHtmlUnit, if your application has code paths for a non-Firefox browser, like Internet Exploreror 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 theset up and mechanics of running Selenium tests: managing a Selenium server andintegrating Selenium with JUnit 411.7.1 Managing the Selenium Server To run Selenium tests, you must use the Selenium server included in the SeleniumRemote 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©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com24 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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, forexample: selenium-remote-control-1.0-beta-2selenium-server-1.0-beta-2. Assumingthe JVM is on your PATH type the following: java -jar selenium-server.jarYou 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 windowsopen and close. The first will contain the tested application, the second will displaycommands 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 Seleniumserver from these tools. We recommend that you manage the server from the test class orsuite directly as we will see next. This allows you, as a developer, to run the tests directlyfrom 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, thereis no out-of-the-box integration with JUnit 4. This is a problem because the performanceassociated with a default SeleneseTestCase is bad; JUnit starts and stops a browser aroundeach 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 testmethods 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-2selenium-server-1.0-beta-2selenium-server.jar. In our first solution, JUnit starts and stops the server once per class run in @BeforeClassand @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)©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 25 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, thenthe Selenium client (3). The @AfterClass method stops the client, then the server (4). Wecan now run tests from ManagedSeleniumServer subclasses, as this next exampledemonstrates. This class does not subclass SeleneseTestCase to avoid inheriting its setUp andtearDown methods, which respectively start and stop a web browser. If you want to subclassSeleneseTestCase, make sure you override the setUp and tearDown methods to donothing.Listing 11.16 Using a ManagedSeleniumServer public class ManagedTestJUnit4v2 extends ManagedSeleniumServer {©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com26 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 @Test public void testSearch() { // test… } } As a ManagedSeleniumServer subclass, this class only needs test methods. JUnit will callthe @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 andoperating systems, using this class as a super class for tests offers a simple solution to getyou 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 eachtest class. To avoid this you could create a test suite with a first and last test class whichstart and stop the server, but you will need to remember to do this for each suite and youwill also need to share the Selenium server through what amounts to a global variable. Wewill take care of this problem next. Our second solution creates a JUnit Suite class to manage a Selenium server. Thiscustom suite will start the Selenium server, run all the test classes in the suite, and thenstop 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<Runner> (3) runners) throws InitializationError { super(klass, runners); } public ManagedSeleniumServerSuite(Class<?> klass, RunnerBuilder (3)©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 27 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 themethod from the super class and inserted calls to our methods to start (5) and stop (6) theSelenium server. The startSeleniumServer (1) and stopSeleniumServer (2) methods arestraightforward enough. The rest of the code consists of duplicating constructors from thesuperclass (3). This allows us to write our test suite simply and succinctly as follows.Lising 11.18 A managed test suite @RunWith(ManagedSeleniumServerSuite.class) @SuiteClasses( { UnmanagedFirstTestJUnit3.class, UnmanagedFirstTestJUnit4.class }) public class ManagedExampleSuiteTest {} Each test class in the suite is responsible for connecting to the local server. Just checkthat classes, like UnmanagedFirstTestJUnit4, connect to the server with the hostname“localhost” and the default port 4444. You can of course further enhance the suite tocustomize these settings.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com28 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/200911.8 Writing Selenium Tests With an efficient test infrastructure in place, we can now explore writing individual testswith Selenium. We will look at how to test for more than one browser, how to navigate theobject 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 stringsChrome *googlechromeFirefox *firefoxFirefox *firefoxproxy 15 *chromeFirefox Chrome URLInternet Explorer *iexploreInternet Explorer HTML *iehta 16ApplicationInternet Explorer *iexploreproxyOpera *operaSafari *safariSpecific executable c:pathtoabrowser.exe”Table 11.2 Selenium browser launcher strings If you do not call SeleneseTestCase’s setUp(String,String), the default web browserlaunch 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. Forexample: selenium = new DefaultSelenium("localhost", 4444, "*iexplore", "http://www.google.com/"); Note that experimental 17 browser launchers exist for elevated security privileges andproxy injection.11.8.2 Testing more than one web browser We can apply the same JUnit @Parameterized feature we used with HtmlUnit in order torun the same test class with more than one browser. Next, we rework our previous examplewith class level and instance level JUnit initialization in order to combine the ability to run alltests with one client driver instance and then repeating the test for different browsers.15 Chrome URL: http://xulplanet.com/tutorials/xultu/chromeurl.html16 IE HTML Application: http://msdn.microsoft.com/en-us/library/ms536496(VS.85).aspx17 Experimental browser launchers: http://seleniumhq.org/documentation/remote-control/experimental.html©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 29Listing 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<String, Selenium> 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<String, Selenium> getSeleniumMap() { if (SeleniumMap == null) { SeleniumMap = new HashMap<String, Selenium>(); } 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 UnmanagedAllBrowsersTest(String browserStartCommand) { this.browserStartCommand = browserStartCommand; } @Before (8) public void setUp() throws Exception { this.selenium = getSelenium(this.browserStartCommand); }©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com30 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 @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 asmany 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 wereturn 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 itall over again with "*firefox". You will need to have both browsers installed on yourmachine for this to work. Let us walk through this JUnit subtlety more carefully. When running the test class, JUnitcreates test class instances for the cross product of the test methods and the test collectionelements. One instance of the class is created for "*iexplorer" and for a single @Testmethods in the class. JUnit runs that @Test method and repeats the process for all @Testmethods in the class. JUnit then repeats that whole process with "*firefox" and so on forall elements in the @Parameters collection (3). We no longer have an @BeforeClass method, instead we use an @Before method (8) toinitialize the selenium instance variable (7) for each test method. The selenium instancevariable gets its value from a lazy-initialized static variable (2). In fact, this can only work byusing an @Before method and lazy-initializing our client driver. Remember, we want our testclass 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 onedriver in the map at any given time. The getSelenium method (4) can safely stop (5) thecurrent driver when creating a new driver since we know that JUnit finished one of itsparameterized runs. Now that we know how to efficiently run tests for a browser suite, let us survey the APIused to navigate an application.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3111.8.3 Application and internet navigation Unlike HtmlUnit, there is no Selenium HTML object model to navigate; instead, you callthe com.thoughtworks.selenium.Selenium interface, using a locator string to addresselements (see Accessing elements with references.) This interface contains over 140methods and provides all of the services and setting toggles needed to write tests. Whilethere is no object model per se, the API provides some methods to work with certain typesof 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 aString format to address elements with different schemes. The two locators we saw are thedefault scheme, id, and link, used to find anchor elements. The format for API argumentsthat take a locator is LocatorType=Argument. The following table describes the differentlocators and their formats.Locator Type Argument Examplecss W3C CSS2 and CSS3 selectors css=a[href="#AnId"]dom JavaScript expression dom=document.forms[f1].intxtid @id attribute id=AnIdidentifier @id attribute or the first element with identifier=AnId @name attribute is idlink anchor element matching the text link=I’m feeling luckyname First element with the @name name=lucky attributeui 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 withbrowser-specific XPath expressions or call allowNativeXpath(false) to force expressions tobe 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 checkedexceptions, nor do APIs you use to write tests. The generated code and APIs will throw©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com32 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009unchecked exceptions to make sure you tests fail under the proper conditions. Even thoughSelenium defines SeleniumException and SeleniumCommandTimedOutException asunchecked 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 anyother elements, calling APIs for typing, clicking, and pressing keys. The following example will recast the HtmlUnit example from the Testing forms section tothe Selenium API. To remind you, in the HtmlUnit section, we created a simple page todisplay a form with an input field and submit button. We included form validation viaJavaScript alerts in the example as a second path to test, as described in the section TestingJavaScript 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 thesubmit 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 andyour test automatically fails. This allows you to focus on the test and let the frameworkhandle 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 examplefrom Testing forms, which includes JavaScript validation code to alert the user of emptyinput values. The following test loads our form page and checks that the browser raised the alert whenthe error condition occurred. The key method is getAlert, which returns the most recentJavaScript alert message. Calling getAlert has the same effect as clicking OK in the dialogbox.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.",©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 33 selenium.getAlert()); (1) SeleneseTestCase.assertEquals(title, selenium.getTitle()); } Let us step through the example. We open the form page, save the current page title andclick the submit button. This will raise the alert since we did not type in a value. Next, we callgetAlert (1) to check that the correct alert was raised. Finally, we make sure we are still onthe 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 normaloperation of our page. If the test generates an alert but getAlert does not consume it, thenext 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 pages 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 ofSeleneseTestCase. Selenium disables this feature by default, to enable it, callsetCaptureScreenShotOnFailure(true). By default, the screenshot is written to a PNG filein the Selenium server directory with the same name as the test name given to theSeleneseTestCase 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 searchexample 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; }©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com34 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 } 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 Throwableargument and calls the Selenium captureScreenshot method (2). We refactored our testmethod by creating a new method testSearchOnErrSaveScreen (3), removing @Test fromtestSearch and added it to the new method instead. To avoid repeating this code pattern in every method that wants to capture a screenshoton failure requires extending JUnit, which is beyond the scope of this section. This concludes our Selenium survey; next, we contrast and compare HtmlUnit andSelenium before presenting our chapter summary.11.9 HtmlUnit vs. Selenium Here is a recap of the similarities and differences you will find between HtmlUnit andSelenium. The similarities are that both are free and open source and both require Java 5 as theminimum platform requirement. The major difference between the two is that HtmlUnit emulates a specific web browserwhile Selenium drives a real web browser process. When using Selenium, the browser itselfprovides support for JavaScript. In HtmlUnit 2.5, Mozilla’s Rhino 18 1.7 Release 2 engineprovides 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 buildprocess, and Cactus can integrate HtmlUnit code for in-container testing as can otherframeworks. HtmlUnit provides an HTML object model, which can validate web pages to thefinest level of detail. HtmlUnit also supports XPath to collect data; Selenium XPath support islimited to referencing elements. The Selenium pros are that the API is simpler and drives native browsers, whichguarantees that the behavior of the tests is as close as possible to a user installation. Finally:18 Mozilla Rhino: http://www.mozilla.org/rhino/©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 35 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 freeopen 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 completeHTML 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 complementarycomponents. The IDE generates test code, but does not maintain it. The strength of theproduct comes from its architecture, which allows the embeddable Selenium Remote ControlServer to control different browsers on assorted operating systems. The Selenium API ismuch simpler and flat than with HtmlUnit, resulting in more concise test code. Use HtmlUnit when your application is independent of operating system features andbrowser 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 specificimplementation of JavaScript, DOM, CSS, etc. In the next chapter, we will add a layer of complexity by considering Ajax technologies inour applications and test cases.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 12 Ajax TestingThis chapter covers: Introducing Ajax testing Testing the Ajax Stack Testing JavaScript Testing Server Services Testing Google Web Toolkit applications©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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, whichdiscusses presentation layer testing in general and introduces two of the libraries and toolsused in this chapter: HtmlUnit and Selenium. We will describe a divide and conquer approachby breaking up tests into three groups: testing client-side scripts, testing server services andfunctional testing. We will end by looking at the unique testing challenges presented by GWTapplication.12.1 Ajax introduction In 2005, the article A New Approach to Web Applications 1 coined the term “Ajax” todescribe the architecture of a new generation of web application like Google Maps 2 andGoogle Suggest 3 . These new applications were richer, more interactive, and responsive thantheir predecessors. Critically for the user experience, they left behind the need to constantlyreload or refresh an entire web page to keep any portion of its information updated. Whilestill browser-based, these applications started to give the web the look and feel of what hadstrictly been the domain of desktop applications. While Ajax is often associated with its all upper case sibling AJAX, the acronym, it istoday much more than Asynchronous JavaScript and XML. An Ajax application is builtcombining 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 webapplications 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-classicinteraction 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 thepage 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.php2 http://maps.google.com/3 http://www.google.com/webhp?complete=1&hl=en©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 3 Web Server Web Browser Initial Page Page 2 Page 3 Page 4 Page 5 The user The server Each time, The user This is slow clicks to returns a the server waits for and forces get data completely returns a the server the user to and waits. new page. new page. again. wait. Click. Click. Click.TimeFigure 12.1 Web-classic application interactions. In an Ajax application, the page communicates with the server to get data for the part ofthe page that needs updating and then only updates that part of the page. Figure 12.2illustrates this process.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 Web Server Web Browser and Initial Page Ajax Click 1 Click 2 Click 3 Click 4 Click 5 Click to get The A script Click, the The data, the browser sorts data user asks browser page stays redraws a for for more redraws a as is. portion of example. data. portion of the page . the page .TimeFigure 12.2 Ajax web application interactions. The user starts by opening an Ajax application’s start page in a browser; this causes theHTML page to load. The browser displays the HTML using any associated CSS and runsclient-side JavaScript code to set up the page’s event handlers. The page is now ready torespond to user interactions. The user interacts with the page, triggering a JavaScript eventhandler. In an application like Google Suggest, each keystroke creates a server request for alist of suggestions that are displayed a drop-down list box. The JavaScript event handlerbuilds an XHR object and calls the server with a specific request using HTTP. The XHRobjects includes a JavaScript callback function the browser will invoke when results areready. The server processes the request and returns a response using HTTP. The browserinvokes the XHR callback and uses the data returned by the server, in the form of XML ortext, to update the page in the browser. In order to update the page, the callback functionuses 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 isloaded, a user interacts with the page causing another page to load, and then repeats thecycle. 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-memoryDOM of the page, which the browser displays to the user. The challenge in writing tests for the application interaction described above is theasynchronous aspect of HTTP communications and the DOM manipulation by JavaScript©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5code. The difficulty then is how to both drive a self-changing application when those changesare asynchronous to the test itself. In addition to testing the traditional page state described in Chapter 11, you should alsotest an Ajax application’s best practices features 4 like drag and drop, form validation andsubmission, event handling, back button, refresh button, undo and redo commands, fancynavigation, state management and caching, and user friendliness (latency, showingprogress, timing out, and multiple clicks.) Further complicating matters, different implementations of Ajax component technologieslike JavaScript, DOM, and XMLHttpRequest exist in different browsers from different vendors.While various free and open source libraries abstract these differences away, an applicationis nonetheless more complicated to test. You may want to ensure test coverage for all codepaths 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 moremanageable tiers.12.3 Testing Patterns for Ajax Let us now survey the testing patterns we use to verify the various aspects of an Ajaxapplication. We can orchestrate testing with three types of tests: Client-side script unit testing covers the JavaScript scripts running in the browser. Theapplication page hosting the script is not tested. Service testing verifies services provided by the server and accessed from JavaScript XHRobjects. Functional testing drives the whole application from the client-browser and usually endsup 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 wedivide the scripts into two piles: the ones that use XHR to manipulate a DOM and the onesthat do not. While we can test some script functions and libraries independently from their hostingpage, where scripts call XHR objects and modify the DOM of the current page, we need aJavaScript engine and browser; the browser in turn may be emulated or live. For thesescripts, we should use a functional test (see below.) For other scripts, we are testing a library of functions and we prefer to deliver thesefunctions 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, wewant 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 reservedwhen scripts cannot be otherwise verified.4 Ajax in Action: http://www.manning.com/crane/©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 So let us look at JavaScript unit testing. What should you look for in a JavaScript testingframework? From the TDD point of view, the most important feature to look for is the abilityto 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 JavaScriptframeworks that provide creating and running tests by embedding test invocation in an HTMLpage (JsUnit 5 , JsUnitTest 6 , script.aculo.us 7 .) while others do it all from JavaScript(RhinoUnit 8 ). The best integration for our purposes must include support for JUnit. WhileJUnit 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 frameworkintegrated with JUnit that goes one step further by providing advanced Selenium-typedistributed configuration options. JsUnit is to JavaScript testing what Selenium is to web application testing. JsUnit allowsyou to test JavaScripts, from JUnit, by controlling a web browser process on a local orremote 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 XHRobjects. The HTML page and JavaScripts are not tested. Since HTTP is the standard used byXHR objects to communicate with the server, we can use any HTTP client to test the serviceindependently of XHR and the browser. Free open source Java HTTP libraries suitable for unittesting include HtmlUnit, HttpUnit and Apache Commons HttpClient. We will see HttpClient inaction in the section Testing Services with HttpClient. In this chapter, we test server services for Ajax applications, which we distinguish fromtesting web services, a different web standard.12.3.3 Functional testing Functional testing drives the whole application from the client-browser and usually endsup exercising all application layers. As we discussed in Chapter 11, a functional testsimulates a user and checks the state of visited web pages. You can choose to have the testsemulate a web browser (HtmlUnit) or drive a live web browser (Selenium.) As always withbrowser emulation software, the key is how well it supports JavaScript.12.4 Functional Testing with SeleniumWe will now look at testing the application stack by continuing our demonstration ofSelenium started in the previous chapter on GUI testing. We will also show you how to useHtmlUnit to write the same tests. We will show how to deal with the asynchronous aspect of5 http://www.jsunit.net/6 http://jsunittest.com/7 http://script.aculo.us/8 http://code.google.com/p/rhinounit/©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7an Ajax application before going on to test specific components that make up the stack likeJavaScript 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 achange in the DOM. Once a page is loaded in the browser, Selenium sees that version ofpage, to see the updated DOM you must use different API than what we showed you in theprevious 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 inputfield 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 toperform the HTTP XML Request.Listing 12.1 testform.html - A simple form (client) <html> <body> <script type="text/javascript"> function newXHR() { (1) if (window.XMLHttpRequest) { return new XMLHttpRequest(); } else if (window.ActiveXObject) { return new ActiveXObject("Microsoft.XMLHTTP"); } else { alert("This browser does not support XMLHTTP."); } } function setMessage() { (2) var xhr = newXHR(); xhr.onreadystatechange=function() { if(xhr.readyState == 4) { document.helloForm.serverMessage.value=xhr.responseText; (3)©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 } } xhr.open("GET","hello-world.asp", true); xhr.send(null); (4) } </script> <form name="helloForm"><input value="Get Message" type="button" (5) name="getMsgBtn" onclick="setMessage();" /> Message: <input (6) type="text" name="serverMessage" /></form> </body> </html> Let us point out the key elements in the HTML, the form (5) defines a button, which whenclicked, invokes the JavaScript function setMessage (2). The first thing the setMessagedoes is call newHXR (1) which creates a new HTTP XML Request object. setMessage thencalls 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 willneed to create a virtual directory for the example webapp directory and use the IISPermission 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 willuse the same JUnit scaffolding from the previous chapter and first set up a test suite tomanage the Selenium server.Listing 12.3 – ManagedFormTest.java […] @RunWith(ManagedSeleniumServerSuite.class) @SuiteClasses( { UnmanagedFormTester.class }) public class ManagedFormTest { // See annotations. } Listing 12.3 shows the test suite ManagedFormTest that runs the test caseUnmanagedFormTester containing the actual @Test methods. The JUnit test runnerManagedSeleniumServerSuite manages the Selenium server and drives the unit test. Without knowing anything about Ajax, you might create the test method testFormNo inlisting 12.4.Listing 12.4 – UnmanagedFormTester.java bad test method /** * Tests a form. The Selenium server must be managed elsewhere.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9 * <p> * Maven will not pick up this test because it does not start or end * with "Test" or end with "TestCase". * </p> * <p> * To test the form in IE, create a virtual directory in IIS and point your * browser and tests to http://localhost/webapp/formtest.html * </p> * * @author <a href="mailto:ggregory@apache.org">Gary Gregory</a> * @version $Id$ */ public class UnmanagedFormTester { 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 togetText (1). The call takes a Selenium locator to retrieve the content of the element named©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009“serverMessage”. The return value is the empty string (2) because the DOM model in theobject model is not up to date. Perhaps we need to wait for the server to do its work and theresult to come back. Calling waitForPageToLoad does not work since the code does notreload the page, recall that this is an Ajax application; pages do not reload. CallingThread.sleep after clicking the button does not work either. Fortunately, Seleniumprovides a single powerful API just for this purpose: waitForCondition. Here it is inaction.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 towaitForCondition reads: selenium.browserbot.getCurrentWindow().document.helloForm.serverMessage.val ue == Hello World’ The waitForCondition method waits for a given condition to become true or a timeoutto expire. The method takes two arguments; the first is JavaScript code where the lastexpression must evaluate to a Boolean, the second is a timeout String expressed inmilliseconds. 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 requiredsince the Selenium server controlling the web browser will run the JavaScript for you. Incontrast, let us go back to HtmlUnit and see how this test looks in that framework.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1112.5 Functional Testing with HtmlUnitWe return now to testing with HtmlUnit, which we document in Chapter 11. Though theHtmlUnit API is more verbose than Selenium, you write tests entirely in Java. Listing 12.6tests 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 clientand getting the application’s start page (2). We get our button, click on it and the result is anew page (3). From this page, we get the entry field which was updated through DOM by theXHR 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, andcheck 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 fromrun to run. While a simple approach is to sleep the thread for a while, HtmlUnit provides APIsto guarantee that Ajax tests are synchronous and predictable. The example above illustratesthis 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:©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 webClient.setAjaxController(new NicelyResynchronizingAjaxController());The setAjaxController method and the NicelyResynchronizingAjaxControllerclass work together to turn asynchronous Ajax calls into synchronous Ajax calls. The classNicelyResynchronizingAjaxController is the only subclass of the AjaxControllerclass delivered with HtmlUnit. By default, a web client initializes itself with an instance ofAjaxController, which leaves Ajax calls asynchronous. If you want finer grained control over the behavior of tests, the framework providesexperimental 9 APIs to wait for various JavaScript tasks to complete. The methodwaitForBackgroundJavaScript waits for all background JavaScript tasks to completeexecution; defined as tasks scheduled for execution via window.setTimeout,window.setInterval and asynchronous XMLHttpRequest. For example, you callwaitForBackgroundJavaScript after an action invokes a script with a timeout inmilliseconds: 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 youhave an idea of when your background JavaScript is supposed to start executing, but you arenot necessarily sure how long it will take to execute, use the methodwaitForBackgroundJavaScriptStartingBefore. You should use the wait methodsinstead of the methods internal to HtmlUnit provided by JavaScriptJobManager. We will move on now to testing other elements of the Ajax stack, JavaScript and webservices.12.6 Choosing a JavaScript Testing Framework Here, you face the same choice you had between HtmlUnit and Selenium: do you want toemulate a browser or drive a live browser? We will look next at two JavaScript testingframeworks, RhinoUnit and JsUnit. RhinoUnit is like HtmlUnit, a 100% Java solution, andJsUnit 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 inJavaScript and invoke them from Ant with the help of the Java Scripting framework and theMozilla Rhino JavaScript engine included in Java 6. If you are on older versions of Java, youwill need the Apache Bean Scripting Framework 10 (BSF) and the Mozilla Rhino JavaScriptengine as documented in Appendix E: Installing Software. As a bonus, RhinoUnit includesJsLint 11 , which allow you to check from Ant that your JavaScript code follows best practices.9 HtmlUnit 2.5 Javadoc warns that these APIs may change behavior or may not exist in future versions.10 http://jakarta.apache.org/bsf/11 http://www.jslint.com/©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13Download and unzip RhinoUnit 12 , if you are on Java 6, you are done; if not some additionalsteps 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 toeval. Note that the path to the file is relative to where we are running the test from; in thiscase, it is the project’s root directory. Next we must call testCases passing in test as thefirst variable (2), followed by our actual test functions. You can pass in any number offunctions; not the “,” separating the test functions. For this simple test, we call our factorialfunction from two different tests and make sure that the computation results in the expectedvalue (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 thevalue we are testing, the actual value; and the second value, the predicate, defines theactual test. Here we use eq to test for equality to its argument, 1307674368000. Thegeneral format is: assert.that(actual, predicate) The RhinoUnit site lists 13 the functions you can use in addition to eq; these are the mostwidely used:12 http://code.google.com/p/rhinounit/13 http://code.google.com/p/rhinounit/wiki/APIDescription©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 isnt. 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) andthen 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 orhave not been invoked. In order to run the tests, you will need an Ant build script. Here is the sample build scriptused to run our examples.Listing 12.10 Ant build.xml for JavaScript unit tests <project name="ch13-ajax-rhinounit" basedir="." default="run-all-tests"> <target name="run-all-tests" depends="run-unit-tests, run-js-lint" /> <property name="src.dir" value="src/main/webapp" /> (1) <property name="test.dir" value="src/test/webapp" /> (1) <property name="rhinounit.dir" value="rhinounit_1_2_1" /> (1) <property name="rhinounit.src" value="${rhinounit.dir}/src" /> (1) <property name="jslint.src" value="${rhinounit.dir}/jslint" /> (1) <!-- Requires Java 6 or Java 5 + BSF --> <scriptdef name="rhinounit" src="${rhinounit.src}/rhinoUnitAnt.js" (2) language="javascript"> <attribute name="options" /> <attribute name="ignoredglobalvars" /> <attribute name="haltOnFirstFailure" /> <attribute name="rhinoUnitUtilPath" /> <element name="fileset" type="fileset" /> </scriptdef>©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 <target name="run-unit-tests"> <rhinounit options="{verbose:true, stackTrace:true}" (3) ignoredglobalvars="rhinounit" rhinoUnitUtilPath="${rhinounit.src}/rhinoUnitUtil.js"> <fileset dir="${test.dir}"> <include name="*.js" /> </fileset> </rhinounit> </target> <scriptdef name="jslintant" src="${jslint.src}/jslintant.js" (4) language="javascript"> <attribute name="options" /> <attribute name="jslintpath" /> <element name="fileset" type="fileset" /> </scriptdef> <target name="run-js-lint"> <jslintant options="{eqeqeq : true, white: true, plusplus : false, (5) bitwise : true, evil: true, passfail: false}" jslintpath="${jslint.src}/fulljslint.js"> <fileset dir="${src.dir}" /> </jslintant> </target> </project> 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 itsarguments by loading its source from rhinoUnitAnt.js. We call the script (3) with afileset 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 RhinoUnitweb site for additional documentation. We now move on to checking our code for best practices with JsLint. As we did for theunit 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©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 thischapter 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- rhinounitsrcmainwebappfactorial.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 fordetails. 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 willuse it from Ant to drive a web browser in order to validate the same JavaScript we justtested in the previous section. JsUnit is similar to Selenium in that it controls a web browserand 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 testfrom Ant. JsUnit is included in the jsunit directory in example source for this chapter, fordetails please see Appendix E: Installing Software.Writing JsUnit testsYou write a JsUnit test by creating an HTML page containing JavaScript test functions. Youthen run the test from a web browser. You use HTML only as the container for theJavaScript. Here is our factorial test.Listing 12.12 jsFactorialTests.html©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>JsUnit Factorial Tests - Chapter 12</title> <link rel="stylesheet" type="text/css" href="../../../jsunit/css/jsUnitStyle.css"> (1) <script type="text/javascript" src="../../../jsunit/app/jsUnitCore.js"></script> (1) <script type="text/javascript" src="../../../src/main/webapp/factorial.js"></script> (2) <script type="text/javascript"> function test15() { (3) assertEquals(factorial(15), 1307674368000); } function testRegEx() { (4) var actual = "JUnit in Action"; assertTrue(actual, /in/.test(actual)); assertFalse(actual, /out/.test(actual)); } </script> </head> <body> <h1>JsUnit Chapter 12 Tests</h1> (5) <p>This page contains tests for the Chapter 12 examples.</p> <p>To see the tests, view the source for this page.</p> </body> </html> You define the jsUnit test in the HTML head element and start with the referencesneeded 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 regularexpression (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 listedbelow. Like JUnit, the API defines a version of assert functions with and without a messageargument. The square brackets in the list below denote that the argument is optional. assert([message,] booleanValue) assertTrue([message,] booleanValue) assertFalse([message,] booleanValue)©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 setUpfunction 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 ourprevious test page into a test suite.Listing 12.13 jsUnitTestSuite.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>JsUnit Test Suite - Chapter 12</title> <link rel="stylesheet" type="text/css" href="../../../jsunit/css/jsUnitStyle.css"> <script type="text/javascript" src="../../../jsunit/app/jsUnitCore.js"></script> <script type="text/javascript"> function suite() { (1) var newsuite = new JsUnitTestSuite();©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 newsuite.addTestPage( (2) "../src/test/webapp/jsFactorialTests.html"); return newsuite; } </script> </head> <body> <h1>JsUnit Test Suite for Chapter 12</h1> <p>This page contains a suite of tests for testing JsUnit.</p> <p>To see the tests, view the source for this page.</p> </body> </html> To define a test suite, create a function called suite (1), which returnx aJsUnitTestSuite object. You then build up a suite object by adding test pages or othersuite objects. In our example, we add one page (2), the page we previously definedby callingthe addTestPage function. The rest of the code in this HTML page is the same as ourprevious example with the exception that we do not need to refer to our JavaScript factoriallibrary. 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, andcall 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; }©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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, enterin the “file” entry field a URL (file:// or http://) or file reference (c:pathtoafile) to anHTML test page for a test or a suite, and click the Run button. In this screenshot, we showthe 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 suitejsunit/tests/jsUnitTestSuite.html. The result will show you the ‘green bar’ with90 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 21 Now that we have a manual way to run tests, let us move on to automating tests withAnt.12.8.3 Running JsUnit tests with Ant JsUnit includes the file jsunit/build.xml, which you can use as a template to callJsUnit tests from Ant. The Ant build file will manage web browsers, invoke tests, and createreports. The following build.xml file excerpts invoke our test suite.Listing 12.15 JsUnit build.xml excerpts <?xml version="1.0" encoding="utf-8"?> <project name="JsUnit" default="standalone_test" basedir="."> <property name="jsunit.dir" value="jsunit"/> (1) <property name="browserFileNames" (2) value="c:program filesinternet exploreriexplore.exe"/> <property id="logsDirectory" name="logsDirectory" value="logs"/> (3) <property id="timeoutSeconds" name="timeoutSeconds" value="60"/> (4) <property id="url" name="url" (5) value="http://localhost:8080/jsunit/jsunit/testRunner.html?testPage=http:// localhost:8080/jsunit/src/test/webapp/jsUnitTestSuite.html"/> <property name="bin" location="${jsunit.dir}/java/bin"/> <property name="lib" location="${jsunit.dir}/java/lib"/> <property name="loggingPropertiesFile" location="logging.properties"/> <path id="classpath"> (6) <fileset dir="${lib}"> <include name="*.jar"/> <include name="*/*.jar"/> </fileset> <fileset dir="${bin}"> <include name="jsunit.jar"/> </fileset> </path> <target name="standalone_test" (7) description="Runs tests on the local machine"> <junit showoutput="true" haltonerror="true" haltonfailure="true"> <formatter type="plain" usefile="false"/> <classpath refid="classpath"/> <sysproperty key="java.util.logging.config.file" value="${loggingPropertiesFile}"/> <sysproperty key="browserFileNames" value="${browserFileNames}"/> <sysproperty key="description" value="${description}"/> <sysproperty key="closeBrowsersAfterTestRuns" value="${closeBrowsersAfterTestRuns}"/> <sysproperty key="logsDirectory" value="${logsDirectory}"/> <sysproperty key="port" value="${port}"/> <sysproperty key="resourceBase" value="${resourceBase}"/>©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 <sysproperty key="timeoutSeconds" value="${timeoutSeconds}"/> <sysproperty key="url" value="${url}"/> <test name="net.jsunit.StandaloneTest"/> </junit> </target> <target name="jsunit_self_test" (8) description="Runs JsUnit self-tests on the local machine"> <junit showoutput="true" haltonerror="true" haltonfailure="true"> <formatter type="plain" usefile="false"/> <classpath refid="classpath"/> <sysproperty key="java.util.logging.config.file" value="${loggingPropertiesFile}"/> <sysproperty key="browserFileNames" value="${browserFileNames}"/> <sysproperty key="description" value="${description}"/> <sysproperty key="closeBrowsersAfterTestRuns" value="${closeBrowsersAfterTestRuns}"/> <sysproperty key="logsDirectory" value="${logsDirectory}"/> <sysproperty key="port" value="${port}"/> <sysproperty key="resourceBase" value="${resourceBase}"/> <sysproperty key="timeoutSeconds" value="${timeoutSeconds}"/> <sysproperty key="url" value="http://localhost:8080/jsunit/jsunit/testRunner.html?testPage=http:// localhost:8080/jsunit/jsunit/tests/jsUnitTestSuite.html"/> <test name="net.jsunit.StandaloneTest"/> </junit> </target> </project> 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 wedefine which web browsers JsUnit will use to test our code with the propertybrowserFileNames (2). This property is a comma-separated list of browser executablepaths. Next, we define logsDirectory (3) to hold the directory location for test report XMLfiles. The property timeoutSeconds (4) is a timeout in seconds to wait for a test run tocomplete, if absent, the default value is 60 seconds. The url property (5) defines which testrunner to use and which test or test suite it should invoke. It is worth breaking down thisURL 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 theJsUnit stock test runner, relative to build.xml which is jsunit/testRunner.html. All ofthis yields the first part of the URL:http://localhost:8080/jsunit/jsunit/testRunner.html. The testPage URL©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 23parameter points to the test page or suite page to run. It too starts with the same localserver plus the jsunit serlvet prefix and is followed by the path to the test suite page relativeto 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 torunning our test with the target standalone_test (7), which we invoke from thecommand 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 consoledetailing the test run, too much to reproduce here. We can just look for the next to last lineof 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 thetarget jsunit_self_test to your ant invocation: ant jsunit_self_test standalone_testFor the self-test to work in your build, make sure that your jsunit_self_test targetpoints to the JsUnit test suite located in jsunit/tests/jsUnitTestSuite.html as ourexample 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 arepossible with JsUnit since it provides support for driving farms of JsUnit servers. A JUnitserver is what allows tests to be performed from Ant, it acts under the covers of ourexamples to drive web browsers on the local machine or on remote machines and alsocreates the result logs.12.9 RhinoUnit vs. JsUnit Should you use RhinoUnit or JsUnit? The answer to this question is quite similar to theHtmlUnit vs. Selenium question, which we presented in the previous chapter. The similarityis that both are free and open source. The major difference between the two is that RhinoUnit emulates a web browser whileJsUnit drives a real web browser process. When using JsUnit, the browser itself provides©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com24 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009support for JavaScript. In RhinoUnit, Java 6 or Apache BSF plus the Mozilla Rhino 14 engineprovides JavaScript support. The RhinoUnit pros are that it is a 100% Java solution and is easy to integrate in a buildprocess. The JsUnit pros are that it drives native browsers, and can manage a farm of JsUnittest 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 showhow 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 eachservice 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 andto the Microsoft ActiveX XMLHTTP objects Microsoft.XMLHTTP and Msxml2.XMLHTTP.Our goal is to emulate an XMLHttpRequest object by using HTTP as the transportmechanism and XML and JSON as example data formats. We will use the Apache CommonsHttpClient to provide HTTP support, Java’s built-in XML support, and jslint4java to check thatJSON 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 IISvirtual directory so that we can run the unit tests from Ant locally. A production Ant buildwould start and stop a web container like Jetty around the unit test invocations. Our firstXML service test will make sure that we are getting back from the server the expected XMLdocument. A second test will validate the document.14 http://www.mozilla.org/rhino/©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 25Listing 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) "<?xml version="1.0" encoding="UTF-8"?>")); (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 isapplication-specific and must include parameters if appropriate for a given test. The test thenexecutes the HTTP GET method (3) and reads the data back from the server. Note the use ofthe Apache Commons IO API IOUtils.toString to read the response stream in a Stringas a one-liner (4). The code does this synchronously, unlike a standard Ajax application. Wethen guarantee that HttpClient resources are freed by calling releaseConnection froma finally block (5). We can now check that the data from the server is as expected. For thisfirst test, all we do is a simple string comparison of the start of the document (6). You couldalso use Java regular expressions to do some further XML string-based checks; instead, wewill 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 theXML syntax is obeyed, nothing more. XML provides a standard called XML Schema, whichyou use to define a vocabulary of XML, specifying which elements and attributes make up avalid 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);©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com26 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 GETmethod, we read the server response directly with an XML parser (1). If the DOM documentparses successfully, we know the document is well formed, a nice sanity-check. If we do notget a valid XML document from the server, we might have a server error message in theresponse or a bug in server-side XML document generation. We can now move to the meatof the test, XML validation. We create a schema factory for the kind of grammar to use, inour 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). Atthis 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 DOMdocument API can be painful to use, so at this point you have several options we outline. TheJDOM 15 API is a friendlier interface to XML than DOM. You can use Java’s XPath 16 support tocheck the contents of a document. You can also use Sun’s JAXB 17 framework, while nottrivial, to transform XML into POJOs. Next, let us consider JSON as the data format.12.10.3 Validating a JSON response JSON 18 (JavaScript Object Notation) is a data-interchange language based on a subset ofthe JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999 19 .15 http://www.jdom.org/16 http://java.sun.com/j2se/1.5.0/docs/api/javax/xml/xpath/package-summary.html17 https://jaxb.dev.java.net/18 http://www.json.org/19 http://www.ecma-international.org/publications/files/ecma-st/ECMA-262.pdf©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 27Applications use JSON instead of XML as their data format because it is text-based, and easyfor people and machines to read and understand. In this first example, we will see a simplecheck 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 anHttpClient, an HTTP GET method that we execute and then read the results from theserver into a String. Unlike XML, JSON has no APIs to support checks for well-formed andvalid documents. We simply strip whitespaces from our JSON document fixture, the serverresponse and compare the two (2). While this check is brute force, we use it to provide asimple check that is free of formatting issues. The Apache Commons Lang APIStringUtils.deleteWhitespace performs the whitespace removal. The next best thing we can do is implement a well-formed check by parsing thedocument. While www.json.org lists many libraries, including Java libraries to parse JSON,we will use the jslint wrapper jslint4java 20 to go beyond a simple well-formed check. As we20 Jslint4java site: http://code.google.com/p/jslint4java/©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com28 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009saw earlier in the RhinoUnit section, jslint provides lint-style reporting for JavaScript. In thisexample, 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<Issue> 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 notprovide options in this example, but the JsLint class provides an addOption method tosupport the underlying jslint options. The test calls the lint method by specifying twoarguments: a String describing the source location and another String for the actualJavaScript code to check, in this case, a JSON document (2). The test uses the lint resultsto create a message String (3) used in the Assert call. If there was a problem, the testprovides 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. Thisis because jslint4java embeds jslint (fulljslint.js) in its jar file. If you need to use a newer ordifferent version of jslint in jslint4java, you will need to download jslint4java, drop in theversion 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 anAjax application independently of the pages and code using them. We have used ApacheCommons HttpClient as our HTTP communication library, Java’s XML APIs and jslint throughjslint4java. 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 tobuild, run, and test an Ajax application with the Google Web Toolkit.12.11 Testing Google Web Toolkit applications©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 29 The Google Web Toolkit 21 (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 webmode, 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 bothextend the JUnit TestCase class. GWT normally integrates with JUnit 3.8.2 and works with4.6. GWT includes junitCreator, a program used to generate empty GWT test cases for agiven GWT module. As a bonus, GWT can also benchmark your application. Since Java andJavaScript 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 userinterface of an application. You use GWTTestCase to test the asynchronous portions of theapplication normally triggered by user actions. This means that you must factor yourapplication and test cases with this element in mind. You can think of GWT tests asintegration 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 usejunitCreator. The example we use in this section is adapted from the GWT StockWatcherexample 22 , and extended with an RPC call. Figure 12.3 shows what the application looks likerunning 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, asit 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©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com30 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Figure 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 withlisting 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<StockPrice[]> callback = new AsyncCallback<StockPrice[]>() { 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) }©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 31 The implementation of refreshWatchList follows the standard pattern for GWT RPC,the method creates a new StockPriceService instance (1) and defines the servicecallback. 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[]), weupdate the application. Next, we call service’s getPrices method (4) with input data andour callback. The key point to remember is that the call to the getPrices method isasynchronous, so the call to refreshWatchList is also asynchronous. Next, we will testthis method with a unit test. To create a GWT test case, you start by creating a subclass of GWTTestCase, along thelines 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<String> 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)©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com32 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 } 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 methodwith the signature public String getModuleName() to return the name of the modulebeing tested (2). We start the test method by instantiating the model class and initializing itwith input data (3), the three stock symbols “S1”, “S2”, and “S3”. Next, we call therefreshWatchList 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 bychecking that the stock watcher RPC was able to run in the first place. We do this bychecking if the asynchronous callback caught an exception (6). This arrangement is helpful indetermining an incorrect test set up; in particular, as it relates to the class path and modulefile (see the tip in the section on running tests.) If no exception is present, the validitychecks on the StockWatcher object can proceed. We check that table headers are stillthere (7) and then check the contents of the table (8) for values we expect to be returnedfrom the service. In this test, we have changed the stock GWT example to return predictablevalues instead of randomly generated values. Now that the Timer object is in place, we call delayTestFinish to tell GWT run thistest in asynchronous mode (10). You give the method a delay period in milliseconds muchlarger than is expected to run the test setup, do the RPC, and perform the assertions. Whenthe test method exits normally, GWT does not mark the test a finished; instead, the delayperiod 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’sschedule method (11). The argument is a delay in milliseconds, after which control returnsto the caller.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 33 We just saw asynchronous testing in GWT; next, we will see how to use junitCreator tocreate 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 towhich you then add your own test methods. junitCreator is a good place to get startedfor your first GWT test. For subsequent tests, you may prefer to clone a template class orwrite test case classes from scratch. junitCreator also creates Eclipse launchconfigurations and command line scripts for hosted and web modes. To get your commandprocessor to find junitCreator and other GWT programs, remember to add GWT to yourpath. 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 testcomgooglegwtsamplestockwatcherclientStockWatcherTest.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 linescripts. Use these files to invoke the generated test case in web or hosted mode. You mayneed 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 RunAs… or Debug As… and then choose GWT Unit Test to run the test in hosted mode and GWTUnit Test (web mode) to run the test in web mode. To use Ant, you will need to makesure your build file points to the GWTSDK. Using a GWT example build fileas a template, edit the gwt.sdkproperty to point to the GWTdirectory.12.11.5 Setup and tear down A GWTTestCase subclass can override the JUnit methods setUp and tearDown with thefollowing restrictions:©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com34 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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 foroverriding: 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. Aperformance gain is possible by using a GWT test suite. GWT sorts test cases in a suite bymodule name as returned by getModuleName. This causes all tests with the same moduleto 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 nodeand choose JUnit Test Suite. The listing below shows the generated code with two changeswe 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. Wehave also replaced the String in the TestSuite constructor with the class name of thegenerated class (2). This allows us to double-click on the class name in the JUnit view andjump to an editor for that test suite. The rest is standard JUnit code; we call addTestSuitewith 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 testsuite with more memory than the default settings allocate. Configure the Java VM runningthe tests with at least 256 megabytes of RAM. With the Sun JVM, use the following option: -©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 35Xmx256M. You must also add to the classpath the source directories for application and testcode. To wrap up GWT testing, recall that GWT test cases verify the asynchronous aspects ofyour application, not the actual user interface. To test the GUI, use functional tests withSelenium or HtmlUnit. While we are done with our brief tour of GWT testing, it is worth noting that GWT includesfeatures to allow you to perform benchmarking with reports by using the Benchmark class.You can also see a graphical representation of benchmarks with the benchmarkViewerutility.12.12 Summary In this chapter, we have built on what we have learned in Chapter 11 about testing thepresentation 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: HTMLand 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 theinternet with HTTP. We have divided our testing strategies along similar patterns. We used functional testsfor the whole application stack as it appears to a user by driving and testing the applicationwith HtmlUnit and Selenium. We isolated JavaScript into libraries and tested thoseindependently with RhinoUnit and JsUnit. We validated server-side XML and JSON servicesusing Apache Commons HttpClient, jslint, and jslint4java. You use RhinoUnit when your application is independent of operating system features andbrowser specific implementations of JavaScript, DOM, CSS, etc; and use JsUnit when yourequire validation of specific browsers and operating systems, especially if the applicationtakes 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 developfor in Java and that translates your code to JavaScript. This chapter concludes our survey of user interface testing and we now move to theserver-side and testing with Cactus.©Manning Publications Co. Please post comments or corrections to the Author Online forum: http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 1 13 Server-side Java testing with CactusThis 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, …©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com2 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009Good 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 fromusing 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 shouldbe aware of now is that there is no absolute truth – the best techniques to use depend onthe situation you are currently in. For example in most of the cases maybe you will findserver-side testing with mocks and stubs sufficient, however, as you saw, this techniquesuffers some significant drawbacks. That is why we cover the in-container testing approach deeper in the book. Furthermorethis chapter will focus on the in-container testing methodologies by means of one of the mostpopular in-container testing frameworks – Cactus. We will start by introducing the Cactusframework, 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 ofexecution of Cactus tests. We will then build a sample application that uses somecomponents from the J2EE spec, and we will write the tests for those components with thehelp of Cactus. The next thing would be to execute those tests – we will show a sampleintegration between Cactus and some of the most popular tools for continuous integration(Ant, Maven …). But well go a bit further than that – we will demonstrate you how todemonstrate from the tight integration between Cactus and other projects, like Cargo andJetty. So, let’s start!13.1 What is Cactus? Cactus is an open-source framework (http://jakarta.apache.org/cactus/) for in-containertesting server-side Java code (mostly J2EE components in the current version of Cactus). Itis 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 wesay Cactus is a framework itself, we actually mean that it provides some API that you haveto extend in order to use it. Also, what in-container really means is that (as you will see later in the chapter) the testsget executed inside the Virtual Machine of the container. And finally, Cactus is an extensionof JUnit because of two reasons – first of all it extends JUnit by empowering it with newfunctionality (Cactus makes JUnit tests get executed inside the container, something whichotherwise 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 ofJUnit’s methods. Let’s see Cactus in action. In later sections, we’ll explain in more detail how it works.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 313.2 Testing with CactusThis section is more theoretical. You need this knowledge before you move on andexperiment on a sample application, because Cactus is different from the normal unit-testingframeworks. Cactus executes the tests inside the container, which on its own raises a lot ofquestions, 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 coreJ2EE components (JSPs, Taglibraries, Servlets, Filters and EJBs). What is worth mentioning isthat this is the only focus of the Cactus project. It does not test any specific framework (lookat the next chapter if your application is framework specific), because it is not intended to doso. A lot of the emails that come to the Cactus mailing list ask if people can use Cactus fortesting an application based on a specific framework (like Struts, JSF or Spring). Actuallythere are quite a lot of tools, dedicate on such testing and we cover some of them later inthe book. Most of those tools are based on Cactus and require Cactus in their classpath, butagain 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 otherway around is not true – none of the JUnit tests are actually Cactus tests. So what actuallydistinguishes the Cactus tests from the JUnit tests? There are a couple of certain rules thatyou need to stick to in order to use Cactus. We already discussed in chapter 7 what in-container testing means. Back then, we had aweb application that uses servlets, and that you wish to unit-test the isAuthenticatedmethod 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 validHttpServletRequest object. Unfortunately, it is not possible to call new©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com4 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009HttpServletRequest to create a usable request. The life cycle of HttpServletRequestis managed by the container. JUnit alone is not enough to write a test for theisAuthenticated method. So what do we do in order to get hold of a valid HttpServletRequest? Wouldn’t it bejust 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 thatactually takes care of providing us the server-side objects that are otherwise managed bythe container (such as HttpServletRequest). Here is a corresponding cactus-test thattests 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 – theycome 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 accessto the container objects, inside our JUnit test-cases. Seen from the previous listing writing acactus 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 5 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. Thisbrings us to the next issue: if Cactus tests live in the container JVM, how do they getexecuted? 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 Cactusworks. 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 Cactustest from listing 13.2. Say now we have a sample servlet, that we want to test, and also atest written for that particular servlet. What we need to do now is package the servlet andthe 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com6 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009container and in our workspace on the client side. You can submit the client-side Cactus testto 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 areexecuted on the server side (inside the container JVM). Client side refers to the JVM in whichyou have started the JUnit test runner. On the client side, the Cactus logic is implemented in the YYYTestCase classes that yourtests extend (where YYY can be Servlet, Jsp, or Filter). More specifically,YYYTestCase overrides JUnit TestCase.runBare, which is the method called by the JUnittest runner to execute one test. By overriding runBare, Cactus can implement its own testlogic, as described later. On the server side, the Cactus logic is implemented in a proxy redirector (or redirector forshort).STEPPING THROUGH A TEST For each test (testXXX methods in the YYYTestCase classes), the six steps shown infigure 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 passinformation to the redirector. The TestSampleServletIntegration example extendsServletTestCase and connects to the Cactus servlet redirector. The servlet redirector isimplemented as a servlet; this is the entry point in the container. The Cactus client side callsthe servlet redirector by opening an HTTP connection to it. The beginXXX method sets upHTTP-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, HTTPheaders, 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 totell 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 7The YYYTestCase opens a connection to its redirector. In this case, the ServletTestCasecode opens an HTTP connection to the servlet redirector (which is a servlet).Step 3: create the server-side TestCase instanceThe redirector creates an instance of the YYYTestCase class. Note that this is the secondinstance created by Cactus; a first one has been created on the client side (by the JUnitTestRunner). Then, the redirector retrieves container objects and assigns them in theYYYTestCase instance by setting class variables. In the servlet example, the servlet redirector creates an instance ofTestSampleServletIntegration and sets the following objects as class variables in it:HttpServletRequest, HttpServletResponse, HttpSession, and so forth. The servletredirector 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. Itacts as a proxy/redirector (hence its name). The redirector then starts the test (see step 4). Upon returning from the test, it storesthe test result in the ServletConfig servlet object along with any exception that mighthave been raised during the test, so the test result can later be retrieved. The redirectorneeds a place to temporarily store the test result because the full Cactus test is completeonly when the endXXX method has finished executing (see step 5).Step 4: call setUp, testXXX, and tearDown on the serverThe redirector calls the JUnit setUp method of YYYTestCase, if there is one. Then it callsthe testXXX method. The testXXX method calls the class/methods under test, and finallythe redirector calls the JUnit tearDown method of the TestCase, if there is one.Step 5: execute endXXXOnce the client side has received the response from its connection to the redirector, it callsan endXXX method (if it exists). This method is used so that your tests can assert additionalresults from the code under test. For example, if you’re using a ServletTestCase,FilterTestCase, or JspTestCase class, you can assert HTTP cookies, HTTP headers, orthe content of the HTTP response: public void endXXX(WebResponse response) { assertEquals("value", response.getCookie("cookiename").getValue()); assertEquals("...", response.getText()); [...] }Step 6: Gathering the test resultIn step 3, the redirector saves the test result in a variable stored with the ServletConfigobject. The Cactus client side now needs to retrieve the test result and tell the JUnit testrunner whether the test was successful, so the result can be displayed in the test runner GUI©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com8 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009or console. To do this, the YYYTestCase opens a second connection to the redirector andasks it for the test result. This process may look complex at first glance, but this is what it takes to be able to getinside the container and execute the test from there. Fortunately, as users, we are shieldedfrom this complexity by the Cactus framework. You can simply use the provided Cactus frontends 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 onmore component-specific tests.13.3 Testing Servlets and FiltersAs 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 areabout to explore these characteristics. When you unit-test servlet and filter code, you must test not only these objects, but alsoany Java class calling the Servlet/Filter API, the JNDI API, or any back-end services. Startingfrom this section we’ll build a real-life sample application that will help demonstrate how tounit-test each of the different kinds of components that make up a full-blown webapplication. This section focuses on unit-testing the servlet and filter parts of thatapplication. 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 performdatabase queries on a relational database. Suppose that the application it administersalready exists. Administrators can perform queries such as listing all the transactions thattook place during a given time interval, listing the transactions that were out of Service LevelAgreement (SLA), and so forth. We set up a typical web application architecture (see figure13.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 toexecute. The request is caught by a security filter that checks whether the SQL query is aSELECT query (to prevent modifying the database). If not, the user is redirected to an errorpage. If the query is a SELECT, the AdminServlet servlet is called. The servlet performsthe requested database query and forwards the result to a JSP page, which displays theresults. The page uses JSP tags to iterate over the returned results and to display them inHTML tables. JSP tags are used for all the presentation logic code. The JSPs contain onlylayout/style tags (no Java code in scriptlets). You’ll start by unit-testing the AdminServletservlet. Then, in the following subsections, you’ll test the other components of theAdministration application.13.3.2 Writing servlet tests with CactusIn this section, we’ll focus on using Cactus to unit-test the AdminServlet servlet (see fig.13.2) from the Administration application.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 9Fig. 13.2 The sample Administration application. You’ll use it as a base sample in this chapter andfollowing 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. Thisstrategy is called Test-Driven Development (TDD) or Test-First, and it’s very efficient fordesigning extensible and flexible code and making sure the unit test suite is as complete aspossible. (See chapter 3 for an introduction to TDD.) Before you begin coding the test, let’s review the requirement for AdminServlet. Theservlet should extract the needed parameter containing the command to execute from theHTTP request (in this case, the SQL command to run). Then it should fetch the data usingthe 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 actionsgetCommand, executeCommand, and callView, respectively.DESIGNING THE FIRST TESTListing 13.3 shows the unit tests for the getCommand method. Remember that you have notyet written the code under test. The AdminServlet class doesn’t exist, and your codedoesn’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)©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com10 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 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), becausethe component that we want to test is a servlet. We also set a parameter in the beginXXXmethod (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 acode that will allow us to compile the project. We need to implement a sample servlet with agetCommand 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 youprobably notice at this point is that if this test get executed it will fail, because of the nullobject that we return. Tests are used to prevent us from making mistakes. That said wealways have to ensure that tests fail if we provide corrupt data, like in the above example. Atthis point we need to ensure that the error is reported successfully. And after that, when youimplement the code under test, the tests should succeed, and we’ll know we’ve accomplishedsomething. 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©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 11 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 minimalimplementation 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. Wewant 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 TESTSAt the beginning of the previous subsection, we mentioned that you need to write threemethods: getCommand, executeCommand, and callView. You implemented getCommand©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com12 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009in listing 13.5. The executeCommand method is responsible for obtaining data from thedatabase. We’ll defer this implementation until the last section, “Testing EJBs”. That leaves the callView method, along with the servlet’s doGet method, which tieseverything together by calling your different methods. One way of designing the applicationis to store the result of the executeCommand method in the HTTP servlet request. Therequest is passed to the JSP by the callView method (via servlet forward). The JSP canthen access the data to display by getting it from the request (possibly using a useBeantag). 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 BeanUtilspackage in the Apache Commons (http://commons.apache.org/beanutils/) includes aDynaBean class that can expose public properties, like a regular JavaBean, but you don’tneed to hard-code getters and setters. In a Java class, you access one of the dyna-propertiesusing 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 arbitrarydata from the database. You can construct dynamic JavaBeans (or dyna beans) that you’lluse to hold database data. The actual mapping of a database to dyna beans is covered in thelast section.TESTING THE CALLVIEW METHOD There’s enough in place now that you can write the tests for callView, as shown in listing13.6.To make the test easier to read, you create a createCommandResult private method. Thisutility method creates arbitrary DynaBean objects, like those that will be returned byexecuteCommand. In testCallView, you place the dyna beans in the HTTP request wherethe 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();©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 13 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 ofthe execution of a JSP page. So, you can use Cactus to verify that the JSP will be able todisplay the data that you created in createCommandResult. Because this would be JSPtesting, 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 JSPin 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. Thatwill change once you test the JSP.TESTING THE DOGET METHODLet’s design the unit test for the AdminServlet doGet method. To begin, you need toverify that the test results are put in the servlet request as an attribute. Here’s how you cando that: Collection results = (Collection) request.getAttribute("result"); assertNotNull("Failed to get execution results from the request", results);©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com14 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 assertEquals(2, results.size());This code leads to storing the command execution result in doGet. But where do you get theresult? 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 doesnothing in AdminServlet. Then, in your test, you can implement executeCommand toreturn 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 mustthrow a ServletException. Because executeCommand can throw an exception, you needto wrap it into a ServletException. If you run this code, you’ll find that you have forgotten to set the command to execute inthe 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);©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 15 } 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 testsdon’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). Youcould return null, but throwing an exception is a better practice. An exception clearly statesthat you have not implemented the method. If the method is called by mistake, there won’tbe 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 probablythe most difficult-to-test part of the application – the frontend.13.4 Testing JSPsIn this section, we’ll continue with the Administration application we introduced in theprevious section. Here, we concentrate on testing the view components—namely the JavaServer Pages (JSPs).13.4.1 Revisiting the Administration applicationYou call the application by sending an HTTP request (from your browser) to theAdminServlet (fig. 13.2). You pass an SQL query to run as an HTTP parameter, which isretrieved by the AdminServlet. The security filter intercepts the HTTP request and verifiesthat the SQL query is harmless (that is, it’s a SELECT query). Then, the servlet executes thequery on the database, stores the resulting objects in the HTTP Request object, and callsthe Results View page. The JSP takes the results from the Request and displays them,nicely formatted, using custom JSP tags from your tag library.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com16 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/200913.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 theservlet that is generated by the compilation of the JSP. We also assume that the JSP is welldesigned, which means there is no Java code in it. If the page must handle any presentationlogic, the logic is encapsulated in a JavaBean or in a taglib. You can perform two kinds oftests 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 thenverifying that the returned page contains the expected data. We’ll use Cactus to demonstratethis type of test. Because mock objects (see chapter 6) operate only on Java code, you can’tuse a pure mock-objects solution to unit-test your JSP in isolation. You could also writefunctional tests for the JSP using a framework such as HttpUnit. However, doing so meansgoing all the way to the back end of the application, possibly to the database. With acombination of Cactus and mock objects, you can prevent calling the back end and keep yourfocus on unit-testing the JSPs themselves. You can also unit-test the custom tags used in theJSP.13.4.3 Unit-testing a JSP in isolation with CactusThe 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 (orJspTestCase): 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)©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 17 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 dataLet’s see some action on the Administration application. In the servlet section (“Testingservlets”), you defined that the results of executing the SQL query would be passed to theJSP by storing them as a collection of DynaBean objects in the HttpServletRequestobject. Thanks to the dynamic nature of dyna beans, you can easily write a generic JSP thatwill display any data contained in the dyna beans. Dynabeans provide metadata about thedata they contain. You can create a generic table with columns corresponding to the fields ofthe 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" %> <html> <head> <title>Results Page</title> </head> <body bgcolor="white"> <table border="1"> <d:properties var="properties" item="${requestScope.results[0]}"/> <tr> <c:forEach var="property" items="${properties}"> <th><c:out value="${property.name}"/></th> </c:forEach> </tr> <c:forEach var="result" items="${requestScope.results}"> <tr> <c:forEach var="property" items="${properties}"> <td><d:getProperty name="${property.name}" item="${result}"/></td> </c:forEach> </tr> </c:forEach> </table> </body> </html>You use both JSTL tags and custom taglibs to write the JSP: The JSTL tag library is astandard 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.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com18 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 You also write two custom tags (<d:properties> and <d:getProperty>), which areused to extract information from the dyna beans. <d:properties> extracts the name of allproperties of a dyna bean, and <d:getProperty> extracts the value of a given dyna beanproperty. There are two reasons for writing these custom tags. The primary reason is that it isn’tpossible 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 yourown.WRITING THE CACTUS TESTNow let’s write a Cactus ServletTestCase for the JSP. The callView method from theAdminServlet 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 theRequest, 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)©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 19 […] 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 inthe request (2). Then in the testCallView (3) method (remember that it is executed onthe 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 executedon the client-side, has a com.meterware.httpunit.WebResponse parameter, holdingthe response from the server. In (7) we assert different statements against the response ofthe server, in order to verify that the jsp displays the results properly. You use the Cactus HttpUnit integration in the endCallView method to assert thereturned HTML page. When Cactus needs to execute the endXXX method, first it looks for anendXXX (org.apache.cactus.WebResponse) signature. If this signature is found, Cactuscalls 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, youcan perform asserts on the content of the HTTP response, such as verifying the returnedcookies, the returned HTTP headers, or the content. The Cactusorg.apache.cactus.WebResponse object supports a simple API. The HttpUnit webresponse API (com.meterware.httpunit.WebResponse) is much more comprehensive.With HttpUnit, you can view the returned XML or HTML pages as DOM objects. In listing13.10, you use the provided HTML DOM to verify that the returned web page contains theexpected HTML table. In this section we just described how to test the frontend part of the Administratorapplication. What we are still missing is a few pages that will reveal us how to unit-test theAdministratorBean EJB, that actually executes our queries upon the database. Thesecrets 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 thatEJBs are components that run inside a container. Thus, you either need to abstract out thecontainer 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 alsocontinue developing our Administrator application, showing you the module that actuallyexecutes the SQL queries.©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com20 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 The Architecture of the Administrator application goes this way – the command to beexecuted gets through the filter, which determines if it is a SELECT query. After that theAdminServlet eventually receives the command/query. The execution flow starts in the AdminServlet doGet method. It receives the HTTPrequests and calls the getCommand method to extract the SQL query from it. It then callsexecuteCommand to execute the database call (using the extracted SQL query) and returnthe results as a Collection. The results are then put in the HTTP request (as a requestattribute) and, at last, doGet calls callView to invoke the JSP page that presents theresults to the user. So far we have given no implementation of the executeCommandmethod. The idea behind it would be to call a given EJB, which would execute the query upona 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, well 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 createa RowSetDynaClass object form the ResultSet (2) and we return its rows (3).©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 21 In order to test the EJB with Cactus you have to instantiate it and then assert against theresult of the execution. Of course you can use, again, Mock objects to simulate the JNDIlookup, but this approach is unnecessary complicated, so we won’t list it here. Let’s have alook 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 iscalled before each test method), and get hold of a valid IAdministratorBeanLocalinstance from the JNDI (2). Then in the each test method we invoke the methods on the EJBwith 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 werush into the section that deals with execution of our Cactus tests, it is essential to getfamiliar with a project called Cargo. The tight integration between Cargo and Cactus is one ofthe 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 thebeginning of the chapter we mentioned that Cactus test cases are executed inside the©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com22 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009container. In this section and in the next ones we will focus on how to integrate execution ofCactus tests in your build lifecycle. In order to keep the extreme programming principles youneed to execute the tests every time you make a build. This requires a tight integrationbetween Cactus and the build system – Ant or Maven. But before we jump straight into thatintegration 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 VincentMassol in 2004. The aim of the project is to automate container management in a genericway so that you could use the same mechanism to start and deploy a war file with Tomcat asyou could with WebLogic or almost any other. Otherwise said it provides an API around mostof the existent J2EE containers, for managing those containers (starting, stopping anddeploying). But Cargo is a little bit more than just an API, and that’s where its strengthcomes from – it provides Ant tasks and Maven (1x and 2x) plugins for facilitating themanagement 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 ideabehind the project is really great, but seems like there’s too much of a burden regarding theprocess of executing the tests – once written the tests need to get packaged in a war or eararchive, then the application descriptors need to get patched with the appropriateredirectors. After that, before the execution gets started, the archive needs to get deployedin a container that is already started. I bet you already noticed the three italicized words inthe previous sentence. I also bet that you already guess that the main idea of the Cactusdevelopment team was to hide all the complexity regarding the management of thecontainer by means of Cargo. This gives us full automation – if we use Cargo’s Ant tasks (or Maven plugins) to start thecontainer, then deploy the war/ear in it and then stop the container, then we have achievedthe 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 doanything magical - we still need to figure out a way to trigger the execution of the testswhen 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. Wewill deal with this integration in the subsequent chapters.13.7 Executing Cactus tests with AntThe first way to fire up Cactus tests that we are going to show seems to be the mostcommon one. Using Ant is easy and straightforward. If you are new to Ant, I would like torecommend you the “Ant in Action” by Steve Loughran and Erik Hatcher – a really marvelousbook. Also, before you read this section, please make sure you have already read Chapter 8.13.7.1 Cactus tasks to prepare the archiveCactus comes bundled with two kinds of Ant tasks – the first one would facilitate you inpreparing the archive (WAR or EAR) to hold the test-cases, and the second will actually©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 23invoke the tests. We will go through these tasks one after the other and will show you howto use these tasks. The process of preparing the archive for executing the tests is called cactification. This isa term initially introduced by the cactus team. Imagine you have at some point an archive (aWAR or EAR file) which is your application that you want to deploy. The cactification processincludes, adding the required jars in the lib folder of the archive and also patching theweb.xml to include desired cactus redirectors. According to the type of archive that you want to cactify, there are two different tasksthat you may want to use – cactifywar and cactifyear tasks. Before we rush into describingthese tasks, let us first take a minute to focus on the build.xml skeleton that we are going touse for our presentation purposes.Listing 13.13. Build.xml skeleton to execute the Cactus tests. <project name=”Cactus sample tests” basedir=”.” xmlns:ivy=”antlib:org.apache.ivy.ant”> <target name="init" depends=""> […] <property name="target.dir" location="${basedir}/target"/> (1) <property name="classes.dir" location="${target.dir}/classes"/> (1) […] <ivy:configure file="ivysettings.xml" /> (2) <ivy:retrieve file="ivy.xml" sync="true"/> (2) <path id="compile.cp"> <fileset dir="${lib.dir}"> <include name="*.*"/> (3) </fileset> </path> </target> <target name="prepare" depends="init"> <mkdir dir="${target.dir}"/> (4) […] <mkdir dir="${lib.dir}"/> (4) </target> <target name="load.tasks" depends="init, prepare"> <taskdef resource="cactus.tasks" classpathref="compile.cp"/> (5) </target> <target name="compile" depends="init,prepare"> <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="compile.cp"/> (6) </target> <target name="prepareJar" depends="init,prepare"> <jar jarfile="${target.dir}/ch13.ejb3"> (7) <fileset dir="${classes.dir}"/> </jar>©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com24 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 </target> <target name="prepareWar" depends="init,prepare"> <war destfile="${target.dir}/ch13.war" webxml="${basedir}/src/webapp/WEB-INF/web.xml"> (8) <classes dir="${classes.dir}"/> </war> </target> <target name="packageEar" depends="init,compile, prepareJar, prepareWar"> <ear destfile="${target.dir}/ch13.ear" appxml="${app.dir}/application.xml"> (9) <fileset dir="${target.dir}" includes="*.jar,*.war"/> </ear> </target> […] </project> This Ant descriptor is pretty simple. As you can see one of the first things to do is declaresome properties in the init target (1). We will use these properties later in the script. Inthis target we also resolve the additional dependencies with Ivy (2) and construct a classpathrefid (3). The prepare target prepares the folder structure for the build (4), and afterthat we use the taskdef task (5) to define the external tasks (remember that cactifyXXXand cargo tasks are external tasks that come with Cactus/Cargo – they are not part of theofficial Ant tasks). Then we compile our code (6) and produce either JAR file containing theEJBs (7) or WAR file (8) containing the Servlets (depending on what part of the code we willtest). 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 firstset of tasks that Cactus provides.THE CACTIFYWAR TASKThis task is used when your application is a WAR file and you want to cactify it. Thecactifywar task extends the built-in war Ant task so it also supports all attributes and nestedelements that the war task supports. Nothing explains better than an example, so let’s try tostart the test cases from this chapter using Ant. We will walk through the build.xml anddiscuss it over.Listing 13.14 Build.xml to present cactifywar task. <target name="cactifywar" depends="init,load.tasks, prepareWar"> <cactifywar srcfile="${target.dir}/ch13.war" destfile="${target.dir}/ch13-cactified.war"/> </target> 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 followingparameters: srcfile, destfile and a list of redirectors we want to define. Of coursethere is a bunch of other, non-required parameters, all of which are perfectly documented on©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.ComLast saved: 7/7/2009 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E 25the web-site of Cactus (http://jakarta.apache.org/cactus), where you can refer for additionalhelp. Also, once again, since the cactifywar task extends the war task, you can pass all theparameters 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 tocactify. The important thing to notice here is that you may need to specify not only the nameof the file, but also the destination path to it. In the destfile parameter you specify thename of the cactified file to produce. You also may want to describe a list of redirectors in the cactifywar task. This list ofredirectors describe URL-patterns to map the Cactus test redirectors to which the nestedelements filterredirector, jspredirector and servletredirector. If you dontspecify those elements, it’s pretty normal - the test redirectors will be mapped to the defaultURL-pattern. After executing the target with ant cactifywarwe should get the desired cactified archive, that you can examine.THE CACTIFYEAR TASKThe cactifyear task is the analogue of the cactifywar task, but instead is used tocactify EAR applications. It is a bit different than the previous one, because in most of thecases 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 extendsthe 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 exampleapplication and discuss the different aspects.Listing 13.15 Build.xml to present cactifyear task. <cactifyear srcfile="${target.dir}/ch13.ear" destfile="${target.dir}/ch13-cactified.ear"> (1) <cactuswar srcfile="${target.dir}/ch13.war" mergewebxml="${src.webapp.dir}/WEB-INF/web.xml" context="/"> <classes dir="${classes.dir}"> <include name="Test*.class"/> </classes> <fileset dir="${src.webapp.dir}"> <include name="*.jsp"/> </fileset> <lib dir="${lib.dir}"> <include name="commons-beanutils*.jar"/> (2) </lib> </cactuswar> </cactifyear>©Manning Publications Co. Please post comments or corrections to the Author Online forum:http://www.manning-sandbox.com/forum.jspa?forumID=502 Licensed to JEROME RAYMOND <pedbro@gmail.com>
    • Download at WoweBook.Com26 Tahchiev, Leme, Massol, and Gregory / JUnit in Action 2E Last saved: 7/7/2009 Once again we use the build.xml skeleton from listing 13.13, and here we just list thetarget that is responsible for the cactification of the already packaged EAR file. As you can see the cactifyear task accepts the srcfile and