Practicing Test-Driven Development by Example Using
零雨其蒙原创 转载请注明
1 测试驱动开发
测试驱动开发不是什么噱头,而是真正有用的开发实践。今天派给我一个任务,让我解决一下退休提醒功能的Bug,我没看出原来的代码有何错误,不过觉得设计思路不十分的好:将数据库中所有的员工都取出来,然后再筛选应该被提醒的员工。而我觉得应该直接到数据库中去做筛选,返回大量无用的数据是资源的巨大浪费(我们在甲方公司里面开发,其网络之差令人发指)。
正好,我在研究TDD(本文有时指的是测试驱动开发,有时指的是测试驱动设计,因为两者都是存在的),想想何不来一次彻底的实践,真正的、完整的来一次TDD,体味一下其中的乐趣。
由于我们的开发工具是Delphi,因此自动测试工具自然而然就要使用DUnit了。尽管有的人说TDD不一定非得用自动测试框架,我也在使用VB进行OO系统开发时,用自制的测试程序进行测试,不过觉得那样都有一种不爽的感觉。因为总需要去维护复杂的测试代码,不能全力投入到测试驱动设计中。
2 准备工作
首先介绍一下业务:
很简单,就是在一个界面上显示即将退休的人员,具体提前多少天显示是从数据库中读取的参数。
然后配置DUnit环境,网上有n多教程,然后安装了一个DUnit plug-in插件,方便开发,网上也有讲解。
将Stop on Delphi Exception前的对号取消,这样就不会在出现异常时跳出了。
3 开始TDD之旅
本文是我进行TDD的实践记录,当然其间的思考要比这个多一些,不过主体部分基本都包含了,而且绝对写实。本文不是TDD的颂歌,我也提出了自己在实践中遇到的困难和疑惑。希望能给读者带来启示。
创建工程文件HR.dpr,然后使用DUnit plug-in,New Project,就自动在HR.dpr所在文件夹建了一个dunit文件夹,新建的测试工程默认名为HRTests,这是很好的规范,默认即可。然后New TestModule,建立一个测试单元。
接下来的工作就是在这两个同时开着的工程中开始工作了,一会我会切换到HRTests编写测试用例,一会我会在HR下编写产品代码,然后再回到HRTests下运行Dunit,进行测试。
3.1 领域驱动设计
首先构建领域层,领域概念就是退休(Retired),退休人员(EmployeeRetired)了。
先创建这两个类,不少文章说先建立测试用例,然后测试时肯定显示红条,因为被测试的类还没有建立,我觉得没建立的话连编译都过不了,怎么运行DUnit啊?
然后就可以开始根据想象编写测试用例了。思考对象的责任和工作方式,然后切换到产品工程添加这些责任。(有点像一边画顺序图一边画类图进行责任分配)
首先,我创建类TRetire
给TRetire类分配一个责任:查找退休提醒参数:
{ 作者:零雨其蒙
创建时间:
Blog:blog.csdn.net/sslaowan
www.blogjava.net/sslaowan
}
function TRetire.getretireAwokeParaList: TObjectList;
var paraList:TObjectList;
begin
end;
这是一个空壳,没有实际的内容,然后切换到HRTest工程,会出现下面的对话框。
在HR工程做了任何改动,保存后,都会在HRTest中有提醒。
可能很多人从来没见过测试用例长什么样子,下面就给出一个完整的例子。
{ 作者:零雨其蒙
创建时间:
Blog:blog.csdn.net/sslaowan
www.blogjava.net/sslaowan
}
unit HRTestsTests;
interface
uses
TestFrameWork,
URetire,
Contnrs;
type
TTestRetire=class(TTestCase)
private
retire:TRetire;
retirePara:TRetirePara;
protected
procedure SetUp; override;
procedure TearDown; override;
published
procedure testGetretireAwokeParaList;
end;
implementation
function UnitTests: ITestSuite;
var
ATestSuite: TTestSuite;
begin
ATestSuite := TTestSuite.Create('Retire tests');
ATestSuite.AddTests(TTestRetire);
Result := ATestSuite;
end;
{ TTestRetire }
procedure TTestRetire.SetUp;
begin
inherited;
retire:=TRetire.Create;
retirePara:=TRetirePara.Create;
end;
procedure TTestRetire.TearDown;
begin
inherited;
retire.Free;
retirePara.Free;
end;
procedure TTestRetire.testGetretireAwokeParaList;
var paraList:TObjectList;
begin
paraList:=retire.getretireAwokeParaList;
retirePara:=TRetirePara(paraList.Items[0]);
check(retirePara._emp_type='管理人员');
check(retirePara._sex='男');
check(retirePara._retireage='60');
check(retirePara._uptime='90');
retirePara:=TRetirePara(paraList.Items[1]);
check(retirePara._emp_type='管理人员');
check(retirePara._sex='女');
check(retirePara._retireage='55');
check(retirePara._uptime='90');
retirePara:=TRetirePara(paraList.Items[2]);
check(retirePara._emp_type='工人');
check(retirePara._sex='男');
check(retirePara._retireage='60');
check(retirePara._uptime='90');