到目前为止,我已经避免了测试多线程代码的噩梦,因为它似乎太多了。我想问一下人们如何去测试依赖于线程的代码才能成功执行,或者人们如何去测试仅当两个线程以给定的方式交互时才会出现的那些类型的问题?
对于当今的程序员来说,这似乎是一个非常关键的问题,将我们的知识集中在这一恕我直言上将很有用。
看,没有简单的方法可以做到这一点。我正在开发一个本质上是多线程的项目。事件来自操作系统,我必须同时处理它们。
处理复杂的多线程应用程序代码的最简单方法是:如果太复杂而无法测试,则说明您做错了。如果您有一个实例,该实例具有作用于其上的多个线程,并且您无法测试这些线程彼此间步调一致的情况,则需要重新设计。这样既简单又复杂。
为多线程编程的方法有很多,可以避免线程同时在实例中运行。最简单的是使所有对象不可变。当然,通常是不可能的。因此,您必须确定设计中线程与同一实例交互的那些位置,并减少这些位置的数量。通过这样做,您隔离了实际发生多线程的几个类,从而降低了测试系统的总体复杂性。
但是您必须意识到,即使这样做,也仍然无法测试两个线程相互踩踏的所有情况。为此,您必须在同一测试中同时运行两个线程,然后精确控制它们在任何给定时刻执行的行。您能做的最好的就是模拟这种情况。但这可能需要您专门为测试编写代码,而这仅是迈向真正解决方案的一半。
测试代码中是否存在线程问题的最佳方法可能是对代码进行静态分析。如果您的线程代码没有遵循一组有限的线程安全模式,那么您可能会遇到问题。我相信 VS 中的代码分析确实包含一些线程知识,但可能不多。
看起来,就目前而言(可能会好时机),测试多线程应用程序的最佳方法是尽可能降低线程代码的复杂性。最小化线程交互的区域,尽可能地进行测试,并使用代码分析来识别危险区域。
提这个问题已经有一段时间了,但仍然没有答案...
kleolb02的答案很好。我将尝试详细介绍。
我为 C#代码实践了一种方法。对于单元测试,您应该能够编写可重现的测试,这是多线程代码中的最大挑战。因此,我的答案旨在将异步代码强制放入可同步工作的测试工具中。
这是 Gerard Meszardos 的书 “ xUnit Test Patterns ” 中的一个想法,被称为 “Humble Object”(第 695 页):您必须将核心逻辑代码与任何闻起来像异步代码的东西相互分离。这将导致核心逻辑类同步工作。
这使您能够以同步方式测试核心逻辑代码。您可以完全控制在核心逻辑上执行呼叫的时间,因此可以进行可重现的测试。这是分离核心逻辑和异步逻辑所获得的收益。
该核心逻辑需要由另一个类包装,该类负责异步接收对核心逻辑的调用,并将这些调用委托给核心逻辑。生产代码将仅通过该类访问核心逻辑。因为此类仅应委托调用,所以它是一个非常 “愚蠢” 的类,没有太多逻辑。因此,您可以使此异步工作类的单元测试最少。
除此之外(组件之间的测试交互),都是组件测试。同样在这种情况下,如果您坚持使用 “谦虚对象” 模式,则应该能够完全控制时序。
确实很强悍!在我的(C ++)单元测试中,按照使用的并发模式将其分为几类:
对在单个线程中运行并且不知道线程的类进行单元测试 - 像往常一样容易进行测试。
暴露同步的公共 API 的 Monitor 对象(在调用者的控制线程中执行同步方法的对象)的单元测试 - 实例化使用该 API 的多个模拟线程。构造适用于被动对象内部条件的方案。包括一个运行时间更长的测试,该测试基本上可以长时间消除来自多个线程的麻烦。我知道这是不科学的,但确实建立了信心。
Active 对象(封装了自己的一个或多个控制线程的对象)的单元测试 - 与上面的#2 相似,具体取决于类设计。公共 API 可能处于阻塞状态或非阻塞状态,调用者可能会获取期货,数据可能会到达队列或需要出队。这里有很多组合。白盒子走了。仍然需要多个模拟线程来对被测对象进行调用。
作为旁白:
在我进行的内部开发人员培训中,我讲授并发支柱和这两种模式作为思考和分解并发问题的主要框架。显然还有更高级的概念,但是我发现这套基础知识有助于使工程师远离困境。如上所述,它还会导致代码更可单元测试。