Cyberithub

How to write JUnit test cases for Threads in Java

Advertisements

In this article, we will see how to write JUnit test cases for threads in Java. We'll primarily focus on thread-base concurrency and the issues it presents in testing. We'll likewise comprehend how we can take care of a portion of these issues and really test multi-threaded code in Java.

How to write JUnit test cases for Threads in Java

How to write Junit test cases for threads in Java

Also Read: Understanding Automation Testing with JUnit [Explained with example]

Concurrent programming alludes to programming where we break down an enormous piece of computation into more modest, moderately free computation. The aim of this exercise is to run these more modest computation simultaneously, potentially even in equal. While there are multiple ways of accomplishing this, the goal constantly is to run the program quicker.

 

Threads and Concurrent Programming

With processors pressing more centers than any other time in recent memory, concurrent writing computer programs is at the very front to productively bridle them. Notwithstanding, the reality stays that simultaneous projects are significantly more hard to design, write, test, and maintain. So in the event that we would be able, all things considered, compose powerful and robotized experiments for simultaneous projects, we can settle a huge lump of these issues.

So, what makes composing tests for concurrent code so troublesome? That's what to comprehend, we should comprehend how we accomplish concurrency in our projects. One of the most famous concurrent programming strategies includes utilizing strings. Presently, threads can be local, in which case they're planned by the hidden working frameworks. We can likewise utilize what are known as green threads, which are planned by a runtime straightforwardly.

 

Difficulty in Testing Concurrent Programs

The bulk of the issue related with concurrent programming emerges out of utilizing local threads with shared memory. Testing such projects is challenging for similar reasons. Multiple threads with admittance to shared memory for the most part require common prohibition. We normally accomplish this through some watching instrument utilizing locks.

However, this might in any case prompt a large group of issues like race conditions, live locks, stops, and thread starvation, to give some examples. Also, these issues are discontinuous, as thread planning for the instance of local threads is totally non-deterministic.

 

Life structures of Thread Interleaving

We realize that local threads can be planned by working frameworks capriciously. In the event that these threads access and alter shared information, it brings about fascinating threads interleaving. While a portion of these interleaving might be totally OK, others might leave the last information in a bothersome state.

Let's take an example. Assume we have a worldwide counter that is increased by each thread. Toward the finish of handling, we'd like the condition of this counter to be the very same as the quantity of threads that have executed:-

private int counter;
public void increment() {
   counter++;
}

Now, to increment a primitive integer in Java is certainly not a nuclear activity. It comprises of perusing the worth, expanding it, lastly saving it. While various threads are doing likewise activity, it might bring about numerous conceivable interleaving.

 

Testing Multi-Threaded Code

Now that we comprehend the essential difficulties in testing multi-threaded code, we'll perceive how to conquer them. We'll fabricate a basic use case and attempt to mimic however many issues connected with concurrency as could reasonably be expected.

public class MyCounter {

private int count;
  public void increment() {
       int temp = count; 
       count = temp + 1;
  } // Getter for count 
}

This is an apparently innocuous piece of code, however understanding that it's not thread-safe. On the off chance that we end up composing a simultaneous program with this class, it will undoubtedly be deficient. The reason for testing here is to distinguish such deformities.

 

Testing Non-Concurrent Parts

As a guideline, testing code by disengaging it from any simultaneous behavior is consistently prudent. This is to sensibly learn that there could be no other imperfection in the code that isn't connected with concurrency. We should perceive how might we do that:-

@Test
public void testCounter() {
    MyCounter counter = new MyCounter();
    for (int i = 0; i < 100; i++) {
         counter.increment();
    }
    assertEquals(100, counter.getCount());
}

 

Testing With Concurrency

We should continue toward testing a similar code once more but this time in a concurrent arrangement. We'll attempt to get to similar occasion of this class with various threads and perceive how it acts:-

