到目前为止,我已经避免了测试多线程代码的噩梦,因为它似乎是一个太大的雷区。我想知道人们是如何测试依赖于线程的代码以获得成功执行的,或者人们是如何测试那些仅在两个线程以给定方式交互时才会出现的问题的?
对于今天的程序员来说,这似乎是一个非常关键的问题,恕我直言,将我们的知识集中在这个问题上是很有用的。
到目前为止,我已经避免了测试多线程代码的噩梦,因为它似乎是一个太大的雷区。我想知道人们是如何测试依赖于线程的代码以获得成功执行的,或者人们是如何测试那些仅在两个线程以给定方式交互时才会出现的问题的?
对于今天的程序员来说,这似乎是一个非常关键的问题,恕我直言,将我们的知识集中在这个问题上是很有用的。
当前回答
这个问题发布已经有一段时间了,但仍然没有答案…
Kleolb02的答案很好。我会试着讲得更详细一些。
有一种方法,我在c#代码中练习过。对于单元测试,您应该能够编写可重复的测试,这是多线程代码中的最大挑战。因此,我的回答旨在将异步代码强制到同步工作的测试装置中。
这是Gerard Meszaros的书“xUnit测试模式”中的一个想法,被称为“Humble Object”(第695页):必须将核心逻辑代码和任何闻起来像异步代码的东西分开。这将产生一个用于核心逻辑的类,它同步地工作。
这将使您能够以同步方式测试核心逻辑代码。您可以绝对控制对核心逻辑进行调用的时间,因此可以进行可重复的测试。这就是分离核心逻辑和异步逻辑的好处。
这个核心逻辑需要由另一个类来包装,这个类负责异步接收对核心逻辑的调用,并将这些调用委托给核心逻辑。产品代码将只通过该类访问核心逻辑。因为这个类应该只委托调用,所以它是一个没有太多逻辑的非常“愚蠢”的类。因此,您可以将这个异步工作类的单元测试保持在最小值。
在此之上的任何测试(测试类之间的交互)都是组件测试。同样在这种情况下,如果你坚持使用“Humble Object”模式,你应该能够完全控制时间。
其他回答
并发是内存模型、硬件、缓存和代码之间复杂的相互作用。在Java的情况下,至少这样的测试主要由jcstress部分解决。众所周知,该库的创建者是许多JVM、GC和Java并发特性的作者。
但是即使是这个库也需要对Java内存模型规范有很好的了解,这样我们才能确切地知道我们在测试什么。但我认为这项工作的重点是微基准测试。不是庞大的业务应用。
运行多个线程并不困难;这是小菜一碟。不幸的是,线程通常需要彼此通信;这就是困难所在。
最初发明的允许模块之间通信的机制是函数调用;当模块A想要与模块B通信时,它只调用模块B中的一个函数。不幸的是,这对线程不起作用,因为当你调用一个函数时,该函数仍然运行在当前线程中。
为了克服这个问题,人们决定采用一种更原始的通信机制:只声明一个特定的变量,并让两个线程都可以访问该变量。换句话说,允许线程共享数据。分享数据是人们自然而然想到的第一件事,这似乎是一个不错的选择,因为它看起来非常简单。我是说,能有多难,对吧?会出什么问题呢?
竞态条件。这就是可能、也将会出错的地方。
当人们意识到他们的软件由于竞争条件而遭受随机的、不可复制的灾难性失败时,他们开始发明复杂的机制,如锁和比较-交换,旨在防止此类事情的发生。这些机制属于广义的“同步”范畴。不幸的是,同步有两个问题:
这是很难做到的,所以很容易出现bug。 它是完全不可测试的,因为您无法测试竞态条件。
精明的读者可能会注意到“非常容易出现bug”和“完全不可测试”是一个致命的组合。
现在,在自动化软件测试的概念变得流行之前,我上面提到的机制已经被行业的大部分人发明和采用了;所以,没有人知道这个问题有多致命;他们只是认为这是一个很难的主题,需要高手程序员,每个人都能接受。
如今,无论我们做什么,我们都把测试放在第一位。所以,如果某些机制是不可测试的,那么使用该机制就是不可能的。因此,同步已经失宠;现在还在练的人已经很少了,而且练的人一天比一天少。
没有同步线程就不能共享数据;然而,最初的要求不是共享数据;它允许线程之间进行通信。除了共享数据之外,还存在其他更优雅的线程间通信机制。
其中一种机制是消息传递,也称为事件。
对于消息传递,整个软件系统中只有一个地方利用了同步,那就是我们用来存储消息的并发阻塞队列收集类。(我们的想法是,我们应该至少能把那一小部分做对。)
消息传递的优点是它不受竞态条件的影响,并且是完全可测试的。
(如果可能的话)不要使用线程,使用actor /活动对象。易于测试。
有一些很好的工具。下面是一些Java的摘要。
一些好的静态分析工具包括FindBugs(提供了一些有用的提示)、JLint、Java Pathfinder (JPF & JPF2)和Bogor。
multithreaddtc是一个非常好的动态分析工具(集成到JUnit中),您必须在其中设置自己的测试用例。
IBM研究院的竞赛很有趣。它通过插入各种线程修改行为(例如sleep & yield)来检测你的代码,试图随机发现错误。
SPIN是对Java(和其他)组件建模的一个非常酷的工具,但是您需要一些有用的框架。它很难使用,但如果你知道如何使用它,它是非常强大的。相当多的工具在底层使用SPIN。
multithreaddtc可能是最主流的,但是上面列出的一些静态分析工具绝对值得一看。
我在测试多线程代码时也遇到了严重的问题。然后我在Gerard Meszaros的“xUnit测试模式”中找到了一个非常酷的解决方案。他描述的模式被称为Humble object。
基本上,它描述了如何将逻辑提取到独立的、易于测试的组件中,该组件与环境解耦。在你测试了这个逻辑之后,你可以测试复杂的行为(多线程,异步执行,等等…)