multithreading 我应该如何对线程代码进行单元测试?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12159/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 00:57:14  来源:igfitidea点击:

How should I unit test threaded code?

multithreadingunit-testing

提问by jkp

I have thus far avoided the nightmare that is testing multi-threaded code since it just seems like too much of a minefield. I'd like to ask how people have gone about testing code that relies on threads for successful execution, or just how people have gone about testing those kinds of issues that only show up when two threads interact in a given manner?

到目前为止,我已经避免了测试多线程代码的噩梦,因为它看起来太像雷区了。我想问一下人们是如何测试依赖线程成功执行的代码的,或者人们是如何测试那些仅在两个线程以给定方式交互时才会出现的问题的?

This seems like a really key problem for programmers today, it would be useful to pool our knowledge on this one imho.

对于今天的程序员来说,这似乎是一个非常关键的问题,恕我直言,汇集我们的知识会很有用。

回答by jkp

Look, there's no easy way to do this. I'm working on a project that is inherently multithreaded. Events come in from the operating system and I have to process them concurrently.

看,没有简单的方法可以做到这一点。我正在研究一个本质上是多线程的项目。事件来自操作系统,我必须同时处理它们。

The simplest way to deal with testing complex, multithreaded application code is this: If its too complex to test, you're doing it wrong. If you have a single instance that has multiple threads acting upon it, and you can't test situations where these threads step all over each other, then your design needs to be redone. Its both as simple and as complex as this.

处理测试复杂的多线程应用程序代码的最简单方法是:如果它太复杂而无法测试,那么您就做错了。如果您有一个有多个线程作用于其上的单个实例,并且您无法测试这些线程相互交错的情况,那么您的设计需要重做。它既简单又复杂。

There are many ways to program for multithreading that avoids threads running through instances at the same time. The simplest is to make all your objects immutable. Of course, that's not usually possible. So you have to identify those places in your design where threads interact with the same instance and reduce the number of those places. By doing this, you isolate a few classes where multithreading actually occurs, reducing the overall complexity of testing your system.

有很多方法可以为多线程编程,以避免线程同时运行通过实例。最简单的方法是使所有对象不可变。当然,这通常是不可能的。因此,您必须确定设计中线程与同一实例交互的位置,并减少这些位置的数量。通过这样做,您可以隔离实际发生多线程的几个类,从而降低测试系统的整体复杂性。

But you have to realize that even by doing this you still can't test every situation where two threads step on each other. To do that, you'd have to run two threads concurrently in the same test, then control exactly what lines they are executing at any given moment. The best you can do is simulate this situation. But this might require you to code specifically for testing, and that's at best a half step towards a true solution.

但是您必须意识到,即使这样做,您仍然无法测试两个线程相互踩踏的每种情况。为此,您必须在同一个测试中同时运行两个线程,然后在任何给定时刻准确控制它们正在执行的行。您能做的最好的事情就是模拟这种情况。但这可能需要您专门为测试编写代码,这充其量只是迈向真正解决方案的半步。

Probably the best way to test code for threading issues is through static analysis of the code. If your threaded code doesn't follow a finite set of thread safe patterns, then you might have a problem. I believe Code Analysis in VS does contain some knowledge of threading, but probably not much.

测试线程问题代码的最佳方法可能是通过代码的静态分析。如果您的线程代码不遵循一组有限的线程安全模式,那么您可能会遇到问题。我相信 VS 中的代码分析确实包含一些线程知识,但可能不多。

Look, as things stand currently (and probably will stand for a good time to come), the best way to test multithreaded apps is to reduce the complexity of threaded code as much as possible. Minimize areas where threads interact, test as best as possible, and use code analysis to identify danger areas.

看,就目前的情况(并且可能会持续一段时间),测试多线程应用程序的最佳方法是尽可能地降低线程代码的复杂性。尽量减少线程交互的区域,尽可能地进行测试,并使用代码分析来识别危险区域。

回答by Theo Lenndorff

