JUnit Parameterization & Parallelization: A great way to improve your test framework

junit parameterization & parallelization

When it comes to writing functional API tests with JUnit, we strive to have a more structured and cleaner code that’s easy to understand, flexible to change and having reusable methods. If we can achieve the ones above, then we’re good to go right? Well, that is questionable, depending on the complexity and the scale of what needs to be covered in our automated tests as well as how many steps do we need to perform in order to have a fully completed test.

What if we have a scenario where we need to perform the same test but we must give the system different input data so that we can expect different results as an output. Or, what if we have many tests that need to be executed but we don’t want the regression test execution to be too long? Well, JUnit has many features that can help in achieving a better test overview like its test annotations.

With parameterization & parallelization, you can achieve that and I will explain in detail what does that means.

JUnit Parameterization

JUnit parameterization
JUnit Parameterization

In order to understand the whole logic and implementation around JUnit parameterization, you need to understand what is a test parameterization first. I’m going to cover this subject in detail but will try to stick with the basics just to understand what we’re having here.

What is Test Parameterization?

Test parameterization is a process where you can specify multiple inputs for one test to be run over and over again. Having specified multiple input data for a test that can accept them as arguments and then use them as inputs for different outcomes. With this, we save many lines of code, many repeatable tests that are the same in one test that does all of that.

There are a couple of things that need to be followed to have a parameterized test:

  • Create a test class
  • The class must be parameterized with @RunWith(Parameterized.class)
  • Declare variables that act as input depending on their type
  • Create a constructor with arguments that accepts the variables you declare in the class
  • A static method needs to be created with all the values that we want to test
  • Create a test method

Let’s take a closer look into parameterized example to understand the basics.

Test Parameterization Example

Lets create a class:

public class ParameterizedTest { private String name; private String lastName }

Parameterize it with:

@RunWith(Parameterized.class) ParameterizedTest { private String name; private String lastName }

Now, create a constructor with arguments:

public ParameterizedTest(String name, String lastName) { super(); this.name = name; this.lastName = lastName; }

And create the parameterized method with values that returns a collection of inputs for each key:

@Parameterized.Parameters public static Collection testData() {return Arrays.asList(new Object [][] { {"Michael", "Johnson"}, {"Jack", "Douglas"} } ); }

Create a test method that is going to be printing the variables with the actual values using the above-mentioned test data:

@Test public void testExample() { System.our.println("The name of the player is: " + name); System.our.println("The last name of the player is: " + lastName); }

Putting it all together:

@RunWith(Parameterized.class) ParameterizedTest { private String name; private String lastName } public ParameterizedTest(String name, String lastName) { super(); this.name = name; this.lastName = lastName; } @Parameterized.Parameters public static Collection testData() { return Arrays.asList(new Object [][] { {"Michael", "Johnson"}, {"Jack", "Douglas"} } ); } @Test public void testExample() { System.our.println("The name of the player is: " + name); System.our.println("The last name of the player is: " + lastName); } }

The test will be invoked two times where name=Michael and lastName=Johnson on the first run and in the second run we would have Jack and Douglas as values in the fields respectively.

There is another way of parameterizing a test and it is easier to understand and write, at least to me.

JUnit DataProvider

DataProvider is another way of having multiple test data inputs for one test. Using the same example from above we can have:

@RunWith(DataProviderRunner.class)
public class testDataProvider {}

@DataProvider
public static Object[][] dataProviderTest() {
return new Object[][]{
{"Michael", "Johnson"},
{"Jack", "Douglas"},
};
}
@UseDataProvider("dataProviderTest")                                                               @Test public void testExample() {                                           System.our.println("The name of the player is: " + name); System.our.println("The last name of the player is: " + lastName); }

There is a huge advantage of using DataProvider instead of the parameterized approach. The main one is that DataProvider is used for a specific test that has the data provider annotation assigned, whereas, with a parameterized approach, all tests that are in the class will be parameterized which would be ideal because we are definitely going to have many tests in a class but the only couple of them should be parameterized, so there is no point of parameterizing the other tests since the other ones can be completed for one input only.

Another advantage is that you can have as many DataProvider methods in one class which can be used for different tests within the same class and only those tests will be parameterized when running the whole test class.

JUnit Parallelization

Executing tests one by one works just fine. Other than the tests to be 100% fully working and stable we also want to make sure that the test execution does not take too much time to be completed. With JUnit, we have something called test parallelization. Let’s see what it is.

What is Test Parallelization?

Test parallelization is the test execution process where multiple tests can be run at the same time in parallel rather than executing sequentially. If we have hundreds or thousands of tests that must be executed in a specific period of time without any errors and we have the setup to run the test in parallel then we should do that. The execution time would be much faster.

But on the other hand, if you try having a parallel test execution for your project, you must be familiar with a couple of things first:

  • Make sure tests are 100% independent from itself
  • Make sure the tests are not using the same variables
  • If a couple of tests is using the same static variables, the first test would initialize them and then the second test would fail since maybe it requires a different value for that variable
  • Try to make sure that each test execution time is not drastically different from the other ones. If it is, it may cause flaky results
  • Due to parallel test execution on multiple threads, tests may well flaky, so you need to make sure the output of one test does not affect the input of the next one
  • The test runner can be stuck and won’t continue

Let’s dive in into an example.

Test Parallelization Example

Lets create two test classes:

import org.junit.Test; public class TestClassOne { @Test public void testOne() { System.out.println("Class One Test One"); System.out.println("Class One Test Two");}}

And the second test class:

import org.junit.Test; public class TestClassTwo { @Test public void testTwo() { System.out.println("Class Two Test Two"); System.out.println("Class Two Test Two");}}

Then we create the final test class that will run those two classes in parallel:

import org.junit.Test; import org.junit.experimental.ParallelComputer; import org.junit.runner.JUnitCore; public class TestParallel { @Test public void test() { Class[] testClasses = { TestClassOne.class, TestClassTwo.class }; JUnitCore.runClasses(new ParallelComputer(true, true), testClasses); } }

The output will be :

  • TestClassTwo Test One
  • TestClassOne Test One
  • TestClassOne Test Two
  • TestClassTwo Test Two

The Bottom Line

Having tests automated regardless of the structure, flow, lines of code, and execution time it”s fine but not good enough. Testers should aim for better test optimization as well as more clear code structure. Having said that, parameterization and parallelization are just ways of how you can achieve greater readability, optimization, and execution.

Share This Post


Latest Posts

Vladimir Simonovski
Vladimir Simonovski

Software Automation Engineer for almost 5 years of experience. Involved in many QA activities for the insurance and banking platforms. Follow QAMIND on Twitter and LinkedIn to get fresh content around Software Testing

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.