2.4. The JUnit test cases

ArgoUML has a set of automatic test cases using JUnit-framework for testing the insides of the code. The purpose of these are to help in pin-pointing problems with code changes before even starting ArgoUML.

The JUnit test cases are residing in a separate directory and run from ant targets in the src_new/build.xml. They are never distributed with ArgoUML but merely a tool for developers.

By running the command build tests guitests in src_new these test cases are started, each in their own jvm.

Each test case writes its result on the Ant log.

The result is also generated into a set of files that can be found at build/test/reports/junit/output/html/index.html.

The test cases' java source code is located under argouml/tests/org/argouml.

2.4.1. How to write a test case

Now this will make all you java-enthusiasts go nuts! We have both class names and method names with a special syntax.

2.4.1.1. About the Test case Class

The name of the test case class starts with "Test" (i.e. Capital T, then small e, s and t) or "GUITest" (i.e. Capital G, U, I, T then small e, s, t). The reason for this is that the special targets in src_new/build.xml search for test case classes with these names. If you write a test case class that does not comply to this rule, you still can run the test cases in this class manually by starting with build run-with-test-panel, but it wont be known and run by other developers and automatic build mechanisms so don't do it.

Test case classes that don't require GUI components in place have filenames like Test*.java. They must be able to run on a headless system. To make sure that this works, always run your newly developed test cases with build tests using jdk1.4 or later.

Test case classes that do require GUI components in place have filenames like GUITest*.java.

We should try to get as many tests from a GUITest* class to the corresponding Test* class because the latter are run by automatic builds regularly.

Every class org.argouml.x.y.z stored in the file src_new/org/argouml/x/y/z.java should have a JUnit test case class called org.argouml.x.y.Testz stored in the file tests/org/argouml/x/y/Testz.java containing all the Unit Test Cases for that class that don't need the GUI components to run. Tests that do need GUI components to run should be part of a class named org.argouml.x.y.GUITestz stored in the file tests/org/argouml/x/y/GUITestz.java

If you only want to run your newly written test cases and not all the test cases, you could start with the command build run-with-test-panel and give the class name of your test case like org.argouml.x.y.Testz or org.argouml.x.y.GUITestz. You will then get the output in the window. You could run all tests in this way by specifying the special test suite org.argouml.util.DoAllTests in the same way.

Every test case class imports the JUnit framework:

import junit.framework.*;

and it inherits TestCase (i.e. junit.framework.TestCase).

2.4.1.2. About the Test case Method

Methods that are tests must have names that start with "test" (i.e. all small t, e, s, t). This is a requirement of the JUnit framework.

Try to keep the test cases as short as possible. There is no need in cluttering them up just to beautify the output. Prefer

// Example from JUnit FAQ
public void testIndexOutOfBoundsExceptionNotRaised()
        throws IndexOutOfBoundsException {
    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);
}

over

public void testIndexOutOfBoundsExceptionNotRaised() {
    try {
        ArrayList emptyList = new ArrayList();
        Object o = emptyList.get(0);
    } catch (IndexOutOfBoundsException iobe) {
        fail("Index out of bounds exception was thrown.");
    }
}

because the code is shorter, easier to maintain and you get a better error message from the JUnit framework.

A lot of times it is useful just to run the compiler to verify that the signatures are correct on the interfaces. Therefor Linus has thought it is a good idea to add methods called compileTestStatics, compileTestConstructors, and compileTestMethods that was thought to include correct calls to all static methods, all public constructors, and all other public methods that are not otherwise tested. These methods are never called. They serve as a guarantee that the public interface of a class will never lose any of the functionality provided by its signature in an uncontrolled way in just the same way as the test-methods serve as a guarantee that no features will ever be lost.

Example 2.1. An example without javadoc comments

package org.argouml.uml.ui;
import junit.framework.*;

public class GUITestUMLAction extends TestCase {
    public GUITestUMLAction(String name) {
	super(name);
    }

    // Testing all three constructors.
    public void testCreate1() {
	UMLAction to = new UMLAction(new String("hejsan"));
	assert("Disabled", to.shouldBeEnabled());
    }
    public void testCreate2() {
	UMLAction to = new UMLAction(new String("hejsan"), true);
	assert("Disabled", to.shouldBeEnabled());
    }
    public void testCreate3() {
	UMLAction to = new UMLAction(new String("hejsan"), true, UMLAction.NO_ICON);
	assert("Disabled", to.shouldBeEnabled());
    }
}

and the corresponding no-GUI-class:

package org.argouml.uml.ui;
import junit.framework.*;

public class TestUMLAction extends TestCase {
    public TestUMLAction(String name) {
	super(name);
    }

    // Functions never actually called. Provided in order to make
    // sure that the static interface has not changed.
    private void compileTestStatics() {
	boolean t1 = UMLAction.HAS_ICON;
	boolean t2 = UMLAction.NO_ICON;
	UMLAction.getShortcut(new String());
	UMLAction.getMnemonic(new String());
    }

    private void compileTestConstructors() {
	new UMLAction(new String());
	new UMLAction(new String(), true);
	new UMLAction(new String(), true, true);
    }

    private void compileTestMethods() {
	UMLAction to = new UMLAction(new String());
	to.markNeedsSave();
	to.updateEnabled(new Object());
	to.updateEnabled();
	to.shouldBeEnabled();
    }
}