白盒单元测试框架
1 引言
当你开始开发新的库/类/程序时,你需要做的第一件事是什么?
没错。你需要从单元测试模块开始。
现在我们来看看C++ 白盒测试比较流行的单元测试框架。
2 Boost.Test
Boost.Test是一个C++03/11/14/17单元测试库,可用于多种平台和编译器。
该库是Boost的一部分。该库的最新版本可以从boost网站上获得。
该库的完整使用说明可以从http://www.boost.org/doc/libs/release/libs/test/。
2.1 官方网站
https://github.com/boostorg/test
2.2 许可
Boost Software License - Version 1.0
2.3 主要特点及使用方法
l 容易上手:
n 下载并使用boost库
n 用下面两行创建一个测试模块:
#define BOOST_TEST_MODULE your_test_module
#include <boost/test/included/unit_test.hpp>
n 编写一个测试案例:
BOOST_AUTO_TEST_CASE( your_test_case ) {
std::vector<int> a{1, 2};
std::vector<int> b{1, 2};
BOOST_TEST( a == b );
}
n 编译运行
l 强大而独特的测试断言宏BOOST_TEST,可用来检测浮点数、集合、字符串......并支持适当的比较范式。
l 自注册测试用例,在测试套件中组织用例,在测试用例、套件或全局上应用Fixture。
l 为故障的高级诊断提供断言。
l 强大且可扩展的数据集测试。
l 可为测试用例和套件添加高级描述、组/标签和依赖关系的装饰。
l 强大的命令行选项和测试案例过滤器。
l 为第三方工具提供可扩展的日志记录、XML和JUNIT输出(例如:连续集成)。
l 各种方法(如共享/静态库/头)用以加快集成和/或编译/构建周期,生成更小的二进制文件。
2.4 用法
2.4.1 简单测试
看一下简单的测试用例。
#include <my_class.hpp>
#define BOOST_TEST_MODULE MyTest
#include <boost/test/unit_test.hpp>
BOOST_AUTO_TEST_CASE( my_test )
{
my_class test_object( "qwerty" );
BOOST_CHECK( test_object.is_valid() );
}
#define BOOST_TEST_MODULE MyTest
#include <boost/test/unit_test.hpp>
int add( int i, int j ) { return i+j; }
BOOST_AUTO_TEST_CASE( my_test )
{
// 七种方法来检测和报告相同的错误:
BOOST_CHECK( add( 2,2 ) == 4 ); // #1 出错后继续
BOOST_REQUIRE( add( 2,2 ) == 4 ); // #2 出错后抛出
if( add( 2,2 ) != 4 )
BOOST_ERROR( "Ouch..." ); // #3 出错后继续
if( add( 2,2 ) != 4 )
BOOST_FAIL( "Ouch..." ); // #4 出错后抛出
if( add( 2,2 ) != 4 ) throw "Ouch..."; // #5 出错后抛出
BOOST_CHECK_MESSAGE( add( 2,2 ) == 4, // #6 出错后继续
"add(..) result: " << add( 2,2 ) );
BOOST_CHECK_EQUAL( add( 2,2 ), 4 ); // #7 出错后继续
}
2.4.2 Fixture
看一下测试用例Fixture。
Fixture设置在测试用例执行前被调用,Fixture拆除在测试用例完成执行后被调用,与测试用例的执行状态无关。
#define BOOST_TEST_MODULE example
#include <boost/test/included/unit_test.hpp>
struct F {
F() : i( 0 ) { BOOST_TEST_MESSAGE( "setup fixture" ); }
~F() { BOOST_TEST_MESSAGE( "teardown fixture" ); }
int i;
};
BOOST_FIXTURE_TEST_CASE( test_case1, F )
{
BOOST_TEST( i == 1 );
++i;
}
BOOST_FIXTURE_TEST_CASE( test_case2, F )
{
BOOST_CHECK_EQUAL( i, 1 );
}
BOOST_AUTO_TEST_CASE( test_case3 )
{
BOOST_TEST( true );
}
2.4.3 Suite
在测试套件开始和结束声明之间定义的测试单元成为测试Suite的成员。一个测试单元总是成为最近的测试Suite的成员。在测试文件范围内声明的测试单元会成为主测试Suite的成员。测试Suite包含的深度没有限制。
#define BOOST_TEST_MODULE example
#include <boost/test/included/unit_test.hpp>
BOOST_AUTO_TEST_SUITE( test_suite1 )
BOOST_AUTO_TEST_CASE( test_case1 )
{
BOOST_WARN( sizeof(int) < 4 );
}
BOOST_AUTO_TEST_CASE( test_case2 )
{
BOOST_REQUIRE_EQUAL( 1, 2 );
BOOST_FAIL( "Should never reach this line" );
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE( test_suite2 )
BOOST_AUTO_TEST_CASE( test_case3 )
{
BOOST_CHECK( true );
}
BOOST_AUTO_TEST_CASE( test_case4 )
{
BOOST_CHECK( false );
}
BOOST_AUTO_TEST_SUITE_END()
3 cppunit
CppUnit是著名的JUnit框架的C++移植,用于单元测试。
测试输出为XML或文本格式,用于自动测试。
3.1 官方网站
http://cppunit.sourceforge.net/
3.2 许可
GNU Library or Lesser General Public License version 2.0 (LGPLv2)
3.3 用法
3.3.1 简单的测试
派生TestCase类。重写方法runTest()。当你想检查一个值时,调用CPPUNIT_ASSERT(bool),并传入一个表达式。
如果测试成功则为真。
class ComplexNumberTest : public CppUnit::TestCase {
public:
ComplexNumberTest( std::string name ) : CppUnit::TestCase( name ) {}
void runTest() {
CPPUNIT_ASSERT( Complex (10, 1) == Complex (10, 1) );
CPPUNIT_ASSERT( !(Complex (1, 1) == Complex (2, 2)) );
}
};
3.3.2 Fixture
Fixture是一组已知的对象,作为一组测试用例的基础。在开发过程中进行测试时,Fixture非常方便。
class ComplexNumberTest : public CppUnit::TestFixture {
private:
Complex *m_10_1, *m_1_1, *m_11_2;
public:
void setUp()
{
m_10_1 = new Complex( 10, 1 );
m_1_1 = new Complex( 1, 1 );
m_11_2 = new Complex( 11, 2 );
}
void tearDown()
{
delete m_10_1;
delete m_1_1;
delete m_11_2;
}
};
3.3.3 测试案例
如何使用Fixture编写和调用单个测试?
这个过程有两个步骤:
1. 把测试用例写成Fixture类的方法
2. 创建一个运行该方法的TestCaller。
class ComplexNumberTest : public CppUnit::TestFixture {
private:
Complex *m_10_1, *m_1_1, *m_11_2;
public:
void setUp()
{
m_10_1 = new Complex( 10, 1 );
m_1_1 = new Complex( 1, 1 );
m_11_2 = new Complex( 11, 2 );
}
void tearDown()
{
delete m_10_1;
delete m_1_1;
delete m_11_2;
}
void testEquality()
{
CPPUNIT_ASSERT( *m_10_1 == *m_10_1 );
CPPUNIT_ASSERT( !(*m_10_1 == *m_11_2) );
}
void testAddition()
{
CPPUNIT_ASSERT( *m_10_1 + *m_1_1 == *m_11_2 );
}
};
可以像这样为每个测试用例创建并运行实例:
CppUnit::TestCaller<ComplexNumberTest> test( "testEquality",
&ComplexNumberTest::testEquality );
CppUnit::TestResult result;
test.run( &result );
测试调用器构造函数的第二个参数是ComplexNumberTest上一个方法的地址。当测试调用器运行时,该特定方法将被运行。
一旦你有几个测试,就把它们组织成一个Suite。
3.3.4 Suite
CppUnit提供了一个TestSuite类,可以一起运行任意数量的TestCases。
要创建一个由两个或多个测试组成的套件,请执行以下操作:
CppUnit::TestSuite suite;
CppUnit::TestResult result;
suite.addTest( new CppUnit::TestCaller<ComplexNumberTest>(
"testEquality",
&ComplexNumberTest::testEquality ) );
suite.addTest( new CppUnit::TestCaller<ComplexNumberTest>(
"testAddition",
&ComplexNumberTest::testAddition ) );
suite.run( &result );
3.3.5 TestRunner
一旦你有了一个测试Suite,你就会想要运行它。CppUnit 提供了一些工具来定义要运行的Suite并显示其结果。你可以通过一个静态方法Suite来使你的Suite可以被TestRunner程序访问,并返回一个测试Suite。
#include <cppunit/ui/text/TestRunner.h>
#include "ExampleTestCase.h"
#include "ComplexNumberTest.h"
int main( int argc, char **argv)
{
CppUnit::TextUi::TestRunner runner;
runner.addTest( ExampleTestCase::suite() );
runner.addTest( ComplexNumberTest::suite() );
runner.run();
return 0;
}
TestRunner将运行这些测试。如果所有的测试都通过了,你会得到一条信息性消息。如果有任何测试失败,你会得到以下信息:
n 失败的测试用例的名称
n 包含测试的源文件的名称。
n 发生故障的行号
n 调用CPPUNIT_ASSERT()检测到故障的所有文本。
CppUnit可以区分失败和错误。失败是预期的,并通过断言进行检查。错误是未预料到的问题,比如零除法和其他由C++运行时或你的代码引发的异常。
4 GoogleTest
谷歌测试和模拟框架。
4.1 官方网站
https://github.com/google/googletest/
4.2 许可
BSD-3-Clause License
4.3 主要特点
n 一个xUnit测试框架。
n 测试发现。
n 丰富的断言集。
n 用户定义的断言。
n 死亡测试。
n 致命和非致命的失败。
n 值参数化测试。
n 类型参数化测试。
n 运行测试的各种选项。
n XML测试报告的生成。
4.4 用法
4.4.1 简单的测试
创建一个测试:
1. 使用TEST()宏来定义和命名一个测试函数。这些都是不返回值的普通C++函数。
2. 在这个函数中,连同你想包含的任何有效的C++语句,使用各种googletest断言来检查值。
3. 测试的结果是由断言决定的,如果测试中的任何断言失败(无论是致命的还是非致命的),或者测试崩溃,整个测试就会失败。否则,就会成功。
TEST(TestSuiteName, TestName) {
... 测试体 ...
}
假设我们有一个简单的函数:
int Factorial(int n); // 返回n的阶乘。
这个功能的测试Suite:
// 测试0的阶乘。
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(Factorial(0), 1);
}
// 测试正数的阶乘。
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(Factorial(1), 1);
EXPECT_EQ(Factorial(2), 2);
EXPECT_EQ(Factorial(3), 6);
EXPECT_EQ(Factorial(8), 40320);
}
4.4.2 Fixture
要创建一个Fixture:
1. 从::testing::Test衍生出一个类。用protected:定义它的主体,因为我们要从子类中访问Fixture成员。
2. 在类的内部,声明计划使用的任何对象。
3. 如果有必要,写一个默认的构造函数或SetUp()函数来为每个测试准备对象。一个常见的错误是将SetUp()写成Setup(),并加上一个小u--在C++11中使用override来确保拼写正确。
4. 如果有必要,写一个析构器或TearDown()函数来释放你在SetUp()中分配的任何资源。
5. 如果需要,为你的测试定义子程序来共享。
当使用Fixture时,使用TEST_F()代替TEST(),允许访问测试Fixture中的对象和子程序。
TEST_F(TestFixtureName, TestName) {
... 测试体 ...
}
class QueueTest : public ::testing::Test {
protected:
void SetUp() override {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// void TearDown() override {}
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
TEST_F(QueueTest, IsEmptyInitially)
{
EXPECT_EQ(q0_.size(), 0);
}
TEST_F(QueueTest, DequeueWorks)
{
int *n = q0_.Dequeue();
EXPECT_EQ(n, nullptr);
n = q1_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 1);
EXPECT_EQ(q1_.size(), 0);
delete n;
n = q2_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 2);
EXPECT_EQ(q2_.size(), 1);
delete n;
}
};
当这些测试运行时,会发生以下情况:
1. googletest构建了一个QueueTest对象(我们称它为t1)。
2. t1.SetUp() 初始化 t1。
3. 第一个测试 (IsEmptyInitially) 在 t1 上运行。
4. t1.TearDown() 在测试结束后进行清理。
5. t1被销毁。
6. 在另一个QueueTest对象上重复上述步骤,这次运行DequeueWorks测试。
5 Catch2
一个现代的、C++-native的、只引入头文件的、用于单元测试、TDD和BDD的测试框架,使用C++11、C++14、C++17和更高版本(或Catch1.x分支的C++03)。
Catch2是一个C++的多范式测试框架,也支持Objective-C(还有C)。它主要是作为一个单一的头文件发布的,尽管某些扩展可能需要额外的头文件。
5.1 官方网站
https://github.com/catchorg/Catch2
5.2 许可
Boost Software License 1.0
5.3 主要特点
n 快速且非常容易上手。只需下载catch.hpp,#include它就可以了。
n 没有外部依赖性。只要你能编译C++11,并且有一个C++标准库就可以使用。
n 可把测试用例写成:自注册的,函数或者方法。
n 将测试用例划分为若干部分,每个部分都是孤立运行的(不需要Fixture)。
n 使用BDD风格的Given-When-Then部分以及传统的单元测试用例。
n 只有一个核心断言宏用于比较。标准的C/C++运算符被用于比较,但完整的表达式被分解,并且lhs和rhs值被记录下来。
n 测试使用自由形式的字符串来命名,不再使用严格标识符来命名。
n 测试可以被标记,以方便运行特设的测试组。
n 失败可以融入Windows和Mac的调试器。
n 输出是通过模块化的报告对象。包括基本的文本和XML报告器。可以很容易地添加自定义报告器。
n 支持JUnit xml输出,以便与第三方工具集成,如CI服务器。
n 提供了一个默认的main()函数,但你可以提供你自己的函数来实现完全控制(例如,集成到你自己的测试运行器GUI中)。
n 提供了一个命令行解析器,你可以提供自己的main()函数。
n Catch可以测试自己。
n 替代断言宏报告失败,但不中止测试用例。
n 浮点容差比较是通过使用一个表达式的Approx()语法建立起来的。
n 内部和友好的宏被隔离,因此可以管理名称冲突。
n 匹配器
5.4 用法
获取Catch2最简单的方法就是下载最新的单头版本。单一头文件是由一组单独的头文件合并生成的,但它仍然只是头文件中的普通源代码。
5.4.1 简单的测试
#define CATCH_CONFIG_MAIN // 这告诉Catch提供一个main()--只在一个cpp文件中这样做
#include "catch.hpp"
unsigned int Factorial( unsigned int number ) {
return number <= 1 ? number : Factorial(number-1)*number;
}
TEST_CASE( "Factorials are computed", "[factorial]" ) {
REQUIRE( Factorial(1) == 1 );
REQUIRE( Factorial(2) == 2 );
REQUIRE( Factorial(3) == 6 );
REQUIRE( Factorial(10) == 3628800 );
}
5.4.2 测试案例和区域
大多数测试框架都有一个基于类的Fixture机制。也就是测试用例映射到类上的方法,常见的设置和拆解可以在setup()和 teardown()方法中进行(或者在C++等支持的构造函数/析构函数)。
Catch采用了一种不同的方法(与NUnit和xUnit都不同),它更自然地适合C++和C语言家族。
TEST_CASE( "vector的尺寸和容量可以调整", "[vector]" ) {
std::vector<int> v( 5 );
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 5 );
SECTION( "调整尺寸和容量" ) {
v.resize( 10 );
REQUIRE( v.size() == 10 );
REQUIRE( v.capacity() >= 10 );
}
SECTION( "缩小尺寸但不改变容量" ) {
v.resize( 0 );
REQUIRE( v.size() == 0 );
REQUIRE( v.capacity() >= 5 );
}
SECTION( "增加容量但不改变尺寸" ) {
v.reserve( 10 );
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 10 );
}
SECTION( "缩小尺寸或者容量" ) {
v.reserve( 0 );
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 5 );
}
}
对于每个SECTION,从一开始就执行TEST_CASE, 所以当我们进入每个SECTION时,我们知道大小是5,容量至少是5。我们用顶层的REQUIREs强制执行了这些要求。
5.4.3 BDD-风格
如果你适当地命名你的测试用例和部分,你可以实现BDD式的规范结构。方案可以使用SCENARIO、GIVEN、WHEN和THEN宏来指定,这些宏分别映射到TEST_CASEs和SECTIONs。
SCENARIO( "vector的尺寸和容量可以调整", "[vector]" ) {
GIVEN( "含有一些项目的Vector" ) {
std::vector<int> v( 5 );
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 5 );
WHEN( "尺寸增加" ) {
v.resize( 10 );
THEN( "尺寸和容量发生变化" ) {
REQUIRE( v.size() == 10 );
REQUIRE( v.capacity() >= 10 );
}
}
WHEN( "尺寸减少了" ) {
v.resize( 0 );
THEN( "尺寸变化但是容量不变" ) {
REQUIRE( v.size() == 0 );
REQUIRE( v.capacity() >= 5 );
}
}
WHEN( "增加容量" ) {
v.reserve( 10 );
THEN( "增加容量但不增加尺寸" ) {
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 10 );
}
}
WHEN( "减少容量" ) {
v.reserve( 0 );
THEN( "尺寸和容量都没有改变" ) {
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 5 );
}
}
}
}
6 参考
https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#C++
https://www.boost.org/libs/test/
http://sourceforge.net/projects/cppunit/
https://github.com/google/googletest/
https://github.com/catchorg/Catch2
https://github.com/onqtam/doctest
https://github.com/martinmoene/lest
https://github.com/etr/liblittletest
https://github.com/mollismerx/elfspy
- 点赞
- 收藏
- 关注作者
评论(0)