A Cookbook to DDTTestCase

This cookbook is related to the JUnit Cookbook by Gamma and Beck. I selected the same document structure to clearly show the similarities and the differences between these two frameworks.

The major differences of the two are

  • you do not write tests by overwriting the method runTest() of the subclassed DDTTestCase. This way of test writing is not supported by DDTUnit. So in the following sections all examples from JUnit cookbook containing this technic will be omitted.
  • you need to specify a xml resource for every DDTTestCase class by overwriting the abstract method void initContext().

Writing Tests

DDTUnit like JUnit tests do not require human judgment to interpret, and it is easy to run many of them at the same time. For testing requirements here is what you have to do:

  1. Create an instance of DDTTestCase by subclassing it.
  2. Create a constructor which accepts a String as a parameter and passes it to the superclass. So this is simply the same as JUnit test case.
  3. Implement the abstract method initContext() of DDTTestCase to specify the resource containing the xml description of test data and the clusterId under which the data is accessible. For convenience the methods
    • void initTestData(String resource, String clusterId) or
    • void initTestData(String clusterId)
    can be used as body of the initContext() method. The later is mapped onto the first by duplicating the parameter.
  4. Write test methods that start with prefix test. E.g. void testMyService(). Here you can access xml defined objects by using method getObject(String) e.a. as provided by DDTTestCase. Where the parameter denotes the name of the object to retrieve from internal cache as specified in xml resource.
  5. If you want to check a value, call assert...() method as of standard JUnit. Or specify asserts inside of the xml resource on a per <test> basis and provide the actual object through addObjectToAssert();
For example, to test that the sum of two Moneys with the same currency contains a value which is the sum of the values of the two Moneys, write:
  public void initContext(){
    initTestData("/DDT-TestResource.xml", "MyClassTest");
  }
  
  public void testSimpleAdd() {
    Money m12CHF= (Money)getObject("m12CHF"); 
    Money m14CHF= (Money)getObject("m14CHF"); 
    Money result= m12CHF.add(m14CHF);
    // here is the JUnit assert way of testing
    Money expected= (Money)getObject("m26CHF"); 
    assertTrue(expected.equals(result));
    // here is part of DDTUnit way of testing
    addObjectToAssert("result", result);
  }
Whats missing now is the xml testdata description of the requested objects like m12CHF. Here it comes
  <?xml version="1.0">
  <ddtunit>
    <cluster id="MyClassTest">
      <group id="testSimpleAdd">
        <test>
         <objs>
          <obj id="m12CHF" type="Money">
            <amount>12</amount>
            <currency>CHF</currency></obj>
          <obj id="m14CHF" type="Money">
            <amount>14</amount>
            <currency>CHF</currency></obj>
         </objs>
         <asserts>
          <assert id="result" type="Money" action="isEqual">
            <amount>26</amount>
            <currency>CHF</currency></assert>
         </asserts>
        </test></group>
    </cluster>
  </ddtunit>
If you want to write a test similar to one you have already written, write a Fixture or if only input and expected output changes just add a new <test> to the xml resource.
When you want to run more than one test, just create a Suite like under JUnit.

Fixture

What if you have two or more tests that operate on the same or similar sets of objects?
Tests need to run against the background of a known set of objects. This set of objects is called a test fixture. When you are writing tests you will often find that you spend more time writing the code to set up the fixture than you do in actually testing values.

To some extent, you can make writing the fixture code easier by paying careful attention to the constructors you write. However, a much bigger savings comes from sharing fixture code. Often, you will be able to use the same fixture for several different tests. Each case will send slightly different messages or parameters to the fixture and will check for different results.

When you have a common fixture, here is what you do:

  1. Create a subclass of DDTTestCase
  2. Create a constructor which accepts a String as a parameter and passes it to the superclass.
  3. Implement the abstract method initContext() of DDTTestCase to specify the resource containing the xml description of test data and the classId under which the data is accessible.
  4. Add an <obj> entry in the global section under <cluster> for each part of the fixture and include an instance variable in the Java test code.
  5. Override setUp() to initialize the test conditions by using the globally defined objects.
  6. Override tearDown() to release any permanent resources you allocated in setUp

For example, to write several test cases that want to work with different combinations of 12 Swiss Francs, 14 Swiss Francs, and 28 US Dollars, first create a fixture:

