用JUnit测试多线程代码的新方法


在下文中,您将看到一个关于如何使用测试多线程Java代码的简单示例JUnit。假设我们想创建一个可以并发使用的计数器。所以我们从类计数器和JUnit测试计数器:

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);
    }

}

通过使用带注释的运行JUnit测试由一个特殊的ConcurrentTestRunner。这个测试运行器在4个线程中并行运行带有“测试”注释的方法。之后,它在主线程中执行标注有“在之后”的方法。

如果我们用像vmlens我们看到以下内容:

我们有一个比赛条件进入现场计数。为了解决这个问题,我们将count声明为volatile,并再次运行测试。

private volatile int count=0;

现在测试用例成功了。至少几乎一直如此。如果您经常运行测试用例,您有时会看到断言失败的异常。为了了解发生了什么,我们在中用“单元测试的延迟同步”来运行它vmlens启用。

现在,您将始终看到以下异常:

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)

实际上count++不是一个操作,而是6字节代码操作,包含一个读和一个写字段计数:

ALOAD 0: this
DUP
GETFIELD Counter.count : int
ICONST_1
IADD
PUTFIELD Counter.count : int

通过在这三个操作之间引入延迟,我们确保两个线程并行执行这些操作。并行执行时,计数总是小于4,有时是3,有时只有2。

为了解决这个问题,我们需要使这些方法原子化。这可以通过使用Java . util . concurrent . atomic . atomicnteger来实现:

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();
    }    
}

现在测试用例总是成功的。作为测试员,我曾经concurrent-junit,作为我曾经的比赛状态捕手vmlens

虽然这是一个相当简单的方法,但我意识到其中有一些棘手的部分。尤其是当你必须测试更复杂的课程时。所以,如果你有任何问题,请在下面的评论中提问。