单元测试采用参数的抽象工厂
本文关键字:抽象 工厂 参数 单元测试 | 更新日期: 2023-09-27 18:22:13
给定一个抽象的工厂实现:
public class FooFactory : IFooFactory {
public IFoo Create(object param1, object param2) {
return new Foo(param1, param2);
}
}
这个类将编写哪些单元测试?如何验证param1和param2是否被转发到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类。这几乎就像一个逻辑证明:
- 通过对具体Foo类的测试,我们知道它正确地使用了构造函数参数/与其进行了交互
- 从这些测试中,我们还知道构造函数参数是作为只读属性公开的
- 通过对FooFactory的测试,我们知道它返回了一个具体Foo类的实例
- 此外,我们从Create方法的测试中知道,它的参数被正确地传递给了Foo构造函数
- Q.E.D
好吧,假设这些参数使返回的IFoo
具有真实性。测试返回实例的真实性。
您可以使FooFactory成为一个期望Foo作为参数的泛型类,并将其指定为mock。呼叫工厂。Create(…)然后创建mock的一个实例,然后您可以确认mock收到了您期望的参数。
工厂通常不进行单元测试,至少根据我的经验-对它们进行单元测试没有太大价值。因为它是接口实现映射的实现,所以它引用了具体的实现。因此,模拟Foo
不可能检查它是否接收到传入的那些参数
另一方面,通常DI容器作为工厂工作,所以如果您正在使用工厂,则不需要工厂。
这取决于Foo对这两个输入对象所做的操作。检查Foo对它们所做的事情,这些事情可以很容易地被查询。例如,如果对对象调用了方法(包括getter或setter),则提供mock或stub,您可以验证是否调用了预期的东西。如果IFoo上有可以测试的东西,那么可以通过mock对象传入已知值,以便在创建后进行测试。对象本身不应该是公开的,以验证您是否通过了它们。
我还可以验证是否返回了预期的类型(Foo),这取决于测试的其他行为是否取决于IFoo实现之间的差异。
如果可能出现任何错误条件,我也会尝试强制执行这些条件,如果你为其中一个或两个对象传递null会发生什么,等等。当然,Foo将单独测试,所以你可以假设在FooFactory测试中Foo做了它应该做的事。