经典单元测试基本上只是将x放入并期望y out,并使该过程自动化。因此,测试不涉及时间的任何内容都是有益的。但是,我遇到的大多数非常重要的错误都与时间有关。线程破坏彼此的数据,或导致死锁。不确定的行为发生了 - 一万分之一。很难的东西。
对于多线程并发系统的“单元测试”部分,有什么有用的东西吗?这些测试如何运作?是不是有必要长时间运行这种测试的主题,并以一种聪明的方式改变环境,以合理地确信它能正常工作?
答案 0 :(得分:12)
这些天我做的大部分工作都涉及多线程和/或分布式系统。大多数错误涉及“发生之前”类型错误,其中开发人员假定(错误地)事件A将始终发生在事件B之前。但是每运行一次程序,第一次发生事件B ,这会导致不可预测的行为。
此外,没有任何好的工具可以检测时间问题,甚至是因竞争条件导致的数据损坏。像Valgrind工具包中的Helgrind和drd这样的工具非常适合于琐碎的程序,但它们在诊断大型复杂系统时并不是很有用。首先,他们经常报告误报(特别是Helgrind)。另一件事,就很难真正检测某些错误,同时下Helgrind / DRD运行,只是因为Helgrind下运行的程序的运行几乎1000倍的速度较慢,而且你经常需要相当长的时间,即使运行一个程序的重现比赛条件。此外,由于在Helgrind下运行完全改变了程序的时间,因此可能不可能重现某个时序问题。这是微妙的时间问题;他们几乎是海森堡人,因为改变一个程序以检测时间问题可能会掩盖原始问题。
可悲的事实是,人类仍然没有充分准备好处理复杂的并发软件。不幸的是,没有简单的方法对它进行单元测试。特别是对于分布式系统,您应该使用Lamport's happens-before diagrams仔细规划您的程序,以帮助您确定程序中必要的事件顺序。但最终,你无法真正摆脱随机变化输入的暴力单元测试。它还有助于在单元测试期间改变线程上下文切换的频率,例如:运行另一个占用CPU周期的后台进程。此外,如果您可以访问群集,则可以并行运行多个单元测试,这可以更快地检测错误并为您节省大量时间。
答案 1 :(得分:5)
如果你可以在Linux下运行测试,valgrind包含一个名为helgrind的工具,它旨在检测使用pthreads的程序中的竞争条件和潜在的死锁;你可能会从运行多线程代码中获得一些好处,因为它会报告潜在的错误,即使它们在特定的测试运行中没有实际发生。
答案 2 :(得分:4)
我从未听说过任何可以做到的事情。
我想如果有人要设计一个,那就必须对线程的执行有精确的控制,并执行所有可能的线程步进组合。
听起来像是一项重大任务,更不用说在有一小部分或更多线程的情况下,非平凡大小线程的数学组合......
尽管如此,快速搜索stackoverflow ... Unit testing a multithreaded application?
答案 3 :(得分:3)
如果测试的系统足够简单,您可以通过阻止外部模型系统中的操作来很好地控制并发性。例如,可以通过等待一些其他操作开始来完成该阻塞。如果您可以控制所有外部调用,则可以通过实现不同的阻塞序列来很好地工作。我已经尝试了这个,如果你很清楚可能有问题的序列,它确实可以很好地揭示锁定级错误。与许多其他并发测试相比,它是非常确定的。然而,这种方法不能很好地检测低水平的竞争条件。我通常只是去进行负载测试以找到那些,但我认为这不完全是单元测试。
我已经看过.net的这些并发测试框架,我假设有人为Java写一个(希望如此)之前的时间问题。
不要忘记好的旧代码阅读。找到并发错误的最好方法之一就是再次阅读代码,让它完全集中。
答案 4 :(得分:1)
也许答案是你不应该。在并发系统中,可能并不总是有一个正确的确定性答案。
以登上火车和选择座位的人为例。每次你都会得到不同的结果。
答案 5 :(得分:1)
Awaitility是一个有用的框架。它允许您等到系统中的某个状态更新。例如:
await().untilCall( to(myService).myMethod(), equalTo(3) );
或
await().until( fieldIn(myObject).ofType(int.class), greaterThan(1));
它还支持Scala和Groovy。