It's been a while when this question was posted, but it's still not answered ...

发布这个问题已经有一段时间了,但仍然没有回答......

kleolb02's answer is a good one. I'll try going into more details.

kleolb02的回答很好。我会尝试深入了解更多细节。

There is a way, which I practice for C# code. For unit tests you should be able to program reproducibletests, which is the biggest challenge in multithreaded code. So my answer aims toward forcing asynchronous code into a test harness, which works synchronously.

有一种方法,我为 C# 代码练习。对于单元测试,您应该能够编写可重现的测试,这是多线程代码中的最大挑战。因此,我的回答旨在将异步代码强制转换为同步工作的测试工具。

It's an idea from Gerard Meszardos's book "xUnit Test Patterns" and is called "Humble Object" (p. 695): You have to separate core logic code and anything which smells like asynchronous code from each other. This would result to a class for the core logic, which works synchronously.

这是 Gerard Meszardos 的书“ xUnit Test Patterns”中的一个想法,被称为“Humble Object”(第 695 页):您必须将核心逻辑代码和任何闻起来像异步代码的东西相互分离。这将导致核心逻辑的类,它同步工作

This puts you into the position to test the core logic code in a synchronousway. You have absolute control over the timing of the calls you are doing on the core logic and thus can make reproducibletests. And this is your gain from separating core logic and asynchronous logic.

这使您能够以同步方式测试核心逻辑代码。您可以绝对控制在核心逻辑上进行的调用的时间,因此可以进行可重复的测试。这是您从分离核心逻辑和异步逻辑中获得的收益。

This core logic needs be wrapped around by another class, which is responsible for receiving calls to the core logic asynchronously and delegatesthese calls to the core logic. Production code will only access the core logic via that class. Because this class should only delegate calls, it's a very "dumb" class without much logic. So you can keep your unit tests for this asychronous working class at a minimum.

该核心逻辑需要被另一个类包裹,该类负责异步接收对核心逻辑的调用,并将这些调用委托给核心逻辑。生产代码将仅通过该类访问核心逻辑。因为这个类应该只委托调用,所以它是一个没有太多逻辑的非常“愚蠢”的类。因此,您可以将这个异步工作类的单元测试保持在最低限度。

Anything above that (testing interaction between classes) are component tests. Also in this case, you should be able to have absolute control over timing, if you stick to the "Humble Object" pattern.

高于此的任何内容(测试类之间的交互)都是组件测试。同样在这种情况下,如果您坚持“Humble Object”模式,您应该能够绝对控制时间。

回答by David Joyner

Tough one indeed! In my (C++) unit tests, I've broken this down into several categories along the lines of the concurrency pattern used:

