NestedTestCaseClasses is technique for OrganizingTestCases that consists of embedding unit tests right in the class they are testing.
This pattern has a number of advantages, some perceived disadvantages (easily dealt with), and some real issues that need watching, all discussed below.
The basic template for a class using this technique is:
package wiki.example;
class Demo {
// class body goes here
public static void main(String[] args) {
junit.textui.TestRunner.run(TestCase.class);
}
public static class TestCase extends junit.framework.TestCase {
public void testDoSomething() {
Demo d = new Demo();
d.doSomething();
assertEquals(control, d.field);
}
}
}
Advantages
- Start coding unit tests without investing in a lot of infrastructure first.
- Programmers edit tests and code at the same time.
- Edit-compile-test loop is fast and easy.
- Compiler forces the class and its tests to always be in sync.
- Test case can examine encapsulated internal state, including UnitTestingNonPublicMemberFunctions, without exposing implementation details via the API.
- Impervious to refactoring with MoveClass.
Perceived Disadvantages
- CodeBloat. For whatever reason, many people consider it important to separate the production code from the test harness. You could just ShipTheTests, but if this is really an issue, just strip the TestCase classes out of the deliverable. This is easy to do since each class (even if it is a nested class) is stored in its own .class file. Or use a preprocessor.
NOTE: #if UNIT_TESTS is an easy way to wrap the test code so it 'disappears' in Ship builds.
Real Issues
- StrongCoupling between tests and code. Because the test code is able to break encapsulation in ways application code can't, it is more brittle than application code. When the implementation of a class is changed, the unit tests may need to change, even if the API is unchanged. To minimize this problem, observe the LawOfDemeter in the test code. Test this object, not its friends.
- You'll need a TestCollector. The easiest approach is to use SimpleTestCollector?, but you can also use MagicFilename?s to identify the test cases. As you recurse over the class files, for any file named Foo$TestCase.class, add Foo.TestCase to your TestSuite.
- You're not testing the class from the outside, so you may lose the usual advantage of test cases showing how other objects can use the class. You also don't know if the class is providing outside objects with everything they need.
PaulKrause