CppExplore

一切像雾像雨又像风

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  29 随笔 :: 0 文章 :: 280 评论 :: 0 Trackbacks
作者:CppExplore  http://www.cppblog.com/CppExplore/http://blog.csdn.net/cppexplore同步发布
 

1 单元测试对象概述

各个对象组织结构如下:

 

 

职责简述如下:

对象

职责

TestAssert

测试断言:判定测试结果是否正确,一般类似断言表达。

TestCase

测试用例:多个测试断言组成一个测试用例。测试对象为一个类中的一个具体方法。

TestSuite

测试套件:多个测试用例组成一个测试套件。测试对象为一个类。

MainTestSuite

主测试套件:单元测试运行主程序入口。测试用例也可绕过测试套件,直接包含在主测试套件中。

TestFixture

测试夹具:用于测试前的初始化操作以及测试后的清理操作,一般包括准备测试的前置条件/测试对象的状态设置等。


 

2 单元测试框架选型

当前存在很多流行的单元测试框架:衍生自JUnitCppUnit,以及简化版本的CppUnitLiteBoost.Test测试框架,Google Test测试框架等。每个测试框架都很完善,都可胜任单元测试任务。

       从使用简单性考虑,依次是Boost.Test>>Google Test>>CppUnitLite>>CppUnit.

本文选择Boost.Test的单元测试框架讲解。对Google Test感兴趣的可参看http://www.cnblogs.com/coderzh/archive/2009/03/31/1426758.html
 

Boost.Test UTF(Unit Test Framework)

3.1 LogLevel

讲解TestAssert前,先说下Boost测试框架的日志级别,有以下9个级别:

级别名称

说明

all / success

报告包括成功测试通知的所有日志信息

test_suite

显示测试套件信息

message

显示用户信息

warning

报告用户发出的警告

error

报告所有错误情况

cpp_exception

报告未捕获的 C++ 异常

system_error

报告系统引起的非致命错误 (例如,超时或浮点数异常)

fatal

用户或系统引起的致命错误 (例如,内存访问越界)

nothing

不报告任何信息

生成测试可执行程序后,可以通过指定--log_level参数指定日志接别。比如,最后可执行程序为testmini,执行./testmini --log_level=warning指定在warning级别运行,默认执行在error级别。

请特别关注级别中黑体部分,TestAssert中将使用到。

3.2 TestAssert

Boost.Test中测试断言包含如下3大类:

类别

功能

说明

WARN

打印warning日志,不增加失败引用计数,继续执行程序

检验不太重要但是正确的方面

CHECK

打印error日志,增加失败引用计数,继续执行程序

实现 assertions

REQUIRE

增加失败应用计数,中断程序的运行

失败就不应该让程序继续运行则使用

 

Boost.Test中详细测试断言包含如下几种:

TestAssert类型

说明

举例

BOOST_WARN

WARN型预言检测

BOOST_WARN(2+2==4); 

BOOST_CHECK

CHECK型预言检测

BOOST_CHECK(2+2==4); 

BOOST_REQUIRE

REQUIRE型预言检测

BOOST_REQUIRE(2+2==4); 

BOOST_WARN_MESSAGE

WARN型预言检测,自定义日志

BOOST_WARN_MESSAGE2+2==4"description…" );

BOOST_CHECK_MESSAGE

CHECK型预言检测,自定义日志

BOOST_CHECK_MESSAGE2+2==4"description…" );

BOOST_REQUIRE_MESSAGE

REQUIRE型预言检测,自定义日志

BOOST_ REQUIRE _MESSAGE2+2==4"description…" );

BOOST_ERROR

BOOST_CHECK_MESSAGE( false, M )

if( 2+2 !=4 )

BOOST_ERROR( "description…" );

BOOST_FAIL

BOOST_REQUIRE_MESSAGE( false, M )

if( 2+2 !=4 )

BOOST_FAIL( "description…" );

BOOST_WARN_EQUAL

WARN型左右值相等检测检测

BOOST_WARN_EQUAL(2+2,4);

BOOST_CHECK_EQUAL

CHECK型左右值相等检测检测

BOOST_CHECK_EQUAL(2+2,4);

BOOST_REQUIRE_EQUAL

