Login | Register
My pages Projects Community openCollabNet

argouml
Wiki: The JUnit test cases

Edit this page | Links to this page | Page information | Attachments | Refresh page

 

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 reside in a separate directory and run from ant targets in trunk/src/argouml-build/build.xml. They are never distributed with ArgoUML; they are merely a tool for developers.

By running the command build tests guitests or build alltests in trunk/src/argouml-build these test cases are run, each in its own JVM.

Each test case writes its result on the Ant log.

The result is also generated into a browsable report that can be found at build/test/reports/junit/output/html/index.html. This is the same report that is presented as a result from the nightly build.

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

About the Test case Class

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

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 trunk/src/argouml-build/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 but they wont be run by other developers and nightly build. If you write support classes for the tests on the other hand, don't name them as a test case to avoid confusion.

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.

When running the tests using build tests the java.awt.headless is set to true. This means that awt is started in a mode that disallows actual presentation so no windows will be opened. On any attempt to realize a window a ?JavaHeadlessException is thrown making the test case fail.

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

Every test case class imports the JUnit framework:

    import junit.framework.TestCase;

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

Naming JUnit tests classes

An ArgoUML class org.argouml.x.y.z stored in the file trunk/src/argouml-app/src/org/argouml/x/y/z.java has its JUnit test case class called org.argouml.x.y.Testz stored in the file trunk/src/argouml-app/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 trunk/src/argouml-app/tests/org/argouml/x/y/GUITestz.java

If, for convenience reasons, you would like to split the tests of a single class into several test classes, just name them with some extra suffix. Either 1, 2, 3, or something describing what part that test case tests.

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.

About the Test case Methods

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. Therefore 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);
    }

    public void setUp() throws Exception {
        super.setUp();
        InitializeModel.initializeDefault();
    }

    // Testing all three constructors.
    public void testCreate1() {
        UMLAction to = new UMLAction(new String("hexagon"));
        assert("Disabled", to.shouldBeEnabled());
    }
    public void testCreate2() {
        UMLAction to = new UMLAction(new String("hexagon"), true);
        assert("Disabled", to.shouldBeEnabled());
    }
    public void testCreate3() {
        UMLAction to = new UMLAction(new String("hexagon"), 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();
    }

    public void testDummy() { }
}

Improving a test case

Test cases are better if they are simpler. Strive to involve as little part of the ArgoUML code as possible. Ideally you are just testing a single class at a time.

The involvement of the Model subsystem is in most cases inevitable since a majority of the classes within ArgoUML use the Model subsystem. Nevertheless, we should, to allow for better and quicker tests, strive to not involve the Model subsystem if possible.

If the Model subsystem is to be involved, it must be initialized. Either with the default implementation (the MDR) or with some other implementation. For testing purposes there exists a Mock implementation that can be used if no functionality is required from the Model subsystem or when testing the Model subsystem itself.

If the Mock model subsystem implementation cannot be used, then the tests have to have the MDR subproject on the class path when running. This is not a problem when running the tests from the ant setup since MDR is always included when running the tests. When running tests from within Eclipse this is a small problem.

This is because the run-time dependency defaults used in Eclipse when creating a JUnit test configuration are based on the compile-time dependency settings and we don't have MDR in the compile-time dependencies because we want to enforce that there is no such dependency. Hopefully an upcoming code reorganization will address this problem.

The simplest way is to create/run the JUnit test configuration and if it fails with a ?ExceptionInInitializer error open it with the Configuration editor, add the argouml-core-model-mdr project to the class path and then run again.

This means that you should have the following priorities:

  1. Don't use the Model subsystem. Only possible in a few simple cases.
  2. Use the Mock model subsystem implementation.

    Call the function org.argouml.model.?InitializeModel#initializeMock() from setUp() in your test case. Only possible in a few simple cases.

  3. Use the real Model subsystem implementation from the setUp() function.

    Call the function org.argouml.model.?InitializeModel#initializeDefault() from setUp() in your test case.

  4. Use the real Model subsystem implementation from the constructor.

    Call the function org.argouml.model.?InitializeModel#initializeDefault() in the constructor of your test case. This is needed if your tests rely on the value in some member variables referencing some object retrieved from the model.

  5. Use the real Model subsystem implementation from the static initialization section of your test class.

    Call the function org.argouml.model.?InitializeModel#initializeDefault() in the static initialization. This is needed if your tests rely on the value of some static member variables referencing some object retrieved from the model.

We should try to get as many tests from a GUITest* class to the corresponding Test* class because The Test*-classes don't involve the GUI components and are run by automatic builds regularly.


CategoryFromCookbook

The JUnit test cases (last edited 2008-12-07 12:23:22 -0700 by linus)