具有具体类作为返回类型的单元测试工厂方法

Posted

技术标签:

【中文标题】具有具体类作为返回类型的单元测试工厂方法【英文标题】:Unit testing factory methods which have a concrete class as a return type 【发布时间】:2010-11-06 21:18:43 【问题描述】:

所以我有一个工厂类,我正在尝试找出单元测试应该做什么。通过这个question,我可以验证返回的接口是否属于我所期望的特定具体类型。

如果工厂返回具体类型,我应该检查什么(因为目前不需要使用接口)?目前我正在做类似以下的事情:

[Test]
public void CreateSomeClassWithDependencies()

    // m_factory is instantiated in the SetUp method
    var someClass = m_factory.CreateSomeClassWithDependencies();

    Assert.IsNotNull(someClass);

问题在于Assert.IsNotNull 似乎有些多余。

另外,我的工厂方法可能会像这样设置特定类的依赖项:

public SomeClass CreateSomeClassWithDependencies()

    return new SomeClass(CreateADependency(), CreateAnotherDependency(),
                         CreateAThirdDependency());

我想确保我的工厂方法正确设置了所有这些依赖项。有没有其他方法可以让我在单元测试中检查这些依赖项public/internal 属性? (我不喜欢修改测试对象以适应测试)

编辑:针对 Robert Harvey 的问题,我使用 NUnit 作为我的单元测试框架(但我不会想到它会产生太大的影响)

【问题讨论】:

你用的是什么测试框架? 一些测试框架要求您的类是虚拟的,以便测试框架可以继承它们。有些没有。巨大的差异。 【参考方案1】:

通常,创建可用于基于状态的测试的公共属性并没有错。是的:这是您为启用测试场景而创建的代码,但它会损害您的 API 吗?可以想象其他客户以后会发现相同的属性有用吗?

测试专用代码和测试驱动设计之间有一条细线。我们不应该引入除了满足测试要求之外没有其他潜力的代码,但是引入遵循普遍接受的设计原则的新代码是完全可以的。我们让测试驱动我们的设计 - 这就是我们称之为 TDD 的原因 :)

在我看来,向一个类添加一个或多个属性以让用户更好地检查该类通常是合理的做法,因此我认为您不应该拒绝引入此类属性。

除此之外,我第二次回答纳德的回答:)

【讨论】:

+1 表示“让测试驱动我们的设计”。使用属性来公开依赖项有助于配置依赖项,并使它们变得明显。两者都是好东西。 @nader:说得好——这就是我想说的:) 有时为了测试而公开类内部是不合适的。例如,您不想意外泄漏类的正确性所依赖的内部可变对象。这可能是坏的几个原因:(a)违反封装原则,和(b)类安全(其他,甚至恶意代码可能以非预期和不受欢迎的方式改变类)。当您不想泄漏抽象时,如何测试工厂? 根据您的 API 的使用范围,我认为推测性地添加这些方法,因为客户可能会发现它们很有用,从长远来看会给您带来麻烦。每次您这样做时,您都必须维护和测试该方法或经历一个弃用过程 - 以防某处的客户正在使用该方法。这似乎是付出了很多代价却收效甚微;特别是如果您在整个 API 中始终或经常这样做。我的建议是让您的 API 尽可能小而紧凑——客户端需要它,您的测试也需要它,或者它不需要。 这是典型的微软借口internal sealed。虽然我承认如果您向成千上万的消费者发货,则必须考虑到这一点,但我坚持认为这个论点仍然不适用于大多数正在生成的代码。世界范围内生成的大多数代码都有非常有限或知名的消费者集,因此我不同意您可以将 Microsoft 论点应用于大多数代码库。【参考方案2】:

如果工厂返回具体类型,并且您保证您的工厂始终返回具体类型而不是 null,那么不,测试中没有太多 值。它确实可以让您确保随着时间的推移不会违反这种期望,并且不会引发异常之类的事情。

