37、Boost.Test 测试框架使用指南

Boost.Test 测试框架使用指南

1. 自定义 main 函数与初始化函数

在使用 Boost.Test 时,若不想让库自动生成 main 函数,可按以下步骤操作:
- 在包含任何库头文件之前,定义 BOOST_TEST_NO_MAIN BOOST_TEST_ALTERNATIVE_INIT_API 宏。
- 在自定义的 main 函数中,调用默认测试运行器 unit_test_main() ,并将默认初始化函数 init_unit_test() 作为参数传入。

示例代码如下:

#define BOOST_TEST_MODULE My first test module
#define BOOST_TEST_NO_MAIN
#define BOOST_TEST_ALTERNATIVE_INIT_API
#include <boost/test/included/unit_test.hpp>
BOOST_AUTO_TEST_CASE(first_test_function)
{
    BOOST_TEST(true);
}
int main(int argc, char* argv[])
{
    return boost::unit_test::unit_test_main(init_unit_test, argc, argv);
}

也可以自定义测试运行器的初始化函数,此时需移除 BOOST_TEST_MODULE 宏的定义,并编写一个无参数且返回 bool 值的初始化函数。示例代码如下:

#define BOOST_TEST_NO_MAIN
#define BOOST_TEST_ALTERNATIVE_INIT_API
#include <boost/test/included/unit_test.hpp>
BOOST_AUTO_TEST_CASE(first_test_function)
{
    BOOST_TEST(true);
}
bool custom_init_unit_test()
{
    std::cout << "test runner custom init" << std::endl;
    return true;
}
int main(int argc, char* argv[])
{
    return boost::unit_test::unit_test_main(
        custom_init_unit_test, argc, argv);
}

若不自定义 main 函数,只需不定义 BOOST_TEST_NO_MAIN 宏,且初始化函数名为 init_unit_test() 即可。

2. 编写和调用测试

Boost.Test 库提供了自动和手动两种方式来注册测试用例和测试套件。自动注册是最简单的方式,只需声明测试单元即可构建测试树。

2.1 准备工作

为了演示测试套件和测试用例的创建,使用以下表示三维点的类:

class point3d
{
    int x_;
    int y_;
    int z_;
public:
    point3d(int const x = 0, 
            int const y = 0, 
            int const z = 0):x_(x), y_(y), z_(z) {}
    int x() const { return x_; }
    point3d& x(int const x) { x_ = x; return *this; }
    int y() const { return y_; }
    point3d& y(int const y) { y_ = y; return *this; }
    int z() const { return z_; }
    point3d& z(int const z) { z_ = z; return *this; }
    bool operator==(point3d const & pt) const
    {
        return x_ == pt.x_ && y_ == pt.y_ && z_ == pt.z_;
    }
    bool operator!=(point3d const & pt) const
    {
        return !(*this == pt);
    }
    bool operator<(point3d const & pt) const
    {
        return x_ < pt.x_ || y_ < pt.y_ || z_ < pt.z_;
    }
    friend std::ostream& operator<<(std::ostream& stream, 
                                point3d const & pt)
    {
        stream << "(" << pt.x_ << "," << pt.y_ << "," << pt.z_ << ")";
        return stream;
    }
    void offset(int const offsetx, int const offsety, int const offsetz)
    {
        x_ += offsetx;
        y_ += offsety;
        z_ += offsetz;
    }
    static point3d origin() { return point3d{}; }
};

注意,本部分的测试用例中故意包含了错误测试,以便产生失败结果。

2.2 创建测试单元
  • 创建测试套件 :使用 BOOST_AUTO_TEST_SUITE(name) BOOST_AUTO_TEST_SUITE_END() 宏。
BOOST_AUTO_TEST_SUITE(test_construction)
// test cases 
BOOST_AUTO_TEST_SUITE_END()
  • 创建测试用例 :使用 BOOST_AUTO_TEST_CASE(name) 宏,测试用例定义在 BOOST_AUTO_TEST_SUITE(name) BOOST_AUTO_TEST_SUITE_END() 之间。
