7

我使用 TDD 编写了一个类,其中包含一个方法(被测方法),该方法将一个简单的值对象作为参数(range)。

代码:

被测方法如下所示:

public List<string> In(IRange range)
{
     var result = new List<string>();
     for (int i = range.From; i <= range.To; i++)
     {
          //...
     }
     return result;
}

此外,我有一个单元测试来验证我的测试方法:

[TestMethod]
public void In_SimpleNumbers_ReturnsNumbersAsList()
{
    var range = CreateRange(1, 2);
    var expected = new List<string>() { "1", "2" };
    var result = fizzbuzz.In(range);
    CollectionAssert.AreEqual(expected, result);
}

private IRange CreateRange(int from, int to)
{
    return new Fakes.StubIRange() 
    { 
        FromGet = () => { return from; }, 
        ToGet = () => { return to; } 
    };
}

问题:

我读过 Roy Osherove 关于单元测试的书(“单元测试的艺术”)。在那里他说

“外部依赖项(文件系统、时间、内存等)应该被存根替换”

他所说的外部依赖是什么意思?我的值对象(范围)是否也是应该伪造的外部依赖项?我应该伪造一个类的所有依赖项吗?

有人可以给我一个建议吗

4

2 回答 2

3

单元测试应该独立运行(完全在内存中),而不必接触任何外部系统,例如文件系统、数据库、Web 服务、邮件服务、系统时钟或任何缓慢、难以设置或不确定的东西(例如不断变化的系统时间)。

为了能够做到这一点,您应该抽象出那些允许您在测试中模拟它们的外部依赖项。

然而,单元测试更进一步。在单元测试中,您通常只想测试单个方法或单个类的逻辑。您对验证多个组件如何集成不感兴趣,而只想验证单个类的逻辑是否正确,以及它是否与其他组件正确通信。

为了能够做到这一点,您需要伪造那些其他组件(类的依赖项)。所以一般来说,你确实应该伪造一个类的所有依赖项(包含行为)。

于 2013-05-02T12:25:44.903 回答
3

TL;博士

做最简单的事情来解决你的问题。


我使用 TDD 的时间越长,我就越体会到务实的价值。编写超级孤立的单元测试本身并不是一个价值。这些测试可以帮助您编写易于理解并解决正确问题的高质量代码。

如果您需要能够切换到另一个具体的范围实现而不必修改依赖于它的代码,那么为范围类添加一个接口是一个好主意。

但是,如果您没有这种需要,添加接口并没有真正的目的,但它确实增加了一些复杂性,这实际上使您远离编写易于理解的代码来解决问题的目标。

小心不要过多考虑未来可能发生的变化。YAGNI是一个很好的遵循原则。如果您一直在做 TDD,那么如果将来出现实际需要,重构代码不会有任何问题,因为您有可靠的测试可以依赖。

一般而言,我不会将适当的值对象视为依赖项。如果它足够复杂以至于您在测试时让其他代码使用它感到不舒服,那听起来它实际上更像是一种服务。

于 2013-05-06T18:27:35.173 回答