最近因工作的关系又再次搞了一些时候的Delphi开发因为Java的影响也就开始思想如何在Delphi开发中用TDD的方法这篇文章就是要谈谈TDD在Delphi中的应用我想网上已有很多的文章谈到这方面的话题我这篇也只是谈谈我自己的经验而已
那从何说起呢?用个具体的例子会更好的说明问题试想我们想开个动物繁殖中心不过我们现阶段还没有特定的动物先把这中心搞起来再说不过中心是离不开动物的我们就先定义个接口
IAnimal = Interface
procedure Mate
procedure Eat
procedure GiveBirth
End
这个接口定义了每种动物的三个基本功能Eat(吃) Mate(交配) GiveBirth(生产) 为了简单起见我们只处理母的公的不在考虑之内有了接口我们就可以定义繁殖中心
TAnimalCare= class
private
FIntf IAnimal
public
constructor Create(AnIntf IAnimal)
procedure Reproduction
end
TAnimalCare 是处理繁殖的一个类它需要依靠IAnimal才可以繁殖不过它并没有锁定任何的动物
所以在建立时需要注人一个基于IAnimal的类
有了IAnimal TAnimalCare 我们需要个测试Project来对它们进行测试DUnit是Delphi的很好的单元测试工具如你用的是Delphi 版 DUnit已经在里面 如用的是早些的版本可在DUnit下载安装
建立好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 TestTAnimalCareSetUp
begin
FMock = TAnimalMockCreate
FAnimalCare = TAnimalCare
Create(FMock)
end
有了TAnimalMock我们已基本满足了编译的要求可以通过编译是可以开始TDD的时候了让我们先完善TestReproduction 先写些期望然后verify看有没有达到
procedure TestTAnimalCareTestReproduction
begin
// Expectations
FMockExpects(Mate)
FMockExpects(Eat)
FMockExpects(GiveBirth)
FAnimalCareReproduction
FMockVerify(check)
end
这些期望是很自然的交配(mate)营养(eat)是生产(GiveBirth)的必须条件否则是无法繁殖的我们跑一下测试失败
TestReproduction EMockVerifyException
at $BA
check
Unexpected call to GiveBirth() <—— Dont match expectations
Expected
Mate()
Eat()
GiveBirth()
Called
GiveBirth() <—— Dont match expectations
为什么?看看
procedure TAnimalCareReproduction
begin
FintfGiveBirth
end
原来我们只调用GiveBirth而没调用Mate() Eat() 不合我们的期望改成如下
procedure TAnimalCareReproduction
begin
FIntFMate
FIntFEat
FintfGiveBirth
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 TAnimalCareCreate(AnIntf: IAnimal);
begin
inherited Create;
FintF := AnIntf;
end;
procedure TAnimalCareReproduction;
begin
FIntFMate;
FIntFEat;
FintfGiveBirth;
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 TestTAnimalCareSetUp;
begin
FMock := TAnimalMockCreate;
FAnimalCare := TAnimalCareCreate(FMock);
end;
procedure TestTAnimalCareTearDown;
begin
FAnimalCareFree;
FAnimalCare := nil;
FMockFree;
FMock := nil;
end;
procedure TestTAnimalCareTestReproduction;
begin
// Expectations
FMockExpects(Mate);
FMockExpects(Eat);
FMockExpects(GiveBirth);
FAnimalCareReproduction;
FMockVerify(check);
end;
{ TAnimalMock }
procedure TAnimalMockEat;
begin
selfAddCall(Eat);
end;
procedure TAnimalMockGiveBirth;
begin
selfAddCall(GiveBirth);
end;
procedure TAnimalMockMate;
begin
selfAddCall(Mate);
end;
initialization
RegisterTest(TestTAnimalCareSuite);
end