JUnit 4 – Parameterized

The winter storm in the Twin Cities of Minneapolis and St. Paul is over. Currently there is light snow falling but the temperature is above freezing. Hopefully all the snow will melt somewhat slowly in order to avoid flooding.

In this post we will revisit JUnit 4 from the parameterized angle. I have covered other aspects of JUnit in this post.

In general, a single unit test is intended to test one and only one method. In addition, if you need to use different arguments, typically developers were duplicating code and changing the values for the test. For example, if one wishes to test if a method to check prime number works, one would tend to have a unit test for a prime number (e.g., 7) and a second one for a non-prime (e.g., 4). Of course there are other possibilities that one should check (i.e., -1, 0, 1, 2). For example, one might also want to check numbers less than 100 or numbers between 0 and 200. If we are using a test for each case, we would spend a lot of time generating tests in which we could make mistakes.

Of course, one can always edit a single unit test and modify it to include multiple test cases. In such a case one must be extremely careful that the test code in JUnit does not include a bug that would not find issues with the specified test fails; in other words the test may not fail at all.

In a previous post I covered JUnit. In this post we will cover an example where the data is being read from a file and we use parameterized tests.

For starters we need a function to test. This time we will use a class with a single method that can be used to determine if a number is prime (return true) or not (return false).

Let’s take a look at the class we will use for testing:

package junit.parameterized.canessa;


/*
 * Class that verifies if a number is prime or not.
 */
public class PrimeNumberChecker {
	
	// **** validate prime number ****
	public Boolean validate(final Integer primeNumber) {

		// **** ****
		if (primeNumber < 2)
			return false;
		
		// **** ****
		for (int i = 2; i <= (primeNumber / 2); i++) {
         if (primeNumber % i == 0) {
            return false;
         	}
		}
      
		// **** ****
		return true;
	}

}

The validate() method should be able to test if the argument passed to it is a prime number or not.

Following is a capture of the console of the Eclipse IDE in which our program was executed:

main <<< int: 0 boolean: true
main <<< int: 1 boolean: false
main <<< int: 2 boolean: true
main <<< int: 3 boolean: false
main <<< int: 4 boolean: true

main <<< 0 true
main <<< pair: 0 true
main <<< 1 false
main <<< pair: 1 false
main <<< 2 true
main <<< pair: 2 true
main <<< 3 false
main <<< pair: 3 false
main <<< 4 true
main <<< pair: 4 true

main <<< al.size: 7
main <<< 2 true
main <<< pair: 2 true
main <<< 4 false
main <<< pair: 4 false
main <<< 6 false
main <<< pair: 6 false
main <<< 17 true
main <<< pair: 17 true
main <<< 19 true
main <<< pair: 19 true
main <<< 22 false
main <<< pair: 22 false
main <<< 23 true
main <<< pair: 23 true

main <<< num: 0 false
main <<< num: 1 false
main <<< num: 2 true
main <<< num: 3 true
main <<< num: 4 false
main <<< num: 5 true
main <<< num: 6 false
main <<< num: 7 true
main <<< num: 8 false
main <<< num: 9 false
main <<< num: 10 false
main <<< num: 11 true
main <<< num: 12 false
main <<< num: 13 true
main <<< num: 14 false
main <<< num: 15 false
main <<< num: 16 false
main <<< num: 17 true
main <<< num: 18 false
main <<< num: 19 true
main <<< num: 20 false
main <<< num: 21 false
main <<< num: 22 false
main <<< num: 23 true

We load and display an array of objects which contain a number and a flag indicating if the number is a prime or not. In this case, we are just testing the mechanism so we alternate true and false values. We will not use this code in our unit testing. It is here just to explore the mechanism used by JUnit 4.

We then iterate through a collection we create with those values.

We then load a set of similar values but using an Array List.

Finally, we check the method that indicates if a number is a prime or not by looping through a set of consecutive numbers starting with 0 and ending with 23. This gives us the warm and fuzzy feeling that the method and class are working as expected.

The following code was used to test the class and a variety of steps that I used to check different aspects of the code.

package junit.parameterized.canessa;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;


/*
 * Class used to experiment with objects.
 */
class Pair {
	
	// **** ****
	int		num;
	boolean isPrime;
	
	// **** constructor ****
	public Pair() {
	}
	
	// **** constructor ****
	public Pair(int num, boolean isPrime) {
		this.num 		= num;
		this.isPrime 	= isPrime;
	}
	
	// **** to string ****
	public String toString() {
		return "" + this.num + " " + this.isPrime;
	}
}


/*
 * Scaffolding for testing.
 */
public class Solution {