public class MoneyTest extends DDTTestCase { 
    private Money f12CHF; 
    private Money f14CHF; 
    private Money f28USD; 
    
    public void initContext(){
      initTestData("/DDT-TestResource.xml", "MyClassTest");
    }
  
    protected void setUp() { 
        f12CHF= (Money)getObject("f12CHF"); 
        f14CHF= (Money)getObject("f14CHF"); 
        f28USD= (Money)getObject("f28USD"); 
    }
}
And the xml resource (in part):
  <?xml version="1.0">
  <ddtunit>
    <cluster id="MyClassTest">
     <group id="MyMethod">
      <objs>
        <obj id="f12CHF" type="Money">
          <amount>12</amount>
          <currency>CHF</currency></obj>
        ...
      </objs>
      <group id="testSimpleAdd">
      ...
  </ddtunit>
The architecture of DDTUnit garanties that the Fixture is executed before and after every entry of <test> inside of <group>. This is conformant to the behavior of JUnit. Once you have the Fixture in place, you can write as many tests as you like for one test method just by adding new data to the xml resource.

DDTTestCase

How do you write and invoke an individual test case when you have a Fixture?
Writing a test case without a fixture is simple - just look at the example above. Writing one with a Fixture is quit as easy.
For example, to test the addition of a Money and a MoneyBag, write:
Remember we allready defined a Fixture above.

public void testMoneyMoneyBag() { 
    // [12 CHF] + [14 CHF] + [28 USD] == {[26 CHF][28 USD]} 
    Money bag[]= { f26CHF, f28USD }; 
    MoneyBag expected= new MoneyBag(bag); 
    assertEquals(expected, f12CHF.add(f28USD.add(f14CHF)));
}
Create an instance of MoneyTest that will run this test case like this:
  new MoneyTest("testMoneyMoneyBag")
When the test is processed, the name of the test is used to look up the method to run. Once you have several tests, organize them into a Suite.

Suite

How do you run several tests at once?
As soon as you have two test methods, you'll want to run them together. You could run the test methods one at a time yourself, but you would quickly grow tired of that. Instead, DDTUnit as JUnit provides an object, TestSuite which runs any number of test cases together.

For example, to run a single test case, you execute:

DDTTestResult result= (new MoneyTest("testMoneyMoneyBag")).run();
To create a suite of two test cases concerning two different Java test methods and run them together, execute:
TestSuite suite= new TestSuite();
suite.addTest(new MoneyTest("testMoneyEquals"));
suite.addTest(new MoneyTest("testSimpleAdd"));
TestResult result= suite.run();
If you want to execute a test scenario with different input/output information for the same java test method just add a new <test> in the xml test resource.

Another way is to let DDTUnit extract a suite from a DDTTestCase class. This is actually the same behavior as of JUnit. To do so you pass the class of your DDTTestCase to the TestSuite constructor.

TestSuite suite= new TestSuite(MoneyTest.class);
TestResult result= suite.run();
Use the manual way when you want a suite to only contain a subset of the test cases. Otherwise the automatic suite extraction is the preferred way. It avoids you having to update the suite creation code when you add a new test method.

TestSuites don't only have to contain TestCases. They contain any object that implements the Test interface. For example, you can create a TestSuite in your code and I can create one in mine, and we can run them together by creating a TestSuite that contains both:

TestSuite suite= new TestSuite();
suite.addTest(Joerg.suite());
suite.addTest(Kai.suite());
TestResult result= suite.run();

TestRunner

How do you run your tests and collect their results?
For now there is no extra implementation of a DDTTestRunner. So you can use a TestRunner implementation provided by JUnit or other packages like the Apache Ant optional target <junit>.
These tools are used to define the suite to be run and to display its results. You make your suite accessible to a TestRunner tool with a static method suite() that returns a test suite like described above.
For example, to make a MoneyTest suite available to a TestRunner, add the following code to MoneyTest:

public static Test suite() { 
    TestSuite suite= new TestSuite(); 
    suite.addTest(new MoneyTest("testMoneyEquals")); 
    suite.addTest(new MoneyTest("testSimpleAdd")); 
    return suite;
}
If a TestCase class doesn't define a suite method a TestRunner will extract a suite and fill it with all the methods starting with "test".
sf logo