Write a test for a feature
Code the simplest thing that can possibly work
Run the test
Test passed ? goto 1 : goto 2
Have a new feature? Write a test.
Want to change the API? Write a test.
Find a bug? Write a test.
JUnit is the de facto standard unit testing framework
Eclipse/NetBeans/IntelliJ IDEA bundle JUnit
Command line/Ant/Maven work too
Each complex number has a real part and an imaginary part: 7+3i, 1.4-2.57i, 982+0.003i, etc.
i2 == -1
Multiply by FOIL, and then change i2 to -1
Create a new Java project in the usual way
Add the junit.jar library from eclipse/plugins/org.junit_3.8.1/junit.jar
Create a new class named ComplexTest. (Naming convention is that test classes end in "Test").
Use File/New/JUnit Test Case
Default options are fine for now
Add junit.jar to your classpath
Create a new class named ComplexTest. (Naming convention is that test classes end in "Test").
Test methods have names like testFoo
Test results are verified by assertEquals()
A simple test for addition:
public void testAdd() {
Complex z1 = new Complex(1, 1);
Complex z2 = new Complex(1, 1);
Complex z3 = z1.add(z2);
assertEquals(2, z3.getRealPart());
assertEquals(2, z3.getImaginaryPart());
}
Let's look at this in Eclipse
Initially this doesn't even compile
Let Eclipse fill in the missing pieces
You'll probably end up with something like this:
public class Complex {
public Complex(int i, int j) {
}
public Complex add(Complex z2) {
return null;
}
public long getRealPart() {
// ???? Auto-generated method stub
return -1; }
public long getImaginaryPart() {
// ???? Auto-generated method stub
return -1;
}
}
Select ComplexTest.java in the Package Explorer
Context Menu/Run/JUnit Test
The test fails!
You want your tests to fail the first time you run them. That's how you know they're testing what you want them to test.
Simplest thing that could possibly work:
public Complex add(Complex z2) {
return new Complex(2, 2);
}
public long getRealPart() {
return 2;
}
public long getImaginaryPart() {
return 2;
}
Next test:
public void testAddNonEqualNumbers() {
Complex z1 = new Complex(1, 1);
Complex z2 = new Complex(2, 2);
Complex z3 = z1.add(z2);
assertEquals(3, z3.getRealPart(), 0);
assertEquals(3, z3.getImaginaryPart(), 0);
}
This test fails!
So fix the code
private int real;
private int imaginary;
public Complex(int real, int imaginary) {
this.real = real;
this.imaginary = imaginary;
}
public Complex add(Complex z) {
return new Complex(this.real+z.real, this.imaginary + z.imaginary);
}
public long getRealPart() {
return this.real;
}
public long getImaginaryPart() {
return this.imaginary;
}
Write a test:
public void testAddComplexWithDecimalPoints() {
Complex z1 = new Complex(1.5, 2.0);
Complex z2 = new Complex(2.1, 2.5);
Complex z3 = z1.add(z2);
assertEquals(3.6, z3.getRealPart(), 0.000001);
assertEquals(4.5, z3.getImaginaryPart(), 0.0000001);
}
Repair the code:
private double real;
private double imaginary;
public Complex(double real, double imaginary) {
this.real = real;
this.imaginary = imaginary;
}
public Complex add(Complex z) {
return new Complex(this.real+z.real, this.imaginary + z.imaginary);
}
public double getRealPart() {
return this.real;
}
public double getImaginaryPart() {
return this.imaginary;
}
Repair the test code:
private double tolerance = 0.000001;
public void testAdd() {
Complex z1 = new Complex(1, 1);
Complex z2 = new Complex(1, 1);
Complex z3 = z1.add(z2);
assertEquals(2, z3.getRealPart(), tolerance);
assertEquals(2, z3.getImaginaryPart(), tolerance);
}
public void testAddNonEqualNumbers() {
Complex z1 = new Complex(1, 1);
Complex z2 = new Complex(2, 2);
Complex z3 = z1.add(z2);
assertEquals(3, z3.getRealPart(), tolerance);
assertEquals(3, z3.getImaginaryPart(), tolerance);
}
public void testAddComplexWithDecimalPoints() {
Complex z1 = new Complex(1.5, 2.0);
Complex z2 = new Complex(2.1, 2.5);
Complex z3 = z1.add(z2);
assertEquals(3.6, z3.getRealPart(), tolerance);
assertEquals(4.5, z3.getImaginaryPart(), tolerance);
}
Run the test
Repeat
Subtraction
Write the test:
public void testSubtract() {
Complex z1 = new Complex(1, 1);
Complex z2 = new Complex(1, 1);
Complex z3 = z1.subtract(z2);
assertEquals(0, z3.getRealPart(), tolerance);
assertEquals(0, z3.getImaginaryPart(), tolerance);
}
Implement the feature:
public Complex subtract(Complex z) {
// ???? Auto-generated method stub
return null;
}
Debug until the test passes:
public Complex subtract(Complex z) {
return new Complex(this.real - z.real, this.imaginary - z.imaginary);
}
Unit testing requires that each test be completely independent of all other tests.
Not a requirement of all types of tests (functional, integration, user acceptance, etc.) but JUnit is primarily a unit testing framework.
No state shared between methods
All instance data reinitialized before each test run.
Each test is run with a clean, new object.
Test adding a number to itself:
private Complex z = new Complex(1, 1);
public void testAddNumberToItself() {
z = z.add(z);
assertEquals(2.0, z.getRealPart(), 0.000001);
assertEquals(2.0, z.getImaginaryPart(), 0.000001);
}
public void testAddNumberToItselfTwice() {
z = z.add(z).add(z);
assertEquals(3.0, z.getRealPart(), 0.000001);
assertEquals(3.0, z.getImaginaryPart(), 0.000001);
}
Both tests pass!
Order in which tests are run doesn't matter
Expensive initialization slows down tests.
One of these tests will fail:
private static Complex w = new Complex(1, 1);
public void testAddNumberToItselfStatic() {
w = w.add(w);
assertEquals(2.0, w.getRealPart(), 0.000001);
assertEquals(2.0, w.getImaginaryPart(), 0.000001);
}
public void testAddNumberToItselfTwiceStatic() {
w = w.add(w).add(w);
assertEquals(3.0, w.getRealPart(), 0.000001);
assertEquals(3.0, w.getImaginaryPart(), 0.000001);
}
Sometimes the setup can be more complex than you want to put in a constructor or a field initializer
Sometimes you want to reinitialize static data
Sometimes you just want to share setup code between different methods.
setUp
method is run before each test:
public void setUp() {
w = new Complex(1, 1);
}
tearDown
method:
protected void tearDown() {
w = null;
z = null;
System.gc();
}
Rarely necessary
First argument to each assertFoo
method can be a string that's printed if the assertion fails:
private final static Complex ZERO = new Complex(0.0, 0.0);
public void testMultiplicationByZero() {
Complex result = z.multiply(ZERO);
double tolerance = 0.0;
assertEquals("Multiplication by zero failed in real part",
0.0, z.getRealPart(), tolerance);
assertEquals("Multiplication by zero failed in imaginary part",
0.0, z.getImaginaryPart(), tolerance);
}
As usual, fix the code after the test fails:
public Complex multiply(Complex z) {
return new Complex(
this.real*z.real - this.imaginary * z.imaginary,
this.real*z.imaginary + this.imaginary * z.real);
}
As always with floating point arithmetic, you want to avoid direct equality comparisons.
public static void assertEquals(double expected, double actual, double tolerance)
public static void assertEquals(float expected, float actual, float tolerance)
public static void assertEquals(String message, double expected, double actual, double tolerance)
public static void assertEquals(String message, float expected, float actual, float tolerance)
Straight-forward because integer comparisons are straight-forward:
public static void assertEquals(boolean expected, boolean actual)
public static void assertEquals(byte expected, byte actual)
public static void assertEquals(char expected, char actual)
public static void assertEquals(short expected, short actual)
public static void assertEquals(int expected, int actual)
public static void assertEquals(long expected, long actual)
public static void assertEquals(String message, boolean expected, boolean actual)
public static void assertEquals(String message, byte expected, byte actual)
public static void assertEquals(String message, char expected, char actual)
public static void assertEquals(String message, short expected, short actual)
public static void assertEquals(String message, int expected, int actual)
public static void assertEquals(String message, long expected, long actual)
assertEquals
uses the equals()
method:
public void testEquals() {
Complex z1 = new Complex(1.0, 1.0);
Complex z2 = new Complex(1.0, 1.0);
assertEquals("1+i != 1+i", z1, z2);
}
Implement the equals
method:
public void testEquals() {
Complex z1 = new Complex(1.0, 1.0);
Complex z2 = new Complex(1.0, 1.0);
assertEquals("1+i != 1+i", z1, z2);
}
assertSame
and assertNotSame
use the ==
operator:
public void testClone() {
Complex z1 = new Complex(1.0, 1.0);
Object z2 = z1.clone();
assertEquals(z1, z2);
assertNotSame(z1, z2);
}
Implement the clone method:
public Object clone() {
try {
return super.clone();
}
catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
For general boolean tests, one can assert that some condition is true
public static void assertTrue(boolean condition)
public static void assertTrue(String message, boolean condition)
Test absolute value:
public void testNegative2GreaterThan1() {
Complex z1 = new Complex(-2, 0);
Complex z2 = new Complex(1, 0);
double a1 = z1.abs();
double a2 = z2.abs();
assertTrue(a1 > a2);
}
Can also assert that some condition is false
public static void assertFalse(boolean condition)
public static void assertFalse(String message, boolean condition)
Test superclass:
public void testSuperclass() {
Object o = new Complex(-2, 0);
assertFalse(o instanceof Number);
}
public static void assertNull(Object o)
public static void assertNull(String message, Object o)
e.g. testing for memory leak:
public void testMemoryLeak() throws InterruptedException {
Complex z1 = new Complex(-2, 0);
WeakReference ref = new WeakReference(z1);
z1 = null;
System.gc();
System.gc();
System.gc();
Thread.sleep(1000);
assertNull(ref.get());
}
public static void assertNotNull(Object o)
public static void assertNotNull(String message, Object o)
public void testToString() {
Complex z1 = new Complex(-2, 0);
assertNotNull(z1.toString());
}
public static void fail()
public static void fail(String message)
Runtime exceptions cause tests to fail
Different than a test failure
But do not shutdown the VM or stop the other tests
Unexpected checked exceptions must be declared to be thrown from the test method
Otherwise treated the same as runtime exceptions
Errors are treated the same as a runtime exception
try
fail()
catch
public void testParseComplexThrowsNumberFormatException() {
try {
Complex z = Complex.parseComplex("Foo");
fail("Parsed Foo as complex number");
}
catch (NumberFormatException success) {
assertTrue(success.getMessage().indexOf("Foo") >= 0);
}
}
Generally more than one test class per project
Often one test class per tested class
To run all tests in a package in Eclipse select the package and use the context menu to Run As JUnit Test
Multiple test classes can be combined in a test suite class:
public class FunctionTests {
public static Test suite() {
TestSuite result = new TestSuite();
result.addTest(new TestSuite(TranslateFunctionTest.class));
result.addTest(new TestSuite(SubstringTest.class));
result.addTest(new TestSuite(SubstringBeforeTest.class));
result.addTest(new TestSuite(SubstringAfterTest.class));
result.addTest(new TestSuite(LangTest.class));
result.addTest(new TestSuite(LastTest.class));
result.addTest(new TestSuite(ConcatTest.class));
result.addTest(new TestSuite(ContainsTest.class));
result.addTest(new TestSuite(StringLengthTest.class));
result.addTest(new TestSuite(StartsWithTest.class));
result.addTest(new TestSuite(CountTest.class));
result.addTest(new TestSuite(LocalNameTest.class));
result.addTest(new TestSuite(NameTest.class));
result.addTest(new TestSuite(NamespaceURITest.class));
result.addTest(new TestSuite(SumTest.class));
result.addTest(new TestSuite(NumberTest.class));
result.addTest(new TestSuite(RoundTest.class));
result.addTest(new TestSuite(StringTest.class));
result.addTest(new TestSuite(BooleanTest.class));
result.addTest(new TestSuite(CeilingTest.class));
result.addTest(new TestSuite(FloorTest.class));
result.addTest(new TestSuite(IdTest.class));
result.addTest(new TestSuite(TrueTest.class));
result.addTest(new TestSuite(FalseTest.class));
result.addTest(new TestSuite(NotTest.class));
result.addTest(new TestSuite(NormalizeSpaceTest.class));
return result;
}
}
public class JaxenTests {
public static Test suite() {
TestSuite result = new TestSuite();
result.addTest(SAXPathTests.suite());
result.addTest(FunctionTests.suite());
result.addTest(CoreTests.suite());
result.addTest(DOMTests.suite());
result.addTest(JDOMTests.suite());
result.addTest(DOM4JTests.suite());
result.addTest(XOMTests.suite());
result.addTest(JavaBeanTests.suite());
result.addTest(PatternTests.suite());
result.addTest(BaseTests.suite());
result.addTest(HelpersTests.suite());
result.addTest(ExprTests.suite());
result.addTest(UtilTests.suite());
return result;
}
}
Test that what should happen, does happen
Test that what should not happen, doesn't
All public APIs
All protected APIs
Anything that is promised not to change
All generated artifacts: file formats, object serialization, network communication, etc.
All acceptable inputs: files, serialized objects, network connections, etc.
Unacceptable inputs
Private API
Package protected API?
Localized strings
Anything that is not promised
Third party details you don't depend on
This presentation: http://www.cafeaulait.org/slides/ad2006/testdriven/
JUnit: http://www.junit.org/
JUnit@yahoogroups.com: http://groups.yahoo.com/group/junit/
Pragmatic Unit Testing in Java with JUnit by Andy Hunt and Dave Thomas
J. B. Rainsberger
Manning, 2004
ISBN 1-93239-423-0