gpt4 book ai didi

c - 契约(Contract)外的输入值是否应该进行单元测试?

转载 作者:太空宇宙 更新时间:2023-11-04 02:50:12 27 4
gpt4 key购买 nike

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

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

外部API.h:

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

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

重构代码.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);

外部API.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;
}

重构代码.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。此类断言不应进行单元测试(即使使用 EXPECT_DEATH 宏),因为单元测试也是正确使用函数的文档,并且不允许使用 NULL。

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

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

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

专业的“声明但不测试”方法:

  1. 维护这样的测试可能会花费我们很多时间,因为我们没有标准的方法来处理编程问题,比如传递 NULL
  2. 传递 NULL 指针应该在函数的契约之外——处理 NULL 会改变契约,所以我们必须始终检查它,这意味着将来添加这些 ifs 需要做很多工作
  3. 断言不会进入发布代码 - 这很好,因为当我们发布代码时,应该对代码进行充分测试以确保不会漏掉任何错误
  4. 传递 NULL 通常是未定义的行为 - 应用程序的上层通常无法正确处理此类错误,因此简单地终止进程更安全
  5. 单元测试应该是函数应该如何使用的示例,传递 NULL 恰恰相反

最后我们谁都无法说服对方,辩论没有得出结论就结束了,我们又回到了Argumentum ab auctoritate,因为对方比我更有经验。

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

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

应该使用 if 检查函数契约之外的参数并进行单元测试,还是断言但不测试?

最佳答案

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

关于c - 契约(Contract)外的输入值是否应该进行单元测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22946700/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com