【问题标题】:How to replace time() function for testing production library?如何替换用于测试生产库的 time() 函数?
【发布时间】:2011-01-11 09:47:08
【问题描述】:

我想为我的 C 库编写黑盒测试,该库使用标准 C 时间函数(time()、localtime()、gmtime() 等)在其输出中使用时间戳。输出是编码的,所以我需要用一些测试模拟或“后门”替换那些时间函数,这样我就可以控制我的库从 time() 函数而不是当前系统时间获得的值,这样我就可以在我的测试中正确编写检查。

实现这一目标的最佳方法是什么?我可以在生产库中获得这样的支持吗(可能有一些旋钮?),或者我应该以两种形式编译我的库:使用 real time() 函数进行生产和使用我自己控制的 time() 函数进行测试?

我对跨平台解决方案很感兴趣,所以像 LD_PRELOAD 这样的技巧不适合我。

更新:我知道我应该为 real time() 函数提供一些包装器,我会做这些包装器。我的问题是:我是否可以在我的生产库中拥有这些库并在运行时进行控制,或者我永远不应该这样做并且总是应该编译 2 个库:一个用于生产,第二个用于测试?

【问题讨论】:

  • 喜欢这个问题。我经常阅读诸如“模拟这个,模拟那个”之类的语句,其中这个和那个是文件系统,网络等东西,但我认为我没有在任何地方的代码中看到过这种情况(虽然没有彻底搜索) .
  • 查看这个 SO 问题/答案:stackoverflow.com/questions/4594652/…
  • @stijn:我可以将对 real time() 函数的调用替换为调用 real time() 函数或我自己的假 time() 函数的包装函数,我只需要弄清楚如何控制运行时将使用哪种函数。

标签: c unit-testing testing time cross-platform


【解决方案1】:

我过去对嵌入式 C 项目的做法是创建两个项目/配置。
一种配置用于生产环境,包含所有生产代码(包括依赖项),另一种用于单元测试,包含测试驱动程序和所有模拟。两种配置共享要测试的代码来源。
在单元测试环境中,我只是链接到被测代码所需的所有外部函数的 mock 或 fake。

您可以通过多种方式将被测代码链接到它所需的模拟:

  • 您只需将带有模拟的目标文件提供给链接器。大多数链接器将首先使用它们在指定目标文件中找到的名称,而无聊地尝试使用库中的名称来解析它们。一些链接器遵循在命令行中指定目标文件和库的顺序,因此在这些情况下,您应该确保在库之前指定模拟(如果您完全指定了库)。
    对于 C 标准库中的函数,这在技术上会导致未定义的行为,但在大多数情况下,即使对于这些函数也能正常工作。
  • 您创建了一组包装宏,您可以使用定义进行切换。例如:

    #ifdef UNIT_TEST
      #define my_time time_mock
    #else
      #define my_time time
    #endif
    
  • 您创建一组包装函数(在不同的源文件中实现),并提供这些函数的模拟。这样做的一个小缺点是,如果不使用上述项目符号中的一种技术,就无法对这些函数进行单元测试。

更新:即使您的生产代码进入库,单元测试配置也应该生成(一个或多个)可执行文件。
单元测试代码不应包含在生产库中(尽管您可以考虑将其包含在源代码分发中),因为它只会增加额外的包袱,可能会导致错误。如果您的一位客户不小心将库切换到单元测试模式并开始抱怨它已损坏,您会喜欢它吗?

【讨论】:

  • 你不能把定义简化为#ifdef UNIT_TEST / #define time(x) time_mock(x) / #endif吗?替换所有函数调用而不更改变量或结构元素的名称。
  • @tomlogic:形式上,它是未定义的行为(time 保留用于实现,如果你有#include <time.h>),它实际上失败的可能性更高(例如,如果<time.h> 也将time() 声明为宏),此外您必须更加小心#include-s 的顺序。
【解决方案2】:

我会将调用包装起来并检查一个静态变量 (unitTest)。因此,您可以随时从配置文件中初始化行为。

time_t myTime( time_t * timer ) 
{
    if ( unitTest ) {
        return TIME_TEST;
    }
    return time( timer );
}

【讨论】:

  • 我需要使用全局unitTest 变量吗?并从我的测试环境中控制其值以强制执行 TIME_TEST?是否可以避免全局变量?
  • @bialix 您可以将其声明为静态,以便范围仅限于您的包装源文件(翻译单元)并提供启用/禁用单元测试行为的方法。
【解决方案3】:

(我认为您不是在询问实现细节,而是您是否应该将其设为编译时、部署时或运行时配置方面。)

我认为这真的取决于。

如果您对该字段有一个众所周知的配置机制(无论是配置文件、环境变量、命令行参数等),我会将其添加为配置项。然后,您可以在生产中使用与测试中完全相同的二进制文件。一些组织对此要求非常严格。

由于您在生产中永远不需要该功能,因此我建议您将其设置为编译时配置项,这样您就可以 100% 确保您永远不会在生产中错误配置应用程序,因为它不支持该功能。 (当然,您必须绝对确保永远不会将编译为测试的二进制文件部署到生产环境。)我认为,条件定义/条件编译在这里是一个不错的选择。

这就是它的全部内容,不是吗?

【讨论】:

  • “那么您可以在生产环境中使用与测试环境完全相同的二进制文件。有些组织对此要求非常严格。”这些要求通常用于带有外部测试的系统级测试。我从未见过这样的单元测试要求。此外,这些组织通常还要求对该二进制文件中 80% 以上的代码进行经过验证的测试覆盖率,这意味着您必须为该二进制文件中的测试用例和模拟创建离线测试。我认为测试用例的执行不会被接受为测试用例本身的覆盖范围。
  • @BartvanIngenSchenau,sqlite 两者兼而有之。他们测试二进制文件以通过测试,然后测试代码的代码覆盖率。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-12
  • 1970-01-01
相关资源
最近更新 更多