REQUIRE型左右值相等检测检测

BOOST_REQUIRE_EQUAL(2+2,4);

该类还有不等,小于,大于,小于等于,大于等于的判别检测,此处只罗列WARN的,其他不再一一罗列:BOOST_WARN_NE/ BOOST_WARN_LT/ BOOST_WARN_LE/ BOOST_WARN_GT/ BOOST_WARN_GE

BOOST_WARN_THROW

WARN型,判别执行函数期,有异常抛出。

BOOST_WARN_THROW(executeSql(“select…”),oracle::ErrorCodeException);

BOOST_CHECK_THROW

CHECK型,判别执行函数期,有异常抛出。

 

BOOST_REQUIRE_THROW

REQUIRE型,判别执行函数期,有异常抛出。

 

BOOST_WARN_EXCEPTION

WARN型,执行函数,当预言为真时,捕获异常。

BOOST_WARN_THROW(executeSql(“select…”),oracle::ErrorCodeException,2+2==4);

 

BOOST_CHECK_EXCEPTION

CHECK型,执行函数,当预言为真时,捕获异常。

 

BOOST_REQUIRE_EXCEPTION

REQUIRE型,执行函数,当预言为真时,捕获异常。

 

BOOST_WARN_NO_THROW

WARN型,判别执行函数期,无异常抛出。

BOOST_WARN_NO_THROW(executeSql(“select…”));

BOOST_CHECK_NO_THROW

CHECK型,判别执行函数期,无异常抛出。

 

BOOST_REQUIRE_NO_THROW

REQUIRE型,判别执行函数期,无异常抛出。

 

BOOST_WARN_CLOSE

WARN型,判定左右值是否足够逼近。用于浮点数比较。

BOOST_WARN_CLOSE2.1131,2.1132,0.01

BOOST_CHECK_CLOSE

CHECK型,判定左右值是否足够逼近。用于浮点数比较

 

BOOST_REQUIRE_CLOSE

REQUIRE型,判定左右值是否足够逼近。用于浮点数比较

 

BOOST_WARN_SMALL

WARN型,判定值是否足够小(是否接近0)。用于浮点数比较

BOOST_WARN_CLOSE0.1,0.01

BOOST_CHECK_SMALL

CHECK型,判定值是否足够小(是否接近0)。用于浮点数比较

 

BOOST_REQUIRE_SMALL

REQUIRE型,判定值是否足够小(是否接近0)。用于浮点数比较

 

3.3 TestCase

对于TestCase/TestSuite等,Boost.Test既支持手动注册方式,也支持自动注册方式,当前Boost官方推荐自动注册方式,手动注册为了保持向前兼容保留,以后版本可能被移除。使用宏BOOST_AUTO_TEST_CASE即可自动注册测试用例。使用如下:

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_CASE(test_case_name)

{

    BOOST_CHECK(true);

}

 

3.4 TestSuite

使用宏BOOST_AUTO_TEST_SUITE(test_suite_name)开始测试套件,使用BOOST_AUTO_TEST_SUITE_END()结束测试套件。使用举例:

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(test_suite_name)

BOOST_AUTO_TEST_CASE(test_case1)

{

    BOOST_CHECK(true);

}

BOOST_AUTO_TEST_CASE(test_case1)

{

    BOOST_CHECK(true);

}

BOOST_AUTO_TEST_SUITE_END()

3.5 MainTestSuite

使用宏BOOST_TEST_MODULE表明主测试套件,一个测试项目中只能存在一个主测试套件。使用举例:

#define BOOST_TEST_MODULE maintest

#include <boost/test/unit_test.hpp>

3.6 TestFixture

测试夹具做测试前的准备工作和测试后的清理工作。而C++RAII机制(构造函数申请资源,析构函数释放资源)恰好能满足该需求。因此Boost中直接使用普通类做夹具。实现夹具举例:该夹具在测试前将整数i初始化为5

struct MyFixture

{

    MyFixture():i(5){}

    ~MyFixture(){}

   Int I;

};

    夹具可以和TestCase一起使用,也可以和TestSuite一起使用,也可以和MainTestSuite一起使用。

    使用宏BOOST_FIXTURE_TEST_CASE(test_case_name, fixure_name)代替BOOST_AUTO_TEST_CASE(test_case_name)即可在TestCase中使用夹具,举例如下:

