基于属性的 C++ 测试:捕获你未曾寻找的 Bug

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

如果您的单元测试在撒谎怎么办?

\n

您写了一百个单元测试。它们都通过了。C++ 代码看起来完美无缺。但当您在 production 环境中运行应用程序时,它在第 100500 次请求时因 segmentation fault 崩溃了。熟悉的感觉吗?

\n

传统的单元测试只检查您自己想到的内容。它们对您未曾想到的场景视而不见。基于属性的测试(property-based testing, PBT)从根本上解决了这个问题:它不检查具体的示例,而是证明您代码的属性(properties)适用于数百万个随机输入数据。

\n

在本文中,我们将探讨如何使用 RapidCheck 库在 C++ 中引入基于属性的测试,并了解它如何捕获普通单元测试遗漏的 Bug。准备好让您的代码真正经受考验了吗?我们开始吧。

\n\n

基于属性的测试:30 秒了解其哲学

\n

假设您正在测试一个排序函数。常规测试:sort([3,1,2]) == [1,2,3]。好的,检查了一个情况。

\n

基于属性的方法:“对于任何整数列表,排序后长度不变,元素按升序排列,并且原始列表中的每个元素都存在于排序后的列表中”。框架生成数百个随机列表(包括空列表、包含重复项的列表、包含负数的列表)并检查这些属性。

\n

关键思想:您描述必须始终成立的不变量(properties),而不是具体的输入和输出。

\n\n

PBT 在 C++ 中何时特别有用?

\n
    \n
  • 算法和数据结构(排序、搜索、图)
  • \n
  • 解析器和序列化器(编码/解码)
  • \n
  • 数学函数(逆运算、值域)
  • \n
  • 内存管理(内存泄漏、双重释放)
  • \n
  • 任何具有明确不变量的代码
  • \n
\n\n

RapidCheck:您新的最佳伙伴

\n

C++ 中有几个 PBT 库,但 RapidCheck 是最成熟且最方便的。它受到 Haskell 库 QuickCheck 的启发,并且与 Google Test 和 Catch2 完美集成。

\n

安装很简单:它是一个 header-only 库,只需将 include/rapidcheck 中的文件复制到您的项目中即可。或者使用 Conan/vcpkg。

\n\n

第一个测试:逆运算的属性

\n

假设我们有一个函数,它将字符串编码为 base64 然后再解码回来。什么属性是显而易见的?“编码后再解码返回原始字符串”。

\n
#include <rapidcheck.h>\n#include <string>\n\nstd::string encode_base64(const std::string& input);\nstd::string decode_base64(const std::string& input);\n\nint main() {\n    rc::check("编码后再解码返回原始字符串",\n              [](const std::string& original) {\n                  // 字符串生成器已内置\n                  auto encoded = encode_base64(original);\n                  auto decoded = decode_base64(encoded);\n                  RC_ASSERT(decoded == original);\n              });\n    return 0;\n}\n
\n

这里发生了什么?RapidCheck 生成随机字符串(包括空字符串、包含 Unicode 的字符串、包含空字节的字符串)。对于每个字符串,它检查该属性。如果找到反例——它会最小化(shrinking)该反例,使其仍然破坏测试。这是金子:您得到的不仅仅是“存在 Bug”,而是一个具体的、可重现的案例。

\n\n

高级属性:不仅仅是相等性

\n

单调性和幂等性

\n

对于函数 remove_duplicates:

\n
    \n
  • 幂等性:如果应用两次,结果不会改变。
  • \n
  • 顺序不重要:结果列表包含与原始列表相同的元素,但没有重复项。
  • \n
\n
#include <vector>\n#include <algo                

博客

书籍推荐