软件开发一般流程包括:需求理解—>概要设计—>详细设计—>编码—>单体测试—>结合测试。单体测试(Uint Test)是软件开发阶段的一个基本环节,一般由开发者完成。那么,什么是单体测试?顾名思义就是对单体进行测试。所谓单体,可以理解为一个函数,而测试就是验证此函数是否能达到预期结果。所以,简单说,单体测试就是测试函数是否实现了预期结果。
目前,单体测试框架有很多。比如,应用于C/C++的Google Test,CppTest;应用于Java的JUint。它们的框架函数都很丰富,可以从不同角度对函数测试,能满足大多数项目的需求。但是,如果项目是基于Qt开发的,再搭建一套Google Test环境可能会很麻烦[1]。而Qt本身也提供了一个单体测试框架—-Qt Test,应用它更方便、快捷。
如何应用Qt Test对一个函数进行测试?下面以一个简单的Qt Console应用程序来进行说明。编程环境为Windwos下Qt Creator,基于Qt 5.5.1。
有这样一个代表书籍的类Quote
[2],下面类的定义与原书中稍有不同。它有两个属性:编号和单价,两个方法:编号和总价。net_price()
前的virtual
关键字先不用关注。现在要测试net_price()
是否实现了通过单价和数量计算总价的功能。
class Quote{
public:
Quote(const string& bookNo, double salePrice):
m_bookNo(bookNo)
,m_salePrice(salePrice)
{}
string isbn() const
{
return m_bookNo;
}
virtual double net_price(int quantity)
{
return m_salePrice*quantity;
}
private:
string m_bookNo;
protected:
double m_salePrice;
};
首先,建立一个Qt Console应用程序,命名为UintTest
,并向其中加入类Quote
。接下来编写测试类和测试函数。测试类用于包含测试函数,测试函数中对被测函数net_price()
进行验证。要注意的是,测试类要继承QObject
并使用Q_OBJECT
,因为测试函数都是以槽(slot
)形式定义的;此外,要包含QTest
头文件。这样,测试框架才能执行测试函数。
测试类如下:
class Test_Quote:public QObject
{
Q_OBJECT
public:
Test_Quote();
~Test_Quote();
private slots:
void net_price();
};
这里测试函数可以和被测函数同名,它是测试真正实行的地方。测试之前需要理清以下几点:
对于第1点,被测函数输出很明确,是一个总价格,为double
类型;输入一个是函数的形参即数量,另一个是Quote
的成员m_salePrice
即单价。至于m_bookNo
,它对实际输出没有影响,可以不算作输入。
这样一来,第2点也就明确了:构造出Quote
对象,调用net_price(int quantity)
得到实际输出,而期待结果事先就可以计算出来。
在实际项目中,输入输出有时并不明朗。输入可能是其他模块提供的值,这就需要构造适当的桩函数来模拟输入。而输出也可能不容易表示。比如,被测函数没有返回值,它执行的结果就是从数据库取得了一条数据、显示了一个画面等等,这时就需要根据实际情况选择恰当方法了。
最后一点,Qt Test的测试框架中提供了很多比较方法,常用的有QCOMPARE(actual, expected)
,QTRY_VERIFY(condition)
,可以通过Qt帮助查询它们的用法。而这里比较的是double
类型,可以用bool qFuzzyCompare(double p1, double p2)
来比较。
测试函数如下:
void Test_Quote::net_price()
{
Quote cppPrimer("0001",128.0);
//total price if buy ten books.
double result = 1280.0;
double actual = cppPrimer.net_price(10);
qFuzzyCompare(actual,result);
}
执行测试函数需要用到QTest
中的方法。在main
函数中实例化Test_Quote
,再调用int QTest::qExec(QObject * testObject, int argc = 0, char ** argv = 0)
就可以了,如下代码所示。
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Test_Quote testQuote;
QTest::qExec(&testQuote);
return a.exec();
}
尤其注意的是,为了编译通过要对工程文件做一些配置,即在UintTest.pro
加入这一句:
QT += testlib
得到的输出如下,它把测试类中的每个测试函数都执行了。
********* Start testing of Test_Quote *********
Config: Using QtTest library 5.5.1, Qt 5.5.1 (i386-little_endian-ilp32 shared (d
ynamic) debug build; by GCC 4.9.2)
PASS : Test_Quote::initTestCase()
PASS : Test_Quote::net_price()
PASS : Test_Quote::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted
********* Finished testing of Test_Quote *********
可能会疑惑的是,测试类中并没有定义initTestCase()
和cleanupTestCase()
,这两个可以认为是框架函数,其中:
initTestCase() //在第一个测试函数执行之前被调用
cleanupTestCase() //在所有测试函数执行结束后被调用
此外还有两个,
init() //每个测试函数执行之前被调用
cleanup() //每个测试函数执行结束后被调用
如有需要,它们可以为测试类数据成员做初始化和清理等工作。
以上,就是应用Qt Test做单体测试的简单介绍。
[1] Qt使用Google Test 单元测试. http://blog.csdn.net/taoerit/article/details/39533563
[2] 《C++ Primer》15.2 Defining Base and Derived Classes