BOOST_AUTO_TEST_CASE(test_constructor)
{
    auto p = point3d{ 1,2,3 };
    BOOST_TEST(p.x() == 1);
    BOOST_TEST(p.y() == 2);
    BOOST_TEST(p.z() == 4); // will fail
}
BOOST_AUTO_TEST_CASE(test_origin)
{
    auto p = point3d::origin();
    BOOST_TEST(p.x() == 0);
    BOOST_TEST(p.y() == 0);
    BOOST_TEST(p.z() == 0);
}
  • 创建嵌套测试套件 :在另一个测试套件中定义测试套件。
BOOST_AUTO_TEST_SUITE(test_operations)
BOOST_AUTO_TEST_SUITE(test_methods)
BOOST_AUTO_TEST_CASE(test_offset)
{
    auto p = point3d{ 1,2,3 };
    p.offset(1, 1, 1);
    BOOST_TEST(p.x() == 2);
    BOOST_TEST(p.y() == 3);
    BOOST_TEST(p.z() == 3); // will fail
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()
  • 添加装饰器 :为测试单元的宏添加额外参数。装饰器可以包括描述、标签、前置条件、依赖项、夹具等。
BOOST_AUTO_TEST_SUITE(test_operations)
BOOST_AUTO_TEST_SUITE(test_operators)
BOOST_AUTO_TEST_CASE(
    test_equal, 
    *boost::unit_test::description("test operator==")
    *boost::unit_test::label("opeq"))
{
    auto p1 = point3d{ 1,2,3 };
    auto p2 = point3d{ 1,2,3 };
    auto p3 = point3d{ 3,2,1 };
    BOOST_TEST(p1 == p2);
    BOOST_TEST(p1 == p3); // will fail
}
BOOST_AUTO_TEST_CASE(
    test_not_equal, 
    *boost::unit_test::description("test operator!=")
    *boost::unit_test::label("opeq")
    *boost::unit_test::depends_on(
        "test_operations/test_operators/test_equal"))
{
    auto p1 = point3d{ 1,2,3 };
    auto p2 = point3d{ 3,2,1 };
    BOOST_TEST(p1 != p2);
}
BOOST_AUTO_TEST_CASE(test_less)
{
    auto p1 = point3d{ 1,2,3 };
    auto p2 = point3d{ 1,2,3 };
    auto p3 = point3d{ 3,2,1 };
    BOOST_TEST(!(p1 < p2));
    BOOST_TEST(p1 < p3);
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()
2.3 执行测试
  • 执行整个测试树 :运行程序(测试模块),不携带任何参数。
chapter11bt_02.exe
Running 6 test cases...
f:/chapter11bt_02/main.cpp(12): error: in "test_construction/test_
constructor": check p.z() == 4 has failed [3 != 4]
f:/chapter11bt_02/main.cpp(35): error: in "test_operations/test_
methods/test_offset": check p.z() == 3 has failed [4 != 3]
f:/chapter11bt_02/main.cpp(55): error: in "test_operations/test_
operators/test_equal": check p1 == p3 has failed [(1,2,3) != 
(3,2,1)]

*** 3 failures are detected in the test module "Testing point 3d"
  • 执行单个测试套件 :运行程序时使用 --run_test 参数指定测试套件的路径。
chapter11bt_02.exe --run_test=test_construction
Running 2 test cases...
f:/chapter11bt_02/main.cpp(12): error: in "test_construction/test_
constructor": check p.z() == 4 has failed [3 != 4]
*** 1 failure is detected in the test module "Testing point 3d"
  • 执行单个测试用例 :运行程序时使用 --run_test 参数指定测试用例的路径。
chapter11bt_02.exe --run_test=test_construction/test_origin
Running 1 test case...
*** No errors detected
  • 执行具有相同标签的测试套件和测试用例集合 :运行程序时使用 --run_test 参数指定标签名,并在前面加上 @
chapter11bt_02.exe --run_test=@opeq
Running 2 test cases...
f:/chapter11bt_02/main.cpp(56): error: in "test_operations/test_
operators/test_equal": check p1 == p3 has failed [(1,2,3) != 
(3,2,1)]
*** 1 failure is detected in the test module "Testing point 3d"
3. 测试树的结构和装饰器

