標籤:
本文主要介紹Visual Studio(2012+)單元測試架構的一些技巧:
- 如何類比類的靜態建構函式
- 如何測試某方法被調用過
- 如何測試某方法執行的次數
- 並行編程測試注意事項
一、如何類比類的靜態建構函式1.1 被測代碼
namespace BlogDemo.UTDemo.Tricks{ public class StaticConstructorExample { static StaticConstructorExample() { //step1:read data from web service //step2:save data into database } public int DoSomething() { return 0; } }}
上面的類有一個靜態建構函式,裡面做兩件事情:
- 從某webservice讀取資料
- 將讀取到的資料儲存到資料庫。
以上兩件事情都是對外界環境的嚴重依賴
我們需要測試的方法是DoSomething,需要測試這個類,就需要執行個體化StaticConstructorExample這個類,執行個體化這個類之前,其靜態建構函式將會被自動執行:這個點就是問題。
- 靜態建構函式依賴外部環境,這個外部環境有可能在某一天就掛了
- 如果外部環境掛了,靜態建構函式就會報錯,那我們的單元測試就無法按照預期進行運行
- DoSOmething本身不直接依賴外部環境,但是通過靜態建構函式間接依賴了外部環境,這違反了單元測試repeatable原則(當外部環境掛了,我們的測試就無法運行)
解決辦法依然是Shim,通過ms.test的Shim可以類比一個類的靜態建構函式,從而改變靜態建構函式的行為,消除外部依賴。
1.2 測試代碼
[TestClass] public class StaticConstructorExampleTests { [TestMethod] public void DoSomethingTest() { using (ShimsContext.Create()) { var isStaticConstructorExecuted = false; ShimStaticConstructorExample.StaticConstructor = () => { //step1:mock web service //step2:mock database isStaticConstructorExecuted = true; }; var example = new StaticConstructorExample(); var data = example.DoSomething(); Assert.AreEqual(0, data); Assert.IsTrue(isStaticConstructorExecuted); } } }
上面的代碼是通過ShimClassName.StaticConstructor來實現對靜態建構函式進行類比的。
二、如何測試某方法被調用過2.1 被測代碼
基礎代碼,在DemoClass中進行調用
public interface IGoodActionHandler { void Action(); } public interface IBadActionHandler { void Action(); } public class GoodHandler : IGoodActionHandler { public void Action() { throw new NotImplementedException(); } } public class BadHandler : IBadActionHandler { public void Action() { throw new NotImplementedException(); } }
要被測試的類:
public class DemoClass { IGoodActionHandler goodHandler; IBadActionHandler badHandler; public DemoClass() { this.goodHandler = new GoodHandler(); this.badHandler = new BadHandler(); } public void DoSomething(int type) { if (type == 1) { DoSomethingGood(); } else { DoSomethingBad(); } } private void DoSomethingGood() { this.goodHandler.Action(); } private void DoSomethingBad() { this.badHandler.Action(); } }
要測試上面的DoSomething方法,上面DoSomethingGood和DoSomethingBad都依賴自介面,在測試的時候都可以進行mock(stub)。在側測DoSomething方法是只需要驗證當Type為1時執行了goodHandler的action,否則執行badHandler的Action。
這也是單元測試的一個關鍵點:關注單元。這裡不關注goodhandler 和badhandler的內部邏輯(這兩個handler的內部邏輯可以單獨測試,屬於另外的單元),這裡只關注是否按照邏輯路由到了正確的handler。
2.2測試代碼
[TestClass] public class DemoClassTests { [TestMethod] public void DoSomething_DoSomethingGood_Tests() { var goodHandlerExecuted = false; StubIGoodActionHandler goodHandler = new StubIGoodActionHandler() { Action = () => { goodHandlerExecuted = true;//如果執行了goodhandler則置為true } }; var badHandlerExecuted = false; StubIBadActionHandler badHandler = new StubIBadActionHandler() { Action = () => { badHandlerExecuted = true;//如果執行了badhandler則置為true } }; var demoClass=new DemoClass(); demoClass.BadHandler=badHandler;//注入badhandler demoClass.GoodHandler=goodHandler;//注入goodhandler demoClass.DoSomething(1); Assert.IsTrue(goodHandlerExecuted);//執行了goodhandler Assert.IsFalse(badHandlerExecuted);//沒有執行badhandler } }
上面使用了兩個技術
- 面向介面編程,使用Stub技術,動態注入Stub(打樁),在測試的時候對對象進行了類比
- 改變了Stub對象的行為,本文只是通過在Stub對象的內部設定標誌位的值來表示是否執行了這一個步驟。
上面的標誌位的方法Shim技術照樣使用,靜態建構函式的類比就是通過Shim後在建構函式內部修改標識位來驗證建構函式被執行的。接下來介紹的測試執行次數也是通過shim後修改標識位來實現的
三、如何測試某方法執行的次數3.1 被測代碼
public class DemoClass { private LoopHandler handler; public DemoClass() { handler = new LoopHandler(); } public void DoSomething(int times) { for (var i = 0; i < times; i++) { handler.Do(); } } } public class LoopHandler { public void Do() { } }
現在要驗證DoSomething是否執行了times次Do方法。
3.2 測試代碼
[TestMethod] public void DoSomething_RunTimes_Test() { using(ShimsContext.Create()) { var times = 0; ShimLoopHandler.AllInstances.Do = (@this) => { times++;//每進入一次加一次 }; var givenTimes = 100; new DemoClass().DoSomething(givenTimes); Assert.AreEqual(times, givenTimes); } }
思想和上面Stub是一樣,在Shim類比的方法內部進行計數器相加。
3.3 並行編程測試注意事項
這裡有一點需要注意一下,因為int的++不是安全執行緒的,如果把DemoClass的迴圈修改成多個線程並存執行的話測試代碼需要做相應的調整。,對times++進行lock。
public void DoSomething(int times) { Parallel.For(0, times, (item) =>//並存執行 { handler.Do(); }); } [TestMethod] public void DoSomething_RunTimes_Test() { using(ShimsContext.Create()) { var times = 0; ShimLoopHandler.AllInstances.Do = (@this) => { lock (this)//lock安全執行緒 { times++;//每進入一次加一次 } }; var givenTimes = 100; new DemoClass().DoSomething(givenTimes); Assert.AreEqual(times, givenTimes); } }
上面的代碼當Times不是特別大的時候一般不加lock也是可以的,不是特別大的時候一般不會產生安全執行緒問題。但是當times比較大的時候不加lock就會出問題。
當times是100000時去掉lock。我運行就出現了問題:
測試就是為了嚴謹,如果有上面並行的問題,測試的時候還是加上lock比較好。
Unit Test Via Visual Studio-Part5