有没有关于一致性地对多线程应用进行单元测试的建议?我曾经做过一个应用程序,其中我们的模拟“工作线程”使用了Thread.Sleep方法,并指定了一个公共成员变量来设置每个线程执行其工作所需的时间。我们使用这种方法来设置特定线程完成其工作所需的时间,然后进行断言。还有更好的方法吗?有没有适用于.Net的良好模拟框架可以处理这个问题?
有没有关于一致性地对多线程应用进行单元测试的建议?我曾经做过一个应用程序,其中我们的模拟“工作线程”使用了Thread.Sleep方法,并指定了一个公共成员变量来设置每个线程执行其工作所需的时间。我们使用这种方法来设置特定线程完成其工作所需的时间,然后进行断言。还有更好的方法吗?有没有适用于.Net的良好模拟框架可以处理这个问题?
第一步是认识到通常大部分需要进行单元测试的代码与线程无关。这意味着你应该将负责工作的代码和负责线程的代码分开。完成此操作后,您可以使用常规的单元测试实践轻松测试执行工作的代码。但是,当然,您已经知道了这一点。
问题在于测试问题的线程部分,但至少现在您有一个接口与处理工作的代码相连,并且希望您在那里有一个可以模拟的接口。现在您对于调用线程代码的代码已经有了一个模拟,我发现最好的做法是向模拟添加一些事件(这可能意味着您需要手动编写模拟)。事件将用于允许测试与正在测试的线程代码同步和阻止。
例如,假设我们有一个非常简单的多线程队列来处理工作项。您将对工作项进行模拟。接口可能包含一个“Process()”方法,该线程调用该方法来执行工作。您会在其中放置两个事件。当调用Process()时,模拟设置一个事件;在设置第一个事件后,模拟等待另一个事件。现在,在您的测试中,您可以启动队列,发布一个模拟工作项,然后等待工作项的“正在被处理”事件。如果您只测试调用过程是否正确,那么可以设置另一个事件并让线程继续。如果您正在测试其他更复杂的内容,例如队列如何处理多个调度等,则可能在释放线程之前执行其他操作(例如发布和等待其他工作项)。由于您可以在测试中使用超时进行等待,因此可以确保只有两个工作项以并行方式进行处理。关键是使用线程代码块的事件使测试变得确定性,以便测试可以控制它们运行的时间。
我相信您的情况更为复杂,但这是我用来测试并发代码的基本方法,它效果非常好。如果您模拟了正确的部分并放置了同步点,那么您可以对多线程代码获得惊人的控制力。
以下是关于这种事情的更多信息,尽管它是关于C++代码库的: http://www.lenholgate.com/blog/2004/05/practical-testing.html
我的建议是不要依赖单元测试来检测并发问题,原因如下:
如果你需要测试一个后台线程是否执行了某个操作,我通常会使用一种简单的技术,即编写一个名为 WaitUntilTrue 的方法,大致如下:
bool WaitUntilTrue(Func<bool> func,
int timeoutInMillis,
int timeBetweenChecksMillis)
{
Stopwatch stopwatch = Stopwatch.StartNew();
while(stopwatch.ElapsedMilliseconds < timeoutInMillis)
{
if (func())
return true;
Thread.Sleep(timeBetweenChecksMillis);
}
return false;
}
用法如下:
volatile bool backgroundThreadHasFinished = false;
//run your multithreaded test and make sure the thread sets the above variable.
Assert.IsTrue(WaitUntilTrue(x => backgroundThreadHasFinished, 1000, 10));
这样,您就不必让主要的测试线程长时间休眠以便让后台线程有足够的时间来完成工作。如果后台线程在合理的时间内没有完成工作,则测试将失败。
TypeMock(商业产品)拥有一个单元测试框架,可以自动尝试查找多线程应用程序中的死锁,并且我认为可以设置以运行具有可预测上下文切换的线程。
我本周在展会上看到了演示--显然它处于Alpha版(称为Racer)。
http://www.typemock.com/Typemock_software_development_tools.html
我将multithreadedTC库从Java移植到.NET,并将其命名为TickingTest。它允许您从单元测试方法启动多个线程并协调它们。它没有原始库的所有功能,但我发现它很有用。最大的缺陷是无法监视在测试期间启动的线程的能力。
在多处理器机器上测试多线程代码非常重要。双核机器可能不足够。我曾经看到在双核单处理器上没有发生的死锁在4个处理器的机器上发生了。然后,您需要创建一个基于客户端程序的压力测试,该程序生成许多线程并对目标应用程序进行多个请求。如果客户端机器也是多处理器的,则有更多的负载会落在目标应用程序上。
虽然不完全是单元测试,但您可以编写一些测试代码,反复调用将在不同线程上执行的代码。尝试在线程之间创建最大交错,并进行周期性或最终一致性检查。当然,这种方法的缺点是无法重现,因此您需要使用广泛的日志记录来找出问题所在。最好将此方法与每个线程各自任务的单元测试相结合。
像GUI单元测试一样,这对于自动化测试来说是一个难点。真正的线程本质上是不可预测的,它们会以无法预先确定的方式干扰。因此,编写真正的测试是困难的,如果不是不可能的话。
然而,你并不孤单...我建议搜索一下testdrivendevelopment Yahoo群组的存档。我记得有一些相关的帖子.. 这是其中较新的一个。 (如果有人能够友好地进行概述和解释,那就太好了。我太困了...需要休息)