确实是硬伤!在我的 (C++) 单元测试中,我按照所使用的并发模式将其分解为几个类别:

  1. Unit tests for classes that operate in a single thread and aren't thread aware -- easy, test as usual.

  2. Unit tests for Monitor objects(those that execute synchronized methods in the callers' thread of control) that expose a synchronized public API -- instantiate multiple mock threads that exercise the API. Construct scenarios that exercise internal conditions of the passive object. Include one longer running test that basically beats the heck out of it from multiple threads for a long period of time. This is unscientific I know but it does build confidence.

  3. Unit tests for Active objects(those that encapsulate their own thread or threads of control) -- similar to #2 above with variations depending on the class design. Public API may be blocking or non-blocking, callers may obtain futures, data may arrive at queues or need to be dequeued. There are many combinations possible here; white box away. Still requires multiple mock threads to make calls to the object under test.

  1. 对在单线程中运行且不知道线程的类进行单元测试——简单,照常测试。

  2. 公开同步公共 API 的Monitor 对象(那些在调用者的控制线程中执行同步方法的对象)的单元测试——实例化多个执行 API 的模拟线程。构建运用被动对象内部条件的场景。包括一个运行时间更长的测试,它基本上可以在很长一段时间内从多个线程中击败它。我知道这是不科学的,但它确实建立了信心。

  3. 活动对象的单元测试(那些封装了它们自己的线程或控制线程的对象)——类似于上面的#2,但根据类设计而有所不同。公共 API 可能是阻塞的或非阻塞的,调用者可能获得期货,数据可能到达队列或需要出队。这里有许多可能的组合;白盒子走了。仍然需要多个模拟线程来调用被测对象。

As an aside:

作为旁白:

In internal developer training that I do, I teach the Pillars of Concurrencyand these two patterns as the primary framework for thinking about and decomposing concurrency problems. There's obviously more advanced concepts out there but I've found that this set of basics helps keep engineers out of the soup. It also leads to code that is more unit testable, as described above.

在我进行的内部开发人员培训中,我教授并发支柱和这两种模式作为思考和分解并发问题的主要框架。显然有更高级的概念,但我发现这组基础知识有助于让工程师摆脱困境。如上所述,它还导致代码更易于单元测试。

回答by Warren Dew

I have faced this issue several times in recent years when writing thread handling code for several projects. I'm providing a late answer because most of the other answers, while providing alternatives, do not actually answer the question about testing. My answer is addressed to the cases where there is no alternative to multithreaded code; I do cover code design issues for completeness, but also discuss unit testing.

近年来,在为多个项目编写线程处理代码时,我多次遇到过这个问题。我提供了一个迟到的答案,因为大多数其他答案虽然提供了替代方案,但实际上并没有回答有关测试的问题。我的回答是针对多线程代码别无选择的情况;为了完整性,我确实涵盖了代码设计问题,但也讨论了单元测试。

Writing testable multithreaded code

编写可测试的多线程代码

The first thing to do is to separate your production thread handling code from all the code that does actual data processing. That way, the data processing can be tested as singly threaded code, and the only thing the multithreaded code does is to coordinate threads.

首先要做的是将生产线程处理代码与进行实际数据处理的所有代码分开。这样,数据处理就可以作为单线程代码进行测试,而多线程代码所做的唯一事情就是协调线程。

The second thing to remember is that bugs in multithreaded code are probabilistic; the bugs that manifest themselves least frequently are the bugs that will sneak through into production, will be difficult to reproduce even in production, and will thus cause the biggest problems. For this reason, the standard coding approach of writing the code quickly and then debugging it until it works is a bad idea for multithreaded code; it will result in code where the easy bugs are fixed and the dangerous bugs are still there.

要记住的第二件事是多线程代码中的错误是概率性的。最不经常出现的错误是那些会潜入生产环境的错误,即使在生产中也很难重现,因此会导致最大的问题。出于这个原因,快速编写代码然后调试它直到它工作的标准编码方法对于多线程代码来说是一个坏主意。这将导致代码中简单的错误得到修复,而危险的错误仍然存​​在。

Instead, when writing multithreaded code, you must write the code with the attitude that you are going to avoid writing the bugs in the first place. If you have properly removed the data processing code, the thread handling code should be small enough - preferably a few lines, at worst a few dozen lines - that you have a chance of writing it without writing a bug, and certainly without writing many bugs, if you understand threading, take your time, and are careful.

相反,在编写多线程代码时,您必须以首先避免编写错误的态度编写代码。如果您正确删除了数据处理代码,线程处理代码应该足够小——最好是几行,最坏的情况是几十行——你有机会在不写错误的情况下编写它,当然也不会写很多错误,如果您了解线程,请花点时间并小心。

Writing unit tests for multithreaded code

为多线程代码编写单元测试

Once the multithreaded code is written as carefully as possible, it is still worthwhile writing tests for that code. The primary purpose of the tests is not so much to test for highly timing dependent race condition bugs - it's impossible to test for such race conditions repeatably - but rather to test that your locking strategy for preventing such bugs allows for multiple threads to interact as intended.

一旦尽可能仔细地编写了多线程代码,为该代码编写测试仍然是值得的。测试的主要目的不是测试高度依赖于时间的竞争条件错误 - 不可能重复测试此类竞争条件 - 而是测试用于防止此类错误的锁定策略是否允许多个线程按预期进行交互.

To properly test correct locking behavior, a test must start multiple threads. To make the test repeatable, we want the interactions between the threads to happen in a predictable order. We don't want to externally synchronize the threads in the test, because that will mask bugs that could happen in production where the threads are not externally synchronized. That leaves the use of timing delays for thread synchronization, which is the technique that I have used successfully whenever I've had to write tests of multithreaded code.

要正确测试正确的锁定行为,测试必须启动多个线程。为了使测试可重复,我们希望线程之间的交互以可预测的顺序发生。我们不想在测试中对线程进行外部同步,因为这会掩盖生产中线程未进行外部同步时可能发生的错误。这就留下了线程同步的时间延迟的使用,这是我在必须编写多线程代码测试时成功使用的技术。

If the delays are too short, then the test becomes fragile, because minor timing differences - say between different machines on which the tests may be run - may cause the timing to be off and the test to fail. What I've typically done is start with delays that cause test failures, increase the delays so that the test passes reliably on my development machine, and then double the delays beyond that so the test has a good chance of passing on other machines. This does mean that the test will take a macroscopic amount of time, though in my experience, careful test design can limit that time to no more than a dozen seconds. Since you shouldn't have very many places requiring thread coordination code in your application, that should be acceptable for your test suite.

如果延迟太短,那么测试就会变得脆弱,因为微小的计时差异——比如在可能运行测试的不同机器之间——可能会导致计时中断和测试失败。我通常所做的是从导致测试失败的延迟开始,增加延迟以便测试在我的开发机器上可靠地通过,然后将延迟加倍,这样测试就有很大的机会在其他机器上通过。这确实意味着测试将花费大量时间,但根据我的经验,仔细的测试设计可以将时间限制在不超过十几秒。由于您的应用程序中不应该有太多需要线程协调代码的地方,因此您的测试套件应该可以接受。

Finally, keep track of the number of bugs caught by your test. If your test has 80% code coverage, it can be expected to catch about 80% of your bugs. If your test is well designed but finds no bugs, there's a reasonable chance that you don't have additional bugs that will only show up in production. If the test catches one or two bugs, you might still get lucky. Beyond that, and you may want to consider a careful review of or even a complete rewrite of your thread handling code, since it is likely that code still contains hidden bugs that will be very difficult to find until the code is in production, and very difficult to fix then.

最后,跟踪测试捕获的错误数量。如果您的测试具有 80% 的代码覆盖率,则可以预期它会捕获大约 80% 的错误。如果您的测试设计良好但没有发现错误,那么您很可能没有其他只会在生产中出现的错误。如果测试发现一两个错误,您可能仍然很幸运。除此之外,您可能需要考虑仔细甚至完全重写您的线程处理代码,因为代码可能仍然包含隐藏的错误,在代码投入生产之前很难找到这些错误,并且非常很难修复。

回答by ollifant

I also had serious problems testing multi- threaded code. Then I found a really cool solution in "xUnit Test Patterns" by Gerard Meszaros. The pattern he describes is called Humble object.

我在测试多线程代码时也遇到了严重的问题。然后我在 Gerard Meszaros 的“xUnit 测试模式”中找到了一个非常酷的解决方案。他描述的模式称为Humble object

Basically it describes how you can extract the logic into a separate, easy-to-test component that is decoupled from its environment. After you tested this logic, you can test the complicated behaviour (multi- threading, asynchronous execution, etc...)

基本上它描述了如何将逻辑提取到一个独立的、易于测试的组件中,该组件与其环境分离。测试完这个逻辑后,你可以测试复杂的行为(多线程、异步执行等...)

回答by xagyg

There are a few tools around that are quite good. Here is a summary of some of the Java ones.

周围有一些工具非常好。下面是一些 Java 的总结。

Some good static analysis tools include FindBugs(gives some useful hints), JLint, Java Pathfinder(JPF & JPF2), and Bogor.

一些好的静态分析工具包括FindBugs(提供一些有用的提示)、JLintJava Pathfinder(JPF 和 JPF2)和Bogor

MultithreadedTCis quite a good dynamic analysis tool (integrated into JUnit) where you have to set up your own test cases.

MultithreadedTC是一个非常好的动态分析工具(集成到 JUnit 中),您必须在其中设置自己的测试用例。

ConTestfrom IBM Research is interesting. It instruments your code by inserting all kinds of thread modifying behaviours (e.g. sleep & yield) to try to uncover bugs randomly.

IBM Research 的ConTest很有趣。它通过插入各种线程修改行为(例如睡眠和产量)来检测您的代码,以尝试随机发现错误。

SPINis a really cool tool for modelling your Java (and other) components, but you need to have some useful framework. It is hard to use as is, but extremely powerful if you know how to use it. Quite a few tools use SPIN underneath the hood.

SPIN是用于对 Java(和其他)组件建模的非常酷的工具,但是您需要有一些有用的框架。它很难按原样使用,但如果你知道如何使用它,它就会非常强大。相当多的工具在引擎盖下使用 SPIN。

MultithreadedTC is probably the most mainstream, but some of the static analysis tools listed above are definitely worth looking at.

MultithreadedTC 可能是最主流的,但是上面列出的一些静态分析工具绝对值得一看。

回答by Johan

Awaitilitycan also be useful to help you write deterministic unit tests. It allows you to wait until some state somewhere in your system is updated. For example:

Awaitility也可用于帮助您编写确定性单元测试。它允许您等到系统中某处的某个状态更新。例如:

await().untilCall( to(myService).myMethod(), greaterThan(3) );

or

或者

await().atMost(5,SECONDS).until(fieldIn(myObject).ofType(int.class), equalTo(1));

It also has Scala and Groovy support.

它还支持 Scala 和 Groovy。

await until { something() > 4 } // Scala example

回答by Robert Gould

Another way to (kinda) test threaded code, and very complex systems in general is through Fuzz Testing. It's not great, and it won't find everything, but its likely to be useful and its simple to do.

另一种(有点)测试线程代码和非常复杂的系统的方法是通过模糊测试。它不是很好,它不会找到所有东西,但它可能很有用并且很容易做到。

Quote:

引用:

Fuzz testing or fuzzing is a software testing technique that provides random data("fuzz") to the inputs of a program. If the program fails (for example, by crashing, or by failing built-in code assertions), the defects can be noted. The great advantage of fuzz testing is that the test design is extremely simple, and free of preconceptions about system behavior.

...

Fuzz testing is often used in large software development projects that employ black box testing. These projects usually have a budget to develop test tools, and fuzz testing is one of the techniques which offers a high benefit to cost ratio.

...

However, fuzz testing is not a substitute for exhaustive testing or formal methods: it can only provide a random sample of the system's behavior, and in many cases passing a fuzz test may only demonstrate that a piece of software handles exceptions without crashing, rather than behaving correctly. Thus, fuzz testing can only be regarded as a bug-finding tool rather than an assurance of quality.

模糊测试或模糊测试是一种软件测试技术,它为程序的输入提供随机数据(“模糊”)。如果程序失败(例如,崩溃或内置代码断言失败),则可以指出缺陷。模糊测试的巨大优势在于测试设计极其简单,并且没有对系统行为的先入之见。

...

模糊测试通常用于采用黑盒测试的大型软件开发项目。这些项目通常有开发测试工具的预算,而模糊测试是提供高收益成本比的技术之一。

...

但是,模糊测试并不能替代详尽测试或形式化方法:它只能提供系统行为的随机样本,并且在很多情况下通过模糊测试可能只能证明某个软件处理异常而不会崩溃,而不是行为正确。因此,模糊测试只能被视为一种错误发现工具,而不是质量的保证。

回答by Kevin Wong

I've done a lot of this, and yes it sucks.

我已经做了很多这样的事情,是的,它很糟糕。

Some tips:

一些技巧:

  • GroboUtilsfor running multiple test threads
  • alphaWorks ConTestto instrument classes to cause interleavings to vary between iterations
  • Create a throwablefield and check it in tearDown(see Listing 1). If you catch a bad exception in another thread, just assign it to throwable.
  • I created the utils class in Listing 2 and have found it invaluable, especially waitForVerify and waitForCondition, which will greatly increase the performance of your tests.
  • Make good use of AtomicBooleanin your tests. It is thread safe, and you'll often need a final reference type to store values from callback classes and suchlike. See example in Listing 3.
  • Make sure to always give your test a timeout (e.g., @Test(timeout=60*1000)), as concurrency tests can sometimes hang forever when they're broken.
  • 用于运行多个测试线程的GroboUtils
  • alphaWorks ConTest到仪器类以导致交错在迭代之间变化
  • 创建一个throwable字段并将其签入tearDown(参见清单 1)。如果您在另一个线程中捕获了一个错误的异常,只需将其分配给 throwable。
  • 我在清单 2 中创建了 utils 类,并发现它非常宝贵,尤其是 waitForVerify 和 waitForCondition,它们将大大提高测试的性能。
  • AtomicBoolean在你的测试中好好利用。它是线程安全的,您通常需要一个最终引用类型来存储来自回调类等的值。请参见清单 3 中的示例。
  • 确保总是给你的测试一个超时(例如,@Test(timeout=60*1000)),因为并发测试有时会在它们被破坏时永远挂起。

Listing 1:

清单 1:

@After
public void tearDown() {
    if ( throwable != null )
        throw throwable;
}

Listing 2:

清单 2:

import static org.junit.Assert.fail;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Random;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.time.StopWatch;
import org.easymock.EasyMock;
import org.easymock.classextension.internal.ClassExtensionHelper;
import static org.easymock.classextension.EasyMock.*;

import ca.digitalrapids.io.DRFileUtils;

/**
 * Various utilities for testing
 */
public abstract class DRTestUtils
{
    static private Random random = new Random();

/** Calls {@link #waitForCondition(Integer, Integer, Predicate, String)} with
 * default max wait and check period values.
 */
static public void waitForCondition(Predicate predicate, String errorMessage) 
    throws Throwable
{
    waitForCondition(null, null, predicate, errorMessage);
}

/** Blocks until a condition is true, throwing an {@link AssertionError} if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param errorMessage message use in the {@link AssertionError}
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, String errorMessage) throws Throwable 
{
    waitForCondition(maxWait_ms, checkPeriod_ms, predicate, new Closure() {
        public void execute(Object errorMessage)
        {
            fail((String)errorMessage);
        }
    }, errorMessage);
}

/** Blocks until a condition is true, running a closure if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param closure closure to run
 * @param argument argument for closure
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, Closure closure, Object argument) throws Throwable 
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    if ( checkPeriod_ms == null )
        checkPeriod_ms = 100;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    while ( !predicate.evaluate(null) ) {
        Thread.sleep(checkPeriod_ms);
        if ( stopWatch.getTime() > maxWait_ms ) {
            closure.execute(argument);
        }
    }
}

/** Calls {@link #waitForVerify(Integer, Object)} with <code>null</code>
 * for {@code maxWait_ms}
 */
static public void waitForVerify(Object easyMockProxy)
    throws Throwable
{
    waitForVerify(null, easyMockProxy);
}

/** Repeatedly calls {@link EasyMock#verify(Object[])} until it succeeds, or a
 * max wait time has elapsed.
 * @param maxWait_ms Max wait time. <code>null</code> defaults to 30s.
 * @param easyMockProxy Proxy to call verify on
 * @throws Throwable
 */
static public void waitForVerify(Integer maxWait_ms, Object easyMockProxy)
    throws Throwable
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for(;;) {
        try
        {
            verify(easyMockProxy);
            break;
        }
        catch (AssertionError e)
        {
            if ( stopWatch.getTime() > maxWait_ms )
                throw e;
            Thread.sleep(100);
        }
    }
}

/** Returns a path to a directory in the temp dir with the name of the given
 * class. This is useful for temporary test files.
 * @param aClass test class for which to create dir
 * @return the path
 */
static public String getTestDirPathForTestClass(Object object) 
{

    String filename = object instanceof Class ? 
        ((Class)object).getName() :
        object.getClass().getName();
    return DRFileUtils.getTempDir() + File.separator + 
        filename;
}

static public byte[] createRandomByteArray(int bytesLength)
{
    byte[] sourceBytes = new byte[bytesLength];
    random.nextBytes(sourceBytes);
    return sourceBytes;
}

/** Returns <code>true</code> if the given object is an EasyMock mock object 
 */
static public boolean isEasyMockMock(Object object) {
    try {
        InvocationHandler invocationHandler = Proxy
                .getInvocationHandler(object);
        return invocationHandler.getClass().getName().contains("easymock");
    } catch (IllegalArgumentException e) {
        return false;
    }
}
}

