Schlagwort-Archive: Parameterized Tests

Enhance JUnit 4 – parameterized tests

In my previous blog I provided an overview of which features are missing in JUnit compared to TestNG. In this blog I am introducing the framework “JUnitParams”. It is hosted on Google code. The current version is 0.4.0 (March 2012). The advantage of JunitParams over JUnit is that it is easier to provide parameters to for testing to a test method as with the build in mechanism from JUnit. Let’s have a look at the features JUnitParams shall provide according to there homepage:

Main differences compared to standard JUnit Parameterized runner:

  • more explicit – params are in test method params, not class fields
  • less code – you don’t need a constructor to set up parameters
  • you can mix parameterised with non-parameterised methods in one class
  • params can be passed as a CSV string or from a parameters provider class
  • parameters provider class can have unlimited parameters providing methods, so that you can group different cases as you need them
  • you can have a test method that provides parameters (no external classes or statics anymore)
  • you can see actual parameter values in your IDE (in JUnit’s Prameterized it’s only consecutive numbers of parameters)

Let’s go into business. Here is a class from one of my projects.

package org.sevendroids.java.junitenhancements.junitparams;

import java.util.Calendar;

/**
 * With this class I will demonstrate some frameworks to enhance the standard
 * functionality in JUnit. The use case for this class is to analyze dates and
 * times from load profile time series to categorize the corresponding values.
 * The values of the time series are categorized in <li>peak and off peak time
 * <li>working day and weekend
 *
 * @author 7droids.de (FA)
 *
 */
public class LoadProfileAnalyzer {

	/**
	 * Peak time is defined as the time period from 8 to 20 o'clock on working
	 * days. Everything else is off peak time.
	 *
	 * @param date
	 *            Calendar object to test
	 * @return True if the date is inside peak time else the date is in the off
	 *         peek period.
	 */
	public boolean isPeakTime(Calendar date) {
		if (isWorkingDay(date)) {
			int hourOfDay = date.get(Calendar.HOUR_OF_DAY);
			if (hourOfDay < 8 || hourOfDay >= 20)
				return false;
			return true;
		}
		return false;
	}

	/**
	 * Working days are all weekdays except Saturday and Sunday.
	 *
	 * @param date
	 *            Calendar object to test
	 * @return False if date is a Saturday or a Sunday else True
	 */
	public boolean isWorkingDay(Calendar date) {
		int weekday = date.get(Calendar.DAY_OF_WEEK);
		switch (weekday) {
		case Calendar.SATURDAY:
		case Calendar.SUNDAY:
			return false;
		default:
			return true;
		}
	}
}

With standard JUnit I would have to write two test classes because I would like to have different parameters for each of the methods. Here is the test class for the method isWorkingDay():

package org.sevendroids.java.junitenhancements.junitparams;

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(value = Parameterized.class)
public class LoadProfileAnalyzerWorkingDayTest {

	private final Calendar testDate;
	private final boolean expected;
	private final LoadProfileAnalyzer analyzer = new LoadProfileAnalyzer();

	public LoadProfileAnalyzerWorkingDayTest(Calendar testDate, boolean excepted) {
		this.testDate = testDate;
		this.expected = excepted;
	}

	@Test
	public final void testIsWorkingDay() {
		assertEquals(expected, analyzer.isWorkingDay(testDate));
	}

	@Parameters
	public static Collection<Object[]> data() {
		Calendar testDate = Calendar.getInstance();
		// boundary value testing
		// Monday -> true
		testDate.set(2012, Calendar.MARCH, 26);
		List<Object[]> parameters = new ArrayList<Object[]>();
		parameters.add(new Object[] { testDate.clone(), true });
		// Friday -> true
		testDate.set(2012, Calendar.MARCH, 30);
		parameters.add(new Object[] { testDate.clone(), true });
		// Saturday -> false
		testDate.set(2012, Calendar.MARCH, 31);
		parameters.add(new Object[] { testDate.clone(), false });
		// Sunday -> false
		testDate.set(2012, Calendar.APRIL, 1);
		parameters.add(new Object[] { testDate.clone(), false });

		return parameters;
	}
}

And here is the test class for the method isPeakTime():

/**
 *
 */
package org.sevendroids.java.junitenhancements.junitparams;

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

/**
 * With this class the method isPeakTime is tested with different parameters.
 *
 * @author 7droids.de (FA)
 *
 */
@RunWith(value = Parameterized.class)
public class LoadProfileAnalyzerPeakTimeTest {

	private final Calendar testDate;
	private final boolean expected;
	private final LoadProfileAnalyzer analyzer = new LoadProfileAnalyzer();

	public LoadProfileAnalyzerPeakTimeTest(Calendar testDate, boolean excepted) {
		this.testDate = testDate;
		this.expected = excepted;
	}

	@Test
	public final void testIsPeakTime() {
		assertEquals(expected, analyzer.isPeakTime(testDate));
	}

