Unit Testing


Unit testing verifies that individual parts of the system comply with the requirements. With an object-oriented language, such as Java, the preferred unit for testing is the class. Typically, individual developers do unit testing, or at least they should do it. Far too many programmers today think that programming is just about coding and that someone else, the tester, is responsible for testing. Methodologies such as RUP and XP clearly identify different types of testing and place unit testing in the developer's set of responsibilities.

The PlanTest-First Programming

We planned on using the practice of Test-First Programming (TFP). This practice requires that you write a test for the functionality you are about to implement . You then write the code to make the test pass. The theory behind this technique is that the test embodies the requirements for the code. By writing the test first, you ensure that you write the code to satisfy the requirements. When you write the test after the code, there is more of a chance that you will write the test to fit your code, which may not accurately reflect the requirements. If you are interested in Test-First Programming, which has evolved to Test-Driven Development, we recommend Kent Beck's book Test-Driven Development: By Example for an overview of the subject, and David Astels' Test-Driven Development: A Practical Guide for an excellent view of the details.

The Reality

TFP makes sense. There is not yet enough empirical evidence to make a definitive statement about its efficacy, but there seem to be enough testimonials and studies to suggest positive results in some situations. Chris and Gary both planned on adopting the practice.

Unfortunately, they didn't follow through on this part of their plans. Does this mean that they failed? No. They learned a lot about developing unit tests. They also learned that performing unit tests on certain types of classes and methods is much more difficult and time-consuming than on other types of classesregardless of whether the tests are written before or after the code. They also learned that the practice requires a lot of discipline, which takes time to acquire.

The classes that required more effort for writing tests were the GUI classes, such as the windows and dialogs, and the classes that required database access. They required more effort than we were willing to expend on this project. We would have needed to research the different ways we might implement and automate the unit tests for these classes. The results of the research would have given us knowledge of how to do the job, and then we would have worked on our skills to do the job effectively. We chose not to pursue this path for the PSP Tools project.

Most projects face decisions such as the one just described. We all want to learn new skills and techniques. However, sometimes we make a decision based upon some criterion, such as the time we think it will take to master the skills enough to successfully use them, to defer the learning until another time. This is, in our opinion, part of being a professional.

In Chapter 11, both Chris and Gary lament their lack of follow-through on unit tests. They both believe that the code would have been better initially had they been more disciplined. They also agree that those classes for which they wrote unit tests before the code had few, if any, defects against them.

Tools and Techniques

When you write unit tests, you need a way to automate them. Every time you write new code, you want to have tests that exercise the code, and you want to execute all previous tests to ensure that you didn't make a change that broke some other code in the system. Manually executing unit tests every time you change the code is not practical.

Fortunately for most programmers today, a unit test framework is freely available for most programming languages. The framework defines a way of writing and executing tests, provides appropriate libraries to support writing and running the tests, and helps you view the results of the tests. The framework goes by different names depending on the programming language. For Java, it is called JUnit. [8] JUnit is available for download at www.junit.org. The Web pages for JUnit contain documentation and other supporting tools, many of them integrations with IDEs . We used the JUnit add-in for Eclipse.

[8] Other languages have unit test frameworks with names like CUnit for C and VBUnit for Visual Basic.

Anatomy of a JUnit Test

Writing JUnit tests is simple. You must follow just a few rules when you write your test code, for example:

  • Each test method must begin with the string "test." JUnit uses the reflection feature of Java to identify the test case's methods that should be run when executing the unit tests.

  • The test case classes can have any name, but we recommend that for any <class>, name your test cases either Test<class> or <class>Test. We chose the latter naming convention.

  • If you have code that you want to run before every test method in the test case class, insert it into the setUp() method.

  • If you have code that you want to run after every test method in the test case class, insert it into the tearDown() method.

  • If you want to run just the tests of the test class, provide a main() method.

The easiest way to get started is to use a code template. Many of the JUnit IDE integrations come with templates for JUnit tests. Start with one of them and modify it for your needs. We used the template that came with the Eclipse add-in for JUnit. The JUnit integration for Eclipse makes it easy to follow the rules and guidelines.

You select the class for which you want a test case, choose the methods that require tests, and select a couple of other properties. The JUnit integration generates a test case source file as shown in Listing 7.5, with stubs for the test methods.

Listing 7.5 JUnit test case
  package  com.skunkworks.psp.main;  import  junit.framework.TestCase; /**  *  @author  Gary Pollice  */  public class  PSPNewUserManagerTest  extends  TestCase {      /**       * Constructor for PSPNewUserManagerTest.       *  @param  arg0 */  public  PSPNewUserManagerTest(String arg0) {  super  (arg0);      }  public static void  main(String[] args) {           junit.textui.TestRunner.run(PSPNewUserManagerTest.  class  );      }      /**       *  @see  TestCase#setUp()       */  protected void  setUp()  throws  Exception {  super  .setUp();      }      /**       *  @see  TestCase#tearDown()       */      protected void tearDown()  throws  Exception {  super  .tearDown();      }  public void  testPSPNewUserManager() {}  public void  testDoNewUser() {} } 