@Test
public void testCounterWithConcurrency() throws InterruptedException {
    int numberOfThreads = 5;
    ExecutorService service = Executors.newFixedThreadPool(5);
    CountDownLatch latch = new CountDownLatch(numberOfThreads);
    MyCounter counter = new MyCounter();
    for (int i = 0; i < numberOfThreads; i++) {
        service.execute(() -> {
               counter.increment();
               latch.countDown();
       });
    }
    latch.await();
    assertEquals(numberOfThreads, counter.getCount());
}

This test is sensible, as we're attempting to work on imparted information to a few threads. As we keep the quantity of threads low, similar to 5, we will see that it passes almost all the time. Curiously, assuming we begin expanding the quantity of threads, tell 50, we will see that the test begins to fail most of time.

 

Testing Tools Available

As the quantity of threads develops, the conceivable number of ways they might interleave develops dramatically. Sorting out all such interleaving and test for them is simply unrealistic. We need to depend on apparatuses to attempt something similar or comparable exertion for us. Luckily, there are two or three them accessible to make our lives simpler.

a) Tempus-fugit

The tempus-fugit Java library assists us with composing and test simultaneous code easily. We'll simply focus  on the test part of this library here. We saw before that creating weight on code with numerous strings builds the possibilities finding deserts connected with concurrency.

While we can write utilities to create the pressure ourselves, tempus-fugit gives advantageous ways of accomplishing something very similar.

public class MyCountTests {
    @Rule
    public ConcurrentRule concurrently = new ConcurrentRule();
    @Rule
    public RepeatingRule rule = new RepeatingRule();
    private static MyCounter counter = new MyCounter();

    @Test
    @Concurrent(count = 20)
    @Repeating(repetition = 20)
    public void runsMultipleTimes() {
             counter.increment();
    }

    @AfterClass
    public static void annotatedTestRunsMultipleTimes() throws InterruptedException {
             assertEquals(counter.getCount(), 100);
    }
}

Here, we are utilizing two of the Standards accessible to us from tempus-fugit. These standards capture the tests and assist us with applying the ideal ways of behaving, similar to redundancy and concurrency. In this way, successfully, we are rehashing the activity under test multiple times each from ten distinct threads.

b) Thread Weaver

Thread Weaver is basically a Java framework for testing multi-threaded code. We've seen beforehand that thread interleaving is very capricious, and thus, we might in all likelihood never track down specific deformities through standard tests. What we successfully need is a method for controlling the interleaves and test all conceivable interleaving. This has shown to be a seriously mind boggling task in our past endeavor.

Thread Weaver permits us to interleave the execution of two separate threads in an enormous number of ways, without stressing over how. It likewise provides us with the chance of having fine-grained command over how we maintain that the threads should interleave.

public class MyCounterTests {
     private MyCounter counter;

     @ThreadedBefore
     public void before() {
        counter = new MyCounter();
     }
     @ThreadedMain
     public void mainThread() {
        counter.increment();
     }
     @ThreadedSecondary
     public void secondThread() {
        counter.increment();
     }
     @ThreadedAfter
     public void after() {
        assertEquals(2, counter.getCount());
     }
     @Test
     public void testCounter() {
        new AnnotatedTestRunner().runTests(this.getClass(), MyCounter.class);
     }
}

Here, we've characterized two threads that attempt to increase our counter. Thread Weaver will attempt to run this test with these threads in all conceivable interleaving situations. Potentially in one of the interleaves, we will get the deformity, which is very clear in our code.

 

Conclusion

In this article , we covered a portion of the fundamentals connected with simultaneous programming. We talked about multi-threaded concurrency in Java specifically detail. We went through the difficulties it presents to us while testing such code, particularly with shared information. Besides, we went through a portion of the instruments and procedures accessible to test concurrent code.

We additionally talked about alternate ways of staying away from concurrency issues, including instruments and procedures other than automated tests.

Leave a Comment