单元测试 - 复杂的方法应该如何进行单元测试?

6

假设我正在测试以下类(~伪代码):

// (...)
public Result Process(Image image)
{
    Image image2 = PreprocessImage(image);
    PartialResult r1 = Process1(image2);
    PartialResult r2 = Process2(r1);
    Result result = FinalProcessing(r2);

    return result;
}

public Image PreprocessImage(Image image)
{
    Image tmp1 = Resize(image);
    Image tmp2 = Blur(tmp1);
    Image tmp3 = Median(tmp2);
    Image tmp4 = ExtractSpecificAreas(tmp3);

    return tmp4;
}

public Image Median(Image image)
{
     // Actual image median algorithm
}

为了让问题更加复杂,假设这些方法(例如Process1、Process2、FinalProcessing、ExtractSpecificAreas)的大多数结果几乎不可预测——例如有一些试图从图像中提取特征的启发式/决策算法:它可能在90%的情况下成功,这是可以接受的。
你将测试哪些方法?除了无效的输入/边界条件外,你将如何对这些方法进行单元测试?该方法应该多简单或多复杂才能有意义地进行单元测试?
2个回答

11

单元测试的一般规则是测试最小可能的部分。一个好的规则是每个测试应该确切地执行公共API中的一个方法。

这意味着它只应该执行这个方法,而不执行其他任何方法,甚至是短暂的。所以如果你想测试 foo() 并且它调用了 bar(),那么你应该模拟 bar() 而不是也测试它。调用内部私有方法 baz() 是可以的。

如果你的方法调用了数百个内部方法,那就需要重构了。

原因是单元测试失败应该指向问题的确切位置。如果你单元测试 main(),那么失败只会告诉你项目代码中某个地方有 bug。如果你单元测试 String.length() 并且测试失败,你就知道 bug 必须在哪里。

这也回答了你的问题:你有一些返回不可预测结果的方法。模拟它们可以让你始终返回一个已知的好/坏结果,这样你就可以正确地测试处理这些结果的方法。
对于不可预测的方法,你将不得不寻找类似的策略。我在这里假设你有神经网络进行了训练。因此,一个测试可以是将一些训练图像传递给该方法N次,直到你确信这些图像被正确分类了90%的时间。
同样,你应该能够将这些方法分成可预测和不可预测的部分,然后使用模拟或统计分析进行测试。
两个很棒的模拟框架是MockitoPowerMock

实际上,没有神经网络,只有复杂的确定性算法。然而,由于它们的复杂性和输入数据的质量,它们很少出现误报或漏报。如果您对它们进行单元测试,失败的测试可能并不一定指出方法的失败,而只是错过了某些情况。 - Spook
这听起来像是它们总是会针对相同的输入返回相同的结果。如果是这样的话,那么你可以像测试其他代码一样对它们进行单元测试。不要使用随机输入编写单元测试。创建一组图像,您知道正确的结果(或者您至少可以确定算法的结果必须是正确的),并在单元测试中使用这些预期结果。 - Aaron Digulla

1
我认为Aaron Digulla已经指出了最重要的事情:测试尽可能小的部分并使用模拟对象。
但是让我再补充一点:您可能还想测试整个应用程序,即小部件是否良好协同工作(这将是集成测试而不是单元测试,但无论如何-您应该两者都做)。对于测试启发式算法,我发现从易解决的任务开始很有用-在您的情况下,这将是一种“简单”的图像。当然,这只给您一个基准,不能保证在90%的情况下有效。但是,在开发过程中它仍然很有帮助,并且如果您愿意,始终可以使用更现实的样本增强您的(集成)测试套件。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接