Less Code, More Tests: Exploring Parameterized Tests in JUnit

tiuwill

Willian Ferreira Moya

Posted on March 6, 2024

Less Code, More Tests: Exploring Parameterized Tests in JUnit

Introduction

Regarding writing tests, some developers are lazy writing fewer test scenarios as possible. And sometimes they can write no scenarios at all. Imagine being able to test many scenarios without writing much code. Isn’t that great?

A parameterized test lets you test many scenarios. You just send a list of parameters. This way, you don’t need to write many test methods.

Understanding Parameterized Tests: A Key Tool for Efficient Testing

Normally our test methods don’t receive any parameters at all. Because we won’t call them directly, JUnit calls them for us when we run the tests. With parameterized tests, you can use a list of parameters in your test.

JUnit will iterate through that list calling our test method for each element in it.

That’s the beauty of this feature. You can increase your test scenarios by only growing the parameter list. No need to write another method to test each different value.

What source of inputs does it support?

This feature lets you use parameters from various sources in your tests. Here are some examples:

  • A list of values
  • Enums
  • CSV format
  • You can create a method that returns a list of arguments.

And much more. You can even combine two sources. You can check all possible ways in the JUnit documentation. This can vary from version to version.

How to use it?

So let’s go to the fun part. I’ll combine this with the boundary testing technique I shared in another post to apply this. If you didn’t read it, don’t worry, I’ll summarize it for you. It consists of testing the borders of your code where it’s most likely that bugs arise. To apply this technique you can follow these steps:

  • Test the exact minimum valid value possible.
  • Test the first invalid value. Which will be the minimum valid value minus one.
  • The minimum valid value plus one.
  • A valid value in the valid range.
  • The maximum valid value in the range minus one.
  • The exact maximum valid value.
  • And the first invalid after the maximum. Which will be valid maximum plus one.

Let’s take this password validation as an example:

public static boolean validatePassword(String password) {
    if (password == null || password.length() < 8 || password.length() > 20) {
        return false;
    }
    return true;
}
Enter fullscreen mode Exit fullscreen mode

We can break it down to identify the partitions, and what the boundaries will be:

  • password == null
    • For this partition, a null and a non-null string would be enough
  • password.length() ≤ 8
    • For this partition:
      • test a password that is 7 characters long. (minimum - 1, first invalid value)
      • test a password that has a length that is exactly 8 characters. (exact minimum)
      • and a test that has a length of 9 characters.
  • password.length() > 20:
    • For this partition:
      • test a password that is 19 characters long. (maximum - 1)
      • test a password that has a length that is exactly 20 characters. (exact maximum)
      • and another that is 21 characters long. (maximum + 1)

Now we can write a parameterized test to help simply achieve this. We started creating a source of data to test. Like said before it can be CSV, Method, Enum, etc. In this case, we’re going to use the MethodSource.

Now we create a method of arguments (input, and expected value) that will be passed to our test method:

public static Stream<Arguments> boundaries() {
    return Stream.of(
        Arguments.of(null, false), // null value
        Arguments.of("1234567", false),  //minimum - 1
        Arguments.of("12345678", true), // exact minimum
        Arguments.of("123456789", true), // minimum + 1
        Arguments.of("123456789012345678", true), // maximum - 1
        Arguments.of("1234567890123456789", true), // exact maximum
        Arguments.of("123456789012345678901", false) // maximum + 1
    );
}
Enter fullscreen mode Exit fullscreen mode

Then we create a simple @ParametizedTestthat will exercise all the scenarios described above:

@ParameterizedTest
@MethodSource("boundaries")
void validatePassword(String password, boolean expected) {
    assertEquals(expected, PasswordValidator.validatePassword(password));
}
Enter fullscreen mode Exit fullscreen mode

See, a straightforward test case, that will test all boundary scenarios of our code. To add more tests for this method, add a line of input and expected values in the boundaries source method. You won’t need to create more test methods.

Conclusion

In this post, you learned a way to write more tests without adding too much code to your tests. Parameterized tests easily add extra scenarios. This ensures everything is okay

Now it’s your turn, to look at your code, and see which places in your code you can start using some parameterized tests. Use this with boundary tests. Your code and tests will be more reliable.

Don’t forget to follow me on social media to be the first to read when my next article comes out!

Willian Moya (@WillianFMoya) / X (twitter.com)

Willian Ferreira Moya | LinkedIn

💖 💪 🙅 🚩
tiuwill
Willian Ferreira Moya

Posted on March 6, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related