2.2 Example 1: Create a Book

     

For the first example, we will create a representation of a book and its title. Since we'll do test-first development, we need to set up a unit test framework prior to writing any code for the book class. This test framework serves both as the foundation for the example's unit tests and also as an illustration of just how simple a functional test framework can be. Building it is Step 0.

The subsequent steps are the usual three steps in the TDD cycle. Step 1 is to write a unit test to verify that a book has been created. At first, the unit test will fail, because the functionality to create a book does not yet exist. Step 2 is to build the functionality to create a book. In Step 3, the test succeeds, proving that the functionality works and providing an example of how to use it.

2.2.1 Step 0: Set Up the Unit Test Framework

The unit test framework initially is built on a single class, UnitTest , shown in Figure 2-1.

Figure 2-1. The class UnitTest
figs/utf_0201.gif

The source code for UnitTest is given in Example 2-1.

Example 2-1. The base class UnitTest
 UnitTest.java public abstract class  UnitTest  {    protected static int  num_test_success  = 0;    public abstract void  runTest( )  throws Exception;    protected void  assertTrue( boolean condition, String msg )  throws Exception {       if (!condition)          throw new Exception(msg);       num_test_success++;    } } 

The class UnitTest is abstract because its purpose is to be the parent class for actual unit tests. It contains a static integer member, num_test_success , which keeps track of the number of successful tests. Descendant classes override the method runTest( ) to run actual tests. The method assertTrue() tests a condition. If the condition is TRUE , the successful test counter is incremented. If it is FALSE , an Exception is thrown containing a message string associated with the condition.

Compile UnitTest with the command javac UnitTest.java (or your compiler's equivalent command). Believe it or not, you now have a simple but functional test framework with which to start building tests.

You could look at this unit test framework and ask, "How is something that can be written in 10 lines of code worth an entire book?" Unit test frameworks are fundamentally simple tools that can be used in sophisticated ways. The xUnit frameworks represent significantly more complicated and powerful pieces of software than the basic framework used in this example. In the subsequent chapters, we will get into the features of more advanced unit test frameworks and how they save coding effort and enable building more complex tests.

2.2.2 Step 1: Create a Unit Test

Now that you have built your simple unit test framework, it's time to create a unit test. The unit test will fail because the functionality it tests has not been built. Remember the TDD process: if your tests do not fail initially, then you are not writing good tests. The test failure also demonstrates that the framework works as expected.

Before writing the first test, take a moment to decide what you want the new code to do and how to test whether it succeeds. We want to represent a book and its title, which sensibly is done with a class named Book having a title attribute. So, we will create a unit test that creates an instance of Book and checks its title .

Example 2-2 shows the implementation of the first unit test, BookTest .

Example 2-2. The unit test class BookTest
 BookTest.java public class  BookTest  extends UnitTest {    public void  runTest( )  throws Exception {       Book book = new Book("Dune");       assertTrue(book.title.equals("Dune"), "checking title");    } } 

BookTest is very simple. It creates a book, giving the title as an argument to the constructor. It then tests that the value of the attribute title has been set correctly. The string checking title describes the test condition.

Compile this new class. The compiler will inform you that it does not know about a class named Book . So, create the most basic implementation of Book that will allow everything to compile, as shown in Example 2-3.

Example 2-3. The class Book
 Book.java public class  Book  {    public String title = "";    Book(String title) {} } 

Someone who cares about software design would have a problem with this code. Making the title attribute public is not good; it would be better to make it private and provide an accessor function such as getTitle( ) to obtain its value. However, adding the accessor now would create two methods that should be unit tested: the constructor and the accessor. This change should wait until the current change is done and tested .

Book and BookTest can now compile. Our first unit test is now built. We still need to run the test to see whether it succeeds or fails. One additional new piece of code is necessary. The class TestRunner runs BookTest and reports success or failure. Example 2-4 gives the implementation of TestRunner .

Example 2-4. The class TestRunner
 TestRunner.java public class  TestRunner  {    public static void  main(String[] args)  {       TestRunner tester = new TestRunner( );    }    public TestRunner( ) {       try {          UnitTest test = new  BookTest  ( );          test.  runTest( )  ;          System.out.println("SUCCESS!");       }       catch (Exception e) {          e.printStackTrace( );          System.out.println("FAILURE!");       }    } } 

TestRunner contains the main() method for the test framework. When an instance of TestRunner is created, it creates a BookTest and calls its runTest( ) method. If there is no error, success is reported . If an exception is thrown, TestRunner reports the location of the failure from the exception stack trace.

Compile the new code and run it using java TestRunner . You should get the following results:

 FAILURE! java.lang.Exception: checking title         at UnitTest.assertTrue(UnitTest.java:10)         at BookTest.runTest(BookTest.java:5)         at TestRunner.<init>(TestRunner.java:10)         at TestRunner.main(TestRunner.java:4) 

A failure is reported. The test description is printed, followed by the stack trace showing where the failure occurred.

Congratulations! You have produced a test failure. This failure is a success of the TDD process. The unit test BookTest has done its job, reporting that the functionality being tested is not implemented. The simple framework has performed its role, running the test and reporting the results, including the location and description of the failure. We now have a working unit test framework.

A semantic distinction is drawn between failures and errors in unit testing. A failure is a unit test reporting that a test condition has evaluated to false. If you're not producing failures, you're not writing good tests. An error is an unexpected problem, such as an uncaught exception. Errors may happen, but producing them is not a goal of the TDD process.

2.2.3 Step 2: Create a Book

In the previous step, we wrote the first unit test, BookTest . In order to get BookTest to compile, we also created a basic implementation of the class Book . When run, BookTest still fails, because Book does not yet contain the functionality being tested. In this step, we add the necessary code to get the test to succeed. As compared to Step 1, the changes required at this point are very minor.

The class Book is modified so that the title attribute is set in the constructor, as shown in Example 2-5.

Example 2-5. The class Book with title attribute set by the constructor
 Book.java public class Book {    public String title = "";    Book(String title) {  this.title = title;  } } 

2.2.4 Step 3: Test Again

The final step is to rebuild the code, re-run the unit test, and see whether the changes produce the desired results.

Compile the code and run it using java TestRunner . You should see the following result:

 SUCCESS! 

Mission accomplished! Creating a class representing a book with a title attribute is a simple task that most developers could accomplish in a few minutes, without feeling the need for a unit test to validate it. However, we accomplished much more than that in this exercise. We created a simple unit test framework, built a unit test, and validated that the framework behaves as expected in both failure and success cases. The initial unit test, although a trivial validation of the class Book , is important as a test of the framework itself.

From the formal software design perspective, the class architecture we've just built is shown in Figure 2-2.

Figure 2-2. Class diagram for the basic unit test framework
figs/utf_0202.gif



Unit Test Frameworks
Unit Test Frameworks
ISBN: 0596006896
EAN: 2147483647
Year: 2006
Pages: 146
Authors: Paul Hamill

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net