文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

介绍

doctest是一个用于单元测试和TDD的完全开源轻量级和功能丰富的C ++ 98 / C ++ 11单头测试框架。

它受到D编程语言中unittest {}功能和Python的 文档注释 的启发----测试可以被认为是一种文档形式,应该能够驻留在他们测试的产品代码附近。 这对任何其他C + +测试框架来说这是不可能的(或至少在实际应用上)。

网站:  https://github.com/onqtam/doctest
版本测试:1.1.3
系统需求:C++98 或更新版本
许可 & 定价::MIT, 免费
支持与维护:问题讨论在GitHub 项目页面 

第 1 段(可获 1.31 积分)

一个编译成可执行文件的自动记录式测试的完整的示例是这样的:

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"

int fact(int n) { return n <= 1 ? n : fact(n - 1) * n; }

TEST_CASE("testing the factorial function") {
    CHECK(fact(0) == 1); // will fail
    CHECK(fact(1) == 1);
    CHECK(fact(2) == 2);
    CHECK(fact(10) == 3628800);
}

上面程序的输出如下:

[doctest] doctest version is "1.1.3"
[doctest] run with "--help" for options
========================================================
main.cpp(6)
testing the factorial function

main.cpp(7) FAILED!
  CHECK( fact(0) == 1 )
with expansion:
  CHECK( 0 == 1 )

========================================================
[doctest] test cases:    1 |    0 passed |    1 failed |
[doctest] assertions:    4 |    3 passed |    1 failed |
第 2 段(可获 0.3 积分)

请注意标准C++运算符对于等式的比较是如何使用的----doctest 有一个核心的宏定义(它也具有小于,等于,大于)----但是完整的表达式被分解,而且左值和右值被记录下来。这是通过表达式模板和C++策略实现的。此外测试用例自动记录 -您不需要手动将其插入列表。

Doctest在 Catch之后被模式化,而 Catch 是当前在C ++中测试的中最受欢迎选择之一 - 查看他们的不同请点击 FAQ。 目前,Catch有一些缺陷,但doctest将最终成为Catch的超集。

第 3 段(可获 1.4 积分)

框架之后的动机 --它为何与众不同

有许多的C++测试框架如:- Catch, Boost.Test, UnitTest++, cpputest, googletest许多其他

而使doctest与众不同的是因为它的超轻量级的编译时间(增强两个数量级)和非侵入性。

和其他测试框架主要的区别是:

  • 超轻量级 -- 低于10ms的编译时间开销,包括编译源文件中的头文件
  • 最快的宏定义 -- 编译50 000 宏定义可以在30s内(甚至10s内)
  • 子用例 -- 用一种直观的方式来共享测试用例的通用设置和重组代码(替代测试用例)
  • 提供了一种方法,从 DOCTEST_CONFIG_DISABLE 标识的二进制文件中删除所有与测试相关的文件
  • 不污染全局命名空间(一切都在doctest命名空间中),并且不需要拖动任何头文件
  •  不会产生任何告警,即使在MSVC / GCC / Clang 等编译器中最容易告警的级别也不会产生告警
    • -Weverything for Clang
    • /W4 for MSVC
    • -Wall -Wextra -pedantic and over 50 other flags!
  • 非常好的移植性,在 C++98 上测试依然表现良好-集成测试中每个提交的测试使用超过220中不同的构建和配置(gcc 4.4-6.1 / clang 3.4-3.9 / MSVC 2008-2015, debug / release, x86/x64, linux / windows / osx, valgrind, sanitizers...)
  • 只有一个头文件,而且除了C/C++标准库(仅在测试运行器中使用)之外没有外部依赖关系
第 4 段(可获 3 积分)

这个框架提供的独特的能力

前面列出的所有优点允许这个框架有更多的方式可以使用,而其它框架使无法支持----测试可以直接写在生产代码中。

  • 这使得编写测试代码的障碍更少--你不需要:1.编写一个单独的源文件 2.引入一大堆其他的东西 3.将其添加到构建系统。和4.将其添加到源代码控制中----你可以只为一个类或一部分功能在其源文件的底部-或者甚至头文件中编写测试代码。
  • 在生产代码中的测试用例可以当成项目文档或者最新的注释--展示API是如果被调用的(编译后正确执行)。
  • 内部测试不通过公共API暴露,并且头文件变的更加简单。
  • 在C++中测试驱动开发从来不是一件容易的事。
第 5 段(可获 1.9 积分)

该框架仍然可以像其他框架那样使用,即使在生产代码中写测试的思想不吸引你--但这是这个框架最大的功能--其他任何框架无法提供!

还有许多 其他功能 和更多的计划在 路线图.

 main() 函数入口点

正如我们在上面的例子中看到的 - 程序中main()函数入口点可以由框架提供。 然而,如果你在生产代码中编写测试,那么你可能已经有一个main()函数。 以下代码示例显示如何从用户main()使用doctest:

#define DOCTEST_CONFIG_IMPLEMENT
#include "doctest.h"
int main(int argc, char** argv) {
    doctest::Context ctx;
    ctx.setOption("abort-after", 5);  // default - stop after 5 failed asserts
    ctx.applyCommandLine(argc, argv); // apply command line - argc / argv
    ctx.setOption("no-breaks", true); // override - don't break in the debugger
    int res = ctx.run();              // run test cases unless with --no-run
    if(ctx.shouldExit())              // query flags (and --exit) rely on this
        return res;                   // propagate the result of the tests
    // your code goes here
    return res; // + your_program_res
}

 

第 6 段(可获 1.38 积分)

使用此设置可能有以下3中情况:

  • 只运行测试(使用--exit选项)
  • 只运行用户代码(使用--no-run 选项)
  • 运行测试和用户代码

如果在生产代码中直接编写测试,这是必须的。

此外,该示例展示了如何为命令行选项设置默认值和覆盖。

请注意,在包含框架头文件之前应定义 DOCTEST_CONFIG_IMPLEMENTDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN  标识符,----但只能在一个源文件中定义,以便在这里实现测试运行器。其他的地方只需要包含头文件和编写一些测试代码。这是单头文件库的一种常见做法,需要将它们的一部分编译到一个源文件中(在这种情况下是测试运行器)。

第 7 段(可获 1.81 积分)

删除所有与测试相关的二进制文件

当你创建将要给用户使用的发行版的编译时,你可能需要删除生产代码中的测试。使用doctest来完成的方法是:通过在你整个项目中定义 DOCTEST_CONFIG_DISABLE  预处理标识符来完成 。

示例中标识符对 TEST_CASE 宏的影响如下--- 它变成一个永远不会实例化的匿名模板:

#define TEST_CASE(name) \

    template <typename T>                     \
    static inline void ANONYMOUS(ANON_FUNC_)()
第 8 段(可获 0.98 积分)

这意味着所有的测试用例都会从生成的二进制文件中删除 - 即使在调试模式下! 链接器不会看到匿名测试用例函数,因为它们从不实例化。

ANONYMOUS() 宏用于在每次调用时获取唯一的标识符 --- 它使用 __COUNTER__ 预处理器宏来返回一个整数,且每次使用都会返回一个大于最后一次的整数。 例如:

int ANONYMOUS(ANON_VAR_); // int ANON_VAR_5;
int ANONYMOUS(ANON_VAR_); // int ANON_VAR_6;

子用例----- 在测试用例之间共享设置/重组代码的最简单方法

第 9 段(可获 1.13 积分)

假设你想在几个测试用例中打开一个文件并从中读取。如果你不想多次复制/粘贴相同的设置代码,你可以使用doctest的子用例机制。

TEST_CASE("testing file stuff") {
    printf("opening the file\n");
    FILE* fp = fopen("path/to/file", "r");
    
    SUBCASE("seeking in file") {
        printf("seeking\n");
        // fseek()
    }
    SUBCASE("reading from file") {
        printf("reading\n");
        // fread()
    }
    printf("closing...\n");
    fclose(fp);
}

将打印下面的文本:

opening the file
seeking
closing...
opening the file
reading
closing...

如您所见,测试用例输入了两次 - 每次输入不同的子文件。子用例也可以无限嵌套。执行模型类似于DFS遍历 - 每次从测试用例的开始处开始,并遍历“树”直到对叶子节点(尚未被遍历的节点)的遍历,然后通过已入栈的嵌套子用例的出栈来退出该测试用例。

第 10 段(可获 1.5 积分)

编译时间基准

因此,有3种类型的编译时基准与doctest相关:

  • 包含头文件的时间开销
  • 宏定义的时间开销
  • 当使用 DOCTEST_CONFIG_DISABLE 标识符删除所有测试时,构建时间会下降多少

综上所述:

  • 包含doctest 头文件的时间开销 大约10ms,和Catch的430ms相比----doctest 时间少25-50倍;
  • 50 000宏定义编译大约60秒,这比Catch快大约25%;
  • 如果使用替代的宏定义(用于高级用户),则编译50 000个宏定义可以低至10秒;
  • 在500个测试用例中使用50 000宏定义 ,只有禁用 DOCTEST_CONFIG_DISABLE  时失效 --小于2秒!
第 11 段(可获 1.55 积分)

基准测试页面 你可以看到基准的设置和更多的详细信息。

结论

doctest框架非常容易上手,并且是完全透明和非侵入性的 - 包括它自身和编写的测试用例在编译时间和集成(警告,构建系统等)方面不会现特殊状况。 使用它将尽可能加快您的开发进程 - 没有其他框架是如此容易使用!

 

第 12 段(可获 0.93 积分)

文章评论