了解 Boost 单元测试框架

发表于:2013-9-02 11:13

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:nodeathphoenix    来源:51Testing软件测试网采编

  清单 11. 由于超过公差限制,比较失败

[arpan@tintin] ./a.out
Running 1 test case...
sq.cpp(18): error in "test": difference between f1{567.010132} and
result * result{567.010193} exceeds 1e-07
*** 1 failure detected in test suite "floatingTest"

  生产软件中另一个常见的问题是比较 double 和 float 类型的变量。BOOST_CHECK_CLOSE_FRACTION 的优点是它不允许进行这种比较。这个宏中的左值和右值必须是相同类型的 — 即要么是 float,要么是 double。在 清单 12 中,如果 f1 是 double,而 result 是 float,在比较时就会出现错误。

  清单 12. 错误:BOOST_CHECK_CLOSE_FRACTION 的左值和右值参数的类型不同

[arpan@tintin] g++ sq.cpp -I/u/c/lib/boost
/u/c/lib/boost/boost/test/test_tools.hpp:
In function
`bool boost::test_tools::tt_detail::check_frwd(Pred,
const boost::unit_test::lazy_ostream&,
boost::test_tools::const_string, size_t,
boost::test_tools::tt_detail::tool_level,
boost::test_tools::tt_detail::check_type,
const Arg0&, const char*,
const Arg1&, const char*, const Arg2&, const char*)
[with Pred = boost::test_tools::check_is_close_t, Arg0 = double,
Arg1 = float, Arg2 = boost::test_tools::fraction_tolerance_t<double>]':
sq.cpp:18:   instantiated from here
/u/c/lib/boost/boost/test/test_tools.hpp:523: error: no match for call to
`(boost::test_tools::check_is_close_t) (const double&, const float&,
const boost::test_tools::fraction_tolerance_t<double>&)'

  定制的断言支持

  Boost 测试工具验证 Boolean 条件。可以通过扩展测试工具支持更复杂的检查 — 例如,判断两个列表的内容是否相同,或者某一条件对于向量的所有元素是否都是有效的。还可以通过扩展 BOOST_CHECK 宏执行定制的断言检查。下面对用户定义的 C 函数生成的列表内容执行定制的检查:检查结果中的所有元素是否都大于 1。定制检查函数需要返回 boost::test_tools::predicate_result 类型。清单 13 给出了详细的代码。

  清单 13. 使用 Boost 测试工具验证复杂的断言

#define BOOST_TEST_MODULE example
#include <boost/test/included/unit_test.hpp>
boost::test_tools::predicate_result validate_list(std::list<int>& L1)
{
std::list<int>::iterator it1 = L1.begin( );
for (; it1 != L1.end( ); ++it1)
{
if (*it1 <= 1) return false;
}
return true;
}
BOOST_AUTO_TEST_SUITE ( test )
BOOST_AUTO_TEST_CASE( test )
{
std::list<int>& list1 = user_defined_func( );
BOOST_CHECK( validate_list(list1) );
}
BOOST_AUTO_TEST_SUITE_END( )

  predicate_result 对象有一个隐式的构造函数,它接受一个 Boolean 值,因此即使 validate_list 的期望类型和实际返回类型不同,代码仍然会正常运行。

  还有另一种用 Boost 测试复杂断言的方法:BOOST_CHECK_PREDICATE 宏。这个宏的优点是它不使用 predicate_result。但缺点是语法有点儿粗糙。用户需要向 BOOST_CHECK_PREDICATE 宏传递函数名和参数。清单 14 的功能与 清单 13 相同,但是使用的宏不同。注意,validate_result 的返回类型现在是 Boolean。

  清单 14. BOOST_CHECK_PREDICATE 宏

#define BOOST_TEST_MODULE example
#include <boost/test/included/unit_test.hpp>
bool validate_list(std::list<int>& L1)
{
std::list<int>::iterator it1 = L1.begin( );
for (; it1 != L1.end( ); ++it1)
{
if (*it1 <= 1) return false;
}
return true;
}
BOOST_AUTO_TEST_SUITE ( test )
BOOST_AUTO_TEST_CASE( test )
{
std::list<int>& list1 = user_defined_func( );
BOOST_CHECK_PREDICATE( validate_list, list1 );
}
BOOST_AUTO_TEST_SUITE_END( )

  在一个文件中包含多个测试套件

  可以在一个文件中包含多个测试套件。文件中定义的每个测试套件必须有一对 BOOST_AUTO_TEST_SUITE... BOOST_AUTO_TEST_SUITE_END 宏。清单 15 给出了在同一个文件中定义的两个测试套件。在运行回归测试时,用预定义的 –log_level=test_suite 选项运行可执行程序。在 清单 16 中可以看到,使用这个选项生成的输出很详细,有助于进行快速调试。

  清单 15. 使用一个文件中的多个测试套件

#define BOOST_TEST_MODULE Regression
#include <boost/test/included/unit_test.hpp>
typedef struct {
int c;
char d;
double e;
bool f;
} Node;
typedef union  {
int c;
char d;
double e;
bool f;
} Node2;
BOOST_AUTO_TEST_SUITE(Structure)
BOOST_AUTO_TEST_CASE(Test1)
{
Node n;
BOOST_CHECK(sizeof(n) < 12);
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(Union)
BOOST_AUTO_TEST_CASE(Test1)
{
Node2 n;
BOOST_CHECK(sizeof(n) == sizeof(double));
}
BOOST_AUTO_TEST_SUITE_END()

  下面是 清单 15 中代码的输出:

  清单 16. 用 –log_level 选项运行多个测试套件

[arpan@tintin] ./a.out --log_level=test_suite
Running 2 test cases...
Entering test suite "Regression"
Entering test suite "Structure"
Entering test case "Test1"
m2.cpp(23): error in "Test1": check sizeof(n) < 12 failed
Leaving test case "Test1"
Leaving test suite "Structure"
Entering test suite "Union"
Entering test case "Test1"
Leaving test case "Test1"
Leaving test suite "Union"
Leaving test suite "Regression"
*** 1 failure detected in test suite "Regression"

  理解测试套件的组织

  到目前为止,本文已经讨论了 Boost 测试实用程序和没有层次结构的测试套件。现在,我们使用 Boost 创建一个测试套件,以外部工具用户常见的方式测试软件产品。在测试框架中,通常有多个套件,每个套件检查产品的某些特性。例如,文字处理程序的回归测试框架应该包含检查字体支持、不同的文件格式等方面的套件。每个测试套件包含多个单元测试。清单 17 提供了一个测试框架示例。注意,代码入口点必须是名为 init_unit_test_suite 的例程。

  清单 17. 创建用于运行回归测试的主测试套件

#define BOOST_TEST_MODULE MasterTestSuite
#include <boost/test/included/unit_test.hpp>
using boost::unit_test;
test_suite*
init_unit_test_suite( int argc, char* argv[] )
{
test_suite* ts1 = BOOST_TEST_SUITE( "test_suite1" );
ts1->add( BOOST_TEST_CASE( &test_case1 ) );
ts1->add( BOOST_TEST_CASE( &test_case2 ) );
test_suite* ts2 = BOOST_TEST_SUITE( "test_suite2" );
ts2->add( BOOST_TEST_CASE( &test_case3 ) );
ts2->add( BOOST_TEST_CASE( &test_case4 ) );
framework::master_test_suite().add( ts1 );
framework::master_test_suite().add( ts2 );
return 0;
}

  每个测试套件(比如 清单 17 中的 ts1)都是使用 BOOST_TEST_SUITE 宏创建的。这个宏需要一个字符串作为测试套件的名称。最终使用 add 方法,把所有测试套件添加到主测试套件中。同样,我们使用 BOOST_TEST_CASE 宏创建每个测试,然后再使用 add 方法把它们添加到测试套件中。也可以把单元测试添加到主测试套件中,但是不建议这么做。master_test_suite 方法属于 boost::unit_test::framework 名称空间的一部分:它在内部实现一个单实例对象。清单 18 中的代码取自 Boost 源代码本身,解释了这个方法的工作方式。

  清单 18. 理解 master_test_suite 方法

master_test_suite_t&
master_test_suite()
{
if( !s_frk_impl().m_master_test_suite )
s_frk_impl().m_master_test_suite = new master_test_suite_t;
return *s_frk_impl().m_master_test_suite;
}

  使用 BOOST_TEST_CASE 宏创建的单元测试以函数指针作为输入参数。在 清单 17 中,test_case1、test_case2 等是 void 函数,用户可以按自己喜欢的方式编写代码。但是注意,Boost 测试设置会使用一些堆内存;每个对 BOOST_TEST_SUITE 的调用都会产生一个新的 boost::unit_test::test_suite(<test suite name>)。

  装备

  从概念上讲,测试装备(test fixture)是指在执行测试之前设置一个环境,在测试完成时清除它。清单 19 提供了一个简单的示例。

  清单 19. 基本的 Boost 装备

#define BOOST_TEST_MODULE example
#include <boost/test/included/unit_test.hpp>
#include <iostream>
struct F {
F() : i( 0 ) { std::cout << "setup" << std::endl; }
~F()          { std::cout << "teardown" << std::endl; }
int i;
};
BOOST_AUTO_TEST_SUITE( test )
BOOST_FIXTURE_TEST_CASE( test_case1, F )
{
BOOST_CHECK( i == 1 );
++i;
}
BOOST_AUTO_TEST_SUITE_END()

  清单 20 给出了输出。

  清单 20. Boost 装备的输出

[arpan@tintin] ./a.out
Running 1 test case...
setup
fix.cpp(16): error in "test_case1": check i == 1 failed
teardown
*** 1 failure detected in test suite "example"

  这段代码没有使用 BOOST_AUTO_TEST_CASE 宏,而是使用 BOOST_FIXTURE_TEST_CASE,它需要另一个输入参数。这个对象的 constructor 和 destructor 方法执行必需的设置和清除工作。看一下 Boost 头文件 unit_test_suite.hpp 就可以确认这一点(见 清单 21)。

  清单 21. 头文件 unit_test_suite.hpp 中的 Boost 装备定义

#define BOOST_FIXTURE_TEST_CASE( test_name, F )       /
struct test_name : public F { void test_method(); };                   /
/
static void BOOST_AUTO_TC_INVOKER( test_name )()       /
{                                                                                       /
test_name t;                                                                        /
t.test_method();                                                                    /
}                                                                                       /
/
struct BOOST_AUTO_TC_UNIQUE_ID( test_name ) {};       /
/
BOOST_AUTO_TU_REGISTRAR( test_name )(                     /
boost::unit_test::make_test_case(                                            /
&BOOST_AUTO_TC_INVOKER( test_name ), #test_name ),                  /
boost::unit_test::ut_detail::auto_tc_exp_fail<                                   /
BOOST_AUTO_TC_UNIQUE_ID( test_name )>::instance()->value() );   /
/
void test_name::test_method()                              /

  在内部,Boost 从 struct F 公共地派生一个类(见 清单 19),然后从这个类创建对象。按照 C++ 的公共继承规则,在函数中可以直接访问 struct 类的所有受保护变量和公共变量。注意,在 清单 19 中修改的变量 i 属于类型为 F 的内部对象 t(见 清单 20)。在回归测试套件中可能只有几个测试需要某种显式的初始化,因此可以只对它们使用装备特性。在 清单 22 给出的测试套件中,三个测试中只有一个使用装备。

54/5<12345>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号