jFIT – a little brother for FIT

it’s common sense to test a single class inside of a unit test. you mostly hear about ‘class under test’ when talking about what to test and how to organize a unit test. have you ever wondered then why it’s called ‘unit’ test and not ‘class’ test? being pragmatic (well, of course we all are pragmatic and agile these days, aren’t we?), why not having a more coarse-grained unit than a class under test?

for example, you may have asked yourself more than one time, why not use JUnit for integration tests as well. having used FIT (Framework for Integrated Tests) as a very productive tool for such tests in the past, i wished to have a similar little brother for direct use within JUnit (let’s say for developer integration tests).
Especially when it comes to a wide range of combinations of input or setup data for the unit under test, an equivalent of FIT’s ColumFixture would be of great value.So why not write a ColumnTestCase, where we could test our units in a similar way like with a ColumnFixture and specifying input fields and assertion methods directly (so there is no need to write and read html and use Type Adapters as with FIT).

let’s say we have a component under test which will calculate a refinancing rate within a loan request for a given credit type. to keep the example simple, the calculated rate should only be dependend on the credit type, the limit and the duration (in months).

a test could look like the following:

public class RefinancingCalculationTest extends ColumnTestCase{

    // the unit under test
    private RefinancingService refinancingService = ...;

    // input
    public CreditType type = null;
    public Limit limit = null;
    public int months = 0;

    // assertion method
    public Rate refinancingRate(){
        return refinancingService.calcRefinancingRateFor( new Credit( type, limit, months ) );
    }

    // here comes the test
    public void testCalculationForAnnuityLoan() throws Exception{

        fixture(   "type",                           "limit",                       "months",    "refinancingRate()" );
        testset( CreditType.Annuity,    new Limit( "20000" ),   24,             new Rate( "0.5" )  );
        testset( CreditType.Annuity,    new Limit( "20000" ),   48,             new Rate( "1.0" )  ); 
        testset( CreditType.Annuity,    new Limit( "80000" ),   24,             new Rate( "3.5" )  ); 
        // ...
        testset( CreditType.Annuity,    new Limit( "80000" ),   0,               DurationEx.class  );
    } 
}

this syntax looks like a simple candidate for a kind of ColumnFixture with a very similar behaviour:
refinancingService is our unit under test. it may be constituted in a setUp() method (we still are in an ordinary unit test case).
next comes the input fields, which will hold the specific values for every so called test set. similar to the Fixture class within FIT, this fields are declared public.
the following assertion method normally refers to the input fields and provides the calculated value (and usually delegates the calculation of the value to the unit under test). the calculated value will be compared against the asserted value within each test set. like with FIT, the assertion method has no arguments. all input needed for calculation is defined by the input fields. as you can see, no type conversion of those input fields is necessary, since we can use the right type directly in the test sets (this is different and more simple than with FIT, where you need some Type Adapters to convert the values out of the html document into the right type)

now we have to define the single tests itself. this is done in a single test method (since we use JUnit 1.3.x, the test methods has to start with the prefix test). first of all we define the ‘fixture’ itself, that is to specify the name of the relevant input fields and assertion method(s). like with FIT, an assertion method is characterized by an opening and closing brace behind the methods name.
all following lines define single test sets. each test set specifies a single, unique combination of input fields along with the asserted value. as you can see, this could be also an asserted exception, maybe for illegal input.

now what’s about the implementation of ColumnTestCase? as you will see, this is also quite simple, as all the behaviour is gathered in one single class with not more than 60 lines of code:

 /**
* Base class for all Test Cases which represent a so called ColumnTestCase.
*
* A ColumnTestCase offers the opportunity - like FITs ColumnFixture -
* to specify a set of input fields and so called assertion methods in a fixture,
* followed by some testsets which specifies a set of concrete input values
* (associated to the specified input fields) and a set of expected values
* (to be compared to the output of the beloning assertion method)
*
* @author Mario Gleichmann
*/
public class ColumnTestCase extends TestCase{
    protected List fields = null;
    protected List assertionMethods = null;

