Integration with jUnit

Introduction

So far we have executed generated tests directly from ecFeed GUI. This is a useful feature for test development and for live feature testing. For sake of better regression testing ecFeed has been integrated with jUnit. EcFeed provides jUnit with runner classes that are able to parse model and execute tests that are either stored within the model, or generated online during test execution.

Project configuration

To be able to use ecFeed model with jUnit test framework it is necessary to download and install ecfeed_X.XX.XX.jar (where X.XX.XX stands for version number) and xom-1.2.10.jar.

Both jar files may be downloaded from the download section.

Be sure to download the version of ecfeed_X.XX.XX.jar with the same number as your ecFeed plugin. Also the type of release (beta or standard) must me the same. The version of plugin can be seen in Eclipse: Help > Installation Details > Tab Installed Software > ecFeed (on software list).

Xom jar is used to parse data models of ecFeed, which are stored in XML format.

Both jars should be added to Java Build Path of the project, where you want to run JUnit tests.
Also JUnit library has to be added to the Java Build Path.

Static testing

Once you added the jars to your build path we can start playing with jUnit integration features of ecFeed. The simplest way of executing ecFeed tests via jUnit is to execute methods with parameters values defined by test cases of the method model. Using the model created in the previous tutorials, go to it’s implementation.

Add annotations @RunWith, @EcModel and @Test as in code below.

import org.junit.Test;
import org.junit.runner.RunWith;
import com.ecfeed.junit.StaticRunner;
import com.ecfeed.junit.annotations.EcModel;

@RunWith(StaticRunner.class)
@EcModel("src/FlightFinderTest.ect")
public class FlightFinderTest {

    @Test
    public void findFlightsTest(String airportFrom, String airportTo, int daysToFlyOut, 
                                boolean isReturnFlight, int daysBetweenFlights, TicketClass ticketClass, 
                                float maxPrice) {
        //for faster exectution, replace existing code from the web integratio tutorial, with this simple print
        System.out.println("findFlightsTest(" + airportFrom + ", " + airportTo + ", " + daysToFlyOut + ", " + 
		                   isReturnFlight + ", " + daysBetweenFlights + ", " + ticketClass + ", " + maxPrice + ")");
    }
}

The @RunWith annotation defines the runner responsible for defining a runner class that will be responsible for executing test methods. The StaticRunner class is a runner class defined in the ecfeed.jar library. The runner loads the model from location provided by @EcModel annotation. It first checks if the method annotated with @Test has parameters. If so it tries to find corresponding method in the model and executes test cases from that model. If the method has no parameters, it passes the execution to default jUnit runner. So methods with no parameters annotated with @Test, will still be executed, even if they are not modeled.

Go to your model generate a cartesian product test suite (leave default name). Save the model. Go back to your source code and run the jUnit tests (e.g. by ALT+SHIFT+X, T or by Run->Run menu). You should see your tests being executed.

Defining test suites

By default all test cases that are defined for given method are executed. However it is possible to select which test suites will be executed for individual methods. This is done by a @TestSuites annotation. The annotation takes either single string or a string array with test suite names to execute as parameters.

Go to the model and generate new test suite for the testMethod, and name it "special suite".

In java class file add an import statement and @TestSuites annotation:

[...]
import com.ecfeed.junit.annotations.TestSuites;
[...]

@RunWith(StaticRunner.class)
@EcModel("src/FlightFinderTest.ect")
public class FlightFinderTest {
    @Test
    @TestSuites("special suite")
    public void findFlightsTest(String airportFrom, String airportTo, int daysToFlyOut, 
                                boolean isReturnFlight, int daysBetweenFlights, TicketClass ticketClass, 
                                float maxPrice) {
        [...]
    }
}

If you run the test, only the test cases from the "special suite" will be executed.

Online testing

Online testing is a bit more advanced feature. It allows to generate tests online during execution. We do not have to store our test cases in the model (which may be inconvenient once it becomes large).

Modify the class annotations as follows:

[...]
import com.ecfeed.junit.OnlineRunner;
import com.ecfeed.junit.annotations.Generator;
import com.ecfeed.core.generators.CartesianProductGenerator;
[...]

