Unit testing 如何打破依赖关系以启用单元测试
我花了很多时间考虑单元测试。我至少买了一本电子书。大部分内容都很有意义,它似乎是一本关于单元测试旧代码的好书。但我仍然认为我需要一个起点,因为我们的Attracs项目很大。另请参见我关于单元测试的概述 该应用程序有一个UML模型来定义类、属性和关系,并使用Bold for Delphi。模型每次更改后,我们都会往返一次。这会自动为文件businessclasses.pas和businessclasses_Interface.inc中的方法生成声明。如果更改需要在数据库中进行更改,则还会生成SQL脚本。这已经很好地工作了很多年,但我们从未使用过任何单元测试 因此,我添加了一个新的testproject,然后依赖项会带来麻烦。 我得到 [DCC错误]Attracs_Interface_Uses.inc(10):未找到F1026文件:“MsxSupport.dcu” 总结一下错误 AttracsTest.dpr使用Unit testing 如何打破依赖关系以启用单元测试,unit-testing,delphi,dependencies,dunit,bold-delphi,Unit Testing,Delphi,Dependencies,Dunit,Bold Delphi,我花了很多时间考虑单元测试。我至少买了一本电子书。大部分内容都很有意义,它似乎是一本关于单元测试旧代码的好书。但我仍然认为我需要一个起点,因为我们的Attracs项目很大。另请参见我关于单元测试的概述 该应用程序有一个UML模型来定义类、属性和关系,并使用Bold for Delphi。模型每次更改后,我们都会往返一次。这会自动为文件businessclasses.pas和businessclasses_Interface.inc中的方法生成声明。如果更改需要在数据库中进行更改,则还会生成SQL
使用
使用
Attracs_Interface_Uses.inc.公司 那么如何打破依赖链呢? 请注意,实际上文件要大得多。模型和BusinessClass中有300多个类。pas有53000多行代码。。。 作为一个测试用例,我只有带有AddResponsibility方法的类TPerson。但是你应该理解这个原则 这是我的档案: AttracsTest.dpr
program AttracsTests;
{$IFDEF CONSOLE_TESTRUNNER}
{$APPTYPE CONSOLE}
{$ENDIF}
uses
Forms,
TestFramework,
GUITestRunner,
TextTestRunner,
BusinessClasses in '..\..\server\code\BusinessClasses.pas',
TestBusinessClasses in 'TestBusinessClasses.pas',
ArrayOfObject in '..\..\server\code\ArrayOfObject.pas';
{$R *.RES}
begin
Application.Initialize;
if IsConsole then
TextTestRunner.RunRegisteredTests
else
GUITestRunner.RunRegisteredTests;
end.
AttracsDefs,
atXMLObjModel,
XMLObjModel,
XMLParser,
Contnrs,
XMLIntf,
ArrayOfObject,
BoldDBInterfaces,
MsxSupport // Line that compiler complain about
(*****************************************)
(* This file is autogenerated *)
(* Any manual changes will be LOST! *)
(*****************************************)
unit BusinessClasses;
{$DEFINE BusinessClasses_unitheader}
{$INCLUDE BusinessClasses_Interface.inc}
{ Includefile for methodimplementations
Have concrete implementation of methods}
{$INCLUDE Person.inc}
// Some get and set methods fopr attributes in the class
// attribute FirstName
function TPerson._Get_M_FirstName: TBAString;
begin
assert(ValidateMember('TPerson', 'FirstName', 14, TBAString));
Result := TBAString(BoldMembers[14]);
end;
function TPerson._GetFirstName: String;
begin
Result := M_FirstName.AsString;
end;
procedure TPerson._SetFirstName(const NewValue: String);
begin
M_FirstName.AsString := NewValue;
end;
procedure InstallBusinessClasses(BoldObjectClasses: TBoldGeneratedClassList);
begin
BoldObjectClasses.AddObjectEntry('Person', TPerson);
end;
var
CodeDescriptor: TBoldGeneratedCodeDescriptor;
initialization
CodeDescriptor := GeneratedCodes.AddGeneratedCodeDescriptorWithFunc('BusinessClasses', InstallBusinessClasses, InstallObjectListClasses, GeneratedCodeCRC);
finalization
GeneratedCodes.Remove(CodeDescriptor);
end.
TestBusinessClasses.pas
unit TestBusinessClasses;
interface
uses
TestFramework,
ArrayOfObject,
AttracsAttributes,
AttracsDefs,
atXMLObjModel,
BoldAttributes
BoldDBInterfaces,
BoldDefs,
BoldDeriver,
BoldDomainElement,
BoldElements,
BoldSubscription,
BoldSystem,
BoldSystemRT,
BusinessClasses, // Trigger the dependency, but also contain info about the classes get and set methods for attributes.
Classes,
Contnrs,
SysUtils,
XMLIntf,
XMLObjModel,
XMLParser;
type
TestTPerson = class(TTestCase)
strict private
FPerson: TPerson;
public
procedure SetUp; override;
procedure TearDown; override;
published
procedure TestAddResponsibility;
end;
implementation
procedure TestTPerson.SetUp;
begin
FPerson := TPerson.Create;
end;
procedure TestTPerson.TearDown;
begin
FPerson.Free;
FPerson := nil;
end;
procedure TestTPerson.TestAddResponsibility;
var
ReturnValue: Boolean;
aSession: TLogonSession;
aDevType: TDevTypeDef;
aMarketArea: TMarketArea;
begin
// TODO: Setup method call parameters
ReturnValue := FPerson.AddResponsibility(aMarketArea, aDevType, aSession);
// TODO: Validate method results
end;
initialization
// Register any test cases with the test runner
RegisterTest(TestTPerson.Suite);
end.
Attracs\u接口\u使用
program AttracsTests;
{$IFDEF CONSOLE_TESTRUNNER}
{$APPTYPE CONSOLE}
{$ENDIF}
uses
Forms,
TestFramework,
GUITestRunner,
TextTestRunner,
BusinessClasses in '..\..\server\code\BusinessClasses.pas',
TestBusinessClasses in 'TestBusinessClasses.pas',
ArrayOfObject in '..\..\server\code\ArrayOfObject.pas';
{$R *.RES}
begin
Application.Initialize;
if IsConsole then
TextTestRunner.RunRegisteredTests
else
GUITestRunner.RunRegisteredTests;
end.
AttracsDefs,
atXMLObjModel,
XMLObjModel,
XMLParser,
Contnrs,
XMLIntf,
ArrayOfObject,
BoldDBInterfaces,
MsxSupport // Line that compiler complain about
(*****************************************)
(* This file is autogenerated *)
(* Any manual changes will be LOST! *)
(*****************************************)
unit BusinessClasses;
{$DEFINE BusinessClasses_unitheader}
{$INCLUDE BusinessClasses_Interface.inc}
{ Includefile for methodimplementations
Have concrete implementation of methods}
{$INCLUDE Person.inc}
// Some get and set methods fopr attributes in the class
// attribute FirstName
function TPerson._Get_M_FirstName: TBAString;
begin
assert(ValidateMember('TPerson', 'FirstName', 14, TBAString));
Result := TBAString(BoldMembers[14]);
end;
function TPerson._GetFirstName: String;
begin
Result := M_FirstName.AsString;
end;
procedure TPerson._SetFirstName(const NewValue: String);
begin
M_FirstName.AsString := NewValue;
end;
procedure InstallBusinessClasses(BoldObjectClasses: TBoldGeneratedClassList);
begin
BoldObjectClasses.AddObjectEntry('Person', TPerson);
end;
var
CodeDescriptor: TBoldGeneratedCodeDescriptor;
initialization
CodeDescriptor := GeneratedCodes.AddGeneratedCodeDescriptorWithFunc('BusinessClasses', InstallBusinessClasses, InstallObjectListClasses, GeneratedCodeCRC);
finalization
GeneratedCodes.Remove(CodeDescriptor);
end.
BusinessClasses\u Interface.inc
(*****************************************)
(* This file is autogenerated *)
(* Any manual changes will be LOST! *)
(*****************************************)
{$IFNDEF BusinessClasses_Interface.inc}
{$DEFINE BusinessClasses_Interface.inc}
{$IFNDEF BusinessClasses_unitheader}
unit BusinessClasses;
{$ENDIF}
{$INCLUDE Attracs.inc} //PATCH
interface
uses
// interface uses
{$INCLUDE Attracs_Interface_Uses.inc} ,
// interface dependencies
// attribute classes
AttracsAttributes,
BoldAttributes,
// other
Classes,
SysUtils,
BoldDefs,
BoldSubscription,
BoldDeriver,
BoldElements,
BoldDomainElement,
BoldSystemRT,
BoldSystem;
type
{ forward declarations of all classes }
TPerson = class;
TPerson = class(TAmStateObject)
public
function AddResponsibility(aMarketArea: TMarketArea; aDevType: TDevTypeDef; aSession: TLogonSession): Boolean;
end;
function GeneratedCodeCRC: String;
implementation
uses
// implementation uses
{$INCLUDE Attracs_Implementation_Uses.inc} ,
// implementation dependencies
// other
BoldGeneratedCodeDictionary;
{$ENDIF}
function TPerson.AddResponsibility(aMarketArea: TMarketArea; aDevType: TDevTypeDef; aSession: TLogonSession): Boolean;
var
vOCL: String;
vDevResponse: TDevResponsible;
begin
vOCL := Format('DevResponsible.allinstances->select((devType.TypeName = ''%s'') and (marketArea.name = ''%s''))->first',
[aDevType.TypeName, aMarketArea.name]);
vDevResponse := GetApplicationKernel.EvaluateExpressionAsDirectElement(vOCL) as TDevResponsible;
if not Assigned(vDevResponse) then
vDevResponse := GetApplicationKernel.CreateAMObject('DevResponsible') as TDevResponsible;
if Assigned(vDevResponse) then
begin
vDevResponse.marketArea := aMarketArea;
vDevResponse.devType := aDevType;
vDevResponse.responsiblePers := self;
NotifyModificationHistory(Now, aSession, Format('Responsible for %s marketarea: %s', [aDevType.TypeName, aMarketArea.Name]));
Result := True;
end
else
Result := False;
end;
商务舱。pas
program AttracsTests;
{$IFDEF CONSOLE_TESTRUNNER}
{$APPTYPE CONSOLE}
{$ENDIF}
uses
Forms,
TestFramework,
GUITestRunner,
TextTestRunner,
BusinessClasses in '..\..\server\code\BusinessClasses.pas',
TestBusinessClasses in 'TestBusinessClasses.pas',
ArrayOfObject in '..\..\server\code\ArrayOfObject.pas';
{$R *.RES}
begin
Application.Initialize;
if IsConsole then
TextTestRunner.RunRegisteredTests
else
GUITestRunner.RunRegisteredTests;
end.
AttracsDefs,
atXMLObjModel,
XMLObjModel,
XMLParser,
Contnrs,
XMLIntf,
ArrayOfObject,
BoldDBInterfaces,
MsxSupport // Line that compiler complain about
(*****************************************)
(* This file is autogenerated *)
(* Any manual changes will be LOST! *)
(*****************************************)
unit BusinessClasses;
{$DEFINE BusinessClasses_unitheader}
{$INCLUDE BusinessClasses_Interface.inc}
{ Includefile for methodimplementations
Have concrete implementation of methods}
{$INCLUDE Person.inc}
// Some get and set methods fopr attributes in the class
// attribute FirstName
function TPerson._Get_M_FirstName: TBAString;
begin
assert(ValidateMember('TPerson', 'FirstName', 14, TBAString));
Result := TBAString(BoldMembers[14]);
end;
function TPerson._GetFirstName: String;
begin
Result := M_FirstName.AsString;
end;
procedure TPerson._SetFirstName(const NewValue: String);
begin
M_FirstName.AsString := NewValue;
end;
procedure InstallBusinessClasses(BoldObjectClasses: TBoldGeneratedClassList);
begin
BoldObjectClasses.AddObjectEntry('Person', TPerson);
end;
var
CodeDescriptor: TBoldGeneratedCodeDescriptor;
initialization
CodeDescriptor := GeneratedCodes.AddGeneratedCodeDescriptorWithFunc('BusinessClasses', InstallBusinessClasses, InstallObjectListClasses, GeneratedCodeCRC);
finalization
GeneratedCodes.Remove(CodeDescriptor);
end.
person.inc
(*****************************************)
(* This file is autogenerated *)
(* Any manual changes will be LOST! *)
(*****************************************)
{$IFNDEF BusinessClasses_Interface.inc}
{$DEFINE BusinessClasses_Interface.inc}
{$IFNDEF BusinessClasses_unitheader}
unit BusinessClasses;
{$ENDIF}
{$INCLUDE Attracs.inc} //PATCH
interface
uses
// interface uses
{$INCLUDE Attracs_Interface_Uses.inc} ,
// interface dependencies
// attribute classes
AttracsAttributes,
BoldAttributes,
// other
Classes,
SysUtils,
BoldDefs,
BoldSubscription,
BoldDeriver,
BoldElements,
BoldDomainElement,
BoldSystemRT,
BoldSystem;
type
{ forward declarations of all classes }
TPerson = class;
TPerson = class(TAmStateObject)
public
function AddResponsibility(aMarketArea: TMarketArea; aDevType: TDevTypeDef; aSession: TLogonSession): Boolean;
end;
function GeneratedCodeCRC: String;
implementation
uses
// implementation uses
{$INCLUDE Attracs_Implementation_Uses.inc} ,
// implementation dependencies
// other
BoldGeneratedCodeDictionary;
{$ENDIF}
function TPerson.AddResponsibility(aMarketArea: TMarketArea; aDevType: TDevTypeDef; aSession: TLogonSession): Boolean;
var
vOCL: String;
vDevResponse: TDevResponsible;
begin
vOCL := Format('DevResponsible.allinstances->select((devType.TypeName = ''%s'') and (marketArea.name = ''%s''))->first',
[aDevType.TypeName, aMarketArea.name]);
vDevResponse := GetApplicationKernel.EvaluateExpressionAsDirectElement(vOCL) as TDevResponsible;
if not Assigned(vDevResponse) then
vDevResponse := GetApplicationKernel.CreateAMObject('DevResponsible') as TDevResponsible;
if Assigned(vDevResponse) then
begin
vDevResponse.marketArea := aMarketArea;
vDevResponse.devType := aDevType;
vDevResponse.responsiblePers := self;
NotifyModificationHistory(Now, aSession, Format('Responsible for %s marketarea: %s', [aDevType.TypeName, aMarketArea.Name]));
Result := True;
end
else
Result := False;
end;
我会做的事情:
- 分支项目,以便所有更改都可以在安全的“沙箱”中完成
- 运行cnWizards使用Cleaner(或类似工具)清理单元依赖项
- 将所有必需的单元添加到测试项目dpr中,以对其进行文档化,但位于库路径上的第三方库除外,这些库是众所周知的依赖项
- 根据可用资源制定计划:搜索意外的依赖项(用于更深入的分析)或低效的依赖项(可以在没有风险的情况下删除)。使用仅为所需第三方库指定库路径的限制性构建脚本进行持续集成会有很大帮助(每当开发人员引入新的依赖项时,构建就会中断)
- 不时地将“安全”更改合并回主干,并使用最新的主干版本开始新的迭代