Listing 3:

清单 3:

@Test
public void testSomething() {
    final AtomicBoolean called = new AtomicBoolean(false);
    subject.setCallback(new SomeCallback() {
        public void callback(Object arg) {
            // check arg here
            called.set(true);
        }
    });
    subject.run();
    assertTrue(called.get());
}

回答by bennidi

Testing MT code for correctness is, as already stated, quite a hard problem. In the end it boils down to ensuring that there are no incorrectly synchronised data races in your code. The problem with this is that there are infinitely many possibilities of thread execution (interleavings) over which you do not have much control (be sure to read thisarticle, though). In simple scenarios it might be possible to actually prove correctness by reasoning but this is usually not the case. Especially if you want to avoid/minimize synchronization and not go for the most obvious/easiest synchronization option.

如前所述,测试 MT 代码的正确性是一个相当困难的问题。最后,它归结为确保您的代码中没有不正确同步的数据竞争。这里的问题是,有线程执行(的交错),而且您没有太多的控制(请务必仔细阅读的无限多的可能性文章,虽然)。在简单的场景中,可能可以通过推理来实际证明正确性,但通常情况并非如此。特别是如果您想避免/最小化同步而不是选择最明显/最简单的同步选项。

An approach that I follow is to write highly concurrent test code in order to make potentially undetected data races likely to occur. And then I run those tests for some time :) I once stumbled upon a talk where some computer scientist where showing off a tool that kind of does this (randomly devising test from specs and then running them wildly, concurrently, checking for the defined invariants to be broken).

我遵循的一种方法是编写高度并发的测试代码,以便可能发生潜在的未被检测到的数据竞争。然后我运行这些测试一段时间 :) 我曾经偶然发现一个演讲,一些计算机科学家在那里展示了一种可以执行此操作的工具(从规范中随机设计测试,然后同时疯狂地运行它们,检查定义的不变量被打破)。

By the way, I think this aspect of testing MT code has not been mentioned here: identify invariants of the code that you can check for randomly. Unfortunately, finding those invariants is quite a hard problem, too. Also they might not hold all the time during execution, so you have to find/enforce executions points where you can expect them to be true. Bringing the code execution to such a state is also a hard problem (and might itself incur concurrency issues. Whew, it's damn hard!

顺便说一句,我认为这里没有提到测试 MT 代码的这一方面:识别可以随机检查的代码的不变量。不幸的是,找到这些不变量也是一个相当困难的问题。此外,它们在执行过程中可能不会一直保持,因此您必须找到/强制执行可以预期它们为真的执行点。将代码执行到这样的状态也是一个难题(并且本身可能会导致并发问题。哇,这太难了!

Some interesting links to read:

一些有趣的链接阅读: