然由程式人自己Unit Tests(元)自己的程式已行之有年,但是大部分的Unit Tests都是在主要的程式已好、好之後。大部分的程式人都有相同的的,在主要程式好之後再加入Unit Test是一困的工作,而且在的力之下Unit Test通常是第一被跳的步。
篇文章要介的Test-driven development (TDD,方法),其主要目的就是要解一,且程式人可以因此出更高品的,完整的程式。其方法就是把整程序反,在主要程式之前就要把Unit Tests好。TDD是所的Extreme Programming(XP,程式作)面所提到主要Practices(、作)之一,在Java中用TDD的程式人不少,在.NET的中只停留在很少的文章到如何使用TDD。
根Ron Jeffries(XP大)的法,所Unit Tests就是「…多段的程式,些程式的目的是用成批行(run in batch mode),以我所的Classes()。每一Unit Test都送一Message一特定的Class,且所回的值是Test所期的答案。」如果我用比的法明的,段的意思就是,你一程式,你用程式在你的主要程式中所有的Classes的Public Interfaces(Public介面)。Unit Tests跟所的Requirement Tests或Acceptance Tests不同,Unit Test的重在於你所的Methods(子程序)所生的果,你所期的一模一。
,做起可能挑性很高。首先,你必先要定用什工具些Unit Tests。以往人通常使用一些很大型、的test engine(引擎),配合一些的scripting languages(本言、述性程式言)些Unit Tests。可能只合的人或部使用,於由程式人自己的Unit Tests,就不那用了。事上於一般的程式人,他所需要的是一套的toolkit(工具,程式),他可以使用他原本在程式程就已熟知的程式言及IDE(工具)出些Unit Tests。
大部分些年流行的Unit Testing Frameworks(Unit Test框架)都是源自於由Kent Beck(XP 始人) 所的Unit Test Framework。Unit Test Framework的背景是所第一XP案(Chrysler C3案)所特的。最起初的Framework是用Smalltalk成,且多次的改版之後,到今天都存在。在Smalltalk版本的Framework之後,Kent和Erich Gamma(Design Pattern迷知道他是)又把Framework改版到Java上,且正式命名叫作jUnit。此之後,Framework就始不被改版、用到各不同的程式言之上,其中包括了C++、VB、Python、Perl、以及多不同的程式言。
本文所的NUnit 2.0是一它的先祖(其他的Framework)非常不一的版本。其他的xUnit家族版本通常都有一base class(基),你要的test classes()都得inherit(承)自base class。除此之外,他法能你Unit Tests。不幸的是,很多的程式言就造成很大的限制。比如,Java及C#就只能允single inheritance(一承)。也就是,如果你想要refactor(重整)你的Unit Tests程式的,你遭遇到一些的限制;除非你引一些的inheritance hierarchies(承)。
有了.NET之後一切又不同了,.NET引了一新的程式的概念 ─ Attributes(性),解了人的。Attributes你可以在你的程式之上再加入metadata(後料/母料/超料,描述程式的料)。一般Attributes不影到主要程式的行,其功能是在你所程式之上添加了外的。Attributes主要使用在documenting your code(解你的程式),但是Attributes也可以用提供有Assembly的外,其他的程式就算有Assembly,也可以使用些。基本上就是NUnit 2.0所作的事。在NUnit 2.0面,有一Test Runner Application(行Unit Tests的程式),Test Runner描你已compile()好的程式,且Attribute面知道哪些classes是test classes,哪些methods是需要行的test methods. 然後,Test Runner使用.NET的Reflection技行些test methods。因故,你就不再需要你的test classes承自所的common base class。你唯一需要作的事,就是使用正的Attribute描述你的test classes及test methods。
NUnit提供了多不同的attributes,你可以自由的你想要的unit tests。些attributes可以用定test fixtures(下一段解)、test methods,以及setup及teardown的methods(及善後工作的methods)。除此之外,有其他的attributes可以定期生的exceptions,或者要求Test Runner跳某些test method不行。
TestFixture attribute主要是用在class上,其作用的class含有需要行的test methods。你在一class的定加上attribute,Test Runner就查class,看看class是否含有test methods。
底下段程式示了如何使用TestFixture Attribute。(本文中所有程式都是用C#成,但是你知道,NUnit也是用於其他的.NET程式言,包括VB.NET。NUnit的相文件。)
namespace UnitTestingExamples
{
using System;
using NUnit.Framework;
[TestFixture]
public class SomeTests
{
}
}
使用TextFixture Attribute的class需要符合另一唯一附加的限制,就是需要有一public的default constructor(或者是有定任何的constructor,其是相同的意思)。
Test attribute主要用示在text fixture中的method,表示method需要被Test Runner application所行。有Test attribute的method必是public的,且必return void,也有任何入的。如果有符合些定,在Test Runner GUI之中是不列出method的,而且在行Unit Test的候也不行method。
底下的程式示了使用attribute的方法:
namespace UnitTestingExamples
{
using System;
using NUnit.Framework;
[TestFixture]
public class SomeTests
{
[Test]
public void TestOne()
{
// Do something...
}
}
}
在Unit Tests的候,有你需要在行每一test method之前(或之後)先作一些或善後工作。然,你可以一private的method,然後在每一test method的一或最末端呼叫特的method。或者,你可以使用我要介的SetUp及Teardown Attributes到相同的目的。
如同Attributes的名字的意思,有Setup Attribute的method在TextFixture中的每一test method被行之前先被Test Runner所行,而有Teardown Attribute的method在每一test method被行之後被Test Runner所行。一般,Setup Attribute及Teardown Attribute被用一些必的objects(物件),例如database connection、等等。
底下的例示了如何使用attributes:
namespace UnitTestingExamples
{
using System;
using NUnit.Framework;
[TestFixture]
public class SomeTests
{
private int _someValue;
[SetUp]
public void Setup()
{
_someValue = 5;
}
[TearDown]
public void TearDown()
{
_someValue = 0;
}
[Test]
public void TestOne()
{
// Do something...
}
}
}
有的候,你希望你的程式在某些特殊的件下生一些特定的exception。要用Unit Test程式是否如期的生exception,你可以用一try..catch的程式段catch(捕捉)exception,然後再一boolean的值明exception的生了。方法固然可行,但是太花功夫。事上,你使用ExpectedException attribute示某method生哪一exception,如同下面的例所示:
namespace UnitTestingExamples
{
using System;
using NUnit.Framework;
[TestFixture]
public class SomeTests
{
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void TestOne()
{
// Do something that throws an InvalidOperationException
}
}
}
如果上面的程式被行的候,如果一旦exception生,而且exception的type(料型)是InvalidOperationException 的,test就利通。如果你期你的程式生多exception的,你也可以一次使用多ExpectedException attribute。但是,一test method只一件事情,一次多功能是不好的做法,你量避免之。另外,attributes不查inheirtance的,也就是,如果你的程式生的exception是承自InvalidOperationException 的subclass(子)的,test行的候不通。而言之,你使用attribute的候,你要明的指明所期的exception是哪type(料型)的。
attribute你大概不常用的,但是一但需要的候,attribute是很方便使用的。你可以使用attribute示某test method,叫Test Runner在行的候,略method不要行。使用Ignore attribute的方法如下:
namespace UnitTestingExamples
{
using System;
using NUnit.Framework;
[TestFixture]
public class SomeTests
{
[Test]
[Ignore("We're skipping this one for now.")]
public void TestOne()
{
// Do something...
}
}
}
如果你想要性的comment out一test method的,你考使用attribute。attribute你保留你的test method,在Test Runner的行果面,也提醒你被略的test method的存在。
除了以上所提到的些用示程式所在的attributes之外,NUnit有一重要的class你要知道如何使用。class就是Assertion class。Assertion class提供了一系列的static methods,你可以用主要程式的果你所期的是否一。底下的例示了如何使用Assertion class:
namespace UnitTestingExamples
{
using System;
using NUnit.Framework;
[TestFixture]
public class SomeTests
{
[Test]
public void TestOne()
{
int i = 4;
Assertion.AssertEquals( 4, i );
}
}
}
(我知道段程式只是用示用的,但是段程式很明白的示了我的意思。)
好,在我已Unit Tests的基本步及方法,在我看看如何行你所的Unit Tests。事上非常。NUnit面有已好的Test Runner applications:一是窗GUI程式,一是console XML(命令列)程式。你可以自由你所喜的方式,基本上是有什差的。
如果你要使用窗GUI的Test Runner app,你只需要行程式,然後告它你要行的test method所在的assembly位置。包含有你所test methods的assembly是那一class library(或是executable,*.dll或*.exe) assembly,其中含有前面到的Test Fixtures。你告Test Runner你的assembly所在的位置,Test Runner自loadasembly,然後把所有的class及test methods都列在窗的左。你按下’Run’按,你就自行所有列出的test methods。你也可以double click其中的一test class,或是一test method之上,自只行class或是method。
底下是窗GUI Test Runner行的子:
在一些的情下,特是你想要在你自己的build script中加入Unit Testing的情下,你大概不使用GUI Test Runner。在自行build script的情下,你一般把你build的果在,或入log file面存作,以供程式人、理或是客可以藉由查知道情。
在情,你可以用NUnit 2.0的console Test Runner application。Test Runner可以入assembly的位置,其行果是一XML字串。你可以用XSLT或是CSS把XML果成HTML,或是其他你想要的格式。如果你需要用到功能的,查看NUnit文件中有console Test Runner application的料。
了多,你已知道怎Unit Tests了,吧?阿哈,跟程式一,只知道法,距可以出好的程式有一大段距的。你需要一些的技巧及方法,你可以真正出水的用程式出。底下我到一些助你始的技巧及方法,但是你必知道,唯一能你出水的Unit Tests的方法只有一,、、再。
如果你完全有TDD的,底下所的西你大概一之法接受。以往多的程式人,花了的及,了多的籍及文章,告我在程式之前要好好的做的功夫,然後才是出程式,最後是小心地你的程式是否正。好了,忘掉一切吧,我接下要告你的,是上面所的完全背道而的流程。
我不再先、再程式、再作,我整把流程反,先。用另外的方式,我不任何一行的主要程式,除非我先了,先行了test methods,先有了一不通的。也就是,整程式的流程成像子:
先一段Unit Test。
行Unit Test。然Testcompile()都不能compile,因你根本都有任何的主要程式。 (我把也作Test有通)
你所能想到最的程式,你的Test可以compile。
在再次行你的Unit Test,你得到失的果。 (如果不幸通了的,表示你的Unit Test根本有中要害,你的Unit Test是不好的Unit Test) 。
在你可以出你的主要程式,你的Unit Test可以利的通。
再行你的Unit Test,在你的Unit Test已利通了。 (如果是不通,你回到第5,查看看你的程式哪出,修正之,然後再行Unit Test) 。
在你可以回到第1步,始新功能的Unit Test!
事上,你在第5步的候,你所用程式的方法,就正好是所的Coding by Intention(目、意向)的方法。所的Coding by Intention就是,你程式是由上而下的。其相的方法就是由下而上的法,也就是,你一段程式的程式的候,如果你你正在的class需要另一A class提供一Foo method,你就先跳到A class去把method好,然後再回你之前正在的class。Coding by Intention正好相反,你程式的候,你假A class已有你所需要的Foo method。等到你完你的程式要compile的候,你的程式工具告你你少了一class或是一method,候才加入所需要的method。如同我之前所的,是一件好事情,程式法compile跟你的Unit Test不通是同的一回事。
你用Coding by Intention的候,你很清楚的用程式言表你想要作的事。不但是助我好Unit Tests,也我所的程式更加的清楚、容易明白、容易debug(除),程式的也更加的完善。在程式方法中,只是用助我我所的程式有。但是在TDD面,Unit Tests可以助我在程式之前,先清楚的定我所要的是什,我的class要有哪些的功能。我不是用TDD比用的方法要容易,但是我的告我,其生的果的程式有大的助益。
如果你已,也有Extreme Programming的,下面的程式你只是你已知道的知。如果TDD和Extreme Programming你很陌生,你可以看一下以下的例。假你要一程式,程式可以你的使用者存在他的行面。在,根TDD的原,我我的BankAccount class之前,我先Unit Test著手。首先,我先我的BankAccountTests class,我第一想到的是我的BankAccount class可以接受存款且告我新的有多少。底下就是我的Unit Test程式:
namespace UnitTestingExamples.Tests
{
using System;
using NUnit.Framework;
[TestFixture]
public class BankAccountTests
{
[Test]
public void TestDeposit()
{
BankAccount account = new BankAccount();
account.Deposit( 125.0 );
account.Deposit( 25.0 );
Assertion.AssertEquals( 150.0, account.Balance );
}
}
}
在我好了,我先compile。哈,不能compile,然,我都有我的BankAccount class 呢。在你知道,就是Test Driven Development的基本原:除非你有一不通的Unit Test,否不要任何的程式。然,不能compile也算是不通。
在我可以我的BankAccount class,我只有我所能想到最、最春的程式,其目的只是我的Unit Test可以被compile:
namespace UnitTestingExamples.Library
{
using System;
public class BankAccount
{
public void Deposit( double amount )
{
}
public double Balance
{
get { return 0.0; }
}
}
}
YES,次compile已了,接下我就用Test Runner行我的Unit Test。嗯,有通,Test Runner告我"TestDeposit: expected: <150> but was <0>"。(者言,我在此不翻error message,竟是你要看得懂的,除非你用的是中文版的NUnit,否看懂error message是必要的)。在,接下我就要一些程式我的Unit Test能真正通,生我想要的果:
namespace UnitTestingExamples.Library
{
using System;
public class BankAccount
{
private double _balance = 0.0;
public void Deposit( double amount )
{
_balance += amount;
}
public double Balance
{
get { return _balance; }
}
}
}
OK,在我的Unit Test了,我可以行下一步的工作。(者言,事上根Extreme Programming或是Kent Beck上的法,你要先作Refactoring(重整)的工作)。
你在Unit Test的候,你碰到一些挑,其中一就是要保每一test method都只有的一一的功能。但是,在一般的情之下,你的test method所要的功能,往往依其他的objects才能行其功能。在,如果你的test method功能,你真正的不只是功能,你也了另外的class。
如果是一,你可以用Mock Objects助你隔出你真正想要的功能。所的Mock Object,其主要的功能就是模的Object,你可以一被模的object是不是有被如期般的正常使用。更要的是,使用Mock Objects有以下的好:
很容易就可以好
很容易就可以好你要的料
行速度快
生的果是可期的
可以你某object是否正的呼叫了呼叫的method,以及是否依照正的序呼叫些methods。
下面的程式例示了一典型的mock object使用例子。注意,在Unit Test得更加清晰,更加容易人明白,而且行test只了我想的程式,而不扯到不相干的objects。
namespace UnitTestingExamples.Tests
{
using DotNetMock;
using System;
[TestFixture]
public class ModelTests
{
[Test]
public void TestSave()
{
MockDatabase db = new MockDatabase();
db.SetExpectedUpdates(2);
ModelClass model = new ModelClass();
model.Save( db );
db.Verify();
}
}
}
如上所示,使用MockDatabase class的工作很容易,也我很容易的就可以是否Savemethod呼叫了MockDatabase class。使用了mock object也我不需要操心真正的料是否作正常。我唯一知道的,就是ModelClass object行Save作的候,它部呼叫Database物件的Update method次。我的Unit Test就是要是否真的有呼叫Update method次。所以我先告MockDatabase object我期Update method被呼叫次,然後我行model.Save(),最後其果。因MockDatabase不涉到料的,我也就不需要心料因行被修改,或是如何有效的料,…此的。我唯一需要心的是,Save method是否真的造成了Update被呼叫次。
"When Mock Objects are used, only the unit test and the target domain code are real." -- Endo-Testing: Unit Testing with Mock Objects by Tim Mackinnon, Steve Freeman and Philip Craig.
(你使用Mock Objects的候,只有你的Unit Test以及你所要的程式是真的西。 ─ 引自Endo-Testing一文,作者Tim Mackinnon, Steve Freeman and Philip Craig)
你的Business Layer(企)程式,是一般程式人在到Unit Test的候最常的例子。如果你小心地及你的Business Layer,你的Business Layer是loosely coupled(其他部分立)以及highly cohesive(部密的)。就上的用,所的coupling指的是你的class其他的classes互相依的程度,如果coupling的程度越低的,如果我要修改其中某class的,我就不需要心其他的class的功能被影到。另一角度看,所的cohrsive就是你的class只一的任,不加入其他的不完全相干的功能。
如果你的Business layer class library(企程式)是loosely coupled以及highly conhesive的,要Unit Test是而易的事。你可以每一business class(在Business Layer的class)都一Unit Test class,且,你可以的每一business class的public methods都出相的。
了,如果你你想要你的business classUnit Test,困重重,你也要考作一些大格局的Refactoring(重整)的工作。然,如果你是先Unit Tests再business classes的,你是不可能落入到地步的。
你著手想要User Interface(使用者介面)的程式的候,有一些就始跑出了。然你可以想法你的User Interface符合loosely coupled的要求,它其他的class都有依。但是,所的User Interface本上就是依於使用者,需要由使用者及。那,到底要如何的解,我可以我的User Interface呢?
答案的之在於,我要小心的把所的logic(程式行的),所的user interface(用示料)清楚的分,User Interface真正只示view(界/)的工作。有很多的pattern(模式)就是用助我做好工作,它的名不相同(Model-View-Controller, Model-View-Presenter, Doc-View,等等),但是都是同的目的。些pattern的始者都深切的到,把view及view所要作事的(也就是controller)分,是一件很有助益的事。
那,我要如何使用些pattern助我Unit TestUser Interface呢?我在所示的技巧是自於Michael Feathers所的The Humble Dialog Box一文。就其本上,技巧就是你的view class(也就是User Interface class)Implement(作)一的Interface。Interface定了view class要示的料,且有getting/setting methods(存取料的metods)。你的view class的只有把料示使用者看,使用者作了一些作(例如按了一按),view class的event handler的任就是的把作交controller理。
我用一例看,就更加的清楚明白。假我要一程式,其中一窗是要使用者入自己的名字及Social Security Number(社安,似身分字)。位都必填,所以我需要查使用者是否入名字,以及社安是否符合正的格式。既然我要先,我就要遵守自己的:
[TestFixture]
public class VitalsControllerTests
{
[Test]
public void TestSuccessful()
{
MockVitalsView view = new MockVitalsView();
VitalsController controller = new VitalsController(view);
view.Name = "Peter Provost";
view.SSN = "123-45-6789";
Assertion.Assert( controller.OnOk() == true );
}
[Test]
public void TestFailed()
{
MockVitalsView view = new MockVitalsView();
VitalsController controller = new VitalsController(view);
view.Name = "";
view.SSN = "123-45-6789";
view.SetExpectedErrorMessage( controller.ERROR_MESSAGE_BAD_NAME );
Assertion.Assert( controller.OnOk() == false );
view.Verify();
view.Name = "Peter Provost";
view.SSN = "";
view.SetExpectedErrorMessage( controller.ERROR_MESSAGE_BAD_SSN );
Assertion.Assert( controller.OnOk() == false );
view.Verify()
}
}
如果你要compile及build段程式,你看到一大堆的息。,是因我都有始MockVitalsView以及VitalsControllerclass的故。好吧,在就我classes的原始架。得我的,我只要最、可以compile就好的程式:
public class MockVitalsView
{
public string Name
{
get { return null; }
set { }
}
public string SSN
{
get { return null; }
set { }
}
public void SetExpectedErrorMessage( string message )
{
}
public void Verify()
{
throw new NotImplementedException();
}
}
public class VitalsController
{
public const string ERROR_MESSAGE_BAD_SSN = "Bad SSN.";
public const string ERROR_MESSAGE_BAD_NAME = "Bad name.";
public VitalsController( MockVitalsView view )
{
}
public bool OnOk()
{
return false;
}
}
咻!在我的Unit Test已成功的compile了,我看是否些Unit Tests可以通。噗!Test Runner告我有tests有通。第一是在在TestSuccessful 面呼叫controller.OnOk的候,因回的果是false,而非我所期的true。第二地方是在TestFailed面呼叫view.Verify的候。
我遵照我先的原作,在我需要的是想法我的Unit Tests可以利通。如果只是要TestSuccessful 通就很,但是要TestFailed 也通我就必要一些真正有用的程式了。比如像:
public class MockVitalsView : MockObject
{
public string Name
{
get { return _name; }
set { _name = value; }
}
public string SSN
{
get { return _ssn; }
set { _ssn = value; }
}
public string ErrorMessage
{
get { return _expectedErrorMessage.Actual; }
set { _expectedErrorMessage.Actual = value; }
}
public void SetExpectedErrorMessage( string message )
{
_expectedErrorMessage.Expected = message;
}
private string _name;
private string _ssn;
private ExpectationString _expectedErrorMessage =
new ExpectationString("expected error message");
}
public class VitalsController
{
public const string ERROR_MESSAGE_BAD_SSN = "Bad SSN.";
public const string ERROR_MESSAGE_BAD_NAME = "Bad name.";
public VitalsController( MockVitalsView view )
{
_view = view;
}
public bool OnOk()
{
if( IsValidName() == false )
{
_view.ErrorMessage = ERROR_MESSAGE_BAD_NAME;
return false;
}
if( IsValidSSN() == false )
{
_view.ErrorMessage = ERROR_MESSAGE_BAD_SSN;
return false;
}
// All is well, do something...
return true;
}
private bool IsValidName()
{
return _view.Name.Length > 0;
}
private bool IsValidSSN()
{
string pattern = @"^/d{3}-/d{2}-/d{4}$";
return Regex.IsMatch( _view.SSN, pattern );
}
private MockVitalsView _view;
}
在我下去之前,我先停一下回看看段程式。首先要注意的是,我完全有更到我起初的Unit Test程式(是什我有把它列在上面的原因)。我所大幅更的,是在MockVitalsView 以及VitalsController之上。我先看MockVitalsView。
在我先前的例子面,MockVitalsView原本有承自任何的base class。在了化我的工作,我把它成承自DotNetMock.MockObject。一MockObject class我一Verify method,method我作了所有的工作。它所有的神奇力量乃是自於expectation classes 。我使用expectation class定我所期mock object生的事。在例子,我期ErrorMessageProperty有一特定的值。因Property是於String料型的,所以我在我的mock object面加入了一叫做ExpectationString的member。接下我了SetExpectedErrorMessage 以及ErrorMessage method及property,它使用ExpectationString object。我在我的中呼叫Verify的候,MockObject base class就自的查我所期的果是否生,且通知我任何期不一致的地方。很酷吧。是不是啊?
另外一被大幅更的就是我的VitalsController class。因是所有真正出力活的程式所在,我知道大幅更乃是在所免。基本上,我主要的,主要的程式都是在OnOKmethod面。在,我用了我的view class所定存取料的method取使用者所填的料,然後如果料有任何不符合的,我用ErrorMessageproperty把息回去。
所以,我的工作已告一段落?早的咧。到目前止,我都只是在controller上面大土木,我只是用模的mock view假我有一User Interface。事上,我有任何西可以秀使用者看呢! ,我在需要的就只是我已好的controller,可以接到真的view上面去。怎做呢?
首先,我需要把MockVitalsView所需要implement的Interface抓出。如果我看一下VitalsController 以及VitalsControllerTests 的程式,我就可以底下的interface就足我的需要:
public interface IVitalsView
{
string Name { get; set; }
string SSN { get; set; }
string ErrorMessage { get; set; }
}
我有了我要的新interface,在,我可以把在controller面用到MockVitalsView的程式,改成使用IVitalsView。然後,我就可以修改MockVitalsView,它 implementsIVitalsView。然,我做了Refactoring的作之後,要快的它行一下我的Unit Test,保我有作了任何的傻事。假一切都正常,所有的都通了,我就可以真正始view了。在例子面我用了ASP.NET的作我的view,你知道,你可以很容易的就一Windows Form的view。
底下是我的.ASPX 案:
<%@ Page language="c#" Codebehind="VitalsView.aspx.cs"
AutoEventWireup="false"
Inherits="UnitTestingExamples.VitalsView" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title>VitalsView</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name=vs_defaultClientScript content="JavaScript">
<meta name=vs_targetSchema content="http://schemas.microsoft.com/intellisense/ie5">
</head>
<body MS_POSITIONING="GridLayout">
<form id="VitalsView" method="post" runat="server">
<table border="0">
<tr>
<td>Name:</td>
<td><asp:Textbox runat=server id=nameTextbox /></td>
</tr>
<tr>
<td>SSN:</td>
<td><asp:Textbox runat=server id=ssnTextbox /></td>
</tr>
<tr>
<td> </td>
<td><asp:Label runat=server id=errorMessageLabel /></td>
</tr>
<tr>
<td> </td>
<td><asp:Button runat=server id=okButton Text="OK" /></td>
</tr>
</table>
</form>
</body>
</html>
底下是ASP.NET的code-behind程式:
using System;
using System.Web.UI.WebControls;
using UnitTestingExamples.Library;
namespace UnitTestingExamples
{
/// <summary>
/// Summary description for VitalsView.
/// </summary>
public class VitalsView : System.Web.UI.Page, IVitalsView
{
protected TextBox nameTextbox;
protected TextBox ssnTextbox;
protected Label errorMessageLabel;
protected Button okButton;
private VitalsController _controller;
private void Page_Load(object sender, System.EventArgs e)
{
_controller = new VitalsController(this);
}
private void OkButton_Click( object sender, System.EventArgs e )
{
if( _controller.OnOk() == true )
Response.Redirect("ThankYou.aspx");
}
#region IVitalsView Implementation
public string Name
{
get { return nameTextbox.Text; }
set { nameTextbox.Text = value; }
}
public string SSN
{
get { return ssnTextbox.Text; }
set { ssnTextbox.Text = value; }
}
public string ErrorMessage
{
get { return errorMessageLabel.Text; }
set { errorMessageLabel.Text = value; }
}
#endregion
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);
okButton.Click += new System.EventHandler( this.OkButton_Click );
}
#endregion
}
}
如你所的,唯一在我的程式所需的,就是implement我之前所提到的IVitalsView interface。 且把我的的ASP.NET Web Controls到IVitalViews所定的存取料的methods。然,我需要保我的view class面有一controller object,且呼叫controller面的methods。如果我用方法User Interface,你View的程得非常容易。而且因所有的工作都在controller面完成,你可以到你大部分的程式,你你的程式非常的有信心。
Test-Driven Development是一很有威力的工具,你立刻上就使用工具提你程式的品,以及你程式作的功力。使用TDD你更加仔思考你的程式是否完善,且保所有你的程式都是千垂百的(:我承我在此了)。如果你有完善的Unit Test,要你有的程式作Refactoring是乎不可能的事。一但跳入TDD的洪流之中,大部分的人都不再回用以前的老方法程式。看,你,嗯~,真的是粉棒的啦。