是否应对合同以外的输入值进行单元测试?

时间:2014-04-08 19:43:28

标签: c unit-testing

我正在使用遗留代码重构一个巨大的C库,其中许多函数都有参数列表上的指针。我还为新创建的函数编写单元测试,以确保我没有破坏任何东西(除了来自单元测试的所有好东西,这是我的主要动机)。我也不允许更改库的API,只允许更改它下面的代码。

通常我的工作结果看起来像这样(它的专有代码,所以我不能发布实际的例子):

externalApi.h:

/**
 * Documentation1
 */
bool someExportedFunction1(uint8_t* buffer, size_t len);

/**
 * Documentation2
 */
bool someExportedFunction2();

refactoredCode.h:

/**
 * Documentation of internal function1
 */
bool internalFuntion1(uint8_t* buffer, size_t len);


/**
 * Documentation of internal function2
 */
bool internalFuntion2(uint8_t* buffer, size_t len);

externalApi.c:

bool someExportedFunction1(uint8_t* buffer, size_t len)
{
    if (NULL == buffer)
    {
        ERROR("Meaningful error log");
        return false;
    }

    if (!internalFunction1(buffer, len))
    {
        ERROR("Other error log");
        return false;
    }

    if (!internalFunction2(buffer, len))
    {
        ERROR("Yet another error log");
        return false;
    }

    return true;
}

bool someExportedFunction2()
{
    uint8_t lBuffer[10] = {};

    if (!internalFunction1(lBuffer, sizeof(lBuffer))
    {
        ERROR("Interesting error log");
        return false;
    }

    uint8_t* ptr = malloc(10);
    if (NULL == ptr)
    {
        ERROR("Malloc error");
        return false;
    }

    if (!internalFunction2(ptr, 10)
    {
        free(ptr);
        ERROR("Boring error log");
        return false;
    }

    free(ptr);

    return true;
}

refactoredCode.c

bool internalFuntion1(uint8_t* buffer, size_t len)
{
    if (NULL == buffer)
    {
        ERROR("Guess what, a meaningful error log");
        return false;
    }

    // Do stuff
    return true;
}

bool internalFuntion2(uint8_t* buffer, size_t len)
{
    if (NULL == buffer)
    {
        ERROR("Last meaningful error log");
        return false;
    }

    // Do stuff
    return true;
}

这些只是简单的例子来说明问题,同样问题还有无数其他版本。

现在,我编写的所有单元测试包括检查如果我将NULL作为参数传递会发生什么,无论我正在测试多低级别的函数(即使我100%确定NULL也不会传递为一个论点)。

然而,我的一位同事不同意我的说法,NULL是在函数契约之外,正确的方法是编写断言而不是if s。此类断言不应进行单元测试(即使使用EXPECT_DEATH宏),因为单元测试也是正确使用函数的文档,并且不允许使用NULL。

我们可以这样总结双方的论点:

Pro“写if并对其进行单元测试”方法:

  1. 使用ERROR日志,我们不仅可以轻松找到哪个函数检测到NULL,还可以找到它被调用的位置(伪栈跟踪),使调试变得更加容易
  2. 如果我们不测试检查NULL,我们怎样才能确定我们覆盖所有情况(使用断言或if s,无所谓)?
  3. 人们通常要么不读文档,要么为时已晚,或者有时他们会在周五下午编写代码 - 我们不能指望他们一直都是完美的程序员
  4. 我们无法预测将来会对代码进行哪些更改,因此最好让测试警告我们每次更改,以便我们可以决定是否有意为
  5. 如果在测试中检测到断言(我们没想到它,完全是由于代码中的错误),整个测试应用程序将被终止,并且在我们解决问题之前不会执行其他无关的测试。
  6. Pro“断言并且不测试”方法:

    1. 维持这样的测试可能会花费我们很多时间,因为我们没有处理编程问题的标准方法,如传递NULL
    2. 传递NULL指针应该在函数的契约之外 - 处理NULL正在改变契约,所以我们必须经常检查它,这意味着将来添加那些ifs需要做很多工作
    3. 断言没有进入发布代码 - 这很好,因为当我们发布时,应该对代码进行充分测试,以确保没有错误通过
    4. 传递NULL通常是未定义的行为 - 应用程序的上层通常无法正确处理此类错误,因此简单地终止进程更安全
    5. 单元测试应该是如何使用该函数的示例,并且传递NULL正好与它相反
    6. 最后,我们没有人能说服对方,辩论结束时没有得出结论,我们又回到了 Argumentum ab auctoritate 因为另一个人比我更有经验。

      但我仍然不相信,所以我在这里问这个问题:

      正在考虑我提出的两种方法的所有论据,以及我不知道的所有方法:

      是否应该使用if检查函数合约之外的参数并进行单元测试,或者声明并且不进行测试?

1 个答案:

答案 0 :(得分:2)

  1. 如果您的代码的某个部分对性能敏感,那么在这些部分中避免这些检查是有意义的。
  2. 在代码的所有其他部分,有必要进行所有检查并创建日志文件以帮助跟踪。这样的防御性编程可以节省很多次。