我们希望采用并行的方式在本地运行单元测试,从而减少测试时间,提高开发人员的工作效率。我们使用了线程池来提供多线程的并行任务。通过配置启动多个线程,并以程序集为单位,启动TestRunner:
var executorWrapper=newExcetorWrapper(assemblyName,null,false); var testRunner=newTestRunner(executorWrapper,newRunnerLoggerWrapper()); testRunner.RunAssembly(); |
其中的RunnerLoggerWrapper是一个自定义的类,实现了Xunit的IRunnerLogger。XUnit的使用并非本文描述的内容,在此略过。
因为是以程序集为单位,所以我们在启动多线程之前,会事先将需要运行的程序集放到一个队列中,然后在启动多线程之后,执行出队列操作。多线程的运行代码如下所示:
privatestaticManualResetEvent[] resetEvents; privatestaticQueue<String>assemblyQue; privatestaticreadonlyObject LockAssembly2Queue=newObject(); publicvoidRun() { for(var index=0; index<numThreads; index++) { resetEvents[index]=newManualResetEvent(false); ThreadPool.QueueUserWorkItem(DoWork, index); } WaitForAllManualEvent(); } privatevoidWaitForAllManualEvent() { if(Thread.CurrentThread.ApartmentState=ApartmentState.STA) { foreach(var manualResetEventinresetEvents) { WaitHandle.WaitAny(newWaitHandle[]{manualResetEvent}); } } else { WaitHandle.WaitAll(resetEvents); } } privatestaticvoidDoWork(Object index) { Thread.CurrentThread.ApartmentState=ApartmentState.STA; while(true) { stringcurrentAssemblyName=null; lock(LockAssembly2Queue) { if(assemblyQue.Count!=0) { currentAssemblyName=assemblyQue.Dequeue(); } else { resetEvents[(int)index].Set(); Console.WriteLine("Exited current thread:{0}", Thread.CurrentThread.Name); break; } } if(currentAssemblyName!=null) { newTestRunnerWrapperWithAssembly(currentAssemblyName).Runner(); } } } |
由于要测试的程序集比较多,采用这种并行方式可以极大地提高运行效率。由于单元测试彼此是独立的,在并行运行时,互相没有干扰。这是我们实现判断的结果。一切看起来很美好,但在真正运行时,却出现了大量的死锁。异常信息为:
Transaction (Process ID) was deadlocked on resources with another process and has been chosen as the deadlock victim. Rerun the transaction.