Linux下使用CppUnit组织单元测试

C++ 专栏收录该内容
15 篇文章 1 订阅

本文介绍CppUnit单元测试的一些基础知识和使用方法,其中的一些样例来自于源码,安装和一些基础介绍也可以参见文献[1-3]。
1. 源代码安装
下载最新的CppUnit的源码包,并解压: tar -zxvf cppunit1.21.1.tar.gz
进入CppUnit安装目录,依次执行:
./configure
make
make install
默认安装路径在/usr/local,如果选择其他安装路径,编译时需要配置相关的路径。

1.Fixture(被测试的目标)
在CppUnit中,一个或一组测试用例的测试对象被称为Fixture。Fixture 就是被测试的目标,可能是一个对象或者一组相关的对象,甚至一个函数。例如最后的示例中,要对编写的mystring类进行测试,那这就是一个Fixture 。

2.建立测试用例
有了被测试的 fixture,就可以对这个fixture 的某个功能、某个可能出错的流程编写测试代码,这样对某个方面完整的测试被称为TestCase。

2.1一个简单的TestCase
在 “cppunit/TestCase.h”
定义了一个简单的单个测试单例类TestCase,基于TestCase可以建立一个简单的测试用例。

class CPPUNIT_API TestCase : public TestLeaf,
                             public TestFixture
{
public:
    TestCase( const std::string &name );
    TestCase();
    ~TestCase(); 
    virtual void run(TestResult *result);
    std::string getName() const;
    //! FIXME: this should probably be pure virtual.
    virtual void runTest();   
private:
    TestCase( const TestCase &other ); 
    TestCase &operator=( const TestCase &other );    
private:
    const std::string m_name;
};

使用时,可以派生一个子类,然后覆盖原有的runTest()方法,就像这样

class mystringTest : public CppUnit::TestCase 
{
public:
void runTest() 
{
    mystring s;
    CPPUNIT_ASSERT_MESSAGE("String Length Non-Zero", s.size() != 0);
}
};

2.2 组织更“复杂”的TestCase
在实际中,通常需要对Fixture进行多个方面的测试,可以将它们组织到TestFixture中。在TestFixture中,可以建立被测试的类的实例,并编写TestCase对类实例进行测试。多个TestFixture可以通过TestSuite来对测试进行管理。
TestFixture是一个抽象基类(ABC),使用时用子类继承该类,并在需要时覆盖其中的方法。

class CPPUNIT_API TestFixture
{
public:
  virtual ~TestFixture() {};

  //! \brief Set up context before running a test.
  virtual void setUp() {};

  //! Clean up after the test run.
  virtual void tearDown() {};
};

可以借助在HelperMacros.h中定义的辅助宏,来对TestFixture进行管理
在”cppunit/extensions/HelperMacros.h”中定义了一些辅助宏,用来声明和管理测试环境。例如:

class MyTest : public CppUnit::TestFixture
{
    CPPUNIT_TEST_SUITE( MyTest );
    CPPUNIT_TEST( testEquality );
    CPPUNIT_TEST( testSetName );
    CPPUNIT_TEST_SUITE_END();
public:
    void setUp() 
    {
        //对测试用例做一些初始化操作
    };
    void tearDown() 
    {
        //当测试结束时,做一些清理工作
    };
    void testEquality();//测试方法
    void testSetName();//测试方法
};

HelperMacros.h中定义的常用
CPPUNIT_TEST_SUITE(ATestFixtureType)宏用于开始声明一个新的测试套件
CPPUNIT_TEST( testMethod ) 向测试套件中加入一个测试方法
CPPUNIT_TEST_SUITE_END()结束对测试套件的声明码的更新,可以在原有的测试类基础上派生出新的测试类,此时可以用
CPPUNIT_TEST_SUB_SUITE( ATestFixtureType, ASuperClass )声明一个新的测试套件,并将其基类的测试用例嵌套其中。就像这样:

#include <cppunit/extensions/HelperMacros.h>
class MySubTest : public MyTest 
{
    CPPUNIT_TEST_SUB_SUITE( MySubTest, MyTest );
    CPPUNIT_TEST( testAdd );
    CPPUNIT_TEST( testSub );
    CPPUNIT_TEST_SUITE_END();
public:
    void testAdd();
    void testSub();
};

