当前位置:主页>仓库管理软件> 列表

关于TDD--以测试为主导的开发 找傻瓜进销存破解版

进销存软件版1楼:   学习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[i];
ItemKeys:= Idents[i];
ItemCosts:= Values[i];
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[i];
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[i], 4, 4));
Ret_Score := AMarker.GetScore(ATeacherID);
Verify_Score:= ini.ReadInteger(Sects[i], ''Score'', -1);
if Ret_Score <> Verify_Score then
Fail(''EspF:分数非预期!'');

ATeacherID:= StrToInt(Copy(Sects[i], 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[i], 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

2楼: 注意:在上面的源码中,凡包含AMarker调用的代码行,对测试来说,指明了所要开发的模块的特征 如傻瓜进销存破解版

3楼: meiyou kan dong

4楼: 等待高手的出现。。。。。。

5楼: 你说的“对比于1+1=2成立否这样的简单问题,在我们软件中的逻辑要复杂的多,那又怎么测试呢?而且更多的时候,测试的期望不能简单的用函数返回值来确定”。

TDD 及xp中是这样说的, 写一点点测试, 再写一点点代码, 由测试来驱动开发,不是先写好代码,再去测试代码。 他们是颠覆了我们的认知。
李维的《面向对象程序设计实践-delphi版》 中, 讲述的是 先做好主要的设计, 或者先做一点设计, 然后测试你的设计。 这本书我还没有看完,但是我觉得这样似乎不够敏捷。 当然我也只是在学习, 我的工作中,基本上不敢大着胆子,写一点点测试,再写一点点代码。 顶多, 在有不确定的代码时,test一下。 主要可能是我顾虑我的时间,没有完全熟练应 用之前, 摸索可能降低我的效率。
Kent Back的《测试驱动开发》我看了前面几章, 我觉得还是不错,只是自己没有真正应用于实践, 尤其是它的重构部分, 我觉得,掌握重构,并有良好的 重构习惯。 才可以游刃有余地实践Tdd 。
蔽人愚见, 楼下斧正。

6楼:   以我认为,让TDD发挥效力,需要三种基本技能:设计或模式(一开始只要应付最简场景,通过重构向模式演化)、重构、测试本身也算一种基本技能。最先实践TDD的程序员,最大的困惑不是TDD实用不实用,而是如果设计测试体系结构和逻辑的问题--我这里指的不是TestFramWork,而是用TestFramWork所设计出来的体系结构和逻辑。
  他们是颠覆了我们的认知,事实确实如此,而这也是TDD的思想所必需。
  为什么“写一点点测试,再写一点点代码”,这种方式不敏捷?因为你对你的测试缺少信心。测试本身就是正确的吗?测试本身也是一项软件工程,其中有也Bug,按照TDD的理念,测试代码不需要写的太好,Ctrl-C,Ctrl_V就可以,等到太乱的时候就先重构测试再开发别的,但是你在开发的模块就要认真一些。
  如果测试和正在开发的模块都是这样同时一点一点的写,可以说,你很可能体会不到TDD的实用之处,因为,你在编码的时候,不知道是在设计测试,还是在设计模块,TDD模式下,你不能同时设计这两件事,当在设计模块时,测试在发挥“检错和辅助Debug”的功能,当在设计测试时,测试本身表达了一种需求分析--也就是Xp的User Story概念
  花时间摸索是值得的,一开始确实不会有明显收效,还可能使开发效率下降,但是认可TDD思想的人,都不应错过大师给我们带来的机会。

进销存软件版7楼: 因为每个测试用例的流程都是一个Story,所以建议你在写测试的时候,可以一次写好几个测试,每个测试代表一项用途,每个测试用例的通行都对模块的进程来说是一个有纪念意义的里程,当你开发完这一轮测试,再升级你的测试,然后再去升级设计模块

8楼: 接受答案了.