这种测试方式只是确保在您将来进行更改时,您的工厂行为不会在您不知情的情况下发生变化。

如果您的语言支持它,对于您的依赖项,您可以使用反射。这并不总是最容易维护的,并且将您的测试与您的实现紧密结合在一起。你必须决定这是否可以接受。这种方法往往非常脆弱。

但是您似乎确实在尝试将构造哪些类与如何调用构造函数分开。使用 DI 框架来获得这种灵活性可能会更好。

通过new-根据需要添加所有类型,您不会给自己很多接缝(接缝是您可以更改程序中的行为而无需在该位置进行编辑的地方)可以使用。

但是,通过您提供的示例,您可以从工厂派生一个类。然后覆盖/模拟CreateADependency()CreateAnotherDependency()CreateAThirdDependency()。现在,当您调用CreateSomeClassWithDependencies() 时,您可以感知是否创建了正确的依赖项。

注意:“接缝”的定义来自 Michael Feather 的书“有效地使用遗留代码”。它包含许多技术的示例,可以为未经测试的代码添加可测试性。您可能会发现它非常有用。

【讨论】:

我喜欢这个答案,如果您必须在没有测试的情况下处理遗留或接近遗留 (.net 1.1) 代码,那本书就是上帝送的。如果您所在的团队以前没有进行过单元测试,并且您有很多想要进行单元测试的棕地代码,那本书也非常有用。【参考方案3】:

我们所做的是使用工厂创建依赖关系,并在运行测试时使用依赖注入框架将模拟工厂替换为真实工厂。然后我们对那些模拟工厂设置适当的期望。

【讨论】:

很好,但是如果工厂创建的具体类型本身有需要检查的私有属性呢?【参考方案4】:

你总是可以用反射来检查东西。没有必要仅仅为了单元测试而暴露一些东西。我发现我需要进行反思的情况很少见,这可能是糟糕设计的标志。

查看您的示例代码,是的,Assert not null 似乎是多余的,这取决于您设计工厂的方式,有些将从工厂返回 null 对象而不是异常。

【讨论】:

【参考方案5】:

据我了解,您想测试依赖项是否正确构建并传递给新实例?

如果我不能使用像 google guice 这样的框架,我可能会这样做(这里使用 JMock 和 Hamcrest):

@Test
public void CreateSomeClassWithDependencies()

    dependencyFactory = context.mock(DependencyFactory.class);
    classAFactory = context.mock(ClassAFactory.class);

    myDependency0 = context.mock(MyDependency0.class);
    myDependency1 = context.mock(MyDependency1.class);
    myDependency2 = context.mock(MyDependency2.class);
    myClassA = context.mock(ClassA.class);

    context.checking(new Expectations()
       oneOf(dependencyFactory).createDependency0(); will(returnValue(myDependency0));
       oneOf(dependencyFactory).createDependency1(); will(returnValue(myDependency1));
       oneOf(dependencyFactory).createDependency2(); will(returnValue(myDependency2));

       oneOf(classAFactory).createClassA(myDependency0, myDependency1, myDependency2);
       will(returnValue(myClassA));
    );

    builder = new ClassABuilder(dependencyFactory, classAFactory);

    assertThat(builder.make(), equalTo(myClassA));

(如果你不能模拟 ClassA,你可以使用 new 为 myClassA 分配一个非模拟版本)

【讨论】:

以上是关于具有具体类作为返回类型的单元测试工厂方法的主要内容,如果未能解决你的问题,请参考以下文章

设计模式工厂方法模式(Factory Method)

如何告诉 Pex 不要存根具有具体实现的抽象类

关于简单工厂模式工厂方法模式和抽象工厂模式的解析

如何对具有依赖项的工厂进行单元测试

我们可以有一个工厂类作为spring bean,并有一个工厂方法根据条件返回多个spring bean吗?

工厂方法-实现数据格式转换器