Parameterized Unit Testing with JUnit

Parameterized Unit Testing with JUnit

Parameterized testing is a feature of JUnit that allows you to run the same test case with different input parameters. This means that you can test a function or method with multiple sets of input data, which can help you identify edge cases or corner cases that may cause problems in your code. This also reduces your effort as a developer of the unit tests as the same unit test works for different sets of inputs.

Here is an example.

Suppose you have a class called "MathUtils" that contains a method called "factorial()" that calculates the factorial of a given integer iteratively. Here is the class:




public class MathUtils {

??public int factorial(int n) {

????if (n < 0) {

??????throw new IllegalArgumentException("Input must be non-negative");

????}

????if (n == 0 || n == 1) {

??????return 1;

????}

????int result = 1;

????for (int i = 2; i <= n; i++) {

??????result *= i;

????}

????return result;

??}

}




You want to test this method with different input values to make sure it works correctly. Instead of writing multiple test methods with hard-coded input values, you can use parameterized testing to write a single test method that runs with different input values.

Here is how you can do it:




import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.params.ParameterizedTest;

import org.junit.jupiter.params.provider.CsvSource;


public class MathUtilsTest {

@ParameterizedTest

@CsvSource( {"0, 1",

"1, 1",

"2, 2",

"3, 6",

"4, 24"

} )

public void testFactorial(int input, int expectedOutput) {

???MathUtils mathUtils = new MathUtils();

int actualOutput = mathUtils.factorial(input);

????assertEquals(expectedOutput, actualOutput);

}

}




In this example, we're using the "@ParameterizedTest" annotation to indicate that this is a parameterized test, and the "@CsvSource" annotation to provide the input parameters in a comma-separated value (CSV) format. Each row in the CSV represents a set of input parameters, with the first value being the input integer and the second value being the expected output of the "factorial()" method.

The test method "testFactorial()" is called for each row in the CSV, with the input integer and the expected output passed as arguments. In the method, we create an instance of the "MathUtils" class, call the "factorial()" method with the input integer, and compare the actual output with the expected output using the "assertEquals()" method.

With this parameterized test, we're able to test the "factorial()" method with multiple input values and expected outputs in a single test method, which makes it easier to write and maintain our test suite. We can also easily add or remove input parameters in the CSV to test the method with more values or edge cases.

In previous versions of JUnit, the "@RunWith" annotation was used to specify a custom test runner for a test class. However, in JUnit 5, the concept of test runners has been replaced by the concept of test engines, which are responsible for discovering and executing tests.

As a result, the "@RunWith" annotation is no longer used in JUnit 5. Instead, JUnit 5 provides a set of test engines that can be used to run tests, including the "JUnitPlatform" test engine, which can be used to run tests on the JUnit Platform.

If you're using JUnit 4, you may still need to use the "@RunWith" annotation to specify a custom test runner as follows:




import static org.junit.Assert.assertEquals;

import static org.junit.Assert.assertThrows;

import java.util.Arrays;

import java.util.Collection;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.junit.runners.Parameterized;

import org.junit.runners.Parameterized.Parameters;


@RunWith(Parameterized.class)

public class MathUtilsTest {

?private int input;

?private int expectedOutput;


?public MathUtilsTest(int input, int expectedOutput) {

??this.input = input;

??this.expectedOutput = expectedOutput;

?}


?@Parameters

?public static Collection<Object[]> testCases() {

??return Arrays.asList(new Object[][] {

????{0, 1},

????{1, 1},

????{2, 2},

????{3, 6},

????{4, 24},

????{5, 120},

????{6, 720},

????{7, 5040}

??});

?}


@Test

public void testFactorial() {

??MathUtils mathUtils = new MathUtils();

??assertEquals(expectedOutput, mathUtils.factorial(input));

}


@Test

?public void testFactorialWithNegativeInput() {

??MathUtils mathUtils = new MathUtils();

??assertThrows(IllegalArgumentException.class, () -> mathUtils.factorial(-1));

?}

}


In the above example, we have defined a parameterized test class called "MathUtilsTest" which uses the "@RunWith(Parameterized.class)" annotation to run tests with different input parameters. The "testCases()" method returns a collection of arrays, where each array represents an input/output pair for the "factorial()" method. The "@Parameters" annotation indicates that the JUnit framework should use this method to generate the input parameters for the test cases.

The constructor for "MathUtilsTest" takes two arguments: the input parameter for the "factorial()" method and the expected output. The "@Test" annotation is used to indicate that the "testFactorial()" and "testFactorialWithNegativeInput()" methods are test methods that should be executed by the JUnit framework.

The "testFactorial()" method tests the "factorial()" method for each input parameter, while the "testFactorialWithNegativeInput()" method tests that an "IllegalArgumentException" is thrown when the input is negative.

要查看或添加评论,请登录

Nikhil Gargatte的更多文章

社区洞察

其他会员也浏览了