CppUnit Cookbook XK翻译版

来源:互联网 发布:身份证复印件软件v3.8 编辑:程序博客网 时间:2024/06/05 18:04

                                  

这是一篇入门文章。

EN: http://cppunit.sourceforge.net/doc/1.11.6/cppunit_cookbook.html

Simple Test Case

 

当想测试代码是否能正确运行的时候, 一般会有以下两种简单的做法:

使用调试程序或者通过屏幕输出来进行测试。

但是它们都有缺点。通过调试程序调试是一个好办法,但是不够自动化,不得不在程序代码更改之后从新调试一次。通过屏幕输出也是一种好的办法,但是测试代码常常弄脏源代码,并且经常在输出了比我需要更多的无用信息。

 

使用CPPUNIT能实现测试的自动化。它的使用很简单并且一旦写成测试代码,它们可以一直被用来测试,以保证代码质量。

 

下面将一个简单的例子:

继承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)) );
  }
};

这是一个非常简单的测试程序。但一般来说,对于一个对象集有一系列的测试用例,这时就需要使用Fixture

Fixture

 

fixture被当作是一个已知对象集的测试用例的集合。在测试程序的开发中, fixture会带来很大的方便。

下面将会使用这种风格开发,在其中有更多关于fixture东西。假设我们需要开发了一个复数类的程序,定义一个空的类Complex.

 

class Complex {};

 

现在创建一个上面这个复数类的实例, 编译代码,我们将会发现一个编译错误。ComplexNumberTest用例使用了 operator == 但是并没有定义。

现在加入operator == 定义:

 

bool operator==( const Complex &a, const Complex &b) 
{ 
  return true; 
}

 

在一次编译这个TEST,并运行它。这次能成功的编译但是TEST失败了, 这说明需要在做一点工作使得operator==正确的工作,于是再次修改代码为:

class Complex { 
  friend bool operator ==(const Complex& a, const Complex& b);
  double real, imaginary;
public:
  Complex( double r, double i = 0 ) 
    : real(r)
        , imaginary(i) 
  {
  }
};
 
bool operator ==( const Complex &a, const Complex &b )
{ 
  return a.real == b.real  &&  a.imaginary == b.imaginary; 
}

这次编译运行,会发现TEST通过。

 

需要添加一个新的操作和一个新的TEST ,需要初始化三个或者四个复数对象并在不同的用例之间重用它们,这时, fixture就很方便了。

 

下面将说明如何使用fixture

1)    fixture添加成员变量。

2)    重载setUp() 方法初始化成员变量。

3)    重载 tearDown()释放成员变量初始化时申请的内存空间。

 

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;
  }
};

 

一旦有了这个fixture,就可以为复数类添加测试用例和其他我们测试需要任何东西。

 

Test Case

 

使用fixture时,怎样编译和运行一个测试用例呢?

有下面两步:

1)      编写一个TEST作为一个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 );
  }
};

 

可以使用下面的格式运行每一个TEST:

CppUnit::TestCaller<ComplexNumberTest> test( "testEquality", 
                               &ComplexNumberTest::testEquality );
CppUnit::TestResult result;
test.run( &result );

TestCaller构造函数的第二个参数是一个ComplexNumberTest类成员函数的地址。当Test caller运行的时候,这个特定的函数也会被调用。这样做并不是必须的,但是如果不这样做TEST出现错误时将不会自动出现诊断信息。 一般都会使用TestRunner 来显示结果。

 

如果存在有多个用例,就可以把它们放入到一个suite中。

 

Suite

 

怎么样让所有的用例一次运行呢/

CppUnit提供一个TestSuite ,它能够一次运行多条Test Case

前面说明了如果运行一个单独的Test Case

例如可以用如下方式创建一个含有两个或是更多的TEST SUITE

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 );

 

TestSuites 不但包含了各个测试用例的调用器, 而且它们还能包含任何实现了 Test 接口的类的对象。例如,在一个代码中可以创建一个TestSuite 在另一段代码中再创建另外一个TestSuite,再创建一个包含这两个TestSuiteTestSuite

CppUnit::TestSuite suite;
CppUnit::TestResult result;
suite.addTest( ComplexNumberTest::suite() );
suite.addTest( SurrealNumberTest::suite() );
suite.run( &result );

 

TestRunner

 

怎样运行测试用例,并检查结果呢?

 

如果存在一个TEST SUITE,并想运行它, CppUnit提供一些工具来定义那些SUITE可以被运行和显示它们运行的结果。可以在测试suite所以类里定义一个静态方法返回一个suite来使得 TestRunner能方便的使用。

 

例如, 为了使得 ComplexNumber Test suite 能被一个 TestRunner使用, 可以在ComplexNumber Test 类里面加入如下代码:

 

