CppUnit Cookbook XK翻译版
来源:互联网 发布:身份证复印件软件v3.8 编辑:程序博客网 时间:2024/06/05 18:04
这是一篇入门文章。
EN: http://cppunit.sourceforge.net/doc/1.11.6/cppunit_cookbook.html
当想测试代码是否能正确运行的时候, 一般会有以下两种简单的做法:
使用调试程序或者通过屏幕输出来进行测试。
但是它们都有缺点。通过调试程序调试是一个好办法,但是不够自动化,不得不在程序代码更改之后从新调试一次。通过屏幕输出也是一种好的办法,但是测试代码常常”弄脏”源代码,并且经常在输出了比我需要更多的无用信息。
使用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东西。假设我们需要开发了一个复数类的程序,定义一个空的类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中。
怎么样让所有的用例一次运行呢/
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,再创建一个包含这两个TestSuite的TestSuite。
CppUnit::TestSuite suite;
CppUnit::TestResult result;
suite.addTest( ComplexNumberTest::suite() );
suite.addTest( SurrealNumberTest::suite() );
suite.run( &result );
怎样运行测试用例,并检查结果呢?
如果存在一个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所在的头文件。
在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++运行时异常。
从前面的叙述中,也许已经会发现一个问题, 为一个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的声明:
这时,一个如下声明的方法就被实现了.
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 用来解决参见的两个问题:
1) 忘记把suite添加到runner中。特别是在suite和runner不在一个文件中时,是很容易忘记的。
2) 包含所有TEST CASE带来的编辑瓶颈。
TestFactoryRegistry 是一个能在初始化的时候注册所有的suite的地方。
在注册 ComplexNumber suite的时候,需要在.cpp文件中加入:
在这背后,一个 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 ®istry = CppUnit::TestFactoryRegistry::getRegistry();
然后,获得由CPPUNIT_TEST_SUITE_REGISTRATION()注册在 TestFactoryRegistry 中的所有TEST SUITE, 并把它们添加到runner中。
runner.addTest( registry.makeTest() );
runner.run();
return 0;
}
- CppUnit Cookbook XK翻译版
- CppUnit Cookbook中文版
- CppUnit Cookbook中文版
- Rails Cookbook翻译(一)
- Rails Cookbook翻译(二)
- Flex CookBook翻译(1)
- Flex CookBook翻译(2)
- 开始翻译Jakarta Commons Cookbook。
- Rails Cookbook翻译(三)
- Rails Cookbook翻译(四)
- Rails Cookbook翻译(五)
- Rails Cookbook翻译(六)
- Flex 3 Cookbook(翻译1)
- Flex CookBook翻译(3) FireBug
- The Yii Cookbook 中文目录(翻译)
- [DEVDIV翻译] iOS 5 Programming Cookbook中文翻译
- yii2-cookbook之单表继承[翻译]
- yii2-cookbook之定制response类型[翻译]
- 用 Tomcat 和 Eclipse 开发 Web 应用程序
- Linux 命令行快捷键
- socket
- 任意对象转换为JSON格式
- 有关 Direct3D技术的初步探索-------基本渲染流程
- CppUnit Cookbook XK翻译版
- Google成立十年现1000万美元征集创意
- 我们还不是主流
- Steve Jobs的十句金玉良言
- Oracle汉字排序SQL
- (转)JSP中文问题解决方案(完整)
- Happy Birthday to ^_^-Stefli
- C/C++语言void及void指针深层探索
- jdk1.5 + sqlserver2000 的配置方法