请考虑以下代码:

type
  TFoo1 = class
  public
    procedure DoSomething1;
  end;

  TFoo2 = class
  private
    oFoo1 : TFoo1;
  public
    procedure DoSomething2;
    procedure DoSomething3;
    constructor Create;
    destructor Destroy; override;
  end;


procedure TFoo1.DoSomething1;
begin
  ShowMessage('TFoo1');
end;

constructor TFoo2.Create;
begin
  oFoo1 := TFoo1.Create;
end;

destructor TFoo2.Destroy;
begin
  oFoo1.Free;
  inherited;
end;

procedure TFoo2.DoSomething2;
begin
  oFoo1.DoSomething1;
end;

procedure TFoo2.DoSomething3;
var
  oFoo1 : TFoo1;
begin
  oFoo1 := TFoo1.Create;
  try
    oFoo1.DoSomething1;
  finally
    oFoo1.Free;
  end;
end;

我正在为一个类创建单元测试,但我被困在它上面。我的问题都是关于模拟对象的最佳方式和我应该使用的设计模式。我正在单元测试的类(class)不是我创建的。
  • 在以下示例中,我需要模拟 Foo1,因为它将请求发送到我在单元测试期间无法调用的 Web 服务。但是 Foo1 是由 TFoo2 构造函数创建的,我无法模拟它。在这种情况下我该怎么办?我应该修改 TFoo2 构造函数以接受这样的 Foo1 对象吗?
    constructor TFoo2.Create(aFoo1 : TFoo1)
    begin
      oFoo1 := aFoo1;
    end;
    

    是否有一种设计模式说我们需要传递一个类所依赖的所有对象,就像上面的例子一样?
  • 方法 TFoo2.DoSomething3 创建 Foo1 对象,然后释放它。我还应该修改该代码以传递 Foo1 对象吗?
    procedure TFoo2.DoSomething3(aFoo1 : TFoo1);
    begin
      aFoo1 := aFoo1.DoSomething1;
    end;
    
  • 是否有任何设计模式支持我提出的建议?如果是这样,我可以告诉我工作的公司中的所有开发人员,我们需要遵循 XXX 模式以使单元测试更容易。
  • 最佳答案

    如果你不能模拟 TFoo1 的创建,那么你就不能模拟 TFoo1 。现在, TFoo2 负责创建 TFoo1 的所有实例,但如果这不是 TFoo2 的主要目的,那么这确实会使单元测试变得困难。

    正如您所建议的那样,一种解决方案是传递 TFoo2 所需的任何 TFoo1 实例。这会使您当前所有已经调用 TFoo2 方法的代码变得复杂。另一种对单元测试更友好的方法是为 TFoo1 提供一个工厂。工厂可以像一个函数指针一样简单,也可以是一个完整的类。在 Delphi 中,元类也可以作为工厂。构造工厂时将工厂传递给 TFoo2,每当 TFoo2 需要 TFoo1 实例时,它就可以调用工厂。

    为了减少对其余代码的更改,您可以在 TFoo2 构造函数中使工厂参数具有默认值。这样您就不必更改应用程序代码。只需更改您的单元测试代码以提供非默认工厂参数。

    无论你做什么,你都需要使 TFoo1.DoSomething1 成为虚拟的,否则模拟将是徒劳的。

    使用元类,您的代码可能如下所示:

    type
      TFoo1 = class
        procedure DoSomethign1; virtual;
      end;
    
      TFoo1Class = class of TFoo1;
    
      TFoo2 = class
      private
        oFoo1 : TFoo1;
        FFoo1Factory: TFoo1Class;
      public
        constructor Create(AFoo1Factory: TFoo1Class = nil);
      end;
    
    constructor TFoo2.Create;
    begin
      inherited Create;
      FFoo1Factory := AFoo1Factory;
      if not Assigned(FFoo1Factory) then
        FFoo1Factory := TFoo1;
    
      oFoo1 := FFoo1Factory.Create;
    end;
    

    现在,您的单元测试代码可以提供 TFoo1 的模拟版本并在创建 TFoo2 时传递它:
    type
      TMockFoo1 = class(TFoo1)
        procedure DoSomething1; override;
      end;
    
    procedure TMockFoo1.DoSomething1;
    begin
      // TODO: Pretend to access Web service
    end;
    
    procedure TestFoo2;
    var
      Foo2: TFoo2;
    begin
      Foo2 := TFoo2.Create(TMockFoo1);
    end;
    

    许多元类示例为基类提供了一个虚拟构造函数,但这并不是绝对必要的。如果构造函数需要被虚拟调用,你只需要一个虚拟构造函数——如果后代构造函数需要使用基类尚未执行的构造函数参数来做一些事情。如果后代(在本例中为 TMockFoo1 )与其祖先执行所有相同的操作,则构造函数不需要是虚拟的。 (还要记住 AfterConstruction 已经是虚拟的,所以这是让后代执行额外操作而不需要虚拟构造函数的另一种方法。)

    关于delphi - 如何模拟另一个类负责实例化的类?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/5857735/

    10-09 22:00