对为什么单元测试虚拟对象很有用感到困惑



我正在阅读Clean Code: A Handbook of Agile Software Craftsmanship,其中一个示例涉及Portfolio类和TokyoStockExchange类。但是,Portfolio不是很可测试,因为它依赖于TokyoStockExchange作为外部 API 来确定投资组合的价值,这是一个非常不稳定的查找,不利于测试。

因此,他们通过创建一个通用的StockExchange接口来解决这个问题,并让TokyoStockExchangeDummyStockExchange都实现基类。因此,实现了依赖反转原则,在PortfolioTest类中,人们可以实例化一个DummyStockExchange,为公司固定股票价格,将DummyStockExchange实例分配给投资组合,并将该公司的一些股票添加到投资组合中,然后断言期望值是否确实是适当的价值。代码如下:

public class PortfolioTest
{
    private DummyStockExchange exchange;
    private Portfolio portfolio;
    protected void setUp()
    {
        exchange = new DummyStockExchange();
        exchange.fix("MSFT", 100);
        portfolio = new Portfolio(exchange);
    }
    public void GivenFiveMSFTTotalShouldBe500()
    {
        portfolio.add(5, "MSFT");
        Assert.assertEquals(500, portfolio.value());
    }
}

我的问题是,很简单,为什么

我们试图测试TokyoStockExchange类是否与Portfolio类协同工作。 显然,如果我们使用新方法创建另一个类来设定股票价格,然后给投资组合提供其中的五只股票,那么一切都会起作用。只是看起来..测试没用。我知道由于股票价格的变化,TokyoStockExchange基本上不可能用Portfolio进行测试,但我不明白在一个相当无用的测试中订阅对这种情况有什么帮助。

这一切似乎类似于不知道我们的加法器程序是否有效,但唯一可用的数字是随机生成的,因此我们创建了一个虚拟类,给我们一个 2 并测试是否2 + 2 = 4。嗯,是的,显然这是真的。我们仍然可以打破TokyoStockExchange测试仍然会成功,因为它正在测试另一个类。如果有的话,这一切似乎都是欺骗性的,它还导致不得不编写额外的代码来测试我们知道会起作用的东西。

我认为这是我在理解单元测试时遇到的最大问题。我知道我错了,我只是没有看到我猜想的光。希望有人可以帮助我。

这个想法是,您可能希望独立于TokyoStockExchange测试Portfolio类中的逻辑。如果您使用像 Moq 或 Rhino Mocks 这样的模拟框架,那么您可以轻松地模拟来自TokyoStockExchange的不同输出和行为,并编写单元测试以确保Portfolio正确响应。您将为 TokyoStockExchange 类编写单独的单元测试。

这并不是说你不需要在两个类之间进行集成测试。只是很难在不使用模拟对象的情况下正确验证所有方案。

这样一个简单的类为例很难理解其价值,但是给定一个更复杂的类,您需要验证测试用例,以应对难以或不可能在"实时"类上安排的情况,单元测试变得更加重要。

您应该执行两种类型的测试,单元测试和集成测试。

单元测试

应该是一个白盒测试,您可以在其中隔离测试每个代码单元。通常,这是指每个类中的公共接口。您模拟出它们的依赖关系,以便您确信给定一组已知的数据,您的单位将返回可预测的结果。

你在单元测试中说,"显然一切都会起作用"。前提是您的代码中没有任何错误。如果您可以做出这样的假设,那么您首先就不需要测试任何东西!而且你真的不需要对所有东西进行单元测试---如果你的Portfolio只是StockExchange上的一层薄薄的,它调用 API 方法并传递结果,你不应该费心对它进行单元测试。

另一方面,如果你的Portfolio有真正的逻辑,你会想要对它进行单元测试。假设Portfolio有一种方法可以从Stock Exchange中提取数据,分析数据,并在股票价格显示某些异常时向用户发送警报消息,例如价格开始快速下跌。您希望确保在预期条件下确实会触发警报,但您不想坐等下一次股市崩盘。因此,在单元测试中,您将创建一个模拟Stock Exchange,以生成要触发警报的值类型,然后检查它是否确实发生。如果是这样,那就太好了,如果没有,你刚刚发现了一个错误。

集成测试将同时测试这两个单元,这一点也很重要。但是,在集成测试中模拟某些类型的场景更加困难,并且在确定错误实际隐藏的位置方面也不太有帮助。如果对应用程序运行集成测试,发现它没有在应该发送警报时发送警报,那么问题出在哪里?第三方 API 中是否存在错误?证券交易所是否向您发送了不良价值?您的警报系统是否将消息发送到错误的地址?您可能需要一段时间才能确定分析方法存在问题。

最新更新