X
xfz8124
Unregistered / Unconfirmed
GUEST, unregistred user!
学习TDD已有半年以上了,一开始的时候,看完了Kent Back的《解析极限编程--掏腰包变化》这本书-中译本,以及《探索极限编程》等一些相关的TDD的书,也看了Kent Beck最新的一本《测试驱动开发》中译本,可能由于文化背景的差异吧,Kent举了个例子,但我看了却没多少味道,有些收获但是不大。同时,也在互联网上疯狂搜索关于TDD的资料,但很不幸,找到的资料少之又少,最流行的还是那个DUnit原带的说明文档,有个叫谢慧强的人做了翻译,但是像其中测试1+1是不是等于2这样的问题,对学习者的用处仅仅展示了DUunit何时报错及如何报错的问题。
对比于1+1=2成立否这样的简单问题,在我们软件中的逻辑要复杂的多,那又怎么测试呢?而且更多的时候,测试的期望不能简单的用函数返回值来确定,因为大家也都知道,函数是有副作用的,在执行的过程中会修改一些东西,然后返回一个值,有时候我们在有意得利用这种副作用,如何测试?
基于组件的开发,对每个组件,作为黑箱来看,大体分两部分,输入和输出,输入表现为设定苦于属性值,调用一系列流程性函数,输出表现为对属性的读取,触发的事件,对磁盘文件系统的操作,等等,如何测试?
你怎么证明一个控件的事件发生了呢,而且其中的实际参数值正是组件编写者期望认为的那样?比如,你怎么证明每个窗体在初始化时一定会触发OnCreate事件?当然你在Form.OnCreate里写上Showmessage也能行,但那不是TDD,TDD是不弹出消息的,并且基本上只需要非常少的鼠标操作,对每个TestCase,流程都是一步到位的。
从网上对TDD资料存在情况看,国内外使用TDD的团队或个人并不多,但是Kent Back, Matin Fowler这些人,颠覆了我对软件开发的认识思想,有TDD开发经验的人可以剪烛西窗,或奇文共赏,本人在掌握了一些TDD开发经验之后,也在期待有人能够提出更有价值的问题,对没有听说TDD的人,或从来没有尝试过这种开发方式的,我不想理会。
本人近期在开发一个教师测评卡算分模块,及动态FR报表模块,正在开发之中。评分模块基本定型,其TDD源码发于下(不含单元源码,仅提供DCU文件):
unit TestUnit_MarkSp;
{设计目标:评分单元测试
建立日期:2006.02.14
作者:月夜风筝
}
interface
uses
TestFramework, Variants, Classes, Dialogs, Windows, Unit_MarkSp, Forms, SysUtils, Controls,
StdCtrls, Graphics, Messages, IniFiles;
const
Cons_Codever = '1.13 @ 06.02.28';
type
TestTMarkSp = class(TTestCase)
strict private
FMarkSp: TMarkSp;
function VerifyCaseResult(AKeyFile: string; AMarker: TMarkSp): Boolean;
public
procedure SetUp; override;
procedure TearDown; override;
published
procedure Test_OnePaper_SurePos_perpendicular;
procedure Test_Twopaper_SurePos_perpendicular;
procedure Test_TwoPaper_DynamicPos_perpendicular;
procedure Test_TwoPaper_DynamicPos_MoreTeacher_perpendicular;
end;
function LoadCaseData(AInputFile: string; AMarker: TMarkSp): Boolean;
implementation
function LoadCaseData(AInputFile: string; AMarker: TMarkSp): Boolean;
const
Options = 'Options';
var
CardMen, CardAmt, CardItem: Integer;
ItemName, ItemKeys, ItemCosts: string;
TEvalDtList, TIDList: string;
i: Integer;
ini: TIniFile;
ReadString: string;
Sects, Idents, Values: TStrings;
EvalListStart, EvalListLen, IDListStart, IDListLen: Integer;
begin
Sects:= TStringList.Create;
Idents:= TStringList.Create;
Values:= TStringList.Create;
AMarker.ClearData;
ini:= TIniFile.Create(AInputFile);
CardMen:= ini.ReadInteger(Options, 'CardMen', -1);
CardItem:= ini.ReadInteger(Options, 'CardItem', -1);
CardAmt:= ini.ReadInteger(Options, 'CardAmt', -1);
AMarker.SetCardSizeAmount(CardMen, CardItem, CardAmt);
ini.ReadSection('ItemNames', Sects);
ini.ReadSection('ItemKeys', Idents);
ini.ReadSection('ItemCosts', Values);
for i:= 0 to Idents.Count -1 do
begin
ItemName:= Sects;
ItemKeys:= Idents;
ItemCosts:= Values;
ItemName:= ini.ReadString('ItemNames', ItemName, '');
ItemKeys:= ini.ReadString('ItemKeys', ItemKeys, '');
ItemCosts:= ini.ReadString('ItemCosts', ItemCosts, '');
AMarker.RegisItem(ItemName, ItemKeys, ItemCosts);
end;
AMarker.RegisDone;
EvalListStart:= ini.ReadInteger(Options, 'EvalListStart', -1);
EvalListLen:= ini.ReadInteger(Options, 'EvalListLen', -1);
IDListStart:= ini.ReadInteger(Options, 'IDListStart', -1);
IDListLen:= ini.ReadInteger(Options, 'IDListLen', -1);
ini.ReadSection('ReadStrings', Idents);
for i:= 0 to Idents.Count - 1 do
begin
ReadString:= Idents;
ReadString:= ini.ReadString('ReadStrings', ReadString, '');
TEvalDtList:= Copy(ReadString, EvalListStart, EvalListLen);
TIDList:= Copy(ReadString, IDListStart, IDListLen);
AMarker.AddPaper(TEvalDtList, TIDList);
end;
ini.Free;
AMarker.ExecCalc;
Values.Free;
Idents.Free;
Sects.Free;
end;
function TestTMarkSp.VerifyCaseResult(AKeyFile: string; AMarker: TMarkSp): Boolean;
var
i, j: Integer;
ini: TIniFile;
Sects: TStrings;
ATeacherID: Integer;
Verify_Score: Double;
Ret_Score: Double;
AKeyChar: Char;
Verify_HitCount: Integer;
Ret_HitCount: Integer;
begin
ini:= TIniFile.Create(AKeyFile);
Sects:= TStringList.Create;
ini.ReadSections(Sects);
for i:= 0 to Sects.Count - 1 do
begin
ATeacherID:= StrToInt(Copy(Sects, 4, 4));
Ret_Score := AMarker.GetScore(ATeacherID);
Verify_Score:= ini.ReadInteger(Sects, 'Score', -1);
if Ret_Score <> Verify_Score then
Fail('EspF:分数非预期!');
ATeacherID:= StrToInt(Copy(Sects, 4, 4));
for j:= Ord('A') to Ord('D') do
begin
AKeyChar:= Chr(j);
Ret_HitCount := AMarker.GetVotedCount(ATeacherID, AKeyChar);
Verify_HitCount:= ini.ReadInteger(Sects, AKeyChar, -1);
if Ret_HitCount <> Verify_HitCount then
Fail('EspF:命中数非预期!');
end;
end;
Sects.Free;
ini.Free;
end;
procedure TestTMarkSp.SetUp;
begin
FMarkSp := TMarkSp.Create;
end;
procedure TestTMarkSp.TearDown;
begin
FMarkSp.Free;
FMarkSp := nil;
end;
procedure TestTMarkSp.Test_OnePaper_SurePos_perpendicular;
begin
LoadCaseData(ExtractFilePath(ParamStr(0)) + 'Input/Case 0 - 虚拟数据(1张卡).ini', FMarkSp);
VerifyCaseResult(ExtractFilePath(ParamStr(0)) + 'Verify/Case 0 - 虚拟数据(1张卡)Key.ini', FMarkSp);
end;
procedure TestTMarkSp.Test_Twopaper_SurePos_perpendicular;
begin
LoadCaseData(ExtractFilePath(ParamStr(0)) + 'Input/Case 1 - 虚拟数据(2张卡).ini', FMarkSp);
VerifyCaseResult(ExtractFilePath(ParamStr(0)) + 'Verify/Case 1 - 虚拟数据(2张卡)Key.ini', FMarkSp);
end;
procedure TestTMarkSp.Test_TwoPaper_DynamicPos_perpendicular;
begin
LoadCaseData(ExtractFilePath(ParamStr(0)) + 'Input/Case 2 - 虚拟数据(2张卡).ini', FMarkSp);
VerifyCaseResult(ExtractFilePath(ParamStr(0)) + 'Verify/Case 2 - 虚拟数据(2张卡)Key.ini', FMarkSp);
end;
procedure TestTMarkSp.Test_TwoPaper_DynamicPos_MoreTeacher_perpendicular;
begin
LoadCaseData(ExtractFilePath(ParamStr(0)) + 'Input/Case 3 - 虚拟数据(2张卡).ini', FMarkSp);
VerifyCaseResult(ExtractFilePath(ParamStr(0)) + 'Verify/Case 3 - 虚拟数据(2张卡)Key.ini', FMarkSp);
end;
initialization
RegisterTest(TestTMarkSp.Suite);
end.
下面的是记事本文件Case 0 - 虚拟数据(1张卡).ini,也就是测试用例的输入内容
Case 0 - 虚拟数据(1张卡).ini
[Options]
CardMen = 15
CardItem = 9
CardAmt = 1
EvalListStart = 65
EvalListLen = 1000
IDListStart = 5
IDListLen = 60
[ReadStrings]
0 = A101000100020003000400050006000700080009001000110012001300140015ADBABDCBCADBADDDDDCBBCDADADCDAADCCCDDCCDADDCABDCDADABBCACDDBADDBDCDDAADADBAACCDCACAADCCCBCDDBBADDACBCABBCDADCAADCDABCADBDCDBCDDCABDDBBA
[ItemNames]
0 = a
1 = b
2 = c
3 = d
4 = e
5 = f
6 = g
7 = h
8 = i
[ItemKeys]
0 = A,B,C,D
1 = A,B,C,D
2 = A,B,C,D
3 = A,B,C,D
4 = A,B,C,D
5 = A,B,C,D
6 = A,B,C,D
7 = A,B,C,D
8 = A,B,C,D
[ItemCosts]
0 = 4,3,2,1
1 = 4,3,2,1
2 = 4,3,2,1
3 = 4,3,2,1
4 = 4,3,2,1
5 = 4,3,2,1
6 = 4,3,2,1
7 = 4,3,2,1
8 = 4,3,2,1
下面是Key文件Case 0 - 虚拟数据(1张卡)Key.ini,也是用于组件输出结果校验的文件,里面存储了预期理论值
Case 0 - 虚拟数据(1张卡)Key.ini
[ID_0001]
A=2
B=3
C=2
D=2
Score=23
[ID_0002]
A=2
B=1
C=0
D=6
Score=17
[ID_0014]
A=1
B=2
C=2
D=4
Score=18
[ID_0015]
A=2
B=3
C=1
D=3
Score=22
对比于1+1=2成立否这样的简单问题,在我们软件中的逻辑要复杂的多,那又怎么测试呢?而且更多的时候,测试的期望不能简单的用函数返回值来确定,因为大家也都知道,函数是有副作用的,在执行的过程中会修改一些东西,然后返回一个值,有时候我们在有意得利用这种副作用,如何测试?
基于组件的开发,对每个组件,作为黑箱来看,大体分两部分,输入和输出,输入表现为设定苦于属性值,调用一系列流程性函数,输出表现为对属性的读取,触发的事件,对磁盘文件系统的操作,等等,如何测试?
你怎么证明一个控件的事件发生了呢,而且其中的实际参数值正是组件编写者期望认为的那样?比如,你怎么证明每个窗体在初始化时一定会触发OnCreate事件?当然你在Form.OnCreate里写上Showmessage也能行,但那不是TDD,TDD是不弹出消息的,并且基本上只需要非常少的鼠标操作,对每个TestCase,流程都是一步到位的。
从网上对TDD资料存在情况看,国内外使用TDD的团队或个人并不多,但是Kent Back, Matin Fowler这些人,颠覆了我对软件开发的认识思想,有TDD开发经验的人可以剪烛西窗,或奇文共赏,本人在掌握了一些TDD开发经验之后,也在期待有人能够提出更有价值的问题,对没有听说TDD的人,或从来没有尝试过这种开发方式的,我不想理会。
本人近期在开发一个教师测评卡算分模块,及动态FR报表模块,正在开发之中。评分模块基本定型,其TDD源码发于下(不含单元源码,仅提供DCU文件):
unit TestUnit_MarkSp;
{设计目标:评分单元测试
建立日期:2006.02.14
作者:月夜风筝
}
interface
uses
TestFramework, Variants, Classes, Dialogs, Windows, Unit_MarkSp, Forms, SysUtils, Controls,
StdCtrls, Graphics, Messages, IniFiles;
const
Cons_Codever = '1.13 @ 06.02.28';
type
TestTMarkSp = class(TTestCase)
strict private
FMarkSp: TMarkSp;
function VerifyCaseResult(AKeyFile: string; AMarker: TMarkSp): Boolean;
public
procedure SetUp; override;
procedure TearDown; override;
published
procedure Test_OnePaper_SurePos_perpendicular;
procedure Test_Twopaper_SurePos_perpendicular;
procedure Test_TwoPaper_DynamicPos_perpendicular;
procedure Test_TwoPaper_DynamicPos_MoreTeacher_perpendicular;
end;
function LoadCaseData(AInputFile: string; AMarker: TMarkSp): Boolean;
implementation
function LoadCaseData(AInputFile: string; AMarker: TMarkSp): Boolean;
const
Options = 'Options';
var
CardMen, CardAmt, CardItem: Integer;
ItemName, ItemKeys, ItemCosts: string;
TEvalDtList, TIDList: string;
i: Integer;
ini: TIniFile;
ReadString: string;
Sects, Idents, Values: TStrings;
EvalListStart, EvalListLen, IDListStart, IDListLen: Integer;
begin
Sects:= TStringList.Create;
Idents:= TStringList.Create;
Values:= TStringList.Create;
AMarker.ClearData;
ini:= TIniFile.Create(AInputFile);
CardMen:= ini.ReadInteger(Options, 'CardMen', -1);
CardItem:= ini.ReadInteger(Options, 'CardItem', -1);
CardAmt:= ini.ReadInteger(Options, 'CardAmt', -1);
AMarker.SetCardSizeAmount(CardMen, CardItem, CardAmt);
ini.ReadSection('ItemNames', Sects);
ini.ReadSection('ItemKeys', Idents);
ini.ReadSection('ItemCosts', Values);
for i:= 0 to Idents.Count -1 do
begin
ItemName:= Sects;
ItemKeys:= Idents;
ItemCosts:= Values;
ItemName:= ini.ReadString('ItemNames', ItemName, '');
ItemKeys:= ini.ReadString('ItemKeys', ItemKeys, '');
ItemCosts:= ini.ReadString('ItemCosts', ItemCosts, '');
AMarker.RegisItem(ItemName, ItemKeys, ItemCosts);
end;
AMarker.RegisDone;
EvalListStart:= ini.ReadInteger(Options, 'EvalListStart', -1);
EvalListLen:= ini.ReadInteger(Options, 'EvalListLen', -1);
IDListStart:= ini.ReadInteger(Options, 'IDListStart', -1);
IDListLen:= ini.ReadInteger(Options, 'IDListLen', -1);
ini.ReadSection('ReadStrings', Idents);
for i:= 0 to Idents.Count - 1 do
begin
ReadString:= Idents;
ReadString:= ini.ReadString('ReadStrings', ReadString, '');
TEvalDtList:= Copy(ReadString, EvalListStart, EvalListLen);
TIDList:= Copy(ReadString, IDListStart, IDListLen);
AMarker.AddPaper(TEvalDtList, TIDList);
end;
ini.Free;
AMarker.ExecCalc;
Values.Free;
Idents.Free;
Sects.Free;
end;
function TestTMarkSp.VerifyCaseResult(AKeyFile: string; AMarker: TMarkSp): Boolean;
var
i, j: Integer;
ini: TIniFile;
Sects: TStrings;
ATeacherID: Integer;
Verify_Score: Double;
Ret_Score: Double;
AKeyChar: Char;
Verify_HitCount: Integer;
Ret_HitCount: Integer;
begin
ini:= TIniFile.Create(AKeyFile);
Sects:= TStringList.Create;
ini.ReadSections(Sects);
for i:= 0 to Sects.Count - 1 do
begin
ATeacherID:= StrToInt(Copy(Sects, 4, 4));
Ret_Score := AMarker.GetScore(ATeacherID);
Verify_Score:= ini.ReadInteger(Sects, 'Score', -1);
if Ret_Score <> Verify_Score then
Fail('EspF:分数非预期!');
ATeacherID:= StrToInt(Copy(Sects, 4, 4));
for j:= Ord('A') to Ord('D') do
begin
AKeyChar:= Chr(j);
Ret_HitCount := AMarker.GetVotedCount(ATeacherID, AKeyChar);
Verify_HitCount:= ini.ReadInteger(Sects, AKeyChar, -1);
if Ret_HitCount <> Verify_HitCount then
Fail('EspF:命中数非预期!');
end;
end;
Sects.Free;
ini.Free;
end;
procedure TestTMarkSp.SetUp;
begin
FMarkSp := TMarkSp.Create;
end;
procedure TestTMarkSp.TearDown;
begin
FMarkSp.Free;
FMarkSp := nil;
end;
procedure TestTMarkSp.Test_OnePaper_SurePos_perpendicular;
begin
LoadCaseData(ExtractFilePath(ParamStr(0)) + 'Input/Case 0 - 虚拟数据(1张卡).ini', FMarkSp);
VerifyCaseResult(ExtractFilePath(ParamStr(0)) + 'Verify/Case 0 - 虚拟数据(1张卡)Key.ini', FMarkSp);
end;
procedure TestTMarkSp.Test_Twopaper_SurePos_perpendicular;
begin
LoadCaseData(ExtractFilePath(ParamStr(0)) + 'Input/Case 1 - 虚拟数据(2张卡).ini', FMarkSp);
VerifyCaseResult(ExtractFilePath(ParamStr(0)) + 'Verify/Case 1 - 虚拟数据(2张卡)Key.ini', FMarkSp);
end;
procedure TestTMarkSp.Test_TwoPaper_DynamicPos_perpendicular;
begin
LoadCaseData(ExtractFilePath(ParamStr(0)) + 'Input/Case 2 - 虚拟数据(2张卡).ini', FMarkSp);
VerifyCaseResult(ExtractFilePath(ParamStr(0)) + 'Verify/Case 2 - 虚拟数据(2张卡)Key.ini', FMarkSp);
end;
procedure TestTMarkSp.Test_TwoPaper_DynamicPos_MoreTeacher_perpendicular;
begin
LoadCaseData(ExtractFilePath(ParamStr(0)) + 'Input/Case 3 - 虚拟数据(2张卡).ini', FMarkSp);
VerifyCaseResult(ExtractFilePath(ParamStr(0)) + 'Verify/Case 3 - 虚拟数据(2张卡)Key.ini', FMarkSp);
end;
initialization
RegisterTest(TestTMarkSp.Suite);
end.
下面的是记事本文件Case 0 - 虚拟数据(1张卡).ini,也就是测试用例的输入内容
Case 0 - 虚拟数据(1张卡).ini
[Options]
CardMen = 15
CardItem = 9
CardAmt = 1
EvalListStart = 65
EvalListLen = 1000
IDListStart = 5
IDListLen = 60
[ReadStrings]
0 = A101000100020003000400050006000700080009001000110012001300140015ADBABDCBCADBADDDDDCBBCDADADCDAADCCCDDCCDADDCABDCDADABBCACDDBADDBDCDDAADADBAACCDCACAADCCCBCDDBBADDACBCABBCDADCAADCDABCADBDCDBCDDCABDDBBA
[ItemNames]
0 = a
1 = b
2 = c
3 = d
4 = e
5 = f
6 = g
7 = h
8 = i
[ItemKeys]
0 = A,B,C,D
1 = A,B,C,D
2 = A,B,C,D
3 = A,B,C,D
4 = A,B,C,D
5 = A,B,C,D
6 = A,B,C,D
7 = A,B,C,D
8 = A,B,C,D
[ItemCosts]
0 = 4,3,2,1
1 = 4,3,2,1
2 = 4,3,2,1
3 = 4,3,2,1
4 = 4,3,2,1
5 = 4,3,2,1
6 = 4,3,2,1
7 = 4,3,2,1
8 = 4,3,2,1
下面是Key文件Case 0 - 虚拟数据(1张卡)Key.ini,也是用于组件输出结果校验的文件,里面存储了预期理论值
Case 0 - 虚拟数据(1张卡)Key.ini
[ID_0001]
A=2
B=3
C=2
D=2
Score=23
[ID_0002]
A=2
B=1
C=0
D=6
Score=17
[ID_0014]
A=1
B=2
C=2
D=4
Score=18
[ID_0015]
A=2
B=3
C=1
D=3
Score=22