最近在看.net單元測(cè)試藝術(shù),我也喜歡單元測(cè)試,這里寫(xiě)一下如何在測(cè)試中使用模擬對(duì)象。
開(kāi)發(fā)的過(guò)程中,我們都會(huì)遇到對(duì)象間的依賴(lài),比如依賴(lài)數(shù)據(jù)庫(kù)或文件,這時(shí),我們需要使用模擬對(duì)象,來(lái)進(jìn)行測(cè)試,我們可以手寫(xiě)模擬對(duì)象,當(dāng)然也可以使用模擬框架。
假如有這樣的一個(gè)需求,當(dāng)用戶(hù)登陸時(shí),我需要對(duì)用戶(hù)名和密碼進(jìn)行驗(yàn)證,然后再將用戶(hù)名寫(xiě)入日志中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class MyLogin { public ILog Log { get ; set ; } { var isValid = userName == "admin" && passWord == "123456" ; Log.Write(userName); return isValid; } } public interface ILog { void Write( string message); } } |
上面的代碼在驗(yàn)證完登陸信息后,需要向日志中寫(xiě)入用戶(hù)名,由于寫(xiě)入日志可能依賴(lài)于文件或數(shù)據(jù)庫(kù),我們可能很難進(jìn)行測(cè)試,所以,這里使用模擬對(duì)象進(jìn)行測(cè)試。手寫(xiě)模擬對(duì)象,代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | [TestFixture] public class MyLoginTest { [Test] public void Vaild_Test() { MyLogin login = new MyLogin(); var log = new TestLog(); login.Log = log; var userNmae = "admin" ; var passWord = "123456" ; var isLogin = login.Valid(userNmae, passWord); Assert.AreEqual(isLogin, true ); Assert.AreEqual(log.Message, userNmae); } } public class TestLog : ILog { public string Message; public void Write( string message) { this .Message = message; } } |
這里我們定義了一個(gè)對(duì)象TestLog對(duì)象,該對(duì)象就是一個(gè)模擬對(duì)像,繼承了ILog接口。該測(cè)試中,一共進(jìn)行了兩項(xiàng)測(cè)試。一項(xiàng)是:驗(yàn)證用戶(hù)名和密碼是否輸入正確。另一項(xiàng)是:驗(yàn)證用戶(hù)寫(xiě)入日志的信息是否正確(比如應(yīng)該寫(xiě)入用戶(hù)名,結(jié)果把密碼寫(xiě)入了日志,測(cè)試會(huì)無(wú)法通過(guò))。
這里我們區(qū)分一下模擬對(duì)象與樁對(duì)象。上一節(jié)中,我們講過(guò)樁對(duì)象的定義,那么模擬對(duì)象與樁對(duì)象是什么關(guān)系呢?
模擬對(duì)象與樁對(duì)象在寫(xiě)法上區(qū)別很小,關(guān)鍵在于模擬對(duì)象需要進(jìn)行斷言,也就是說(shuō)模擬對(duì)象可以導(dǎo)致測(cè)試失敗。樁對(duì)象只是為了方便測(cè)試所定義的一個(gè)對(duì)象,不需要進(jìn)行斷言,所以,樁對(duì)象永遠(yuǎn)不會(huì)導(dǎo)致測(cè)試失敗。
上面的測(cè)試中,如果我們?nèi)サ糇詈笠恍写a,即我們不進(jìn)行寫(xiě)入日志的斷言,則該對(duì)象就是一個(gè)樁對(duì)象。
1 | Assert.AreEqual(log.Message, userNmae); |
上面的模擬對(duì)象是我們自己寫(xiě)的,自己寫(xiě)模擬對(duì)象比較費(fèi)時(shí),我們可以使用模擬框架進(jìn)行編寫(xiě)。這里我使用了Rhino Mocks框架。如果要執(zhí)行下面的代碼,需要下載Rhino.Mocks.dll文件,然后直接引用即可。
測(cè)試框架這里我選用了NUnit框架。測(cè)試代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | [TestFixture] public class MyLoginTest { [Test] public void Mock_Vaild_Test() { MockRepository mock = new MockRepository(); var log = mock.DynamicMock<ILog>(); var userName = "admin" ; var passWord = "123456" ; using (mock.Record()) { log.Write(userName); } MyLogin login = new MyLogin(); login.Log = log; var isLogin = login.Valid(userName, passWord); Assert.AreEqual(isLogin, true ); mock.VerifyAll(); } |
這里我沒(méi)有編寫(xiě)一個(gè)類(lèi)去繼承ILog接口,而是通過(guò)模擬框架,動(dòng)態(tài)生成了一個(gè)ILog對(duì)象。代碼是這句:
1 2 3 | MockRepository mock = new MockRepository(); var log = mock.DynamicMock<ILog>(); |
這里便生成了Log對(duì)象。通過(guò)錄制-回放的模式進(jìn)行模擬對(duì)象測(cè)試,首先需要定義我們的期望行為,最后驗(yàn)證實(shí)際行為與期望行為是否一致。這里,需要錄制我們期望行為,代碼如下:
1 2 3 4 | using (mock.Record()) { log.Write(userName); } |
這里我們期望向日志中寫(xiě)入用戶(hù)名。再通過(guò)回放來(lái)進(jìn)行驗(yàn)證,代碼如下:
1 | mock.VerifyAll(); |
該方法會(huì)驗(yàn)證,期望向日志中寫(xiě)入的信息與實(shí)際向日志中寫(xiě)入的信息是否一致,如果不一致,測(cè)試失敗。
這里我們便完成了使用模擬框架進(jìn)行單元測(cè)試。如果我們不需要測(cè)試日志寫(xiě)入方法,則把模擬對(duì)象換成樁對(duì)象就可以了,生成樁對(duì)象的方法如下:
1 2 3 | MockRepository mock = new MockRepository(); var log = mock.Stub<ILog>(); |
把回放的方法(mock.VerifyAll())去掉,就完成了模擬對(duì)象向樁對(duì)象的轉(zhuǎn)變。注意,這里錄制的代碼還是需要的。
總結(jié):編寫(xiě)模擬對(duì)象和樁對(duì)象是非常有意義的,使用框架可以幫助我們簡(jiǎn)化單元測(cè)試。一般情況下,一個(gè)測(cè)試中,可以有多個(gè)樁對(duì)象,但最好只有一個(gè)模擬對(duì)象。模擬對(duì)象太多,證明一個(gè)測(cè)試方法做了太多項(xiàng)測(cè)試,不利于維護(hù)測(cè)試代碼,一旦代碼變改,很容易使單元測(cè)試失敗。
下一節(jié),寫(xiě)一下測(cè)試框架的一些常用功能,如:如何模擬異常、如何模擬返回值等。。。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注