测试树由测试套件和测试用例组成。测试套件可以包含一个或多个测试用例和其他嵌套测试套件。测试套件类似于命名空间,可以在同一个文件或不同文件中多次停止和重新启动。

自动注册测试套件使用 BOOST_AUTO_TEST_SUITE BOOST_AUTO_TEST_SUITE_END 宏,自动注册测试用例使用 BOOST_AUTO_TEST_CASE 宏。测试单元(无论是用例还是套件)都会成为最近的测试套件的成员,在文件作用域级别定义的测试单元会成为主测试套件的成员。

测试套件和测试用例都可以使用一系列属性进行装饰,这些属性会影响测试单元在测试模块执行期间的处理方式。目前支持的装饰器如下表所示:
| 装饰器 | 说明 |
| ---- | ---- |
| depends_on | 表示当前测试单元与指定测试单元之间的依赖关系 |
| description | 提供测试单元的语义描述 |
| enabled / disabled | 将测试单元的默认运行状态设置为 true false |
| enable_if | 根据编译时表达式的计算结果,将测试单元的默认运行状态设置为 true false |
| fixture | 指定在测试单元执行前后调用的一对函数(启动和清理) |
| label | 将测试单元与标签关联,同一个标签可以用于多个测试单元,一个测试单元也可以有多个标签 |
| precondition | 将一个谓词与测试单元关联,在运行时用于确定测试单元的运行状态 |

如果测试用例的执行导致未处理的异常,框架会捕获该异常并以失败终止测试用例的执行。不过,框架提供了几个宏来测试特定代码是否会抛出异常。

测试模块的测试树中的测试单元可以全部或部分执行。执行测试单元时,只需执行代表测试模块的二进制程序。若要仅执行部分测试单元,可使用 --run_test 命令行选项(或 --t 作为简称),该选项允许过滤测试单元,并指定路径或标签。路径由一系列测试套件和/或测试用例名称组成,标签是使用 label 装饰器定义的名称,在 run_test 参数中需在前面加上 @ ,该参数可重复使用,即可以指定多个过滤器。

4. 使用 Boost.Test 进行断言

一个测试用例包含一个或多个测试。Boost.Test 库以宏的形式提供了一系列 API 来编写测试。 BOOST_TEST 宏是最重要且最常用的宏,以下是一些常用的测试 API:
- 普通形式的 BOOST_TEST :用于大多数测试。

int a = 2, b = 4;
BOOST_TEST(a == b);
BOOST_TEST(4.201 == 4.200);
std::string s1{ "sample" };
std::string s2{ "text" };
BOOST_TEST(s1 == s2);
  • BOOST_TEST tolerance() 操纵器 :用于指定浮点数比较的容差。
BOOST_TEST(4.201 == 4.200, 
           boost::test_tools::tolerance(0.001));
  • BOOST_TEST per_element() 操纵器 :用于对容器进行逐元素比较(即使是不同类型的容器)。
std::vector<int> v{ 1,2,3 };
std::list<short> l{ 1,2,3 };
BOOST_TEST(v == l, boost::test_tools::per_element());
  • BOOST_TEST 与三元运算符和使用逻辑 || && 的复合语句 :需要额外的括号。
BOOST_TEST((a > 0 ? true : false));
BOOST_TEST((a > 2 && b < 5));
  • BOOST_ERROR :无条件使测试失败并在报告中生成消息,相当于 BOOST_TEST(false, message)
BOOST_ERROR("this test will fail");
  • BOOST_TEST_WARN :在测试失败时在报告中生成警告,不会增加错误数量,也不会停止测试用例的执行。
