網(wǎng)絡(luò)上可以搜索到很多TDD的文章,但是很大一部分只是講述怎樣使用NUnit等工具的使用。只有切身的去體會(huì)TDD的每一個(gè)環(huán)節(jié),才能真正理解TDD。
工具:NUnit的原理
NUnit的原理很簡(jiǎn)單,是新建一個(gè)TestFixture實(shí)例,然后依次調(diào)用TestFixture中的Test Case,然后紀(jì)錄Test Case的運(yùn)行結(jié)果
NUnit的核心對(duì)象是TestCase, TestSuit, TestResult
TestCase指一個(gè)Test Case,比如一個(gè)[Test]屬性標(biāo)記的方法
TestSuit指一組Test Case,比如一個(gè)[TestFixture]屬性標(biāo)記的對(duì)象
TestResult指Test Case運(yùn)行的結(jié)果,TestResult是一個(gè)抽象類,在NUnit中,有兩個(gè)類是繼承自TestResult的:TestCaseResult和TestSuiteResult
NUnit是怎樣運(yùn)行Test Case的
NUnit定義了一個(gè)處理Test Case的抽象類TestCase
TestCase類重要的方法是Run()
public override TestResult Run(EventListener listener)
public abstract void Run(TestCaseResult result);
TestCase運(yùn)行的結(jié)果會(huì)存入一個(gè)TestCaseResult對(duì)象
調(diào)用Run方法如果傳入實(shí)現(xiàn)了EventListener接口的對(duì)象話,可以在TestCase實(shí)際運(yùn)行之前以及TestCase運(yùn)行之后進(jìn)行自定義的處理
……
listener.TestStarted(this);
Run(testResult);
listener.TestFinished(testResult);
……
NUnit還定義了一個(gè)實(shí)現(xiàn)抽象類TestCase的通用模版
public abstract class TemplateTestCase : TestCase
TemplateTestCase中Run方法的基本框架為
public override void Run(TestCaseResult testResult )
{
try
{
InvokeSetUp(); //設(shè)置環(huán)境
InvokeTestCase(); //運(yùn)行Test Case
InvokeTearDown(); //恢復(fù)環(huán)境
ProcessNoException(testResult); //無(wú)異常退出
}
catch
{
ProcessException(testResult); //異常處理
}
}
在Run方法中還會(huì)計(jì)算Test Case實(shí)際運(yùn)行的時(shí)間和所用的內(nèi)存
DateTime start = DateTime.Now;
long before = System.GC.GetTotalMemory( true );
…. //run test case
long after = System.GC.GetTotalMemory( true );
testResult.Leakage = after – before;
DateTime stop = DateTime.Now;
TimeSpan span = stop.Subtract(start);
testResult.Time = (double)span.Ticks / (double)TimeSpan.TicksPerSecond;
下面幾個(gè)類都是繼承自TestCase類或者TemplateTestCase類
NormalTestCase //一般的Test Case
NotRunnableTestCase //不可運(yùn)行的Test Case
ExpectedExceptionTestCase //定了期望異常的Test Case
為什么寫(xiě)的Test Case沒(méi)有自動(dòng)運(yùn)行
寫(xiě)Test Case時(shí)候要注意,Test Case必須是public的,無(wú)參數(shù)的,無(wú)返回值的函數(shù)
參考:
public class NotRunnableTestCase : TestCase
{
public NotRunnableTestCase(MethodInfo method) : base(method.DeclaringType.FullName, method.Name)
{
string reason;
if (method.IsAbstract)
reason = “it must not be abstract”;
else if (method.IsStatic)
reason = “it must be an instance method”;
else if (!method.IsPublic)
reason = “it must be a public method”;
else if (method.GetParameters().Length != 0)
reason = “it must not have parameters”;
else if (!method.ReturnType.Equals(typeof(void)))
reason = “it must return void”;
else
reason = “reason not known”;
ShouldRun = false;
IgnoreReason = String.Format(“Method {0}’s signature is not correct: {1}.”, method.Name, reason);
}
}