我們希望采用并行的方式在本地運行單元測試,從而減少測試時間,提高開發人員的工作效率。我們使用了線程池來提供多線程的并行任務。通過配置啟動多個線程,并以程序集為單位,啟動TestRunner:
var executorWrapper=newExcetorWrapper(assemblyName,null,false);
var testRunner=newTestRunner(executorWrapper,newRunnerLoggerWrapper());
testRunner.RunAssembly();
其中的RunnerLoggerWrapper是一個自定義的類,實現了Xunit的IRunnerLogger。XUnit的使用并非本文描述的內容,在此略過。
因為是以程序集為單位,所以我們在啟動多線程之前,會事先將需要運行的程序集放到一個隊列中,然后在啟動多線程之后,執行出隊列操作。多線程的運行代碼如下所示:
privatestaticManualResetEvent[] resetEvents;
privatestaticQueueassemblyQue;
privatestaticreadonlyObject LockAssembly2Queue=newObject();
publicvoidRun()
{
for(var index=0; 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.
在我們的單元測試中,大多數測試需要訪問的資源都是在內存中進行,但有一部分單元測試必須與數據庫通信,對數據表進行讀寫。除了極少數特殊的測試用例外,對數據表的操作都放在事務中進行,并在執行完畢后,通過回滾事務,避免對真實數據的提交,保證單元測試不會影響數據庫。
注:單元測試應該訪問數據庫嗎?這其實還有待確認。在《修改代碼的藝術》一書中,Feathers這樣寫道:
單元測試運行得快。運行得不快的不是單元測試。
有些測試容易跟單元測試混淆起來。譬如下面這些測試就不是單元測試:
(1)跟數據庫有交互;
(2)進行了網絡間通信;
(3)調用了文件系統;
(4)需要你對環境作特定的準備(如編輯配置文件)才能運行的。
以上可以看到Feathers的態度是單元測試不應與外部資源進行交互。顯然,如果出現了這些交互,就應該采用Mock的方式來模擬對外部資源的訪問。然而,某些實現功能卻是與外部資源息息相關,又或者我們測試的目的本身就是驗證對外部資源的訪問是否正確。從測試的范圍來看,它們仍然算是單元測試,但因其特殊性,而應該將這些測試放到系統測試的范疇。在持續集成中,我們常常用金字塔來表示單元測試、系統測試和集成測試的數量。如下圖所示:
單元測試的數量最多,如果還需要訪問外部資源,就會嚴重影響運行單元測試的速度。關于單元測試、Mock等內容,我希望在以后的文章里詳細論述。
原文轉自:http://www.uml.org.cn/Test/201204112.asp