@RunWith(OnlineRunner.class)
@EcModel("src/FlightFinderTest.ect")
@Generator(CartesianProductGenerator.class)
public class FlightFinderTest {
    @Test
    public void findFlightsTest(String airportFrom, String airportTo, int daysToFlyOut, 
                                boolean isReturnFlight, int daysBetweenFlights, TicketClass ticketClass, 
                                float maxPrice) {
        [...]
    }
}

Now we have provided jUnit with OnlineRunner class. The @Generator annotation tells the runner which generator it should use. The generator classes that can be used here are described in another place in the documentation.
Run the test. The CartesianProductGenerator class will generate new test cases on the fly and provide it to the runner until all tests are executed.

Generators parameters

We started from cartesian product generator, since it is the simplest one in use. We do not need to provide it with any parameters. For generators that require parameters we can provide them with special annotations.

Add annotations to the findFlightsTest method as follows:

[...]
import com.ecfeed.junit.annotations.GeneratorParameter;
import com.ecfeed.core.generators.RandomGenerator;
[...]

@RunWith(OnlineRunner.class)
@EcModel("src/FlightFinderTest.ect")
@Generator(CartesianProductGenerator.class)
public class FlightFinderTest {
    @Test
    @Generator(RandomGenerator.class)
    @GeneratorParameter(name = "length", value = "100")
    public void findFlightsTest(String airportFrom, String airportTo, int daysToFlyOut, 
                                boolean isReturnFlight, int daysBetweenFlights, TicketClass ticketClass, 
                                float maxPrice) {
        [...]
    }
}

The class uses globally CartesianProductGenerator class for generating test cases. For the method we have specified another generator that is RandomGenerator. Random generator requires a parameter that specifies length of generated test suite. We provide this parameter by @GeneratorParameter annotation.

When you execute tests you will notice that the method was executed not 100 times but much less. This is because the random generator also takes the other parameter that is duplicates. It is an optional parameter that is by default set to false. This means that the generator will not generate two identical test cases. We cannot annotate the method with two @GeneratorParameter annotations, but there is another way to provide multiple parameters to generator.

Modify the method annotations as follows:

[...]
import com.ecfeed.junit.annotations.GeneratorParameterNames;
import com.ecfeed.junit.annotations.GeneratorParameterValues;
[...]

    @Test
    @Generator(RandomGenerator.class)
    @GeneratorParameterNames({"length", "duplicates"})
    @GeneratorParameterValues({"100", "true"})
    public void findFlightsTest(String airportFrom, String airportTo, int daysToFlyOut, 
                                boolean isReturnFlight, int daysBetweenFlights, TicketClass ticketClass, 
                                float maxPrice) {
        [...]
    }

By combination of annotations @GeneratorParameterNames and @GeneratorParameterValues we provide to generator arrays of parameters. We set the length of test suite to 100 and the duplicates parameter to true. Now our test suite will contain 100 test cases with many duplicates.

Using constraints with online generator

In previous examples we did not define constraints for the online generator.

Modify as follows the method annotations to take into account all constraints:

[...]
import com.ecfeed.junit.annotations.Constraints;
[...]

@RunWith(OnlineRunner.class)
@EcModel("src/FlightFinderTest.ect")
@Generator(CartesianProductGenerator.class)
@Constraints(Constraints.ALL)
public class FlightFinderTest {
    @Test
    @Generator(RandomGenerator.class)
    @GeneratorParameter(name = "length", value = "100")
    @Constraints(Constraints.NONE)
    public void findFlightsTest(String airportFrom, String airportTo, int daysToFlyOut, 
                                boolean isReturnFlight, int daysBetweenFlights, TicketClass ticketClass, 
                                float maxPrice) {
        [...]
    }
    [...]
}

In the code above we specified that for all test methods in class FlightFinderTest we will use all defined constraints by default. However for the method findFlightsTest this seting is overwritten by @Constraints(Constraints.NONE) which means that generator will ignore all constraints for this particular method. You can also specify individual constraints that will be used by the generator.

    [...]
    @Constraints({"constraint1", "constraint2"})
    public void findFlightsTest(String airportFrom, String airportTo, int daysToFlyOut, 
                                boolean isReturnFlight, int daysBetweenFlights, TicketClass ticketClass, 
                                float maxPrice) {
        [...]
    }
    [...]