	public static void main(String[] args) {

		// **** ****
		final int ENTRIES	= 5;
		
		// **** declare array of objects ****
		Object[][] arr = new Object[ENTRIES][2];
		
		// **** populate the array of objects with a number and a boolean ****
		for (int i = 0; i < ENTRIES; i++) {
			arr[i][0] = i;
			arr[i][1] = ((i % 2) == 0) ? true : false;
		}
		
		// **** ****
		for (int i = 0; i < ENTRIES; i++)
			System.out.println("main <<< int: " + arr[i][0] + " boolean: " + arr[i][1]);
		System.out.println();
		
		// **** convert the array to a list ****
		List<Object[]> list = Arrays.asList(arr);

		// **** iterate though the list displaying and creating pairs  ****
		Iterator<Object[]> it = list.iterator();
		while (it.hasNext()) {
			
			// **** ****
			Object[] obj = it.next();
			System.out.println("main <<< " + obj[0] + " " + obj[1]);
			
			// **** ****
			Pair pair = new Pair(((Number)obj[0]).intValue(), ((Boolean)obj[1]));
			System.out.println("main <<< pair: " + pair.toString());
		}
		System.out.println();
		
		
		// **** declare an array of objects with numbers and flags 
		//		that indicate if the number is prime or not ****
		Object[][] oa = new Object[][] {
									     {  2, true },
									     {  4, false },
									     {  6, false },
									     { 17, true },
									     { 19, true },
									     { 22, false },
									     { 23, true }};
									     
		// **** instantiate an array list ****
		ArrayList<Object[]> al = new ArrayList<Object[]>();

		// **** populate the array list ****
		for (Object[] obj : oa) {
			al.add(obj);
		}
		System.out.println("main <<< al.size: " + al.size());

		// **** iterate though the array list displaying and creating pairs ****
		Iterator<Object[]> alit = al.iterator();
		while (alit.hasNext()) {
			
			// **** ****
			Object[] obj = alit.next();
			System.out.println("main <<< " + obj[0] + " " + obj[1]);
			
			// **** ****
			Pair pair = new Pair(((Number)obj[0]).intValue(), ((Boolean)obj[1]));
			System.out.println("main <<< pair: " + pair.toString());
		}
		System.out.println();

		
//		// **** open scanner ****
//		Scanner sc = new Scanner(System.in);
		
		// **** instantiate class ****
		PrimeNumberChecker pnc = new PrimeNumberChecker();
		
		// **** test value(s) ****
		for (int num = 0; num <= 23; num++) {
			System.out.println("main <<< num: " + num + " " + pnc.validate(num));
		}
		
//		// **** close scanner ****
//		sc.close();
	}

}

Now let’s check the contents of a file which we will use in the actual JUnit testing.

C:\Users\John>type c:\temp\prime_list.txt
1 false
2 true
3 true
4 false
6 false
17 true
19 true
22 false
23 true
71 true

I randomly selected a few values and assigned them the proper values indicating whether they are prime or not.

Now let’s take a look at the output of the JUnit test which run inside the Eclipse IDE. The output was sent to the console. All the tests passed.

primeNumbers <<< fileName ==>c:\temp\prime_list.txt<==
primeNumbers <<< sa[0]: 1 sa[1]: false
primeNumbers <<< sa[0]: 2 sa[1]: true
primeNumbers <<< sa[0]: 3 sa[1]: true
primeNumbers <<< sa[0]: 4 sa[1]: false
primeNumbers <<< sa[0]: 6 sa[1]: false
primeNumbers <<< sa[0]: 17 sa[1]: true
primeNumbers <<< sa[0]: 19 sa[1]: true
primeNumbers <<< sa[0]: 22 sa[1]: false
primeNumbers <<< sa[0]: 23 sa[1]: true
primeNumbers <<< sa[0]: 71 sa[1]: true

primeNumbers <<< 1 false
primeNumbers <<< 2 true
primeNumbers <<< 3 true
primeNumbers <<< 4 false
primeNumbers <<< 6 false
primeNumbers <<< 17 true
primeNumbers <<< 19 true
primeNumbers <<< 22 false
primeNumbers <<< 23 true
primeNumbers <<< 71 true

testPrimeNumberChecker <<<    inputNumber: 1
testPrimeNumberChecker <<< expectedResult: false
testPrimeNumberChecker <<<    inputNumber: 2
testPrimeNumberChecker <<< expectedResult: true
testPrimeNumberChecker <<<    inputNumber: 3
testPrimeNumberChecker <<< expectedResult: true
testPrimeNumberChecker <<<    inputNumber: 4
testPrimeNumberChecker <<< expectedResult: false
testPrimeNumberChecker <<<    inputNumber: 6
testPrimeNumberChecker <<< expectedResult: false
testPrimeNumberChecker <<<    inputNumber: 17
testPrimeNumberChecker <<< expectedResult: true
testPrimeNumberChecker <<<    inputNumber: 19
testPrimeNumberChecker <<< expectedResult: true
testPrimeNumberChecker <<<    inputNumber: 22
testPrimeNumberChecker <<< expectedResult: false
testPrimeNumberChecker <<<    inputNumber: 23
testPrimeNumberChecker <<< expectedResult: true
testPrimeNumberChecker <<<    inputNumber: 71
testPrimeNumberChecker <<< expectedResult: true