CPPUNIT_TEST_SUITE_REGISTRATION( ATestFixtureType )
用于将特定的ATestFixtureType测试套件加入一个无命名的注册表中。这个宏定义了一个静态变量,其构造函数可以使得一个测试组件工厂加入到全局的注册表中,如果要获得该注册表中的注册项
可以使用静态函数CppUnit::TestFactoryRegistry::getRegistry()。

3.验证测试结果
通常,通过使用断言来验证测试的正确性,在“TestAssert.h”中定义了一些断言用于检查实际结果是否和预期结果一致,并输出一些指定的消息。

CPPUNIT_ASSERT(condition)//其条件为真,则调用CPPUNIT_SOURCELINE()输出本语句所在的文件和代码行,如果为假则调用CPPUNIT_NS::Message输出"assertion failed",以及条件表达式的内容。

CPPUNIT_ASSERT_MESSAGE(message,condition)//当条件为假时,会比CPPUNIT_ASSERT(condition)多输出指定的message中的内容

CPPUNIT_FAIL(message) //测试失败,调用CPPUNIT_NS::Message输出"forced failure",以及指定的message中内容。

CPPUNIT_ASSERT_EQUAL(expected, actual) //检查预期结果expected和实际结果actual是否一致

CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual)//检查预期结果expected和实际结果actual是否一致,失败的打印指定message内容

对于浮点数,有时不能直接用equal这样的语句来判断,就像实践中判断浮点数a是否为0时,不用(a == 0)一样CPPUNIT_ASSERT_DOUBLES_EQUAL,提供了这种类似的方法,
CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual, delta)
当expected和actual之间差大于delta时,预示测试的失败
同样也有对应的指定失败时,要输出message的方法
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(message,expected,actual,delta)

4.运行TestCase
需要组织测试用例到TestSuite中,并使用TestRuner来运行测试用例。在此介绍使用TestFactoryRegistry注册TestSuite,使用TestFactoryRegistry可以方面的管理这些测试用例。

在“cppunit/extensions/TestFactoryRegistry.h”中定义了用于管理测试套件的方法

CPPUNIT_TEST_SUITE_REGISTRATION():方法用于将测试套件加入一个无命名的注册表中,使用如下

CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
CppUnit::TestSuite *suite = registry.makeTest();

CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()用于将测试套件加入一个命名的注册表中

CppUnit::TestFactoryRegistry &mathRegistry = CppUnit::TestFactoryRegistry::getRegistry( "Test" );
CppUnit::TestSuite *Suite = mathRegistry.makeTest();

注册表由类NamedRegistries管理
NamedRegistries内部有三个private属性的成员变量:

Registries m_registries;        // 代表一个注册名称-注册项的映射表
Factories m_factoriesToDestroy; // 代表即将被销毁的注册项序列
Factories m_destroyedFactories; // 代表已经被销毁的注册项序列

其中,Registries和Factories的定义如下:

typedef std::map<std::string, TestFactoryRegistry *> Registries;
typedef std::set<TestFactory *> Factories;

getRegistry()方法可以用于访问注册项
makeTest()方法则可以返回注册项中的包含测试方法的测试组件

TestRunner类用于加载和运行测试用例或者测试组件,并提供测试结果的输出。目前提供了3类TestRunner,包括:

CppUnit::TextUi::TestRunner   // 文本方式
CppUnit::QtUi::TestRunner    // QT方式
CppUnit::MfcUi::TestRunner    // MFC方式

在Linux环境下,通常使用CppUnit::TextUi::TestRunner;此外在头文件TextTestRunner.h定义了TextTestRunner

typedef CppUnit::TextUi::TestRunner TextTestRunner;

TextTestRunner类的主要方法有:
void addTest( Test *test );//加载测试方法
void run( TestResult &controller,const std::string &testPath = “” );//用于运行测试方法,参数TestResult用以收集测试过程中的相关信息

下面以一段使用代码,回顾以上所述。需要指出的是,从某种意义说代码的组织方式并不最好的,例如superTest中包含了mystringTest中的重复测试,但在此只是为了说明一些使用方法。下面的测试主要是针对,自己编写是string类进行的。

"MyString.h"
#ifndef _MY_STRING_H_
#define _MY_STRING_H_

#include <iostream>
using namespace std;
class mystring
{
public:
    mystring(const char *s="");
    ~mystring();
    mystring(const mystring &st);

