单元测试一个带参数的抽象工厂

Jon*_*yMC 8 c# dependency-injection abstract-factory

给出一个抽象的工厂实现:

public class FooFactory : IFooFactory {
   public IFoo Create(object param1, object param2) {
      return new Foo(param1, param2);
   }
}
Run Code Online (Sandbox Code Playgroud)

为这堂课写什么单元测试?如何验证param1和param2是否已转发到Foo的创建?我是否必须制作Foo的这些公共属性?这不会破坏封装吗?或者我应该将其留给集成测试?

Mar*_*ann 11

以下是我为这样一个工厂(使用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);
}
Run Code Online (Sandbox Code Playgroud)

它会破坏封装吗?是的,不是......一点点.封装不仅涉及数据隐藏 - 更重要的是,它涉及保护对象的不变量.

我们假设Foo公开了这个公共API:

public class Foo : IFoo
{
    public Foo(object param1, object param2);

    public void MethodDefinedByInterface();

    public object Object1 { get; }
}
Run Code Online (Sandbox Code Playgroud)

虽然该Object1属性略微违反了得墨忒耳法,但它并没有弄乱类的不变量,因为它是只读的.

此外,该Object1属性是具体的Foo类的一部分 - 而不是IFoo接口:

public interface IFoo
{
    void MethodDefinedByInterface();
}
Run Code Online (Sandbox Code Playgroud)

一旦您意识到在松散耦合的API中,具体成员就是实现细节,这种仅具体的只读属性对封装的影响非常小.以这种方式思考:

公共Foo构造函数也是具体Foo类的API的一部分,因此只需检查公共API,我们就可以了解它param1并且param2是类的一部分.从某种意义上说,这已经"打破了封装",因此将每个参数作为具体类的只读属性提供并没有太大变化.

这些属性提供的好处是我们现在可以对工厂返回的Foo类的结构形状进行单元测试.

这比重复一组行为单元测试要容易得多,我们必须假设这些测试已经覆盖了具体的Foo类.这几乎就像一个逻辑证明:

  1. 从具体的Foo类的测试中我们知道它正确地使用/与其构造函数参数交互.
  2. 从这些测试中我们也知道构造函数参数是作为只读属性公开的.
  3. 通过对FooFactory的测试,我们知道它返回了具体Foo类的实例.
  4. 此外,我们从Create方法的测试中知道它的参数被正确地传递给Foo构造函数.
  5. QED