问题 数据驱动测试不好吗?


我已经开始使用googletest来实施测试,并在有关的文档中偶然发现了这个引用 价值参数化测试

  • 您希望通过各种输入测试代码(a.k.a.数据驱动   测试)。此功能很容易滥用,所以请锻炼你的好处   这样做的感觉!

我认为在做以下事情时我确实“滥用”了系统,并希望听取您对此事的意见和建议。

假设我们有以下代码:

template<typename T>
struct SumMethod {
     T op(T x, T y) { return x + y; }   
};

// optimized function to handle different input array sizes 
// in the most efficient way
template<typename T, class Method> 
T f(T input[], int size) {
    Method m;
    T result = (T) 0;
    if(size <= 128) {
        // use m.op() to compute result etc.
        return result;
    }
    if(size <= 256) {
        // use m.op() to compute result etc.
        return result;
    }
    // ...
}

// naive and correct, but slow alternative implementation of f()
template<typename T, class Method>
T f_alt(T input[], int size);

好的,所以使用这段代码,测试肯定是有道理的 f() (与...比较 f_alt())用不同的输入数组大小随机生成数据来测试分支的正确性。最重要的是,我有几个 structs 喜欢 SumMethodMultiplyMethod等等,所以我也为不同的类型运行了大量的测试:

typedef MultiplyMethod<int> MultInt;
typedef SumMethod<int> SumInt;
typedef MultiplyMethod<float> MultFlt;
// ...
ASSERT(f<int, MultInt>(int_in, 128), f_alt<int, MultInt>(int_in, 128));
ASSERT(f<int, MultInt>(int_in, 256), f_alt<int, MultInt>(int_in, 256));
// ...
ASSERT(f<int, SumInt>(int_in, 128), f_alt<int, SumInt>(int_in, 128));
ASSERT(f<int, SumInt>(int_in, 256), f_alt<int, SumInt>(int_in, 256));
// ...
const float ep = 1e-6;
ASSERT_NEAR(f<float, MultFlt>(flt_in, 128), f_alt<float, MultFlt>(flt_in, 128), ep);
ASSERT_NEAR(f<float, MultFlt>(flt_in, 256), f_alt<float, MultFlt>(flt_in, 256), ep);
// ...

当然,我的问题是:这有什么意义,为什么这会很糟糕?

事实上,我在运行测试时发现了一个“bug” float在哪里 f() 和 f_alt() 会给出不同的价值 SumMethod 由于四舍五入,我可以通过预先输入数组等来改进。从这个经验我认为这实际上是一些好的做法。


11015
2017-10-06 15:26


起源



答案:


我认为主要问题是使用“随机生成的数据”进行测试。从您的问题中不清楚每次运行测试工具时是否重新生成此数据。如果是,那么您的测试结果是不可重现的。如果某些测试失败,那么每次运行它都会失败,而不是一次在蓝色的月亮,一些奇怪的随机测试数据组合。

因此,在我看来,您应该预先生成测试数据并将其作为测试套件的一部分。您还需要确保数据集足够大且多样化,足以提供足够的代码覆盖率。

此外,As Ben Voigt在下面评论,使用随机数据进行测试 只要 是不足够的。您需要在算法中识别极端情况并单独测试它们,并为这些情况专门定制数据。但是,在我看来,如果您不确定是否知道所有角落情况,那么使用随机数据进行额外测试也是有益的。您可以使用随机数据偶然击中它们。


10
2017-10-08 18:08



