CppUnit Cookbook中文版

这是我的第一份完整的翻译文章。欢迎大家指正,联系我:zgump@sina.com

***************************************************************************


本文是一篇简单的入门指导,帮助你快速上手。

简单测试用例(Simple Test Case)

你希望知道你的代码是否正在工作。
你该怎么办?
有很多种方法。使用调试器直接调试或者在你的代码里乱丢一些流输出指令是两种简单的方法,但是它们都有自己的缺点。直接调试代码是个好主意,但是它不是自动进行的。你不得不在每次改动代码以后重新调试。输出流文本也不错,但是它使代码变得面目可憎,并且大多数情况下,它输出的信息比你想要的要多。

在CppUnit中测试可以自动进行。这些测试可以很容易被建立,并且一旦你书写完毕,他们可以帮助你时刻了解你代码的质量。

为了做一个简单的测试,下面这些是你要做的:

从TestClass中派生一个类。Override runTest()方法。当你希望检查一个值的时候,调用 CPPUNIT_ASSERT(bool),如果测试成功这个assert表达式可以被顺利通过。

比如,为了测试一个复数类的等值比较,书写如下:

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测试用例,编译它们看看会发生什么。我们注意到的第一件事是有一些编译错误。测试使用了操作符==,但是它并没有被定义。修改如下:

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

现在再次编译并运行之。这次编译通过了,但是没有通过测试。我们需要再写些代码使操作符==可以正确工作,所以我们再次修改代码:

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 eq( a.real, b.real )  &&  eq( a.imaginary, b.imaginary );
}

如果我们现在编译并运行,就可以顺利通过测试。

现在我们准备添加一些新的操作符和新的测试用例。这时使用一个fixture会很方便。如果我们实例化3到4个复数并在测试中反复使用它们,可能我们的测试会更好些。
我们这样做:
* 为fixture的每个部分添加成员变量。
* Override setUp() 初始化这些变量。
* Override tearDown()释放你在setUp()中使用的资源。

class ComplexNumberTest : public CppUnit::TestFixture {
private:
  Complex *m_10_1, *m_1_1, *m_11_2;
protected:
  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来调用单独的测试,该如何做呢?
分为两个步骤:
*以一个method的形式,在fixture类中写一个测试用例
*创建TestCaller来运行那个method

这里是我们加了一些额外的用例method书写的测试类:
private:
  Complex *m_10_1, *m_1_1, *m_11_2;
protected:
  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 );

TestCaller的构造函数的第二个参数是ComplexNumberTest中对应method的地址。当这个TestCaller运行的时候,指定的method会运行。但是,这个办法也效果不彰,因为它不显示诊断信息。我们可以使用TestRunner(下面会讲到)来显示这个诊断信息。

一旦我们有了几个测试用例,可以把它们归入一个suite中。

Suite
为了建立多个用例并且让它们一次全部运行,你该如何做呢?
CppUnit提供了一个TestSuite类来同时运行任意个用例。
在上面我们看到了如何运行一个测试用例。
为了创建含有两个或更多用例的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不必仅仅含有测试用例的caller.它们可以包含实现Test 接口的任意对象。例如:你可以在你的代码中创建一个TestSuite,我也可以在我的代码里建立一个,我们通过建立一个同时包含它们两个的TestSuite使它们得以同时运行。
CppUnit::TestSuite suite;
CppUnit::TestResult result;
suite.addTest( ComplexNumberTest::suite() );
suite.addTest( SurrealNumberTest::suite() );
suite.run( &result );

TestRunner
你该如何运行你的用例并收集测试结果呢?

一旦你有了一个TestSuite, 你会想运行它。CppUnit提供了定义这些suite并显示结果的工具。你可以通过在一个TestSuite中加入一个名为suite的静态的method使你的suite与TestRunner建立联系。

