在我們的項目中,是通過注入Fixture的形式生成測試數據。例如,我們可能希望注入Client、Associate等對象,從而完成對某些行為的測試。例如:
[Fixture(typeof(client_hastings))]
public Client client;
[Fixture(typeof(Samuel))]
public Associate Samuel;
通過Fixture準備數據時,如果采用了持久化方式,則意味著需要對數據表進行操作。如上代碼就可能操作多張表,例如對Client表和 Associate表進行寫操作。由于單元測試采用并行方式進行。假設存在兩個單元測試均需要對Client和Associate注入Fixture,生成測試數據;并且不幸的是,這兩個測試用例準備數據的順序剛好相反,即A測試用例的順序為Client->Associate,B測試用例的順序為 Associate->Client,就可能發生死鎖。
為什么?讓我們分析數據庫發生死鎖的情況。它必然是多個進程(或線程)對兩個或兩個以上的資源形成了交叉訪問。例如進程A在占有了資源1的同時,還需要訪問資源2;與此同時,進程B在占有了資源2的同時,需要訪問資源1。由于資源1已經被進程A占用,無法釋放,進程B就會等待;而進程A希望訪問的資源2又被等待中的進程B持有;二者互不相讓,最終產生死鎖。這正是并行運行單元測試導致死鎖的根本原因。我們可以運行SQL Server Profiler來監視數據庫的執行。注意,倘若需要跟蹤死鎖的情況,需要在Trace Properties中勾選“Deadlock Graph”和“Lock: Deallock”選項,如下圖所示:
創建Trace后,利用并行方式運行單元測試,可能得到這樣的Deadlock graph:
圖中,橢圓代表進程(線程),矩形代表資源。左邊的橢圓打了一把叉,說明是競爭失敗的進程(線程)。從橢圓出發,箭頭所指的資源,代表進程請求的資源;而發出箭頭的資源,則代表箭頭指向的進程持有該資源??梢园l現,兩個進程與兩個資源之間的箭頭,事實上形成了一個封閉的環。這正是死鎖的典型表現。
當我們將單元測試的Fixture注入順序保持一致時,這樣的死鎖就能夠避免了。這是一種限制,它很難被編寫單元測試的開發人員所接受,即使勉強接受,仍然很容易疏漏。因此,我們的結論仍然是“不到迫不得已,單元測試不要訪問外部資源”?;蛘哒f,我們可以將訪問外部資源的單元測試,看成是特殊的單元測試,如果確實需要并行運行測試,以提高測試效率,可以通過引入多個Agent,以物理方式隔離資源,避免出現資源的爭用導致死鎖。
那么,這是否意味著我們的產品代碼不夠嚴謹,沒有充分考慮并發的情況呢?不完全對。因為這里產生死鎖的時機發生在準備測試數據的階段,實際操作時,一般不會出現這種頻繁對多張表進行操作的情況。然而,即使幾率很低,始終存在死鎖的隱患,這就為我們的開發敲響了警鐘。因此在開發過程中,有必要通過對業務的分析,制訂一些指導原則,通過規范寫數據表操作的順序,避免出現死鎖。這是這次并行運行單元測試給我們帶來的啟示。
原文轉自:http://www.uml.org.cn/Test/201204112.asp