给定一个抽象工厂实现:
public class FooFactory : IFooFactory {
public IFoo Create(object param1, object param2) {
return new Foo(param1, param2);
}
}
将为这个类编写哪些单元测试?如何验证 param1 和 param2 是否已转发到 Foo 的创建?我必须制作 Foo 的这些公共属性吗?那不会破坏封装吗?还是我应该把它留给集成测试?
给定一个抽象工厂实现:
public class FooFactory : IFooFactory {
public IFoo Create(object param1, object param2) {
return new Foo(param1, param2);
}
}
将为这个类编写哪些单元测试?如何验证 param1 和 param2 是否已转发到 Foo 的创建?我必须制作 Foo 的这些公共属性吗?那不会破坏封装吗?还是我应该把它留给集成测试?
以下是我如何为这样的工厂(使用xUnit.net)编写几个单元测试之一:
[Fact]
public void CreateReturnsInstanceWithCorrectParam1()
{
var sut = new FooFactory();
var expected = new object();
var actual = sut.Create(expected, new object());
var concrete = Assert.IsAssignableFrom<Foo>(actual);
Assert.Equal(expected, concrete.Object1);
}
它会破坏封装吗?是的,不是的……一点点。封装不仅仅关乎数据隐藏——更重要的是,它关乎保护对象的不变量。
假设 Foo 公开了这个公共 API:
public class Foo : IFoo
{
public Foo(object param1, object param2);
public void MethodDefinedByInterface();
public object Object1 { get; }
}
虽然该Object1
属性略微违反了Demeter 定律,但它不会与类的不变量混淆,因为它是只读的。
此外,该Object1
属性是具体 Foo 类的一部分,而不是 IFoo 接口:
public interface IFoo
{
void MethodDefinedByInterface();
}
一旦你意识到在松散耦合的 API 中,具体成员是实现细节,这种具体的只读属性对封装的影响非常小。这样想:
公共 Foo 构造函数也是具体 Foo 类的 API 的一部分,因此只需检查公共 API,我们就可以了解这一点param1
并param2
成为该类的一部分。从某种意义上说,这已经“破坏了封装”,因此将每个参数作为具体类的只读属性提供并没有太大变化。
这些属性提供的好处是我们现在可以对工厂返回的 Foo 类的结构形状进行单元测试。
这比必须重复一组行为单元测试要容易得多,我们必须假设这些单元测试已经涵盖了具体的 Foo 类。这几乎就像一个合乎逻辑的证明:
好吧,大概这些参数使返回的IFoo
内容具有真实性。测试返回的实例是否属实。
您可以使 FooFactory 成为一个期望 Foo 作为参数的泛型类,并将其指定为模拟。调用 factory.Create(...) 然后创建一个模拟实例,然后您可以确认模拟接收到您期望的参数。
这取决于 Foo 对 2 个输入对象的作用。检查 Foo 对它们所做的可以轻松查询的操作。例如,如果在对象上调用方法(包括 getter 或 setter),则提供可以验证是否调用了预期事物的模拟或存根。如果 IFoo 上有可以测试的东西,您可以通过模拟对象传递已知值以在创建后进行测试。对象本身不需要公开可用以验证您是否通过了它们。
我还可以验证是否返回了预期的类型 (Foo),这取决于测试的其他行为是否取决于 IFoo 实现之间的差异。
如果可能导致任何错误情况,我也会尝试强制执行这些情况,如果您为其中一个或两个对象传递 null 会发生什么,等等。当然,Foo 将单独测试,因此您可以在FooFactory 测试 Foo 做它应该做的。
工厂通常不进行单元测试,至少根据我的经验 -对它们进行单元测试没有太大价值。因为它是该接口实现映射的实现,因此它指的是具体实现。因此,模拟Foo
无法检查它是否接收到传入的那些参数。
另一方面,通常 DI 容器作为工厂工作,因此如果您使用的是工厂,则不需要工厂。