A new way to junit test your multithreaded java code

July 08, 2016

In the following you see a simple example on how to test multithreaded java code with JUnit. Suppose we want to create a counter which can be used concurrently. So we start with the class Counter and the JUnit test TestCounter:

public class Counter { private int count=0; public void addOne() { count++; } public int getCount() { return count; }	 }

import static org.junit.Assert.assertEquals;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.anarsoft.vmlens.concurrent.junit.ConcurrentTestRunner;
@RunWith(ConcurrentTestRunner.class)
public class TestCounter {	
	private Counter counter = new Counter();
	@Test
	public void addOne()
	{
		counter.addOne();
	}
	@After
	public void testCount()
	{
		assertEquals("4 Threads running addOne in parallel should lead to 4" , 4 , counter.getCount());
	}
}
By using the RunWith annotation the JUnit test is run by a special ConcurrentTestRunner. This test runner runs the method annotated with "Test" parallel in 4 threads. After that it executes the methods marked with the annotation After" in the main thread. flow If we run the test case with a race condition catcher like vmlens we see the following:

Race Condition at Count

We have a race condition accessing the field count. To solve this we declare count as volatile and run the test again.

private volatile int count=0;

Now the test case succeeds. At least almost all the time. If you run the test case very often, you will sometimes see an assertion failed exception.
java.lang.AssertionError: 4 Threads running addOne in parallel should lead to 4 expected:<4> but was:<3>
	at org.junit.Assert.fail(Assert.java:88)
	at org.junit.Assert.failNotEquals(Assert.java:834)
	at org.junit.Assert.assertEquals(Assert.java:645)
	at TestCounter.testCount(TestCounter.java:21)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at com.anarsoft.vmlens.concurrent.junit.internal.InvokeListOfMethods.evaluate(InvokeListOfMethods.java:23)
	at com.anarsoft.vmlens.concurrent.junit.internal.ConcurrentStatement.evaluateStatement(ConcurrentStatement.java:12)
	at com.anarsoft.vmlens.concurrent.junit.ConcurrentTestRunner.evaluateStatement(ConcurrentTestRunner.java:212)
	at com.anarsoft.vmlens.concurrent.junit.ConcurrentTestRunner.runChildrenConcurrently(ConcurrentTestRunner.java:172)
	at com.anarsoft.vmlens.concurrent.junit.ConcurrentTestRunner.access$0(ConcurrentTestRunner.java:78)
	at com.anarsoft.vmlens.concurrent.junit.ConcurrentTestRunner$1.evaluate(ConcurrentTestRunner.java:72)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Actually count++ is not one operation but 6 byte code operations, containing one read and one write to the field count:
ALOAD 0: this
DUP
GETFIELD Counter.count : int
ICONST_1
IADD
PUTFIELD Counter.count : int
If we look at the volatile field in vmlens we see the order of read and writes:

Order of events in case of race condition

To make sure that the test case always fails we can use a “wait point” at the write to the field:

Wait Point

By letting the threads wait before the write operation, we make sure that two threads read the field in parallel. And reading in parallel causes the bug.

To solve this we need to make those methods atomic. This can be done by using java.util.concurrent.atomic.AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger; public class Counter { private final AtomicInteger  count= new AtomicInteger(); public void addOne() { count.incrementAndGet(); } public int getCount() { return count.get(); }	 }

Now the test case always succeed. As test runner I used concurrent-junit, as race condition catcher I used vmlens

And although it is a fairly simple method, I realize that there are some tricky parts. Especially when you have to test more complicated classes. So, if you have any questions, just ask them in a comment below.

Make your application thread safe

LEARN MORE