	@Parameters
	public static Collection<Object[]> data() {
		Calendar testDate = Calendar.getInstance();
		testDate.set(Calendar.MILLISECOND, 0);
		// boundary value testing
		// Working day 7:59:59 -> false
		testDate.set(2012, Calendar.MARCH, 28, 7, 59, 59);
		List<Object[]> parameters = new ArrayList<Object[]>();
		parameters.add(new Object[] { testDate.clone(), false });
		// Working day 8:00:00 -> true
		testDate.set(Calendar.HOUR_OF_DAY, 8);
		testDate.set(Calendar.MINUTE, 0);
		testDate.set(Calendar.SECOND, 0);
		parameters.add(new Object[] { testDate.clone(), true });
		// Working day 19:59:59 -> true
		testDate.set(Calendar.HOUR_OF_DAY, 19);
		testDate.set(Calendar.MINUTE, 59);
		testDate.set(Calendar.SECOND, 59);
		parameters.add(new Object[] { testDate.clone(), true });
		// Working day 20:00:00 -> false
		testDate.set(Calendar.HOUR_OF_DAY, 20);
		testDate.set(Calendar.MINUTE, 0);
		testDate.set(Calendar.SECOND, 0);
		parameters.add(new Object[] { testDate.clone(), false });
		// Weekend -> true
		testDate.set(2012, Calendar.MARCH, 31, 15, 42, 21);
		parameters.add(new Object[] { testDate.clone(), false });

		return parameters;
	}
}

Now we will have a look how the same tests can be done in one class with the framework JUnitParams.

package org.sevendroids.java.junitenhancements.junitparams;

import static junitparams.JUnitParamsRunner.$;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.util.Calendar;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;

import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JUnitParamsRunner.class)  // 1
public class LoadProfileAnalyzerTest {

	private final LoadProfileAnalyzer analyzer = new LoadProfileAnalyzer();

	@Test
	@Parameters(method = "peakValues") // 2
	public void testIsPeakTime(Calendar testDate, boolean expected) {
		assertThat(analyzer.isPeakTime(testDate), is(expected));
	}

	protected final Object[] peakValues() { // 3
		Calendar testDate75959 = Calendar.getInstance();
		testDate75959.set(Calendar.MILLISECOND, 0);
		// boundary value testing
		// Working day 7:59:59 -> false
		testDate75959.set(2012, Calendar.MARCH, 28, 7, 59, 59);
		// Working day 8:00:00 -> true
		Calendar testDate80000 = Calendar.getInstance();
		testDate80000.set(Calendar.MILLISECOND, 0);
		testDate80000.set(2012, Calendar.MARCH, 28, 8, 0, 0);
		// Working day 19:59:59 -> true
		Calendar testDate195959 = Calendar.getInstance();
		testDate195959.set(Calendar.MILLISECOND, 0);
		testDate195959.set(2012, Calendar.MARCH, 28, 19, 59, 59);
		// Working day 20:00:00 -> false
		Calendar testDate200000 = Calendar.getInstance();
		testDate200000.set(Calendar.MILLISECOND, 0);
		testDate200000.set(2012, Calendar.MARCH, 28, 20, 0, 0);
		// Weekend -> true
		Calendar testDateWeekend = Calendar.getInstance();
		testDateWeekend.set(Calendar.MILLISECOND, 0);
		testDateWeekend.set(2012, Calendar.MARCH, 31, 15, 42, 21);

		return $($(testDate75959, false), $(testDate80000, true), // 4
				$(testDate195959, true), $(testDate200000, false),
				$(testDateWeekend, false));
	}

	@Test
	@Parameters(method = "workingDayValues") // 5
	public void testIsWorkingDay(Calendar testDay, boolean expected) {
		assertThat(analyzer.isWorkingDay(testDay), is(expected));
	}

	protected final Object[] workingDayValues() { // 6
		Calendar testDate26 = Calendar.getInstance();
		// boundary value testing
		// Monday -> true
		testDate26.set(2012, Calendar.MARCH, 26);
		// Friday -> true
		Calendar testDate30 = Calendar.getInstance();
		testDate30.set(2012, Calendar.MARCH, 30);
		// Saturday -> false
		Calendar testDate31 = Calendar.getInstance();
		testDate31.set(2012, Calendar.MARCH, 31);
		// Sunday -> false
		Calendar testDate1 = Calendar.getInstance();
		testDate1.set(2012, Calendar.APRIL, 1);
		return $($(testDate26, true), $(testDate30, true),
				$(testDate31, false), $(testDate1, false));
	}
}

Let’s go into the details of the number labeled rows.

Row // 1: To enable the framework you need to specify a special test runner – JUnitParamsRunner

Row // 2: In contrast to standard JUnit the parameters are past on as method parameters to the test method. As in TestNG the connection between test method and parameter provider is done by name. With @Parameters(method=…) you declare the name of the method which provides the parameters.

Row // 3: So here is the method which was named in the Parameters annotation in row 2. The method has to return an Object[]. Each element of the array contains one set of parameters for the test method.

Row // 4: There is a specialty in the return type. Every element of the array and the array itself is of the class JUnitParamsRunner.$.

Row // 5: It is possible with JUnitParams  to declare as much parameter providing methods as needed. In this case another method (“workingDayValues”) is referenced.

Row // 6: And here is the referenced method itself.

A specialty is the possibility to provide parameters within the annotation. This cannot be done in TestNG either. The parameter sets are provide by a String array. So a sample with two parameter sets could look like this:

@Parameters(value={“Set 1, 1, 2.0, true”,”Set 2, 3, 4.0, false”})

I haven’t tried all possibilities until now but as far as I found out only primitive values and Strings can be used.

The third option with @Parameters is to reference a whole class as parameter provider. This is done very similar to TestNG.

Summery: With the framework JUnitParams you safe a lot of writing. Different sets of parameters can be used in one test class and for simple parameters the framework provides a very elegant and easy to use solution.