例如,为了使TestRunner可以看到一个ComplexNumberTest suite,在ComplexNumberTest中加入一下代码:
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中包含以下头文件:
#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会运行这些用例。如果所有的测试都顺利通过,你会得到一个反馈。如果任何一个测试没有通过,你会得到以下信息:
*失败的测试用例的名字
*包含这个测试源文件的名字
*错误发生的行号
*发现错误的CPPUNIT_ASSERT()调用中的所有文字。

Helper Macros
你可能已经注意到了,实现fixture中的static suite()是一个要反复要做的,并且容易出错的任务。我们可以使用一组写 test fixture的宏来自动执行这些静态的suite method.

下面是使用这些宏重写了类ComplexNumberTest后的代码:
#include <cppunit/extensions/HelperMacros.h>

class ComplexNumberTest : public CppUnit::TestFixture  {

首先我们声明这个suite,把这个类的名字传递给宏:
CPPUNIT_TEST_SUITE( ComplexNumberTest );

这个使用静态的suite() method建立的suite以类的名字来命名。然后,我们为每个测试用例声明:
CPPUNIT_TEST( testEquality );
CPPUNIT_TEST( testAddition );

最后,我们结束这个suite声明:
CPPUNIT_TEST_SUITE_END();

在这里下面这个method已经被实现了:
static CppUnit::TestSuite *suite();

剩下的fixture保持不动:
private:
  Complex *m_10_1, *m_1_1, *m_11_2;
protected:
  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 );
  }
};

加入这个suite的TestCaller的名字是这个fixture名字和method名字的组合。
对目前这个用例来说,名字会是"ComplexNumberTest.testEquality" 和"ComplexNumberTest.testAddition".

helper macros帮你写些常用的断言。例如。检查当一个数被零除的时候ComplexNumber是否会抛出MathException异常:
*把这个测试用例加入使用CPPUNIT_TEST_EXCEPTION的suite中,指定希望的异常的类型。
*写这个测试用例的method

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是用来解决以下两个缺陷的:
*忘了把你的fixture suite加入test runner(因为它在另外一个文件中,很容易忘)
*因为加入所有测试用例头文件造成的编译瓶颈。

TestFactoryRegistry是在初始化的时候注册suite的地方。

为了注册ComplexNumber suite,在.cpp中加入:
#include <cppunit/extensions/HelperMacros.h>

CPPUNIT_TEST_SUITE_REGISTRATION( ComplexNumber );

事实上,桌面下的动作是,一个静态的AutoRegisterSuite类型变量被声明。在构建的时候,它会注册一个TestSuiteFactory到TestFactoryRegistry。 TestSuiteFactory返回ComplexNumber::suite()返回的TestSuite。

为了运行这些用例,使用文字的test runner,我们不必包含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();
然后我们获得并添加一个由TestFactoryRegistry产生的新的TestSuite,它包含了使用CPPUNIT_TEST_SUITE_REGISTRATION()注册的所有的test suite.
  runner.addTest( registry.makeTest() );
  runner.run();
  return 0;
}

Post-build check
好了,现在我们已经可以使测试运行了,那么把它集成到编译过程中去怎么样?
为了达到这个目的,应用程序必须返回一个非0值表明出现了错误。
TestRunner::run()返回一个布尔值来表明run()是否成功。
更新一下我们的main函数,我们得到:
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>

int main( int argc, char **argv)
{
  CppUnit::TextUi::TestRunner runner;
  CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
  runner.addTest( registry.makeTest() );
  bool wasSucessful = runner.run( "", false );
  return wasSucessful;
}

现在,你需要编译后运行你的应用程序。
使用 Visual C++的话,可以在Project Settings/Post-Build step中加入下面的命令。它被扩展到应用程序的执行路径。使用这个技术的时候看看project examples/cppunittest/CppUnitTestMain.dsp 中是如何设置的。

Original version by Michael Feathers. Doxygen conversion and update by Baptiste Lepilleur.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值