    mystring& operator=(const mystring &);
    mystring& operator=(const char *);

    char& operator[](int i);
    const char& operator[](int i)const;

    int length()const;
    friend ostream& operator<<(ostream &os,const mystring &st);
private:
    char *str;
    int len;
};

#endif
"MyString.cpp"
#include "MyString.h"
mystring::mystring(const char *s)
{
    len = strlen(s);
    str = new char[len+1];
    strcpy(str,s);
}

mystring::~mystring()
{
    delete [] str;
    str = NULL;
}

mystring::mystring(const mystring &st)
{
    len = st.len;
    str = new char [len+1];
    strcpy(str,st.str);
}

mystring& mystring::operator=(const mystring &st)
{
    if(this == &st)
        return *this;
    delete [] str;
    str = NULL;
    len = st.len;
    str = new char [len+1];
    strcpy(str,st.str);
    return *this;
}

mystring& mystring::operator=(const char *s)
{
    delete [] str;
    str = NULL;
    len = strlen(s);
    str = new char [len + 1];
    strcpy(str,s);
    return *this; 
}
char& mystring::operator[](int i)
{
    return str[i];
}

const char& mystring::operator[](int i) const
{
    return str[i];
}

int mystring::length()const
{
    return len;
}

ostream& operator<<(ostream &os,const mystring &st)
{
    os<<st.str;
    return os;
}
"main.cpp"
#include <iostream>
#include "MyString.h"
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TextTestRunner.h>
#include <cppunit/extensions/HelperMacros.h>

class mystringTest: public CppUnit::TestFixture 
{
public:
    void setUp() 
    { 
        std::cout << "Do some initialization here…\n";
    }

    void tearDown() 
    { 
        std::cout << "Cleanup actions post test execution…\n";
    }

    void checkLength() 
    {
        mystring s;
        CPPUNIT_ASSERT_MESSAGE("String Length Non-Zero", s.length() == 0);
    }

    void checkValue() 
    {
        mystring s;
        s = "hello world!\n";
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Corrupt String Data", s[0], 'w');
    }

    CPPUNIT_TEST_SUITE( mystringTest );
    CPPUNIT_TEST( checkLength );
    CPPUNIT_TEST( checkValue );
    CPPUNIT_TEST_SUITE_END();
};


class superTest: public mystringTest 
{
public:

    void testModify() 
    {
        mystring s("superTest");
        s[0] = 'S';
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Test Operator[]", s[0], 'S');
    }

    CPPUNIT_TEST_SUB_SUITE( superTest, mystringTest );
    CPPUNIT_TEST( testModify );
    CPPUNIT_TEST_SUITE_END();
};

int main ()
{
    //使用辅助宏注册测试套件到全局的命名空间
    CPPUNIT_TEST_SUITE_REGISTRATION ( mystringTest );
    CPPUNIT_TEST_SUITE_REGISTRATION ( superTest );
    CppUnit::Test *test =  CppUnit::TestFactoryRegistry::getRegistry().makeTest();
    CppUnit::TextTestRunner runner;
    runner.addTest(test);
    //运行测试,自动显示进度和结果
    runner.run("",true);
    return 0;
}

Result:
.Do some initialization here…
Cleanup actions post test execution…
.Do some initialization here…
FCleanup actions post test execution…
.Do some initialization here…
Cleanup actions post test execution…
.Do some initialization here…
Cleanup actions post test execution…
.Do some initialization here…
FCleanup actions post test execution…

!!!FAILURES!!!
Test Results:
Run: 5 Failures: 2 Errors: 0

1) test: superTest::checkValue (F) line: 31 main.cpp
equality assertion failed
- Expected: h
- Actual : w
- Corrupt String Data

2) test: mystringTest::checkValue (F) line: 31 main.cpp
equality assertion failed
- Expected: h
- Actual : w
- Corrupt String Data


由于组织的不好,setUp和tearDown各被调用了三次,mystringTest中一次,superTest中两次。


[1].Arpan Sen.开放源码C/C++单元测试工具,第2部分:了解CppUnit. http://www.ibm.com/developerworks/cn/aix/library/au-ctools2_cppunit/
[2].李群.便利的开发工具 CppUnit 快速使用指南. http://www.ibm.com/developerworks/cn/linux/l-cppunit/
[3].CppUnit源码解读http://morningspace.51.net/resource/cppunit/cppunit_anno.html

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值