使用 ConTest 进行多线程单元测试
此试验的寓义是什么?设想 NamePrinter 以及它的同步协议是并行类。 您运行单元测试 -- 也许很多次 -- 并且它每次都运行得很好。自然地,您认为可以放心它是正确的。但是正如您刚才所看到的,在根本没有同步协议的情况下输出同样也是正确的,并且您可以安全地推断在有很多错误的协议实现的情况下输出也是正确的。因此,当您认为?已经测试了您的协议时, 您并没有真正地?测试它。
现在我们看一下另外的一个例子。
下面的类是一种常见的并行实用程序模型:任务队列。它有一个能使任务入队的方法和另外一个使任务出队的方法。在从队列中删除一个任务之前,work()
?方法进行检查以查看队列是否为空,如果为空则等待。enqueue()
?方法通知所有等待的线程(如果有的话)。 为了使此示例简单,目标仅仅是字符串,任务是把它们打印出来。再一次,main()
?充当单元测试。顺便说一下,此类有一个 bug。
ConTest 背后的基本原理是非常简单的。instrumentation?阶段转换类文件,注入挑选的用来调用 ConTest 运行时函数的位置。在运行时,ConTest 有时试图在这些位置引起上下文转换。 挑选的是线程的相对顺序很可能影响结果的那些位置:进入和退出 synchronized 块的位置、访问共享变量的位置等等。通过调用诸如?yield()
?或?sleep()
?方法来尝试上下文转换。决定是随机的以便在每次运行时尝试不同的交错。使用试探法试图显示典型的 bug。
注意 ConTest 不知道实际是否已经显示出 bug -- 它没有预期程序将如何运行的概念。是您,也就是用户应该进行测试并且应该知道哪个测试结果将被认为是正确的以及哪个测试结果表示 bug。ConTest 只是帮助显示出 bug。另一方面,没有错误警报:就 JVM 规则而言所有使用 ConTest 产生的交错都是合法的。
正如您看到的一样,通过多次运行同一个测试得到了多个值。实际上,我们推荐整个晚上都反复运行它。然后您就可以很自信地认为所有可能的交错都已经执行过了。