36

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

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

[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 作为我的单元测试框架(但我不会想到它会产生太大的影响)

4

5 回答 5

39

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

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

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

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

于 2009-06-30T04:44:10.300 回答
25

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

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

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

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

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

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

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

于 2009-06-30T04:12:24.500 回答
4

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

于 2009-06-30T03:37:47.777 回答
3

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

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

于 2009-06-30T03:38:48.773 回答
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 分配一个非模拟版本)

于 2011-10-24T16:45:11.967 回答