2011-12-29 94 views
2

我花了很多時間考慮單元測試。我至少買了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; 
+0

MsxSupport是一個很大的可怕依賴項,它在測試中不起作用嗎?還是隻是編譯器找不到它的問題?如果它在測試中不起作用,那麼你的「接口」單元不應該首先依賴它 - 你可能需要另一層間接尋址。如果編譯器找不到它,那麼您只需更新測試項目的搜索路徑或軟件包列表。 – 2011-12-29 15:44:34

+1

在這一點上,我不會擔心你需要添加到測試項目中的單元,將它們全部添加或使用搜索路徑。此時,我應該嘗試限制對象之間的依賴關係。要測試你的TPerson,你不應該先創建一個數十億個其他的對象。如果你這樣做,先嚐試解決*這些*依賴關係(DI someone?)。最後,這也會緩解一些單位間的依賴關係,但就像我說的那樣,我現在不會爲這些問題憂心忡忡。 – 2011-12-29 15:46:02

+0

推薦閱讀:xUnit測試模式:Gerard Meszaros的重構測試代碼 – mjn 2011-12-29 16:47:22

回答

1

事我會做的事:

  • 分支項目,使所有的變化可以在一個安全的「沙箱」
  • 運行專家包使用清潔劑來完成(或類似工具)的清除單元依賴關係
  • 將所有必需單元添加到測試項目dpr中,以使它們成爲文檔,除了庫路徑上的知名依賴關係的第三方庫
  • 根據可用資源制定計劃:搜索意想不到的相關性(用於更深入的分析)或者低掛果(可以無風險地移除)。使用限制性構建腳本進行持續集成,只爲所需的第三方庫指定庫路徑可能會有很大幫助(只要開發人員引入新的依賴關係,構建就會中斷)
  • 不時合併「安全」更改回幹線和使用最新的中繼版本開始新的迭代
+0

cnWizards使用清潔程序+1。沒有意識到這一點。 – 2011-12-30 07:19:16

相關問題