BOOST_FIXTURE_TEST_CASE(test_case_nameMyFixture)

{

    BOOST_CHECK(i==5);

}

    使用宏BOOST_FIXTURE_TEST_SUITE(suite_name, fixure_name)代替BOOST_AUTO_TEST_SUITE(suite_name)即可在TestSuite中使用夹具,举例如下:

BOOST_FIXTURE_TEST_SUITE(test_suite, MyFixture );

BOOST_AUTO_TEST_CASE (test_case1)

{

    BOOST_CHECK(i==5);

}

BOOST_AUTO_TEST_CASE(test_case2)

{

    BOOST_CHECK(++i==6);

}

BOOST_AUTO_TEST_SUITE_END()

       使用宏BOOST_GLOBAL_FIXTURE(MyFixture);MyFixture声明为全局夹具,即可和MainTestSuite一起使用。

 

3.7 Boost.Test使用

对已经完成的项目做单元测试,假定该项目具有很好的测试性:

1) 对项目中的每个类对象创建一个测试套件,一个测试套件对应一个cpp文件。对类的每个类方法创建一个测试用例,这些测试用例均包含上前面的测试套件中。每个测试用例可以有多个测试断言,对该方法进行充分测试。

2) 在测试主文件中定义宏BOOST_TEST_MODULE,并包含所有的测试套件文件。

3) Linux下,将被测试项目编译成静态库(将main函数外的所有文件编译打包)供测试项目连接。Window下为测试项目做静态库工程,设置测试工程依赖该工程。并将头文件路径设置正确,即可编译运行。

附件为一示例项目以及对应单元测试工程举例,项目目录下make编译生成静态库以及可执行程序,test目录下make生成单元测试可执行程序。(略)
 

实行单元测试

4.1 现实困难

1、 内部依赖问题

类之间相互协作共同完成功能,类之间的依赖必不可少。为了测试某个类,必须实例化它依赖的类,而它依赖的类又可能依赖其他类,因此必须实例化其他类。如此一环扣一环,可能把整个项目大部分类都包含在了这次测试中,最后做的不是单元测试,而是挂着单元测试外壳的集成测试

2、 外部依赖问题

很多项目,尤其是我们的网络应用服务器,运行期间需要依赖外部的其他服务器或者数据库或者本地的文件系统。而对很多外部的依赖很难模拟,或者说模拟成本太高,往往让测试者望而却步。

3、 函数本身问题

项目中的很多或者可能是大部分函数,是没有明确返回值或者无异常抛出,而只是和其他外设交互。难于使用测试断言判定。

以上造成很难将某个类从项目中隔离出来,难以设置单元测试点。

4.2 解决困难

上述困难均为依赖造成。

1、内部解依赖

对被测代码进行解依赖,强化设计,减少耦合,提高代码可测性。解依赖的过程也即为对代码重构过程,减少类间耦合,制造接口层。常用手段有:虚函数、函数指针、传递参数等方式。而对于难于进行解依赖情况,就要考虑提取分化重写方法。

2、写桩代码模拟外部环境

单元测试不能直接依赖外部环境,必须写桩代码模拟。而外部环境的可模拟性与内部解依赖紧密相关。对于外部的随机性和各种不确定性,桩代码必须尽可能模拟。

3、开辟访问类私有属性通道

有些类方法虽然没有明确返回值,但可能修改类的内部状态,可以通过判断类的私有属性来判定类方法的执行情况。可以给类增加Get()方法或者将私有属性设置为protected

更重要的是在代码开发期,引入TDD思维,强化设计,提高代码可测性,提高代码的整体质量

posted on 2010-06-28 09:15 cppexplore 阅读(4623) 评论(1)  编辑 收藏 引用

评论

# re: 【原创】单元测试分享 2010-06-28 16:17 陈梓瀚(vczh)
不管单元测试的框架支持多少功能,我们应该把项目写成容易测试+容易维护+容易集成的形式,这也是单元测试可以改进项目代码的一个地方。如果单元测试可以成为一个指标的话,那么项目代码就会写得更好一点了。  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理