|
Unit Testing in Java. How Tests Drive the Code Authors: Link J., Frohlich P. Published year: 2003 Pages: 39-40/144 |
This chapter concentrated on clues and theoretical basics to explain how you can improve and expand your existing test cases and how you find missing ones. Focusing on individual tests, improvements in test method naming, length, and simplicity were suggested. Test cases should concentrate on the typical usage of the CUT, on threshold values, and on equivalence classes. Error cases are equally important to test and the interaction of several objects must not be neglected. Moreover, the ideas in design by contract can give valuable hints for certain kinds of tests. Last but not least, one should never forget to adapt the test suite during and after refactoring; this can induce considerable but worthwhile effort.
I intentionally did not discuss the algorithmic and formal implementation of various software models in test cases—an aspect frequently discussed in the test literature. My reason was twofold: First, there are very few up-front models available in the course of test-first development. Second, the methods suggested there usually generate an overwhelming number of different test cases. This has a deterrent effect on "normal" software developers, and it also omits the meaningful balancing between testing costs and benefits. [13] In addition, although formal test case generation methods theoretically achieve a larger code coverage, their drawback is that they can hinder intuitive testing. Effective testing derives from intuition; restrictive rules hinder the intuition. Nevertheless, those who reach the limits of the test ideas described here, or those who are simply curious , will hopefully find more help in Bibliography and List of References, Further Reading.
[13] [URL:TestingAgile] is all about finding the balance between no testing and complete testing given the needs of different kinds of software.
As mere users of a test framework, we should not bother about its internal structure, unless we are programmers and, as such, interested in the inner parts of each piece of software we can get a hold of. In addition, unit testing is such an individual activity that we may sometimes not get around expansions and adaptations of the framework. JUnit does not cover every functionality anyone may need during testing; it is an open core that can be expanded and completed in different places. And if we find that JUnit limits our own test needs too much, then it may still serve us as an example for the benefits and drawbacks of certain design decisions in the development of our own test support tools.
So far, we have introduced three classes: TestRunner (in three different variants), TestCase , and TestSuite . But JUnit has much more in store. The inner structure of JUnit— mainly the package junit.framework —is a small tutorial in itself for the use of a large number of design patterns [Gamma95]. Kent Beck and Erich Gamma [Beck99] describe the patterns used and their motivation. [1] The article and the accompanying study of the JUnit sources is recommended to everyone and is an absolute must for all who want to understand and expand JUnit, even though JUnit has been further developed in the interim.
Figure 5.1 shows a small section from the inner life of JUnit Version 3.8. Except for TestDecorator , this class diagram shows a section from the junit.framework package. What is new for us are the "extension hooks" for project-specific and functional expansions of the JUnit framework:
TestResult is the container of all results from a test run. All classes implementing the test interface— TestCase , TestSuite , and TestDecorator —become an instance of the class TestResult in its run() method.
A TestListener can be registered with a TestResult by using addListener() to obtain information about start, end, failure, and error events in a test. This possibility is used by all of the three TestRunner classes to get notification about the current state of a test run.
The class TestDecorator serves as a superclass for a large number of expansions of the test framework. As the name implies, it implements the decorator pattern described by Gamma et al. [95], allowing the use of several concurrent expansions. Practical examples are included in the junit.extensions package.
Most JUnit expansions are developed as subclasses of TestCase . If we use TestDecorator instead, then we have the benefit that several expansions can be used concurrently. The drawback is that the application becomes more complex, because a decorator instance has to be added explicitly to a test or test suite.
AssertionFailedError is an exception thrown when an assert call fails, is caught by TestCase instances, and finally passed on to a TestResult for registration.
Assert is a superclass of TestCase and accommodates all variants of the assert method, all of which are also static. In this way, other classes, like test decorators, can also use the assert functionality. The extent of the assert variants available and their implementations is the focus of constant discussion in the JUnit discussion group [URL:YahooJUnit]. Project-specific expansions normally include a new assert method in one form or another to compare certain collection classes or other data structures.
Figure 5.1:
JUnit class diagram.
Knowing how JUnit is built inside is very useful, especially when you decide to start peeking behind the TestCase scene to enjoy some well written source text of the framework.
[1] This article is included in the JUnit documentation as cookstour.htm .
|
Unit Testing in Java. How Tests Drive the Code Authors: Link J., Frohlich P. Published year: 2003 Pages: 39-40/144 |
![]() Clean Code: A Handbook of Agile Software Craftsmanship | ![]() Head First Design Patterns | ![]() Pragmatic Unit Testing in Java with JUnit | ![]() JUnit Recipes: Practical Methods for Programmer Testing |