请考虑以下代码:
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;
最佳答案
如果你不能模拟 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/