Notice that the test methods in Listing 7.5 do not have a return value. Each test is self-contained and uses special functions for testing the correctness of the code. These methods are associated with the Assert class that is part of the JUnit framework. The Assert class is used to group static functions that allow you to code conditions in your JUnit tests. As long as the assertion is valid, the test continues. If the assertion is invalid, the test stops and reports an error. Listing 7.6, taken from the PSPUserTest class, illustrates the use of the assertTrue() method. We make two assertions. The first ensures that two PSPUser objects with the same user name and login name compare as equal. The second assertion ensures that when the user names and login names are different, then the objects are not equal.

Listing 7.6 Using assertions in unit tests
 /** Test of equals method, of class PSPUser. */  public void  testEquals() {         System.out.println("testEquals");         PSPUser u =  new  PSPUser("Gary Pollice", "gpollice");         Assert.assertTrue(u.equals(  new  PSPUser("Gary Pollice",               "gpollice")));         Assert.assertTrue(!(u.equals(  new  PSPUser("test", "test"))));     } 

The astute reader will note that we have not tested all possible conditions in Listing 7.6, such as the same user name, but different login names and so on. You have to decide when you have done enough, based on the possible damage an error in the method would cause.

Test Suites Group Tests

Sometimes you want to run one specific test, such as the one shown in Listing 7.5. More often, you want to run more than one test, so you use a test suite to collect the set of tests to run. For example, after you change your code, you usually want to run all your tests to ensure that you have not introduced regressions with the changes, and to ensure that the tests still run successfully.

The structure of a test suite is similar to that of an individual test, except that in the test suite you specify which tests to run (remember that in an individual test, you specify which methods to call). You can specify a combination of tests and other test suites. This allows you to have a single test suite file that runs all your tests. Listing 7.7 illustrates our top-level test suite. It simply runs all the tests specified in the test suites of the subpackages.

Listing 7.7 Main PSP Tools test suite
  public class  PSPSuite  extends  TestCase {  public  PSPSuite(java.lang.String testName) {  super  (testName); }  public static void  main(java.lang.String[] args) {         junit.textui.TestRunner.run(suite());     }  public static  Test suite() {         ///$JUnit-BEGIN$         TestSuite suite =  new  TestSuite("PSPSuite");         suite.addTest(com.skunkworks.psp.main.MainSuite.suite());         suite.addTest(com.skunkworks.psp.util.UtilSuite.suite());         suite.addTest(com.skunkworks.psp.dataobjects.DataObjectSuite.suite());         suite.addTest(com.skunkworks.psp.database.DatabaseSuite.suite());         //$JUnit-END$  return  suite;     } } 

The layout of a test suite is very simple. The constructor and main() method are part of our template. The suite() method does the following:

  1. It creates a new TestSuite object. The TestSuite is one of the classes provided in the JUnit framework.

  2. It adds the tests returned by the suite() methods of the test suites in the subpackages to the TestSuite object created in Step 1. The suite() method in the TestSuite objects in the subpackages gathers the objects' tests and passes them back in the form of a TestSuite .

The test suites in the subpackages can further invoke test suites in their subpackages or specifically add the tests contained in their own packages. Listing 7.8 shows how the test suite for the main package adds the individual tests. The example has been shortened to just show it adding the tests from the PSPNewUserManagerTest class shown in Listing 7.5.

Listing 7.8 TestSuite class in the main package
  package  com.skunkworks.psp.main;  import  junit.framework.*;  public class  MainSuite  extends  TestCase {  public  MainSuite(java.lang.String testName) {  super  (testName);     }  public static void  main(java.lang.String[] args) {         junit.textui.TestRunner.run(suite());     }     public static Test suite() {         ////$JUnit-BEGIN$         TestSuite suite =  new  TestSuite("MainSuite");         suite.addTest(  new  TestSuite(com.skunkworks.psp.main.PSPNewUserManagerTest              .  class  ));         ////$JUnit-END$  return  suite;     } } 

Adding the tests from the PSPNewUserManagerTest class takes advantage of Java's ability to examine class contents dynamically. The second statement in the suite() method of Listing 7.8 creates a new TestSuite object that contains just the tests defined in PSPNewUserManagerTest . When we add code to bundle the rest of the tests into the test suite, we return the composite test suite to the caller, in this case the PSPSuite object.

Once you establish a method for using JUnit and you create the templates, it is easy to add to your test cases. You then can concentrate on the hard work of writing good tests.

Running the Unit Tests

You can run the unit tests by executing the main() method from specific test classes or test suite classes. Many IDEs have integrations that let you run your tests directly from them. The Eclipse platform provides a nice JUnit integration. We simply select the test case or test suite and run it as a JUnit test. The results are shown graphically. As long as you are not color blind, [9] the status of your tests is instantly obvious by glancing at the result bargreen means everything passed, red means something failed and requires investigation. The actual failures are displayed, with stack traces and other useful information in the error windows.Figure 7.8 shows the result of running just one of our test cases in Eclipse. The Hierarchy tab shows the single test that was run ( testMakeDatabase ) and the green bar tells us that the test passed.

[9] Red and green are the hardest colors for people afflicted with color blindness to distinguish.

Figure 7.8. JUnit test results viewed in Eclipse

graphics/07fig08.jpg



Software Development for Small Teams. A RUP-Centric Approach
Software Development for Small Teams: A RUP-Centric Approach (The Addison-Wesley Object Technology Series)
ISBN: 0321199502
EAN: 2147483647
Year: 2003
Pages: 112

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