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

时间:2009-06-30 03:14:32

标签: unit-testing tdd factory

所以我有一个工厂类,我正在尝试研究单元测试应该做什么。从这个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属性? (我不是修改测试对象以适应测试的忠实粉丝)

编辑:回应罗伯特哈维的问题,我正在使用NUnit作为我的单元测试框架(但我不会认为这会产生太多差异)

5 个答案:

答案 0 :(得分:34)

通常,创建可用于基于状态的测试的公共属性没有任何问题。是的:这是您为启用测试场景而创建的代码,但它是否会损害您的API?是否可以想到其他客户会在以后找到相同的属性?

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

在我看来,向一个类添加一个或多个属性以便让用户更好地检查该类是一件合理的事情,所以我认为你不应该忽略引入这些属性。

除此之外,我是第二个nader的回答:)

答案 1 :(得分:24)

如果工厂正在返回具体类型,并且您保证工厂始终返回具体类型,而不是null,那么不,测试中没有太多值。它确实允许您确保随着时间的推移不会违反此期望,并且不会抛出例外情况。

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

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

但是你似乎真的试图分离构造哪些类,以及如何调用构造函数。使用DI框架获得这种灵活性可能会更好。

通过new - 根据需要添加所有类型,你不会给自己很多接缝(接缝是你可以改变程序行为而不在那个地方编辑的地方)来工作的用。

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

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

答案 2 :(得分:3)

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

答案 3 :(得分:3)

你总是可以用反射检查东西。没有必要为单元测试公开一些东西。我觉得很少需要用反射进入,这可能是设计糟糕的表现。

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

答案 4 :(得分:0)

据我所知,你想测试依赖关系是否正确构建并传递给新实例?

如果我无法使用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)