我是依赖注入容器的新手,我正在尝试将它们与模拟结合使用。
假设我有一个控制器和一个列表(模型):
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;
然后,当然,我会向GlobalContainer
注册这个类(以及 IBlahList 的一个):
GlobalContainer.RegisterType<TController>.Implements<IController>;
我把 TController 放在 implementation
部分,正如各种来源所建议的那样(好吧,无论如何,Nick Hodges!),这样我们就不能直接引用 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 传递给我尚未找到的控制器?
如果你在实现部分有具体的类,那么你可以公开一个工厂函数(即在接口部分有它),它创建一个具有所需参数的 IController。
IMO 拥有一个无法实例化的实现是绝对没有意义的。
interface
...
function CreateController(AList: IBlahList): IController;
implementation
function CreateController(AList: IBlahList): IController;
begin
Result := TController.Create(AList);
end;
好吧,您可能也应该在测试项目中使用模拟框架,但是在这些情况下,我通常会"作弊"并使用DUNIT条件变量将implementation
移动到我需要它的位置:
// 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条件的控制下进行。
我只能说:在这种情况下不要听尼克的话。
将类放在单元的实现部分内只是有缺点,你面临着其中一个缺点。
使用依赖注入的全部意义在于分离代码片段。
现在你删除了 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.