Parametrize Your Tests

 class​ DistanceConversionTest {
 
» @Test
 void​ testConversionRoundTrip() {
  assertRoundTrip(1);
  assertRoundTrip(1_000);
  assertRoundTrip(9_999_999);
  }
 
»private​ ​void​ assertRoundTrip(​int​ kilometers) {
  Distance expectedDistance = ​new​ Distance(
  DistanceUnit.KILOMETERS,
  kilometers
  );
 
  Distance actualDistance = expectedDistance
  .convertTo(DistanceUnit.MILES)
  .convertTo(DistanceUnit.KILOMETERS);
 
  Assertions.assertEquals(expectedDistance, actualDistance);
  }
 }

Sometimes, you need to test a method or chain of methods in the same way, but with many different input parameters. That’s when you want to make sure that the method works for a large range of values. It’s easy to enumerate the parameters in a test method, like in the code above, but this can complicate testing.

The method above tests that you can convert distance values back and forth and get the same result. Can you spot an issue with this method? At a glance, it looks clean and readable. The private method assertRoundTrip() also conforms to our structure of given-when-then.

Think about test execution. What happens if the first assertion, the call to assertRoundTrip(1), already fails? In that case, JUnit marks the complete test as failed and skips the other two assertions with the integers 1000 and 9999999. You’d have to fix the bug that caused assertRoundTrip(1) to fail, run the test again, only to find that now the second assertion, assertRoundTrip(1000), fails as well. Put differently, the assertions hide the ones that follow in the code above.

You can’t fix this with a for loop in the test method that iterates over a set of parameters. The only solution is to execute a different test for every parameter and have one assertion per test. Adding new test methods is easy, but what if you need tests for thirty different integers here? This would be a lot of code repetition.

Luckily, JUnit has special assertions for exactly this situation:

 class​ DistanceConversionTest {
 
» @ParameterizedTest(name = ​"#{index}: {0}km == {0}km->mi->km"​)
  @ValueSource(ints = {1, 1_000, 9_999_999})
 void​ testConversionRoundTrip(​int​ kilometers) {
  Distance expectedDistance = ​new​ Distance(
  DistanceUnit.KILOMETERS,
  kilometers
  );
 
  Distance actualDistance = expectedDistance
  .convertTo(DistanceUnit.MILES)
  .convertTo(DistanceUnit.KILOMETERS);
 
  Assertions.assertEquals(expectedDistance, actualDistance);
  }
 }

The solution here consists of parameterized tests with the @ParameterizedTest and @ValueSource annotations. They allow us to separate the parameters (i.e., the input and expected output) from the actual test code. Upon execution, JUnit runs a separate test for every parameter.

We declared the parameters in the @ValueSource(ints = { ... }) annotation as an array of integers. Here, we’re lucky to have the input match the expected output, due to the roundtrip nature of the test. Sometimes, you just expect that no exception is thrown (see Let JUnit Handle Exceptions).

We also apply Describe Your Tests here, with the name variable inside @ParameterizedTest(name = ""). The variable {index} refers to the index of the test and we can also reference the arguments of the test method with curly braces. {0} stands for the first one and, in this case, the only one. That way, our test description reads like a compressed form of the whole test.

You can inject parameters from different sources such as CSV files, return values of methods, or explicitly in code, as we’ve done. As a rule of thumb, if you have only a few parameters, it’s better to declare them explicitly. CSV files or method return values are better if their amount is very large.

Parameterized tests are only the beginning. Property-based testing libraries such as junit-quickcheck[40] that automatically generate inputs and expected outputs are the next step. With such a technique, it’s easy to cover a large range of parameters without having to maintain a large list of parameters in code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.189.228