C# 单元测试采用参数的抽象工厂

C# 单元测试采用参数的抽象工厂,c#,dependency-injection,abstract-factory,C#,Dependency Injection,Abstract Factory,给定抽象工厂实现: public class FooFactory : IFooFactory { public IFoo Create(object param1, object param2) { return new Foo(param1, param2); } } 这个类将编写什么单元测试?如何验证param1和param2是否已转发到Foo的创建中?我必须把富的这些公共财产公开吗?这不是破坏封装吗?或者我应该将此留给集成测试吗?您可以将FooFactory设置

给定抽象工厂实现:

public class FooFactory : IFooFactory {
   public IFoo Create(object param1, object param2) {
      return new Foo(param1, param2);
   }
}

这个类将编写什么单元测试?如何验证param1和param2是否已转发到Foo的创建中?我必须把富的这些公共财产公开吗?这不是破坏封装吗?或者我应该将此留给集成测试吗?

您可以将FooFactory设置为一个通用类,该类期望Foo作为一个参数,并将其指定为一个模拟类。
调用factory.Create(…)然后创建一个mock实例,然后您可以确认mock收到了您期望的参数。

这取决于Foo对这两个输入对象所做的操作。检查Foo对他们所做的一些可以轻松查询的事情。例如,如果对对象调用了方法(包括getter或setter),则提供mock或stub,您可以验证是否调用了预期的内容。如果IFoo上有可以测试的内容,那么可以通过模拟对象传入已知值,以便在创建后进行测试。对象本身不需要公开,就可以验证是否通过了它们

我还可以验证是否返回了预期类型(Foo),这取决于测试的其他行为是否取决于IFoo实现之间的差异


如果有任何可能导致的错误条件,我也会尝试强制这些条件,如果为其中一个或两个对象传递null,会发生什么情况,等等。当然,Foo将被单独测试,因此您可以在FooFactory测试期间假设Foo做了它应该做的事情。

工厂通常不进行单元测试,至少根据我的经验-对它们进行单元测试没有多大价值。因为它是接口实现映射的实现,所以它引用了具体的实现。因此,mock
Foo
无法检查它是否接收到传入的那些参数


另一方面,通常DI容器作为工厂工作,因此如果使用工厂,则不需要工厂。

那么,假设这些参数使返回的
IFoo
具有真实性。测试返回的实例是否正确

以下是我将如何为这样一个工厂编写几个单元测试中的一个(使用):

虽然
Object1
属性稍微违反了,但它不会弄乱类的不变量,因为它是只读的

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

public interface IFoo
{
    void MethodDefinedByInterface();
}
一旦您意识到在松散耦合的API中,这种具体的只读属性对封装的影响很小。这样想:

public Foo构造函数也是具体Foo类的API的一部分,因此仅通过检查public API,我们就知道
param1
param2
是类的一部分。从某种意义上说,这已经“破坏了封装”,因此使每个参数作为具体类上的只读属性可用并没有多大变化

这样的属性提供了一个好处,我们现在可以对工厂返回的Foo类的结构形状进行单元测试

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

  • 通过对具体的Foo类的测试,我们知道它正确地使用了构造函数参数/与构造函数参数交互
  • 从这些测试中,我们还知道构造函数参数是作为只读属性公开的
  • 通过对FooFactory的测试,我们知道它返回一个具体Foo类的实例
  • 此外,我们从Create方法的测试中知道,它的参数被正确地传递给Foo构造函数
  • Q.E.D

  • 单元测试应取决于被测试组件的功能需求和期望。如果没有上下文的其余部分,我看不到对该类进行单元测试的任何价值。@ivowiblo我认为您将单元测试与BDD样式的测试混淆了。如果您没有为此单元编写测试,那么您如何知道它是否有效?进行单元测试,但您将测试什么?对该单元的期望是什么?我说我不认为在不知道其他任何事情的情况下进行该类的单元测试有什么价值。仅仅因为进行单元测试而进行单元测试是毫无意义的。测试(单元、行为、完整功能,无论您想要什么)应该始终由期望和需求驱动。如果不是,那就是snobism(如果这个词存在)。@ivowiblo测试确保将正确的参数传递给Foo的创建。除了提高覆盖率之外,这还有什么价值?亲爱的Gödel no.这是在测试一个实现细节。@Jason:更糟。。。它提出了一个问题,即为该工厂创建一个工厂,以简化对象的生成,但是如果JontyMC提出问题,您会测试返回类型吗?那到底给你买了什么?您已经设置了所有这些抽象,现在要编写一个测试,一旦更改了实现细节,该测试就会中断。Yikes.@Jason如果我要为多个工厂编写测试,每个工厂都创建一个IFoo实现,我会检查每个工厂是否返回我期望的具体类型。我质疑是否有必要测试一家几乎不做所谓“新建”的工厂,但这家工厂在被要求的范围内……没有。这不是工厂消费者可以依赖的行为(退货类型是
    IFoo
    ),因此不应该对其进行测试。@Jason“真正的”消费者绝对不应该依赖退货类型,你是对的。但是,测试可以根据您需要做什么来验证正确的行为而进行。我将编辑为“可能”,因为这是真的,并不总是需要寻找的东西。测试也不应该依赖它!他们和其他消费者一样真实。这难道不意味着你需要知道吗
    public class Foo : IFoo
    {
        public Foo(object param1, object param2);
    
        public void MethodDefinedByInterface();
    
        public object Object1 { get; }
    }
    
    public interface IFoo
    {
        void MethodDefinedByInterface();
    }