随机生成的数据很糟糕有两个原因 - 首先,因为正如您所提到的,测试不可重现。其次,因为角落情况可能不被随机生成的数据所覆盖。保存随机向量对第二个缺点没有任何作用。 - Ben Voigt
谢谢。我修改了我的答案,你当然是对的。 - haimg
@haimg - 如果您正在进行黑盒测试,您如何知道所使用的算法及其角落情况? :-) - Bo Persson
好吧,也许我误解了原来的问题,但那里没有什么可以说是测试 必须 不使用实施的知识。此外, en.wikipedia.org/wiki/Black_box_testing 特别说“选择并测试了域边缘的元素”,这主要是测试极端情况。 - haimg
随机数据测试有一个简单的解决方案:确保所有随机性都由相同的可播种生成器生成,并存储该生成器的种子。随机测试可以是一个很好的工具,但就像任何其他工具一样,您必须知道如何使用它以及有哪些优点和缺点。 - KillianDS


答案:


我认为主要问题是使用“随机生成的数据”进行测试。从您的问题中不清楚每次运行测试工具时是否重新生成此数据。如果是,那么您的测试结果是不可重现的。如果某些测试失败,那么每次运行它都会失败,而不是一次在蓝色的月亮,一些奇怪的随机测试数据组合。

因此,在我看来,您应该预先生成测试数据并将其作为测试套件的一部分。您还需要确保数据集足够大且多样化,足以提供足够的代码覆盖率。

此外,As Ben Voigt在下面评论,使用随机数据进行测试 只要 是不足够的。您需要在算法中识别极端情况并单独测试它们,并为这些情况专门定制数据。但是,在我看来,如果您不确定是否知道所有角落情况,那么使用随机数据进行额外测试也是有益的。您可以使用随机数据偶然击中它们。


10
2017-10-08 18:08



随机生成的数据很糟糕有两个原因 - 首先,因为正如您所提到的,测试不可重现。其次,因为角落情况可能不被随机生成的数据所覆盖。保存随机向量对第二个缺点没有任何作用。 - Ben Voigt
谢谢。我修改了我的答案,你当然是对的。 - haimg
@haimg - 如果您正在进行黑盒测试,您如何知道所使用的算法及其角落情况? :-) - Bo Persson
好吧,也许我误解了原来的问题,但那里没有什么可以说是测试 必须 不使用实施的知识。此外, en.wikipedia.org/wiki/Black_box_testing 特别说“选择并测试了域边缘的元素”,这主要是测试极端情况。 - haimg
随机数据测试有一个简单的解决方案:确保所有随机性都由相同的可播种生成器生成,并存储该生成器的种子。随机测试可以是一个很好的工具,但就像任何其他工具一样,您必须知道如何使用它以及有哪些优点和缺点。 - KillianDS


问题是你不能像浮点数那样断言浮点数的正确性。

检查某个epsilon内的正确性,这是计算值和期望值之间的微小差异。这是你能做的最好的事情。对于所有浮点数都是如此。

我认为在执行以下操作时我确实“滥用”了系统

在你阅读那篇文章之前,你认为这是不好的吗?你能说清楚它有什么不好吗?

您必须在某个时候测试此功能。您需要数据来执行此操作。虐待在哪里?


6
2017-10-06 15:30



当然。我忘了把它正确地放在上面的例子中。编辑。除此之外,我对反对编写这种测试的论点更感兴趣。 - bbtrb


可能不好的原因之一是数据驱动的测试难以维护,并且在更长的时间内,更容易在测试中引入错误。 详情请看: http://googletesting.blogspot.com/2008/09/tott-data-driven-traps.html

同样从我的角度来看,当你进行严肃的重构并且你不确定你是否以错误的方式改变逻辑时,unittests是最有用的。 如果您的随机数据测试在这种更改后失败,那么您可以猜测:是因为数据还是因为您的更改?

但是,我认为它可能是有用的(与压力测试相同,也不是100%可重复)。但是如果你使用的是一些持续集成系统,我不确定是否应该在其中包含大量随机生成数据的数据驱动测试。 我宁愿单独部署,也可以定期进行 很多 一次进行随机测试(因此,每次运行时,发现坏事的机会应该非常高)。但它作为普通测试套件的一部分太资源了。


0
2017-10-13 23:28