【问题标题】:Is data-driven testing bad?数据驱动测试不好吗?
【发布时间】:2011-10-06 15:26:33
【问题描述】:

我已经开始使用 googletest 来实施测试,并在有关 value-parameterized tests 的文档中偶然发现了这句话

  • 您希望通过各种输入(也称为数据驱动)测试您的代码 测试)。这个功能很容易被滥用,所以请锻炼你的好 做的时候感觉!

我认为我在执行以下操作时确实在“滥用”系统,并希望听到您对此事的意见和意见。

假设我们有以下代码:

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);
// ...

现在我的问题当然是:这有什么意义吗?为什么会不好?

事实上,我在使用floats 运行测试时发现了一个“错误”,其中f()f_alt() 由于四舍五入会给出不同的值SumMethod,我可以通过对输入数组进行预排序来改进等等。根据这次经验,我认为这实际上是一种很好的做法。

【问题讨论】:

    标签: c++ testing googletest data-driven


    【解决方案1】:

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

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

    此外,正如 Ben Voigt 在下面评论的那样,仅使用随机数据进行测试是不够的。您需要识别算法中的极端情况并单独测试它们,并使用专门为这些情况量身定制的数据。但是,在我看来,当/如果您不确定自己是否了解所有极端情况时,使用随机数据进行额外测试也是有益的。您可能会使用随机数据偶然击中它们。

    【讨论】:

    • 随机生成的数据不好有两个原因——首先,因为正如你提到的,测试是不可重现的。其次,因为随机生成的数据可能无法覆盖极端情况。保存随机向量对第二个缺点没有任何帮助。
    • @haimg - 如果您进行黑盒测试,您如何知道使用的算法及其极端情况? :-)
    • 好吧,也许我误读了原始问题,但没有任何内容表明测试必须不使用实施知识。此外,en.wikipedia.org/wiki/Black_box_testing 明确表示“选择并测试域边缘的元素”,这实质上是测试极端情况。
    • +1 表示“当/如果您不确定是否了解所有极端情况时,使用随机数据进行额外测试也是有益的”我们最近遇到了一个未识别的极端情况的问题,我添加了一些(伪)随机测试用例,以尝试检测任何其他未识别的极端情况。
    • 随机数据测试有一个简单的解决方案:确保所有随机性都由同一个可种子生成器生成,并为该生成器存储种子。随机测试可能是一个很好的工具,但就像任何其他工具一样,您必须知道如何使用它以及有哪些优点和缺点。
    【解决方案2】:

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

    在某个 epsilon 内检查正确性,这是计算值和预期值之间的微小差异。这是你能做的最好的。这适用于所有浮点数。

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

    在阅读那篇文章之前,您是否认为这很糟糕?你能说出它的坏处吗?

    您必须在某个时候测试此功能。你需要数据来做到这一点。虐待在哪里?

    【讨论】:

    • 当然。我只是忘了把它正确地放在上面的例子中。已编辑。除此之外,我对反对编写这种测试的论点更感兴趣。
    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-20
      • 2016-08-12
      • 2017-12-14
      相关资源
      最近更新 更多