How do I load externalized data for parameterized testing?

The development of parameterized tests or theories involves maintaining sets of data. Data can be maintained inside of the test, externally in a data source, or both internally and externally. We will explain these approaches and the reasons why to use each in the section below.

Maintaining data internally is the practice of storing static parameterized test data, inside of the test class. Here is an example.

[Parameters]
public static var someData:Array = [[1], [2], [3]];

Writing a parameterized test that uses small data sets, is a good example of a situation where it is best to maintain test data internally. In this case a simple change to the data, a change to an element in an array for example, is a trivial task. Another example of a situation in which this approach is best used is when a specific set of static data must be tested every time a test is run. One benefit of maintaining data internally is that it eliminates the overhead associated to creating custom external dependency loaders, which will be explained in sections below, in order to load data from an external source.

public static var dataRetriever1:ParamDataHelper = new ParamDataHelper( "PurelyFakeExample.xml" );

[DataPoints(loader="dataRetriever1")]
public static var dataTwo:Array;

Using the ParamDataHelper type above, we have declared a custom ExternalDependencyLoader. The loader is assigned to a collection using the [DataPoints(loader="LOADER HERE")} metadata.

The two parameterized test styles under which we have organized the walkthrough are JUnit style and TestNG style. JUnit is a better style for cases where the same data is used from test method to test method. TestNG style is used for test cases in which the methods are more ad-hoc, and share data less consistently.

JUnit Style

package flexUnitTests.flexUnit4.suites.frameworkSuite.cases
{
    import flexUnitTests.flexUnit4.suites.frameworkSuite.cases.helper.ParamDataHelper;

    import org.flexunit.Assert;
    import org.flexunit.runners.Parameterized;

    [RunWith("org.flexunit.runners.Parameterized")]
    public class TestParameterized{

        private var foo:Parameterized;

        public static var dataRetriever1:ParamDataHelper = new ParamDataHelper( "webservicesUrl" );

        [Parameters(loader="dataRetriever1")]
        public static var someData:Array;

        public function TestParameterized( param1:int, param2:int ) {
            _input = param1;
            _expected = param2;
        }

        public function doubleTest():void {
            Assert.assertEquals(_expected, _input*2);
        }           
    }
}

As you can see, JUnit style takes advantage of the class constructor, called to assign each parameter. The tests are then run for the amount of parameters available. It will run every test for every parameter defined, so ideally it will have only tests that use the parameters contained within the case.

TestNG Style

package flexUnitTests.flexUnit4.suites.frameworkSuite.cases {
    import flexUnitTests.flexUnit4.suites.frameworkSuite.cases.helper.ParamDataHelper;

    import org.flexunit.asserts.assertEquals;
    import org.flexunit.runners.Parameterized;

    [RunWith("org.flexunit.runners.Parameterized")]
    public class TestParameterized2 {

        private var foo:Parameterized;

        public static var dataRetriever1:ParamDataHelper = new ParamDataHelper( "PurelyFakeExample.xml" );

        [DataPoints(loader="dataRetriever1")]
        public static var dataTwo:Array;

        [DataPoints]
        [ArrayElementType("int")]
        public static var dataThree:Array = [ [ 1, 2, 3 ],
                            [ 10, 20, 30 ],
                            [ 100, 200, 300 ] ]

        [Test(dataProvider="dataTwo")]
        public function timesTwoTest( value:int, result:int ):void {
            assertEquals( 2*value, result );
        }

        [Test(dataProvider="dataThree")]
        public function addTwoValuesTest( value1:int, value2:int, result:int ):void {
            assertEquals( value1 + value2, result );
        }

        [Test]
        public function justARegularTest():void {
        }

        public function TestParameterized2() {
        }
    }
}

In the above example, externally loaded data is mixed with internally instantiated data (dataThree). These test cases obviously require two different data sets, and this is a good case for the TestNG method.