5

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

// (...)
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% 的情况下成功,这是可以接受的。

您将对这些方法中的哪一种进行单元测试?除了无效的输入/边框条件之外,您将如何对这些方法进行单元测试?对于单元测试来说,该方法应该有多基本(或有多复杂)才有意义?

4

2 回答 2

9

单元测试的一般规则是测试您可以测试的最小可能部分。一个好的规则是,每个测试都应该使用公共 API 中的一个方法。

这意味着它应该只执行这个方法而不能执行其他方法,甚至不能暂时执行。因此,如果您想测试foo()并且它调用bar(),那么您应该模拟bar()而不是测试它。不过,调用内部私有方法baz()就可以了。

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

原因是单元测试中的失败应该指出问题的确切位置。如果您要进行单元测试main(),那么失败只会告诉您项目代码中的某处存在错误。如果您对String.length()进行单元测试并且该测试失败,那么您将非常准确地知道错误必须在哪里。

这也回答了您的问题:您有返回不可预测结果的方法。模拟它们将允许您始终返回已知的好/坏结果,因此您可以测试正确处理这些结果的方法。

对于不可预测的方法,您将不得不找到类似的策略。例如,我在这里假设您在某处有经过训练的神经网络。因此,测试可能是将一些训练图像传递给该方法 N 次,直到您确信图像在 90% 的时间内正确排序。

同样,您应该能够将这些方法拆分为可预测和不可预测的部分,然后您可以使用模拟或统计分析对其进行测试。

两个伟大的模拟框架是MockitoPowerMock

于 2013-03-05T08:33:53.023 回答
1

我认为 Aaron Digulla 已经指出了最重要的一点:测试尽可能小的部分并使用模拟对象。

但是让我再补充一点:您可能还想测试您的整个应用程序,即小部分是否可以很好地协同工作(这将是集成测试而不是单元测试,但无论如何 - 您应该两者都做)。为了测试启发式算法,我发现从一个容易解决的任务开始很有用- 在你的情况下,这将是一个“简单”的图像。当然,这只是给你一个基线,它不能保证它在 90% 的情况下都能工作。但无论如何,它在开发过程中很有帮助,如果你愿意,你总是可以用更真实的样本来增强你的(集成)测试套件。

于 2013-03-05T08:48:14.060 回答