public: 
  static CppUnit::Test *suite()
  {
    CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite( "ComplexNumberTest" );
    suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTest>( 
                                   "testEquality", 
                                   &ComplexNumberTest::testEquality ) );
    suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTest>(
                                   "testAddition",
                                   &ComplexNumberTest::testAddition ) );
    return suiteOfTests;
  }

 

在这种情况下,在main.cpp中包含测试suite所在的头文件。

#include <cppunit/ui/text/TestRunner.h>
#include "ExampleTestCase.h"
#include "ComplexNumberTest.h"

 

main()函数里面加入 addTest(CppUnit::Test *) 函数调用。

int main( int argc, char **argv)
{
  CppUnit::TextUi::TestRunner runner;
  runner.addTest( ExampleTestCase::suite() );
  runner.addTest( ComplexNumberTest::suite() );
  runner.run();
  return 0;
}

 

TestRunner会运行被加入的suite中的每一条用例,如果所有的TEST都成功, 将会得到一条成功的提示消息, 否则可能会得到如下消息:

1)      失败的TEST CASE的名字。

2)      失败的 TEST CASE 所在的源文件名字。

3)      错误出现时在源文件中的行号。

4)      在调用 CPPUNIT_ASSERT()失败时候,表达式中的所有字符。

 

CppUnit 区别对待失败和错误两种类型,失败是指确定的断言失败,而错误是指一些不可预料的问题,如除零或者是其他c++运行时异常。

 

Helper Macros

 

从前面的叙述中,也许已经会发现一个问题, 为一个fixture实现一个静态的suite()方法是一件重复而累赘的事情。在Writing test fixture中介绍了一些宏,它们被创建来自动实现静态的suite方法。

 

下面的例子中将会使用这些宏来重写ComplexNumberTest类:

#include <cppunit/extensions/HelperMacros.h>
 
class ComplexNumberTest : public CppUnit::TestFixture  {

 

 

首先,声明suite时候, 传递类名作为参数。

CPPUNIT_TEST_SUITE( ComplexNumberTest );

这样,静态suite方法就在类名之后被创建。 然后,可以声明FIXTURE中的每一个TEST CASE.

 

CPPUNIT_TEST( testEquality );
CPPUNIT_TEST( testAddition );

最后必须结束suite的声明:

CPPUNIT_TEST_SUITE_END();

这时,一个如下声明的方法就被实现了.

static CppUnit::TestSuite *suite();

 

fixture后面的代码不用做出任何更改。

 

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 );
  }
};

 

一个使用了fixture名和test case方法名组成的suite就能被添加到 TestCaller 中了 

从这个例子上来看,被添加的用例将会是 "ComplexNumberTest.testEquality" "ComplexNumberTest.testAddition".

 

 helper macros 能用来书写一些通用的断言。例如,检查ComplexNumber类在除0时候抛出的算术异常。

 

1)  suite声明中使用CPPUNIT_TEST_EXCEPTION添加用例,并指定异常的类型

2)  编写test case方法

 

CPPUNIT_TEST_SUITE( ComplexNumberTest );
// [...]
CPPUNIT_TEST_EXCEPTION( testDivideByZeroThrows, MathException );
CPPUNIT_TEST_SUITE_END();
 
// [...]
 
  void testDivideByZeroThrows()
  {
    // The following line should throw a MathException.
    *m_10_1 / ComplexNumber(0);
  }

 

如果期望的异常没有被抛出,这时断言将会失败。

 

TestFactoryRegistry

 

TestFactoryRegistry  用来解决参见的两个问题:

1)      忘记把suite添加到runner中。特别是在suiterunner不在一个文件中时,是很容易忘记的。

2)      包含所有TEST CASE带来的编辑瓶颈。

 

TestFactoryRegistry  是一个能在初始化的时候注册所有的suite的地方。

在注册 ComplexNumber suite的时候,需要在.cpp文件中加入:

#include <cppunit/extensions/HelperMacros.h>
 
CPPUNIT_TEST_SUITE_REGISTRATION( ComplexNumberTest );

在这背后,一个 AutoRegisterSuite 类型的静态变量被声明了。 在构造的时候, 它会在TestFactoryRegistry 注册(register)一个 TestSuiteFactory 这个TestSuiteFactory返回一个ComplexNumber::suite().返回的TestSuite

 

运行这些实例,使用TEXT TEST RUANNER(文本界面运行器) 这样不用再包含任何fixture了。

 

#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>
 
int main( int argc, char **argv)
{
  CppUnit::TextUi::TestRunner runner;

首先获得TestFactoryRegistry 的实例。

CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();

然后,获得由CPPUNIT_TEST_SUITE_REGISTRATION()注册在 TestFactoryRegistry 中的所有TEST SUITE 并把它们添加到runner中。

  runner.addTest( registry.makeTest() );
  runner.run();
  return 0;
}

 

原创粉丝点击