最近因工作的关系又再次搞了一些时候的Delphi开发,因为Java的影响也就开始思想如何在Delphi开发中用TDD的方法, 这篇文章就是要谈谈TDD在Delphi中的应用。我想网上已有很多的文章谈到这方面的话题,我这篇也只是谈谈我自己的经验而已。
那从何说起呢?用个具体的例子会更好的说明问题。试想我们想开个动物繁殖中心,不过我们现阶段还没有特定的动物, 先把这中心搞起来再说,不过中心是离不开动物的,我们就先定义个接口:
这个接口定义了每种动物的三个基本功能:Eat(吃), Mate(交配), GiveBirth(生产), 为了简单起见,我们只处理母的, 公的不在考虑之内。有了接口我们就可以定义繁殖中心:
为什么?看看:
测试码如下:
- IAnimal = Interface
- procedure Mate;
- procedure Eat;
- procedure GiveBirth;
- End;
- TAnimalCare= class
- private
- FIntf : IAnimal;
- public
- constructor Create(AnIntf : IAnimal);
- procedure Reproduction;
- end;
TAnimalCare 是处理繁殖的一个类,它需要依靠IAnimal才可以繁殖,不过它并没有锁定任何的动物,
所以在建立时需要注人一个基于IAnimal的类。
有了IAnimal, TAnimalCare 我们需要个测试Project来对它们进行测试,DUnit是Delphi的很好的单元测试工具, 如你用的是Delphi 2007版, DUnit已经在里面, 如用的是早些的版本, 可在
DUnit下载,安装。
建立好Test Project后,我们可以加入下面的这个TestCase:
建立好Test Project后,我们可以加入下面的这个TestCase:
- TestTAnimalCare = class(TTestCase)
- strict private
- FAnimalCare: TAnimalCare;
- public
- procedure SetUp; override;
- procedure TearDown; override;
- published
- procedure TestReproduction;
- end;
在SetUp这里,需要个基于IAnimal的类才可以,至到目前我们并没有定义一个,同时我们也不需要,因为我们还没有特定的动物。 那么如何来满足这条件以便测试能进行下去呢?这里我们可以用到一种叫Mock的东西, 在Delphi可用
PascalMock, 大家可在这下载, 安装后,我们就可以定义个TAnimalMock:
- TAnimalMock = class(TMock, IAnimal)
- public
- procedure Mate;
- procedure Eat;
- procedure GiveBirth;
- end;
- procedure TestTAnimalCare.SetUp;
- begin
- FMock := TAnimalMock.Create;
- FAnimalCare := TAnimalCare.Create(FMock);
- end;
有了TAnimalMock我们已基本满足了编译的要求,可以通过编译。是可以开始TDD的时候了,让我们先完善TestReproduction, 先写些期望,然后verify,看有没有达到:
- procedure TestTAnimalCare.TestReproduction;
- begin
- // Expectations
- FMock.Expects('Mate');
- FMock.Expects('Eat');
- FMock.Expects('GiveBirth');
- FAnimalCare.Reproduction;
- FMock.Verify('check');
- end;
这些期望是很自然的,交配(mate),营养(eat)是生产(GiveBirth)的必须条件,否则是无法繁殖的,我们跑一下测试,失败:
- TestReproduction: EMockVerifyException
- at $0049B30A
- check
- Unexpected call to GiveBirth() <-- Don't match expectations
- Expected:
- Mate()
- Eat()
- GiveBirth()
- Called:
- GiveBirth() <-- Don't match expectations
- procedure TAnimalCare.Reproduction;
- begin
- Fintf.GiveBirth;
- end;
原来我们只调用GiveBirth而没调用Mate(), Eat(), 不合我们的期望,改成如下:
- procedure TAnimalCare.Reproduction;
- begin
- FIntF.Mate;
- FIntF.Eat;
- Fintf.GiveBirth;
- end;
再跑下测试,成功!
总结:
总结:
- 用Dependency Injection, 将基于IAnimal的类注入TAnimalCare, 把有关IAnimal的一些具体的实现从TAnimalCare中分离出来。
- 用Mock来实现一个基于IAnimal的类以便测试.
IAnimal, TAnimalCare码如下:
- unit MyObjects;
- interface
- uses
- classes;
- type
- IAnimal = Interface
- procedure Mate;
- procedure Eat;
- procedure GiveBirth;
- End;
- TAnimalCare= class
- private
- FIntf : IAnimal;
- public
- constructor Create(AnIntf : IAnimal);
- procedure Reproduction;
- end;
- implementation
- { TAnimalCare }
- constructor TAnimalCare.Create(AnIntf: IAnimal);
- begin
- inherited Create;
- FintF := AnIntf;
- end;
- procedure TAnimalCare.Reproduction;
- begin
- FIntF.Mate;
- FIntF.Eat;
- Fintf.GiveBirth;
- end;
- end.
- unit TestMyObjects;
- interface
- uses
- TestFramework, classes, MyObjects, PascalMock;
- type
- TAnimalMock = class(TMock, IAnimal)
- public
- procedure Mate;
- procedure Eat;
- procedure GiveBirth;
- end;
- TestTAnimalCare = class(TTestCase)
- strict private
- FMock : TAnimalMock;
- FAnimalCare: TAnimalCare;
- public
- procedure SetUp; override;
- procedure TearDown; override;
- published
- procedure TestReproduction;
- end;
- implementation
- procedure TestTAnimalCare.SetUp;
- begin
- FMock := TAnimalMock.Create;
- FAnimalCare := TAnimalCare.Create(FMock);
- end;
- procedure TestTAnimalCare.TearDown;
- begin
- FAnimalCare.Free;
- FAnimalCare := nil;
- FMock.Free;
- FMock := nil;
- end;
- procedure TestTAnimalCare.TestReproduction;
- begin
- // Expectations
- FMock.Expects('Mate');
- FMock.Expects('Eat');
- FMock.Expects('GiveBirth');
- FAnimalCare.Reproduction;
- FMock.Verify('check');
- end;
- { TAnimalMock }
- procedure TAnimalMock.Eat;
- begin
- self.AddCall('Eat');
- end;
- procedure TAnimalMock.GiveBirth;
- begin
- self.AddCall('GiveBirth');
- end;
- procedure TAnimalMock.Mate;
- begin
- self.AddCall('Mate');
- end;
- initialization
- RegisterTest(TestTAnimalCare.Suite);
- end.