Testing Complex Packages with GrandTestAuto
In the
two minute guide to GrandTestAuto we saw how our test
classes must relate to our production code in order to
be run by GrandTestAuto (GTA), as well as how to invoke the tool. We saw that GTA requires tests for
all public classes and for all public and protected methods in those classes. This tutorial answers the following
questions:
- how can protected methods tested?
- how do we name the test for methods in a class that have the same name?
- how does coverage assurance take inheritance into account?.
There are lots of real-life use of GTA in the unit tests for GTA itself (these, and the rest of the source code, are
in the download).
Testing protected methods and classes with protected constructors
By the design motivation for GTA, protected methods and constructors will need to be invoked from outside the
package in which they are defined. There is a little trick to doing this. Suppose that
X is a public class in a package
p that we are
developing:
package p;
public class X {
protected X( String str ) {
//Constructor body ...
}
protected String a() {
//Method body ...
}
}
The trick is to have an extension of X along with our class XTest:
package p.test;
public class XTest {
public boolean constructorTest() {
X x = new XExt( "a string" );
//Test state of x ...
}
public boolean aTest() {
XExt x = new XExt( "a string" );
String str = x.a();
//Test str ...
}
}
class XExt extends X {
protected XExt( String str ) {
super( str );
}
protected String a() {
return super.a();
}
}
This trick will work so long as the class or method under test is not final. Classes that are
final and have a protected constructor are exempt from being tested, and so are methods that are protected and
final.
Naming tests for overloaded method names
As we saw in the first tutorial, test methods take no parameters. How then is it possible to have separate tests
for methods that have the same name? Suppose that a class has testable methods as follows:
public boolean meth() ...
public void meth( String str ) ...
protected String meth( char[] c )...
GTA uses a naming pattern to differentiate the tests for these methods. The pattern works by incorporating the
parameter
types for a method into the test name. So the names of the test methods for the methods above would be:
public boolean methTest() ...
public boolean meth_String_Test() ...
public boolean meth_charArray_Test() ...
The naming pattern for test methods is:
- suppose that method meth has name m and that no other testable method in the class
has this name. Then the name of the test method for meth is mTest.
- Suppose that there are several methods meth1, meth2,.... with the name m. Then the test methods for these
have names that take into account the
types of the parameters for the methods. These compound names are calculated as follows:
- the first part of the name is m
- for each parameter type, the string '_TYPE' is appended, where
- for object types, 'TYPE' is the unqualified class name of the type
- for primitive types, 'TYPE' is the name of the type
- for arrays of object type, 'TYPE' is the unqualified name of the type,
followed by "Array", followed by the empty string for arrays of dimension 1 or by the
dimension of
the array for higher dimensional arrays.
- for arrays of primitive type, the same rules as for object-type arrays, but with the type of
the primitive.
(By unqualified class name is meant the name of the class stripped of its package name.)
- the last part of the name is _Test.
As an example of this naming pattern, suppose that class X includes the testable methods
- m()
- m( String )
- m( int )
- m( String[], int[][] )
Then the names of the corresponding test methods would be
- m_Test()
- m_String_Test()
- m_int_Test()
- m_StringArray_intArray2_Test()
Testing of inherited methods
In this section we look in detail at the way that GTA ensures that all accessible methods in a package are tested.
As usual, we'll work with an example. Consider a package with public classes:
- I which is an interface
- A which is an abstract class with methods:
- public boolean a();
- public void b( String );
- protected abstract void c();
- private void d();
- B which extends A, is not abstract, and
has methods:
- public void b( String[] );
- protected void c();
- private void d();
- C which extends A, is not abstract, and
has methods:
- public void e( int ) throws IOException;
- protected void c();
- String f();
The test sub-package must contain test classes for B and C.
The test for B must contain tests for its implementation
of b and c. The test for
C must contain tests for e and
its implementation of c.
To test the methods implemented in A, we have two choices.
We can either test them in BTest and
CTest, or define a class ATest.
The class ATest could either use instances of B or
C in its
tests, or might define and use a concrete extension of A.
If the methods of A are to be tested
in BTest and CTest, then
the method A.a defined can be tested in either BTest or
CTest.
The method b defined in A is inherited
only by C so must be tested in CTest.