Executing tests across large data sets necessarily involves maintaining that data. FlexUnit 4.1 allows the data for [Theories] and Parameterized Test to be loaded from an external source.

Assumptions

This entry assumes you have an understanding of Parameterized Testing and Theories.

Why test with Internal / External Data

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.

Testing with Internal Data

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

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

'' Example of defining data internally ''

Writing a parametrized 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.

Testing with External Data

Maintaining data externally involves executing parameterized tests with data loaded from external data sources at run time.

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

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

''An example of loading data from an external source''

As data sets become larger they also become increasingly difficult to maintain internally. In this case it may be ideal to store data in an external data source. External Data sources can range from xml files to databases, from web services to text files. Because the data is loaded at runtime, it is also possible to use data that varies ever time a test is run.

Using both Internal and External Data

There may be a case where a large set of data is tested; however some data must not change. In this case the mandatory static data would be maintained internally while the larger dynamic data is maintained externally.

Writing Parameterized Tests using External Data

There are two styles of writing parameterized unit tests in FlexUnit, TestNG Style and JUnit Style. TestNG Style works very well with ad-hoc type test classes that do not share similar data sets between tests. JUnit Style, on the other hand, is best used for test classes that do share similar data sets between tests. For more information see Parameterized Test Styles

To load external data into a test or theory for either style there are three basic steps:

*Create and initialize a public static instance of a custom external dependency loader. A custom external dependency loader is a class that works with the runner to load data from an external data source. See Custom External Dependency Loaders below.

*Create a public static variable to store the loaded data.

*Link the data variable and instances of the external dependency loader by applying the appropriate metadata to the data variable.

JUnit Style

The first step is to create a public static instance of the custom external dependency loader.

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

The second step is to create a public static variable to store the loaded data.

public static var someData:Array;

The final step is to link the data variable and instances of the external dependency loader

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

This is a complete example of a parameterized test, written in JUint Style, that loads data from an external source.

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

TestNG Style

The first step is to create a public static instance of the custom external dependency loader.

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

The second step is to create a public static variable to store the loaded data.

public static var dataTwo:Array;

The final step is to link the data variable and instances of the external dependency loader

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

This is a complete example of a parameterized test, written in TestNG Style style, that loads data from an external source.

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;
        
        [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() {
        }
    }
}

Custom External Dependency Loaders

There are many types of data sources that can be used to populate data sets for unit testing. In order to take advantage these different data sources, Flex Unit provides the concept of an External Dependency Loader. An External Dependency Loader is a custom class that, along with a test runner, dynamically loads data from an external source into a unit test. All External Dependency Loaders must implement the IExternalDependencyLoader interface. This interface requires that a class contain the retriveDependency method in order for the runner to access the loaders external dependency token. An external dependency token is a class used for communication between the test runner and external dependency loader. This will be explained in detail below. Also below are examples of a custom External Dependency Loader that will load data from a web service and pass it to a parameterized test.

Custom External Dependency Loader example

package dependencyLoaders
{
    import org.flexunit.runner.external.ExternalDependencyToken;
    import org.flexunit.runner.external.IExternalDependencyLoader;
    
    public class ExternalDependencyLoader implements IExternalDependencyLoader{

                public function retrieveDependency(testClass:Class):ExternalDependencyToken{
            return null;
        }
        
        public function ExternalDependencyLoader(){
        }
    }
}

In the first example we have created a new class named ExternalDependencyLoader that implements the IExternalDependencyLoader. Initially the ExternalDependencyLoader will consist of a constructor and a retrieveDependency function. The retrieveDependency function will be called to return a reference to the loaders ExternalDependencyToken and in turn “kick off” the process of loading data.

package dependencyLoaders 
{
    import org.flexunit.runner.external.ExternalDependencyToken;
    import org.flexunit.runner.external.IExternalDependencyLoader;
    
    public class ExternalDependencyLoader implements IExternalDependencyLoader{
        
                private var myToken:ExternalDependencyToken;

        protected function wsdlLoadHandler(event:LoadEvent):void{
        }
        
        public function retrieveDependency(testClass:Class):ExternalDependencyToken{
            return null;
        }
        
        public function ExternalDependencyLoader(){
            myToken = new ExternalDependencyToken();
        }
    }
}

We add to the first example by creating and initializing, in the constructor, a private variable named myToken of type ExternalDependencyToken. The loader will use this token to pass the external data, when loaded, to the runner so that the runner can load the external data in the appropriate unit tests. In the case that there was a problem in retrieving data in the loader, the loader uses the token to notify the runner that there was a fault. For more information about tokens, see Tokens

package dependencyLoaders 
{
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;
    import mx.rpc.soap.WebService;
    
    import org.flexunit.runner.external.ExternalDependencyToken;
    import org.flexunit.runner.external.IExternalDependencyLoader;
    
    public class ExternalDependencyLoader implements IExternalDependencyLoader{
        
                private var myToken:ExternalDependencyToken;
        
        private var myWebService:WebService;
        
        protected function webServicesFaultHandler(event:FaultEvent):void{
            myToken.notifyFault( event.message );
        }
        
        protected function webServiceResultHandler(event:ResultEvent):void{
            myToken.notifyResult( event.result );
        }
        
        protected function wsdlLoadHandler(event:LoadEvent):void{
            myWebService.addEventListener(ResultEvent.RESULT, webServiceResultHandler);
            myWebService.addEventListener(FaultEvent.FAULT, webServiceFaultHandler);
        }
        
        public function retrieveDependency(testClass:Class):ExternalDependencyToken{
            myWebService.getTestData();
            return myToken;
        }
        
        public function ExternalDependencyLoader( url:String ){
            myToken = new ExternalDependencyToken();
            
            myWebService = new WebService();
            myWebService.addEventListener(LoadEvent.LOAD, wsdlLoadHandler);
            webService.wsdl = url;
            webService.loadWSDL();  
        }
    }
}

We will wrap-up the example by adding the functionality to consume a web service in the ExternalDependencyLoader.
As mentioned earlier, a call to retrieveDependency will “kick off” the process of loading data. In this example, when runner makes a call to retrieveDependency, the getTestData method of the web services is invoked. When the web service returns with data, the loader calls the ExternalDependencyToken’s notifyResult method with the data that has been returned. This will notify the runner that the given data is ready to be loaded into the test. If the web service returns a fault, the loader calls the ExternalDependencyToken’s notifyFault method to notify the runner that there was a fault and no data was returned.

  • No labels