如何使用junit运行并发测试?
假设我有一个班级
public class MessageBoard
{
public synchronized void postMessage(String message)
{
....
}
public void updateMessage(Long id, String message)
{
....
}
}
我想同时测试对这个postMessage的多次访问。 有什么建议吗?我希望针对我的所有setter函数(或任何涉及create / update / delete操作的方法)运行这种并发性测试。
答案 0 :(得分:21)
不幸的是,我不相信你可以通过运行时测试明确地证明你的代码是线程安全的。你可以根据需要抛出任意数量的线程,它可能会/可能不会通过,具体取决于调度。
也许您应该查看一些静态分析工具,例如PMD,它们可以确定您如何使用同步并识别使用问题。
答案 1 :(得分:16)
我建议使用MultithreadedTC - 由并发主人自己编写Bill Pugh(和Nat Ayewah)。引用他们的overview:
MultithreadedTC是一个框架 测试并发应用程序它 有一个常用的节拍器 提供对序列的精细控制 多线程中的活动。
此框架允许您在单独的测试中确定性地测试每个线程交错
答案 2 :(得分:9)
您只能证明并发错误的存在,而不是缺席。
但是你可以编写一个专门的测试运行器来生成几个并发线程,然后调用你的@Test注释方法。
答案 3 :(得分:7)
在.NET中,有TypeMock Racer或Microsoft CHESS等工具专为单元测试并发而设计。这些工具不仅可以找到死锁等多线程错误,还可以为您提供重现错误的线程交错集。
我认为Java世界有类似的东西。
答案 4 :(得分:5)
并发运行可能会导致意外结果。例如,我刚刚发现,虽然我的测试套件有200个测试在逐个执行时通过,但是并发执行失败,我挖了它并且它不是线程安全问题,而是依赖于另一个测试,是一件坏事,我可以解决问题。
Mycila work on JUnit ConcurrentJunitRunner and ConcurrentSuite非常有趣。与最新的GA版本相比,这篇文章似乎有点过时,在我的示例中,我将展示更新后的用法。
如下所示注释测试类将导致并发执行测试方法,并发级别为6:
import com.mycila.junit.concurrent.ConcurrentJunitRunner;
import com.mycila.junit.concurrent.Concurrency;
@RunWith(ConcurrentJunitRunner.class)
@Concurrency(6)
public final class ATest {
...
您还可以同时运行所有测试类:
import com.mycila.junit.concurrent.ConcurrentSuiteRunner;
@RunWith(ConcurrentSuiteRunner.class)
@Suite.SuiteClasses({ATest.class, ATest2.class, ATest3.class})
public class MySuite {
}
<dependency>
<groupId>com.mycila</groupId>
<artifactId>mycila-junit</artifactId>
<version>1.4.ga</version>
</dependency>
我目前正在调查如何多次运行方法并同时使用此包。如果有人有一个例子让我知道,可能已经可以在我的自制解决方案之下了。
@Test
public final void runConcurrentMethod() throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(16);
for (int i = 0; i < 10000; i++) {
exec.execute(new Runnable() {
@Override
public void run() {
concurrentMethod();
}
});
}
exec.shutdown();
exec.awaitTermination(50, TimeUnit.SECONDS);
}
private void concurrentMethod() {
//do and assert something
}
正如其他人所指出的那样,你永远无法确定是否会出现并发错误,但是数以万计,或者数十万次执行并发,例如16,统计数据就在你身边。
答案 5 :(得分:2)
尝试查看JUnit附带的ActiveTestSuite。它可以同时启动多个JUnit测试:
public static Test suite()
{
TestSuite suite = new ActiveTestSuite();
suite.addTestSuite(PostMessageTest.class);
suite.addTestSuite(PostMessageTest.class);
suite.addTestSuite(PostMessageTest.class);
suite.addTestSuite(PostMessageTest.class);
suite.addTestSuite(PostMessageTest.class);
return suite;
}
以上将在paralell中运行相同的JUnit测试类5次。如果你想在你的并列测试中有变化,那就创建一个不同的类。
答案 6 :(得分:1)
在您的示例中,postMessage()方法是同步,因此您实际上不会在单个VM中看到任何并发效果,但您可能能够评估同步版本的性能。
您需要在不同的VM中同时运行多个测试程序副本。你可以使用
如果您无法让测试框架完成,您可以自行启动一些虚拟机。 流程构建器的东西是路径和诸如此类的痛苦,但这里是一般草图:
Process running[] = new Process[5];
for (int i = 0; i < 5; i++) {
ProcessBuilder b = new ProcessBuilder("java -cp " + getCP() + " MyTestRunner");
running[i] = b.start();
}
for(int i = 0; i < 5; i++) {
running[i].waitFor();
}
我通常会做这样的事情进行简单的线程测试,就像其他人发布的那样,测试不是正确性的证明,但它通常会在实践中消除愚蠢的错误。它有助于在各种不同的条件下进行长时间的测试 - 有时在测试中会出现并发错误需要一段时间。
public void testMesageBoard() {
final MessageBoard b = new MessageBoard();
int n = 5;
Thread T[] = new Thread[n];
for (int i = 0; i < n; i++) {
T[i] = new Thread(new Runnable() {
public void run() {
for (int j = 0; j < maxIterations; j++) {
Thread.sleep( random.nextInt(50) );
b.postMessage(generateMessage(j));
verifyContent(j); // put some assertions here
}
}
});
PerfTimer.start();
for (Thread t : T) {
t.start();
}
for (Thread t : T) {
t.join();
}
PerfTimer.stop();
log("took: " + PerfTimer.elapsed());
}
}**strong text**
答案 7 :(得分:1)
测试并发错误是不可能的;您不仅需要验证输入/输出对,还必须在测试期间可能会或可能不会发生的情况下验证状态。不幸的是,JUnit没有这样做。
答案 8 :(得分:1)
您可以使用tempus-fugit库并行运行测试方法多次以模拟负载测试类型环境。虽然之前的注释指出post方法是同步的并且受到保护,但是可能涉及其自身未受保护的关联成员或方法,因此其可能加载/浸泡类型测试可以抓住这些。我建议你设置一个相当粗粒度/端到端的测试,以便最好地捕获任何循环漏洞。
请参阅documentation的JUnit集成部分。
顺便说一句,我是上述项目的开发人员:)答案 9 :(得分:0)
答案 10 :(得分:0)
这里最好的方法是使用系统测试来查询代码,看看它是否会崩溃。然后使用单元测试来检查逻辑正确性。我接近这个的方法是为异步调用创建一个代理,让它在测试中同步进行。
但是,如果你想在完全安装和完整环境以外的级别执行此操作,可以在junit中通过在单独的线程中创建对象来执行此操作,然后创建大量线程来触发对对象的请求并阻止直到它完成的主线程。如果你做得不对,这种方法可以使测试间歇性地失败。
答案 11 :(得分:0)
您也可以尝试HavaRunner。它默认并行运行测试。