   /**
    * Sets up the current fixture.
    * Determines the specified input fields and assertion methods
    * and collects the associated fields and Methods.
    *
    * @param inOuts
    * Specifies the input fields and assertion methods of the current test fixture.
    */
    public void fixture( String...inOuts ){
        fields = new ArrayList();
        assertionMethods = new ArrayList();
        Class clazz = getClass();

        for( String inOut : inOuts ){
            try{
                if( isAssertionMethod( inOut ) ){
                    assertionMethods.add(
                        clazz.getMethod( inOut.substring( 0, inOut.length() - 2 ) ) );
                }
                else{
                    fields.add( clazz.getField( inOut ) );
                }
            }
            catch (Exception e) {
                throw new RuntimeException( e );
            }
        }
    }

   /**
    * Runs a specific testset within the current fixture
    *
    * @param testset
    * set of current values for the specified input fields and expected
    * values for the assertion methods to call.
    */
    public void testset( Object...testset ){
        initFieldsFrom( testset );
        int numberOfFields = fields.size(); 

        for( int currentAssertionNumber = 0;
             currentAssertionNumber < assertionMethods.size();
             currentAssertionNumber++ ){

            Object expectedValue = testset[ numberOfFields + currentAssertionNumber ];
            Object actualValue = null;
            Method assertionMethod = assertionMethods.get( currentAssertionNumber );

            try {
                actualValue = assertionMethod.invoke( this );
            }
            catch ( Throwable throwable ) {
                if( isExpectedException( throwable, expectedValue ) ){
                    continue;
                }
                throw new RuntimeException(
                    "Failure during execution of " +
                    assertionMethod.getName() +
                    "() - expected: " + expectedValue, throwable );
            }
            assertEquals( assertionMethod.getName() + "() : ", expectedValue, actualValue );
        }
    }

   /**
    * Shows if the given Throwable's cause is the expected
    * Result of a call to an assertion method by comparing it
    * with the expected value.
    *
    * @param throwable
    * Throwable to test if it is an expected result
    *
    * @param expectedValue
    * The expected value in respect to a call of an assertion method
    * Should be of type Class of the expected Throwable (or Superclass)
    *
    * @return true, if the expected value is of the same type of class or a
    * superclass of the Throwable's cause.
    * false, otherwise
    */
    private boolean isExpectedException( Throwable throwable, Object expectedValue ) {
        return
            expectedValue instanceof Class &&
            ((Class) expectedValue).isAssignableFrom( throwable.getCause().getClass() );
    }

   /**
    * Initializes the fields with the given values of a testset
    *
    * @param testset
    * Hold the values of the input fields of the current testset
    */
    private void initFieldsFrom( Object... testset ) {
        for( int i=0; i<fields.size(); i++ ){
            try {
                fields.get( i ).set( this, testset[i] );
            }
            catch (Exception e) {
                throw new RuntimeException( e );
            }
        }
    }

   /**
    * Shows if the given definition (within a fixture specification)
    * constitute an assertion method
    */
    private boolean isAssertionMethod( String definition ){
        return definition.endsWith( ")" );
    }
}

as you can see, there’s no magic at all, at the core just some reflection and that’s it (you will find it under http://sourceforge.net/projects/bricks4j).
now with ColumnTestCase, we have the opportunity to define FIT like tests inside a JUnit test. wheter it’s convenient or useful for you depends on the kind of tests you’ll run.

all in all it’s another little ‘tool’ that may help to decrease our test idleness. and every tool that ease our efforts and support writing tests is a good one …

Posted in FIT, java, test. 2 Comments »

2 Responses to “jFIT – a little brother for FIT”

  1. Anonymous Says:

    I thought the point of FIT was that even customers and business analysts could use Excel. Moving that information into Java code seems to defeat the purpose.

  2. Mario Gleichmann Says:

    i absolutely agree with you. FIT is especially suited for an early integration of customers / domain experts into the development process. since customers like to work with Excel (and with an easy way to export such sheets into html), this is an ideal way to increase communication, having a verifiable set of functional assertions that leads to automatic, repeatable acceptance tests.
    ColumnTestCase wouldn’t help in this areea. it isn’t suited for collaboration between domain experts and the IT staff. but it may be of help when it comes to tests that are in need of a more complex setup (most of time in the case of integration tests of a more goarse grained unit) or when testing a large number of combinations. even if you keep at testing a single class – if you want to have a structured, concise way of expressing a wide range of combinations next to the expected behaviour, i thing the way FIT structures those test sets is worth to adopt in unit tests.


Leave a comment