2011-12-29 66 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

相关问题