The name of the file is displayed. We then load the values from the file and display them. Next we iterate through the array list to make sure all things are going fine. Then the JUnit code calls the test method  testPrimeNumberChecker() with each number and flag we specified.

There are a set of steps we need to take when writing a parameterized JUnit test.  These are:

  1. Annotate the test class with @RunWith(Parameterized.class).
  2. Create a public static method annotated with @Parameterized.Parameters that returns a Collection of Objects (as Array) as test data set.
  3. Create a public constructor that takes a single row with the values for a single test. In our case we used two values. Other tests might require more or less parameters.
  4. Create an instance variable for each value in the test data. In our case we need the number to test and the value for the expected result.
  5. Create the test case(s) using the instance variables as the source of the test data.

Each of these steps is flagged in the following class:

package junit.parameterized.canessa;

import static org.junit.Assert.*;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;

import org.junit.Test;
import org.junit.Before;

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


/*
 * Class tester with annotation [step 1]
 */
@RunWith(Parameterized.class)
public class PrimeNumberCheckerTest {
	
	// **** instance variables used for single test [step 4] ****
	private Integer 				inputNumber;
	private Boolean 				expectedResult;
	   
	private PrimeNumberChecker 		primeNumberChecker;

	// **** instantiate the class to be checked ****
	@Before
	public void initialize() {
		primeNumberChecker = new PrimeNumberChecker();
	}

	// **** constructor [step 3] ****
	// Every time runner triggers, it will pass the arguments
	// from parameters we defined in primeNumbers() method
	public PrimeNumberCheckerTest(Integer inputNumber, Boolean expectedResult) {
		this.inputNumber = inputNumber;
		this.expectedResult = expectedResult;
	}
  
	// **** returns collection to test [step 2] ****
	@Parameterized.Parameters
	public static Collection<Object[]> primeNumbers() throws FileNotFoundException {
		
		// **** instantiate an array list ****
		ArrayList<Object[]> al = new ArrayList<Object[]>();
		
		// **** open a scanner with the specified file name ****
		String fileName = "c:\\temp\\prime_list.txt";
		
		// ???? ????
		System.out.println("primeNumbers <<< fileName ==>" + fileName + "<==");
		
		Scanner sc = new Scanner(new File(fileName));
		
		// **** read the lines from the scanner and populate the array list ****
		while (sc.hasNextLine()) {
			
			// **** ****
			String[] sa = sc.nextLine().split(" ");
			
			// ???? ????
			System.out.println("primeNumbers <<< sa[0]: " + sa[0] + " sa[1]: " + sa[1]);
			
			// **** declare array of objects ****
			Object[][] arr = new Object[1][2];
			
			// **** populate the array ****
			arr[0][0] = Integer.parseInt(sa[0]);
			arr[0][1] = Boolean.parseBoolean(sa[1]);

			// **** add the object to the list ****
			al.add(arr[0]);
		}
		
		// ???? ????
		System.out.println();

		// **** close the scanner ****
		sc.close();
		
		// ???? iterate through the array list ????
		Iterator<Object[]> alit = al.iterator();
		while (alit.hasNext()) {
			Object[] obj = alit.next();
			System.out.println("primeNumbers <<< " + obj[0] + " " + obj[1]);
		}
		System.out.println();

		// **** return the list of objects ****
		return al;
	}
	   
	// **** test case used to test with the instance variables [step 5] ****
	@Test
	public void testPrimeNumberChecker() {
		   
		// ???? ????
		System.out.println("testPrimeNumberChecker <<<    inputNumber: " + inputNumber);
		System.out.println("testPrimeNumberChecker <<< expectedResult: " + expectedResult);
	   
		// **** ****
		assertEquals(expectedResult, primeNumberChecker.validate(inputNumber));
	}

}

I was supposed to have this code done a week or so ago, but got busy with work. Sorry it took so long.

The entire code is in my GitHub repository. For convenience, the prime_list.txt file has also been included.

In a future post I will cover testing using JUnit 5.

If you have comments or questions regarding this or any other post in this blog, or if you need technical or managerial help with a software development project, please do not hesitate and leave me a note bellow. Requests for help will not be made public.

Keep on reading and experimenting. It is the best way to learn!

John

Twitter:  @john_canessa

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.