BOOST_TEST_WARN(a == 4, "something is not right");
  • BOOST_TEST_REQUIRE :确保测试用例的前置条件得到满足,否则停止测试用例的执行。
BOOST_TEST_REQUIRE(a == 4, "this is critical");
  • BOOST_FAIL :无条件停止测试用例的执行,增加错误数量,并在报告中生成消息,相当于 BOOST_TEST_REQUIRE(false, message)
BOOST_FAIL("must be implemented");
  • BOOST_IS_DEFINED :用于检查特定预处理器符号在运行时是否定义,通常与 BOOST_TEST 一起使用进行验证和日志记录。
BOOST_TEST(BOOST_IS_DEFINED(UNICODE));

BOOST_TEST 宏有三种变体:
- BOOST_TEST_CHECK :与 BOOST_TEST 相同,用于执行检查。
- BOOST_TEST_WARN :用于提供信息但不增加错误数量和停止测试用例执行的断言。
- BOOST_TEST_REQUIRE :用于确保测试用例继续执行所需的前置条件得到满足,失败时会增加错误数量并停止测试用例的执行。

BOOST_TEST 宏的一般形式是 BOOST_TEST(statement) ,它提供了丰富而灵活的报告功能。默认情况下,它不仅会显示语句,还会显示操作数的值,以便快速识别失败原因。用户也可以提供替代的失败描述,此时该消息会记录在测试报告中。

该宏还支持特殊的比较过程:
- 浮点数比较 :可以定义容差来测试相等性。
- 容器比较 :支持默认比较(使用重载的 operator== )、逐元素比较和字典序比较。逐元素比较可以比较不同类型的容器,并考虑容器的大小。
- 按位比较 :失败时,框架会报告比较失败的位的索引。

BOOST_TEST 宏有一些限制,不能用于使用逗号的复合语句,以及使用逻辑运算符 || && 的复合语句,对于后一种情况,可以使用额外的括号作为解决方法。

还有几个宏可用于测试特定表达式在求值期间是否会抛出异常,其中 <level> 可以是 CHECK WARN REQUIRE
- BOOST_<level>_NO_THROW(expr) :检查表达式 expr 是否会抛出异常。
- BOOST_<level>_THROW(expr, exception_type) :检查表达式 expr 是否会抛出指定类型的异常。
- BOOST_<level>_EXCEPTION(expr, exception_type, predicate) :检查表达式 expr 是否会抛出指定类型的异常,并将表达式传递给谓词进行进一步检查。

5. 在 Boost.Test 中使用夹具

随着测试模块变大且测试用例变得更加相似,很可能会有一些测试用例需要相同的设置、清理和数据。包含这些内容的组件称为测试夹具或测试上下文。Boost.Test 提供了几种方法来为测试用例、测试套件或整个模块(全局)定义测试夹具。

5.1 准备工作

使用以下类和函数来指定测试单元的夹具:

struct standard_fixture
{
    standard_fixture()  {BOOST_TEST_MESSAGE("setup");}
    ~standard_fixture() {BOOST_TEST_MESSAGE("cleanup");}
    int n {42};
};
struct extended_fixture
{
    std::string name;
    int data;
    extended_fixture(std::string const & n = "") : name(n), data(0) 
    {
        BOOST_TEST_MESSAGE("setup "+ name);
    }
    ~extended_fixture()
    {
        BOOST_TEST_MESSAGE("cleanup "+ name);
    }
};
void fixture_setup()
{
    BOOST_TEST_MESSAGE("fixture setup");
}
void fixture_cleanup()
{
    BOOST_TEST_MESSAGE("fixture cleanup");
}
5.2 定义测试夹具的方法
  • 为特定测试用例定义夹具 :使用 BOOST_FIXTURE_TEST_CASE 宏。
BOOST_FIXTURE_TEST_CASE(test_case, extended_fixture)
{
    data++;
    BOOST_TEST(data == 1);
}
  • 为测试套件中的所有测试用例定义夹具 :使用 BOOST_FIXTURE_TEST_SUITE 宏。
