我花了很多時間考慮單元測試。我至少買了Working Effectively with Legacy Code作爲電子書。它大部分是有意義的,它似乎是一本關於單元測試舊代碼的好書。但我認爲我們需要一個起點,因爲我們的Attracs項目很大。另請參閱關於單元測試的我的基因question。如何打破依賴關係以啓用單元測試
該應用程序有一個UML模型來定義類,屬性和關係,並使用Delphi的Bold。在模型的每一次改變之後,我們都會進行往返。這會自動爲文件businessclasses.pas和BusinessClasses_Interface.inc中的方法生成聲明。如果更改需要數據庫中的更改,則還會生成SQL腳本。這種方式多年來運行良好,但我們從未使用任何單元測試。
因此,我添加一個新的測試項目,然後依賴關係導致麻煩。 我
[DCC錯誤] Attracs_Interface_Uses.inc(10):找不到F1026文件: 'MsxSupport.dcu'
所以總結錯誤
AttracsTest.dpr使用
BusinessClasses.pas使用
BusinessClasses_Interface.inc使用
Attracs_Interface_Uses.inc
那麼我怎麼能打破依賴鏈?
請注意,實際上文件要大得多。模型中有300多個類,businessClasses.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.
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_Interface_Uses
AttracsDefs,
atXMLObjModel,
XMLObjModel,
XMLParser,
Contnrs,
XMLIntf,
ArrayOfObject,
BoldDBInterfaces,
MsxSupport // Line that compiler complain about
BusinessClasses_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}
Businessclasses.pas
(*****************************************)
(* 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.
人。INC
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;
MsxSupport是一個很大的可怕依賴項,它在測試中不起作用嗎?還是隻是編譯器找不到它的問題?如果它在測試中不起作用,那麼你的「接口」單元不應該首先依賴它 - 你可能需要另一層間接尋址。如果編譯器找不到它,那麼您只需更新測試項目的搜索路徑或軟件包列表。 – 2011-12-29 15:44:34
在這一點上,我不會擔心你需要添加到測試項目中的單元,將它們全部添加或使用搜索路徑。此時,我應該嘗試限制對象之間的依賴關係。要測試你的TPerson,你不應該先創建一個數十億個其他的對象。如果你這樣做,先嚐試解決*這些*依賴關係(DI someone?)。最後,這也會緩解一些單位間的依賴關係,但就像我說的那樣,我現在不會爲這些問題憂心忡忡。 – 2011-12-29 15:46:02
推薦閱讀:xUnit測試模式:Gerard Meszaros的重構測試代碼 – mjn 2011-12-29 16:47:22