Software Design
Lectures
A Unit Testing Framework

Recapitulation

Unit testing follows a pattern

Lots of small, independent tests

Reporting

Some optional shared setup and teardown

Aggregation (combine tests into test suites)

When you see a pattern, build a framework

Write shared code once

Make it easy for people to do things the right way

JUnit and Its Children

JUnit testing framework written by Erich Gamma in 1997

Now hosted at www.junit.org

Has become the unofficial standard for Java testing

Supported by many IDEs

Widely imitated: C++, Perl, Python, .NET, etc.

Once you know one, you can easily use others

Organization

[JUnit Organization]

Using JUnit Directly

Derive a class containing tests from junit.framework.TestCase

Each independent test is a method

Construct instances of the test class

Identify which test each one is to run by passing method name to constructor

Add instances to a suite

Usually an instance of TestSuite

Pass the suite to TestRunner.run()

Checking Results

TestCase has methods to check test results

assertEquals(a, b)

assertNotSame(a, b)

Etc.

Allows JUnit to distinguish expected from unexpected behavior

TestRunner only reports things that require attention

Remember, the more you print, the less people read

Direct Example

public class TestDirect extends TestCase {

    public static void main(String[] args) {
        TestSuite suite = new TestSuite();
        suite.addTest(new TestDirect("testIdentical"));
        suite.addTest(new TestDirect("testDouble"));
        ...etc...
        TestRunner.run(suite);
    }

    public TestDirect(String name) {
        super(name);
    }

    public void testIdentical() {
        Rect left   = new Rect(0, 0, 1, 1);
        Rect right  = new Rect(0, 0, 1, 1);
        Rect expect = new Rect(0, 0, 1, 1);
        assertEquals(left.overlap(right), expect);
    }

    public void testDouble() {
        Rect left   = new Rect(0, 0, 1, 1);
        Rect right  = new Rect(0, 0, 2, 2);
        Rect expect = new Rect(0, 0, 1, 1);
        assertEquals(left.overlap(right), expect);
    }

    ...etc...
}

Direct Output

Use a slightly broken Rect class

java ...flags... TestDirect
..F.....
Time: 0.02
There was 1 failure:
1) testDouble(TestDirect)junit.framework.AssertionFailedError: expected:<null> but was:<<0,0,1,1>>
	at TestDirect.testDouble(TestDirect.java:26)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at TestDirect.main(TestDirect.java:73)

FAILURES!!!
Tests run: 7,  Failures: 1,  Errors: 0

Saving Your Wrists

JUnit can use reflection to look up methods given their names

Covered in a later lecture

Pass in the class of your tests

JUnit will decide which methods to run based on their names

If the method name starts with "test", TestRunner will execute it

Remember: programs are data!

Reflection Example

public class TestReflect extends TestCase {

  public static void main(String[] args) {
    TestSuite suite = new TestSuite(TestReflect.class);
    TestRunner.run(suite);
  }

  ...constructor as before...

  ...test methods as before...
}

Visualizing Tests

JUnit comes with a graphical interface

Give it the name of the test class to run

The origin of the phrase "getting a green bar"

	$ java ...options... junit.swingui.TestRunner TestReflect
[JUnit Swing UI]

Testing for Exceptions

Suppose a method is supposed to throw an exception

For example, throw IllegalArgumentException for null parameter

Pattern is:

Call method

Call TestCase.fail() on the next line

Catch specific exception (and do nothing)

(Optional) catch other exceptions, and fail again

Method to test:

public boolean contains(Rect other) throws IllegalArgumentException {
    if (other == null) {
        throw new IllegalArgumentException("null argument");
    }
    return (fLoX <= other.fLoX) && (fLoY <= other.fLoY)
        && (fHiX >= other.fHiX) && (fHiY >= other.fHiY);
}

Test code:

public void testThrowForNull() {
    Rect a = new Rect(0, 0, 1, 1);
    try {
        boolean b = a.contains(null);
        fail();
    }
    catch (IllegalArgumentException e) {
        // success
    }
    catch (Exception e) {
        fail();
    }
}

Setup and Teardown

Sometimes want to run many tests on the same fixture

JUnit supports this:

Calls setUp() before each test

Call tearDown() after each test

Default implementations do nothing

But you can override them...

Remember: every test must be independent

General Rules for Unit Tests

Keep tests small

Each tests exactly one thing

Make tests independent

No side effects

No order dependencies

Don't hard-code things like filenames

Want to be able to move tests around

Use meaningful names

Check results with assertion methods, not raw assert statements


$Id: junit.html,v 1.1.1.1 2004/01/04 05:02:31 reid Exp $