BOOST_FIXTURE_TEST_SUITE(suite1, extended_fixture)
BOOST_AUTO_TEST_CASE(case1)
{
    BOOST_TEST(data == 0);
}
BOOST_AUTO_TEST_CASE(case2)
{
    data++;
    BOOST_TEST(data == 1);
}
BOOST_AUTO_TEST_SUITE_END()
  • 为测试套件中的所有测试单元定义夹具,但排除一个或多个测试单元 :使用 BOOST_FIXTURE_TEST_SUITE 宏,并使用 BOOST_FIXTURE_TEST_CASE BOOST_FIXTURE_TEST_SUITE 为特定测试单元覆盖夹具。
BOOST_FIXTURE_TEST_SUITE(suite2, extended_fixture)
BOOST_AUTO_TEST_CASE(case1)
{
    BOOST_TEST(data == 0);
}
BOOST_FIXTURE_TEST_CASE(case2, standard_fixture)
{
    BOOST_TEST(n == 42);
}
BOOST_AUTO_TEST_SUITE_END()
  • 为测试用例或测试套件定义多个夹具 :使用 boost::unit_test::fixture BOOST_AUTO_TEST_SUITE BOOST_AUTO_TEST_CASE 宏。
BOOST_AUTO_TEST_CASE(test_case_multifix,
    * boost::unit_test::fixture<extended_fixture>
        (std::string("fix1"))
    * boost::unit_test::fixture<extended_fixture>
        (std::string("fix2"))
    * boost::unit_test::fixture<standard_fixture>())
{
    BOOST_TEST(true);
}
  • 使用自由函数作为夹具的设置和清理操作 :使用 boost::unit_test::fixture
BOOST_AUTO_TEST_CASE(test_case_funcfix,
    * boost::unit_test::fixture(&fixture_setup, 
                                &fixture_cleanup))
{
    BOOST_TEST(true);
}
  • 为整个模块定义夹具 :使用 BOOST_GLOBAL_FIXTURE 宏。
BOOST_GLOBAL_FIXTURE(standard_fixture);

综上所述,Boost.Test 提供了丰富的功能来编写、组织和执行测试,包括自定义 main 函数和初始化函数、创建测试套件和测试用例、使用装饰器、进行断言以及使用夹具等。通过合理使用这些功能,可以提高测试的效率和可维护性。

Boost.Test 测试框架使用指南

6. 测试执行流程总结

为了更清晰地了解如何执行测试,下面通过一个 mermaid 流程图来展示整个测试执行的流程:

graph TD;
    A[开始执行测试程序] --> B{是否有 --run_test 参数?};
    B -- 否 --> C[执行整个测试树];
    B -- 是 --> D{参数是路径还是标签?};
    D -- 路径 --> E{路径指向测试套件还是测试用例?};
    E -- 测试套件 --> F[执行指定测试套件];
    E -- 测试用例 --> G[执行指定测试用例];
    D -- 标签 --> H[执行具有指定标签的测试单元];
    C --> I[检查测试结果];
    F --> I;
    G --> I;
    H --> I;
    I -- 有失败 --> J[输出失败信息];
    I -- 无失败 --> K[输出无错误信息];
    J --> L[结束测试];
    K --> L;

从这个流程图可以看出,测试执行的核心步骤如下:
1. 启动测试程序。
2. 判断是否使用了 --run_test 参数。
- 如果没有使用,执行整个测试树。
- 如果使用了,进一步判断参数是路径还是标签。
- 若是路径,再判断是指向测试套件还是测试用例,分别执行相应的测试单元。
- 若是标签,执行具有该标签的所有测试单元。
3. 检查测试结果,根据是否有失败输出相应信息。
4. 结束测试。

7. 不同测试场景下的最佳实践

在实际使用 Boost.Test 时,不同的测试场景可能需要不同的策略。以下是一些常见测试场景及其对应的最佳实践:

7.1 小型项目测试

对于小型项目,测试用例和测试套件相对较少,建议采用自动注册的方式创建测试单元,这样可以简化测试代码的编写。同时,可以使用默认的初始化函数和 main 函数,减少不必要的配置。例如:

#define BOOST_TEST_MODULE SmallProjectTest
#include <boost/test/included/unit_test.hpp>

BOOST_AUTO_TEST_CASE(simple_test)
{
    int a = 1;
    BOOST_TEST(a == 1);
}
7.2 大型项目测试

大型项目通常有大量的测试用例和复杂的依赖关系。此时,建议使用装饰器来管理测试单元之间的依赖关系和运行顺序,使用夹具来共享测试数据和设置。例如:

#define BOOST_TEST_MODULE LargeProjectTest
#define BOOST_TEST_NO_MAIN
#define BOOST_TEST_ALTERNATIVE_INIT_API
#include <boost/test/included/unit_test.hpp>

struct ProjectFixture
{
    ProjectFixture() { /* 初始化操作 */ }
    ~ProjectFixture() { /* 清理操作 */ }
};

BOOST_FIXTURE_TEST_SUITE(project_tests, ProjectFixture)

BOOST_AUTO_TEST_CASE(test_case1, *boost::unit_test::depends_on("project_tests/test_case2"))
{
    // 测试代码
}

BOOST_AUTO_TEST_CASE(test_case2)
{
    // 测试代码
}

BOOST_AUTO_TEST_SUITE_END()

bool custom_init_unit_test()
{
    // 自定义初始化逻辑
    return true;
}

int main(int argc, char* argv[])
{
    return boost::unit_test::unit_test_main(custom_init_unit_test, argc, argv);
}
7.3 性能测试

如果需要进行性能测试,可以使用 Boost.Test 的装饰器来标记性能测试用例,然后使用专门的性能测试工具进行分析。例如:

#define BOOST_TEST_MODULE PerformanceTest
#include <boost/test/included/unit_test.hpp>

BOOST_AUTO_TEST_CASE(performance_test, *boost::unit_test::label("performance"))
{
    // 性能测试代码
}

之后可以通过 --run_test=@performance 来执行所有标记为性能测试的用例。

8. 常见问题及解决方法

在使用 Boost.Test 过程中,可能会遇到一些常见问题,以下是一些问题及对应的解决方法:

问题 解决方法
BOOST_TEST 宏在复合语句中出错 对于使用逻辑运算符 || && 的复合语句,使用额外的括号,如 BOOST_TEST((statement))
测试用例抛出未处理的异常 使用 BOOST_<level>_NO_THROW BOOST_<level>_THROW BOOST_<level>_EXCEPTION 宏来测试异常情况
测试用例执行顺序不符合预期 使用 depends_on 装饰器来明确测试单元之间的依赖关系
测试报告信息不够详细 可以使用 description 装饰器为测试单元添加详细描述,或者在 BOOST_TEST 宏中提供自定义的失败描述
9. 总结

Boost.Test 是一个功能强大的 C++ 测试框架,它提供了丰富的功能来满足不同的测试需求。通过自定义 main 函数和初始化函数,我们可以灵活控制测试的启动过程;使用自动注册的宏创建测试套件和测试用例,简化了测试代码的编写;装饰器可以帮助我们管理测试单元的依赖关系、运行状态和提供详细描述;各种断言宏让我们能够准确地验证代码的正确性;而夹具则可以共享测试数据和设置,提高测试代码的可维护性。

在实际应用中,我们可以根据项目的规模和特点选择合适的测试策略,同时注意避免常见问题,以确保测试的有效性和可靠性。通过合理使用 Boost.Test 的这些功能,我们可以构建出高质量的测试体系,为代码的稳定性和可维护性提供有力保障。

希望通过本文的介绍,你对 Boost.Test 有了更深入的了解,并能够在自己的项目中熟练运用它进行测试工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值