3

我是一个依赖注入容器的新手,我正在尝试将它们与 Mocking 结合使用。

假设我有一个控制器和一个列表(模型):

IBlahList = interface
  property Items[AIndex: integer]: IBlah read GetItem;
end;

IController = interface
  property List: IBlahList read GetList;
end;

IController 的实现看起来像(注意,它在以下implementaion部分:

implementation

TController = class (TInterfacedObject, IController)
private
  FList: IBlahList;
  function GetList: IBlahList;

public
  constructor Create(const AList: IBlahList);

end;

然后,当然,我会注册这个类(以及 IBlahList 的一个)GlobalContainer

GlobalContainer.RegisterType<TController>.Implements<IController>;

implementation根据各种来源的建议(好吧,无论如何,尼克霍奇斯!),我将 TController 放在该部分中,这样我们就不能直接引用 TController 类。

现在,只是说我想在单元测试中测试我的 ICollection 实现:

procedure TestSomething
var
  LMockList: TMock<IBlahList>;
  LController: IController;
begin
  LMockList := TMock<IBlahList>.Create;

  // Oops, I can't do this, I can't access TController
  LController := TController.Create(LMockList);

end;

所以,我的问题是,我应该将 TController 类移到我的interface部分中以便我可以测试它,还是有其他方法可以将模拟 IBlahList 传递给我尚未找到的控制器?

4

3 回答 3

4

如果您在实现部分中有具体类,那么您可以公开一个工厂函数(即在接口部分中有它),它创建一个具有所需参数的 IController。

拥有一个无法实例化的实现绝对没有意义,IMO。

interface

...

function CreateController(AList: IBlahList): IController;

implementation

function CreateController(AList: IBlahList): IController;
begin
  Result := TController.Create(AList);
end;
于 2012-06-09T17:28:29.287 回答
2

好吧,您可能也应该在测试项目中使用模拟框架,但在这些情况下,我通常“作弊”并implementation使用 DUNIT 条件变量将其移动到我需要的地方:

// In the real app, we want the implementation and uses clauses here.
{$IFNDEF DUNIT}  
implementation
uses
  classes;
{$ENDIF}

type
  TClassUnderTest = class(TObject)
    // ...
  end;

// In test projects it is more convenient to have the implemenation and 
// uses clauses down here.
{$IFDEF DUNIT}
implementation
uses
  classes;
{$ENDIF}

然后确保任何测试项目都定义了 DUNIT 条件变量,并将 TClassUnderTest 声明所需的任何单元移动到接口部分。后者您可以永久执行或在 DUNIT 条件控制下执行。

于 2012-06-09T16:18:41.133 回答
2

我只能说:在这种情况下不要听尼克的。

将一个类放在一个单元的实现部分中只是有缺点,你正面临其中之一。

使用依赖注入的全部意义在于解耦你的代码片段。

现在你删除了 TController 的静态依赖和一些实现 IBlahList 的类,但是你引入了另一个(更糟糕的 imo)依赖:对 DI 容器的依赖。

不要将类放在单元的实现部分中,只是为了防止有人直接在您的生产代码中创建它。也不要将对 DI 容器的依赖项放入该单元中。

更好的方法是拥有 3 个单元:接口、类、注册。

编辑:我建议阅读这篇文章并注意下划线部分:http ://www.loosecouplings.com/2011/01/dependency-injection-using-di-container.html

Edit2 - 添加了一些伪代码来说明我的意思。单元测试代码可能与问题完全相同。

unit Interfaces;

interface

type
  IBlahList = interface
    property Items[AIndex: integer]: IBlah read GetItem;
  end;

  IController = interface
    property List: IBlahList read GetList;
  end;

implementation

end.

-

unit Controller;

interface

uses
  Classes,
  Interfaces;

type
  TController = class (TInterfacedObject, IController)
  private
    FList: IBlahList;
    function GetList: IBlahList;
  public
    constructor Create(const AList: IBlahList);
  end;

implementation

...

end.

-

unit Registration;

interface

implementation

uses
  Interfaces,
  Controller,
  Spring.Container;

initialization
  GlobalContainer.RegisterType<TController>.Implements<IController>;

end.
于 2012-06-10T13:00:14.357 回答