手頭的項(xiàng)目越來越大,很多以前不會(huì)出現(xiàn)的問題開始浮現(xiàn)。
比如:我修改了一個(gè)基礎(chǔ)的類庫(kù),卻意外的影響了九重天外的客戶項(xiàng)目,直接導(dǎo)致一個(gè)功能無法實(shí)現(xiàn)。我郁悶?。。。?/p>
因此開始要有組織、有預(yù)謀、有計(jì)劃的對(duì)項(xiàng)目過程進(jìn)行測(cè)試驅(qū)動(dòng)了。最終目標(biāo)是,我修改了底層某個(gè)dll的某個(gè)方法,測(cè)試框架能夠自動(dòng)幫我找出來所有收到影響的類,全部執(zhí)行一次回歸測(cè)試,并發(fā)送一份漂亮的報(bào)告到我手里。
這個(gè)目標(biāo)估計(jì)1、2個(gè)星期才能實(shí)現(xiàn),不過現(xiàn)在先放出一個(gè)非常漂亮的MOCK核心代碼。
研究過程
在不斷收集各種資料過程中,學(xué)習(xí)了很多,例如以下關(guān)鍵字,有興趣的兄弟可以自己搜索一下:
testdriven.net, nunit, typemock, cruiseControl.net, Confluence, JIRE, NUnitForms, WatiN, MBUnit, CSUnit, NBehave, Gallio
ranorex, dynamicProxy...
估計(jì)各位有時(shí)間看看上面的簡(jiǎn)介,就能夠掌握現(xiàn)在測(cè)試驅(qū)動(dòng)的大致發(fā)展。
- nunit的核心代碼非常容易理解,大伙自己下載看看就行了。
- testdriven.net 的前身是:NUnitAddin, 如果要研究如何testdriven集成到vs,看看不錯(cuò)。
- WatiN的核心代碼雖然我沒有看,不過猜也能猜到,就是調(diào)用了IE的核,然后搜索上面的html標(biāo)簽操作。
- typeMock有點(diǎn)混蛋,源碼混淆了,無法拿到核心技術(shù),不過從介紹來看是源自了castle.DynamicProxy,那么各位可以參觀一下一篇非常垃圾的文章,但是起碼讓我入門了: http://www.cublog.cn/u/23701/showart_1414436.html
- 最后,我來到了Moq,開始因?yàn)槁犝f是.net3.5,就沒有看源碼,不過剛才研究了一下頓時(shí)非常興奮。起碼Moq能讓我解決了50%的工作。
接下來就說下Mock技術(shù)和測(cè)試驅(qū)動(dòng)中的作用。
Mock技術(shù)
我最不喜歡老外造名詞,所以會(huì)用自己的體會(huì)去介紹。
mock的本質(zhì)就是模擬目標(biāo)對(duì)象的一個(gè)假對(duì)象。
這個(gè)性質(zhì)在測(cè)試驅(qū)動(dòng)中非常有用,例如我的代碼是:
代碼 public DateTime GetNextFiredDate(DateTime now, IOrmScheduleTrigger trigger, int triggeredtimes)
{
return GetNextFiredDate(now, trigger.TriggerType, trigger.TriggerExpression, triggeredtimes);
}
現(xiàn)在要測(cè)試這個(gè)代碼,就需要傳入一個(gè)IOrmScheduleTrrigger的接口對(duì)象。但是不幸的是,這個(gè)對(duì)象是個(gè)ORM,要啟動(dòng)這個(gè)對(duì)象,就涉及到了數(shù)據(jù)庫(kù)。。。。
老大,我只是想測(cè)試一下一輛寶馬的玻璃是否堅(jiān)硬,不需要啟動(dòng)我的寶馬加速到120km,然后再用手去翹翹那塊玻璃吧。
所以,我希望能夠有個(gè)模擬對(duì)象,繼承了這個(gè)接口, 同時(shí)提供了我期望的返回值,讓這個(gè)方法能夠順利執(zhí)行。
傳統(tǒng)的傻逼方法,就是自己寫一個(gè)類,繼承了這個(gè)接口,然后才傳入進(jìn)去。例如:
public class OrmScheduleTriggerTestObject : IOrmScheduleTrigger
{
// some method here
}
這樣不就更加的傻逼了?我為了測(cè)一塊玻璃,還親自造了另外一臺(tái)簡(jiǎn)易的寶馬出來? 于是我開始翻閱各種文獻(xiàn),甚至考慮使用動(dòng)態(tài)代理(DynamicProxy)。動(dòng)態(tài)代理的核心思想就是在代碼運(yùn)行中寫IL生成一個(gè)繼承類。這個(gè)技術(shù)很有用,但是現(xiàn)在我還用不上(就像martin fowler說的,typemock就等于把核武器交給了一個(gè)4歲小孩)。
于是我繼續(xù)尋找,終于翻開了Moq的源碼,找到了答案。
先看看以下一段代碼,是我摘自Moq源碼的核心部分,稍加改造了:
代碼 using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
namespace Pixysoft.Framework.TestDrivens
{
public class Mock<TInterface> : RealProxy
{
public Mock()
: base(typeof(TInterface))
{
}
public TInterface Value
{
get
{
return (TInterface)this.GetTransparentProxy();
}
}
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage methodCall = msg as IMethodCallMessage;
//我返回int = 1
return new ReturnMessage(1, null, 0, null, methodCall);
}
}
public interface IMock
{
int Devide(int a, int b);
}
public class testrealproxy //測(cè)試代碼在這里?。。?/span>
{
public void test()
{
IMock mock = new Mock<IMock>().Value;
Console.WriteLine(mock.Devide(1, 2));
//輸出 = 1
}
}
}
這段代碼就是Moq的核心思想。
大概意思是:我希望調(diào)用接口IMock的方法Devide,但是我壓根不想寫這個(gè)接口的實(shí)現(xiàn)。
那么我先寫一個(gè)通用的模擬對(duì)象Mock<TInterface>,繼承了RealProxy。
然后通過調(diào)用Value就可以返回需要的接口對(duì)象。而這個(gè)對(duì)象就是 return (TInterface)this.GetTransparentProxy();是個(gè)透明代理。
最后當(dāng)我調(diào)用了 int Devide(int a, int b); 方法的時(shí)候,等于調(diào)用了public override IMessage Invoke(IMessage msg)方法(有點(diǎn)點(diǎn)的AOP感覺)。
后記
上文就是Moq的核心思想了。非常的精彩!估計(jì)有了思路,各位就可以制造自己的原子彈了。
這里插句題外話,很多人抨擊重復(fù)造輪子。我就奇怪了。如果我造一個(gè)輪子花費(fèi)的時(shí)間和學(xué)習(xí)用一個(gè)輪子的時(shí)間差不遠(yuǎn),為什么不造一個(gè)?
而且,用別人的輪子,經(jīng)常出現(xiàn)的情況是:很多輪子不知道挑哪個(gè)。一旦挑上了,項(xiàng)目進(jìn)展到一般才發(fā)現(xiàn)不適合、有bug,于是又重頭挑另外的輪子。
這個(gè)經(jīng)歷是真實(shí)的。當(dāng)年讀大學(xué),我的室友就是典型的挑輪子,他懂得很多框架(java),webwork,hibernate, spring。和人砍起來朗朗上口,但是需要深入做項(xiàng)目了,出現(xiàn)問題基本上不知所措,不是翻文獻(xiàn),就是問師兄,最后整個(gè)項(xiàng)目組從來就沒有一個(gè)成品。
我自從學(xué)電腦依賴,從來就沒有用過別人的輪子,即使是hibernate,我的確也沒有用過,不過他的核心文檔倒是看過,對(duì)比之下,和oracle的toplink相比簡(jiǎn)直就是小孩。
比我牛逼的兄弟大有人在,希望各位牛人不要浪費(fèi)自己的時(shí)間去挑別人的輪子,直接自己造一個(gè)算了。
最后說說接下來的工作。
基于接口的測(cè)試驅(qū)動(dòng)完成了,剩下的就是面對(duì)sealed class 等頑固分子了, 必然需要?jiǎng)佑梅浅R?guī)武器,DynamicProxy。下回再見。