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 有了更深入的了解,并能够在自己的项目中熟练运用它进行测试工作。
超级会员免费看
367

被折叠的 条评论
为什么被折叠?



