朱健
摘要: Qt是跨平臺(tái)的應(yīng)用軟件開(kāi)發(fā)框架,Qt Test是Qt框架的單元測(cè)試庫(kù)?;赒t Test的單元測(cè)試可以進(jìn)行功能和性能測(cè)試。持續(xù)集成系統(tǒng)可以自動(dòng)化單元測(cè)試的構(gòu)建、部署、運(yùn)行和結(jié)果統(tǒng)計(jì)過(guò)程。工程實(shí)踐表明:基于Qt Test的單元測(cè)試與持續(xù)集成系統(tǒng)的結(jié)合可以降低軟件的缺陷率,優(yōu)化軟件架構(gòu)的設(shè)計(jì),提高軟件工程的自動(dòng)化。
Abstract: Qt is a cross platform application development framework. Qt Test is the unit testing library provided by Qt framework. The Qt Test based unit testing can be used for functionality and benchmarking testing. Continuous integration system can automate the build, deployment, run and result statistic processes of unit testing. The engineering practice shows the combination of Qt Test based unit testing and continuous integration system can be used for decreasing the defect rate of software, optimizing the design of software architecture, improving the automation of software engineering.
關(guān)鍵詞: 單元測(cè)試;Qt;持續(xù)集成;自動(dòng)化
Key words: unit testing;Qt;continuous integration;automation
中圖分類號(hào):TP31 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1006-4311(2017)14-0216-04
0 引言
基于Qt框架的大型應(yīng)用軟件通常包括用戶界面,業(yè)務(wù)邏輯,工具庫(kù),UI組件庫(kù),平臺(tái)適配層等,軟件規(guī)模大,邏輯復(fù)雜,通常進(jìn)行層次化和模塊化設(shè)計(jì),軟件的整體穩(wěn)定性和效率與每個(gè)基礎(chǔ)模塊的代碼質(zhì)量息息相關(guān)。單元測(cè)試是對(duì)軟件模塊的功能測(cè)試,可及早發(fā)現(xiàn)軟件設(shè)計(jì)和實(shí)現(xiàn)中的問(wèn)題,便于問(wèn)題的定位[2]。為了降低軟件的缺陷率,業(yè)界提出并實(shí)施了測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(Test Driven Development)方法,該方法主張先開(kāi)發(fā)單元測(cè)試程序,由單元測(cè)試結(jié)果驅(qū)動(dòng)軟件的設(shè)計(jì)和編碼,以到達(dá)更具針對(duì)性的架構(gòu)設(shè)計(jì)和更低的首次發(fā)布缺陷率[6,7]。所以單元測(cè)試是大型尤其是采用測(cè)試驅(qū)動(dòng)開(kāi)發(fā)的Qt軟件工程的必然要求。
在工程實(shí)踐中常用的通用單元測(cè)試框架包括:適用于C++語(yǔ)言的CppUnit[3],適用于Java語(yǔ)言的JUnit[2]等。但是基于Qt的軟件大都依賴于Qt對(duì)C++語(yǔ)言的擴(kuò)展,如信號(hào)-槽(signal-slot)機(jī)制,屬性(property)機(jī)制,元對(duì)象(meta-object)框架等;構(gòu)建過(guò)程依賴于Qt特有的工具鏈(如qmake,moc等);運(yùn)行過(guò)程依賴于Qt特有的基礎(chǔ)類庫(kù),所以很難使用CppUnit等通用單元測(cè)試框架對(duì)基于Qt的軟件進(jìn)行單元測(cè)試。而Qt Test是Qt框架的單元測(cè)試模塊,可以與Qt的工具鏈、基礎(chǔ)類庫(kù)、應(yīng)用框架、集成開(kāi)發(fā)環(huán)境(IDE)等良好結(jié)合[5]。
1 Qt Test單元測(cè)試框架分析
1.1 Qt Test單元測(cè)試原理
在Qt Test單元測(cè)試體系中,每個(gè)測(cè)試文件是一個(gè)測(cè)試集合,測(cè)試集合是一個(gè)繼承自QObject的測(cè)試對(duì)象(Test Object),測(cè)試對(duì)象的每個(gè)私有槽(private slot)都是一個(gè)測(cè)試函數(shù)。Qt Test單元測(cè)試框架通過(guò)QObject::metaObject()獲取測(cè)試對(duì)象的QMetaObject屬性,通過(guò)QMetaObject::method()依次獲取測(cè)試對(duì)象的私有槽,逐個(gè)調(diào)用并輸出測(cè)試結(jié)果。一個(gè)典型的測(cè)試對(duì)象可以用如下的類定義:
class TestSuite : public QObject {
Q_OBJECT
public:
TestSuite();
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testcase1();
void testcase2();
…
};
其中initTestCase()和cleanupTestCase()是兩個(gè)特殊的測(cè)試函數(shù),initTestCase()在測(cè)試程序開(kāi)始運(yùn)行時(shí)調(diào)用,主要用于準(zhǔn)備測(cè)試環(huán)境,包括構(gòu)造測(cè)試相關(guān)的對(duì)象、樁對(duì)象、其他基礎(chǔ)Qt庫(kù)對(duì)象,初始化局部變量,進(jìn)行數(shù)據(jù)庫(kù)連接,準(zhǔn)備測(cè)試數(shù)據(jù)等。cleanupTestCase()在測(cè)試程序結(jié)束運(yùn)行前調(diào)用,是initTestCase()的逆過(guò)程,用于銷毀測(cè)試過(guò)程中構(gòu)造的對(duì)象資源,刪除測(cè)試運(yùn)行過(guò)程中生成的數(shù)據(jù)等。其余的私有槽都是獨(dú)立測(cè)試函數(shù),測(cè)試函數(shù)的實(shí)現(xiàn)需要根據(jù)測(cè)試?yán)鞒踢M(jìn)行一系列的操作,驗(yàn)證功能和性能要求是否達(dá)到預(yù)期。
每個(gè)Qt Test測(cè)試文件最終構(gòu)建成一個(gè)單元測(cè)試程序,單元測(cè)試程序可以獨(dú)立運(yùn)行,不需要運(yùn)行工具(Test Runner),可以在資源受限的嵌入式Linux設(shè)備上運(yùn)行。單元測(cè)試的運(yùn)行流程如圖1:測(cè)試程序啟動(dòng)后,測(cè)試框架通過(guò)QTest::qExec()運(yùn)行測(cè)試對(duì)象。首先,測(cè)試對(duì)象的initTestCase()被調(diào)用;之后測(cè)試對(duì)象的各個(gè)測(cè)試函數(shù)會(huì)被依次調(diào)用,并記錄測(cè)試結(jié)果;最后cleanupTestCase()被調(diào)用,測(cè)試結(jié)果輸出,單元測(cè)試結(jié)束。
1.2 Qt Test單元測(cè)試內(nèi)容
1.2.1 功能性測(cè)試
單元測(cè)試的基本功能是進(jìn)行功能性的測(cè)試,通過(guò)調(diào)用待測(cè)對(duì)象的接口,完成測(cè)試?yán)?guī)定的流程,比對(duì)期望值和實(shí)際值,并輸出測(cè)試結(jié)果。Qt Test提供一系列宏,用于在測(cè)試代碼中比對(duì)期望值與實(shí)際值。例如,QVERIFY(expr)用于校驗(yàn)二元表達(dá)式expr,若expr為假(false)則判定當(dāng)前測(cè)試?yán)?;QCOMPARE(value1, value2)用于判斷兩個(gè)值value1,value2是否相等,若不相等則判定測(cè)試?yán) ?/p>
在功能性覆蓋率達(dá)到100%的情況下,需要進(jìn)一步提高代碼路徑覆蓋率。為提高單個(gè)測(cè)試?yán)瘮?shù)的代碼路徑覆蓋率,可以采用數(shù)據(jù)驅(qū)動(dòng)測(cè)試(Data Driven Test)方法:即設(shè)計(jì)多組數(shù)據(jù),多次運(yùn)行該測(cè)試?yán)瘮?shù),以提高測(cè)試效率。可根據(jù)待測(cè)模塊的代碼實(shí)現(xiàn)、測(cè)試?yán)男枨笠?guī)約,設(shè)計(jì)出覆蓋邊界條件、錯(cuò)誤處理分支的測(cè)試數(shù)據(jù)集,提高單元測(cè)試的代碼路徑覆蓋率。Qt Test支持?jǐn)?shù)據(jù)驅(qū)動(dòng)測(cè)試:實(shí)現(xiàn)以"_data"為結(jié)尾的測(cè)試函數(shù)為同名測(cè)試函數(shù)提供測(cè)試數(shù)據(jù),測(cè)試代碼通過(guò)QFETCH獲取測(cè)試數(shù)據(jù)。以下代碼構(gòu)造了{(lán)20, 23, "Type A"}, {2000, 2098, "Type B"}兩組數(shù)據(jù)對(duì)代碼中的算法Classifier::doClassifier()進(jìn)行了測(cè)試。
void TestSuite::testcase1_data() {
QTest::addColumn
QTest::addColumn
QTest::addColumn
// 第一組測(cè)試數(shù)據(jù)
QTest::newRow << 20 << 23 << QString("Type A");
// 第二組測(cè)試數(shù)據(jù)
QTest::newRow << 2000 << 2098 << QString("Type B");
}
void TestSuite::testcase1() {
// 獲取數(shù)據(jù)并測(cè)試
QFETCH(int, inputX);
QFETCH(int, inputY)
QFETCH(QString, result);
QString type = Classifier::doClassify(inputX, inputY);
QVERIFY(type == result);
}
1.2.2 GUI測(cè)試
通常的GUI測(cè)試主要是黑盒測(cè)試,通過(guò)模擬鍵盤(pán)、鼠標(biāo)事件驅(qū)動(dòng)GUI應(yīng)用程序運(yùn)行,捕捉屏幕上的GUI對(duì)象比對(duì)測(cè)試結(jié)果,需要復(fù)雜的Test Runner支持[1],而且這類GUI測(cè)試不能對(duì)單個(gè)GUI控件進(jìn)行單元測(cè)試。Qt Test為繼承自QWidget的控件提供了GUI單元測(cè)試功能:使用QTest::keyClick(),QTest::mouseClick()模擬鍵盤(pán)和鼠標(biāo)事件驅(qū)動(dòng)控件運(yùn)行;通過(guò)QVERIFY,QCOMPARE比對(duì)期望值和實(shí)際結(jié)果,不需要復(fù)雜的對(duì)象捕捉技術(shù)。GUI測(cè)試同樣支持?jǐn)?shù)據(jù)驅(qū)動(dòng)的測(cè)試,QTestEventList用于定義作用在QWidget上的事件序列,例如:
QTestEventList events;
CustomWidget w;
// 定義事件序列
events.addMouseClick(Qt::LeftButton, 0, QPoint(20, 30));
events.addKeyClick(Qt::Key_Backspace);
events.addDelay(100);
// 模擬運(yùn)行事件序列
events.simulate(&w);
// 比對(duì)測(cè)試結(jié)果
QCOMPARE(w.myProperty(), expectedValue);
Qt Test的GUI測(cè)試不依賴任何測(cè)試工具,以輕量級(jí)方式模擬鼠標(biāo)、鍵盤(pán)事件,便于團(tuán)隊(duì)在設(shè)計(jì)和實(shí)現(xiàn)GUI組件時(shí)做充分的測(cè)試,避免系統(tǒng)集成時(shí)因GUI組件缺陷導(dǎo)致系統(tǒng)異常。
1.2.3 性能測(cè)試
軟件的整體性能與基礎(chǔ)模塊的性能密切相關(guān),只對(duì)基礎(chǔ)模塊做功能性單元測(cè)試,不能保證其性能指標(biāo)。Qt Test可以使用QBENCHMARK進(jìn)行性能測(cè)試。QBENCHMARK可以對(duì)塊內(nèi)代碼片段的運(yùn)行時(shí)間進(jìn)行測(cè)定。單元測(cè)試運(yùn)行時(shí),QBENCHMARK塊內(nèi)的代碼片段會(huì)被運(yùn)行多次并記錄運(yùn)行時(shí)間,從而得到比較精確的結(jié)果。性能測(cè)試的反復(fù)運(yùn)行次數(shù),性能計(jì)量單位,最小可接受的性能值等可以通過(guò)單元測(cè)試命令行參數(shù)設(shè)定。在單元測(cè)試中引入性能測(cè)試,可以在設(shè)計(jì)和實(shí)現(xiàn)階段及時(shí)發(fā)現(xiàn)性能問(wèn)題,避免軟件集成時(shí)運(yùn)行效率無(wú)法達(dá)標(biāo)的問(wèn)題;在軟件維護(hù)階段,基礎(chǔ)模塊的性能測(cè)試也可以及時(shí)定位不當(dāng)修改引入的性能問(wèn)題,保證軟件質(zhì)量平穩(wěn)。
1.3 Qt Test單元測(cè)試的改進(jìn)
單元測(cè)試過(guò)程應(yīng)該由大到小,劃分為若干測(cè)試模塊,最終細(xì)化到函數(shù)級(jí)別,在函數(shù)級(jí)別完成邏輯性測(cè)試,覆蓋代碼分支,再由小到大完成集成測(cè)試和功能性測(cè)試[2]。從代碼分支到系統(tǒng)功能的充分測(cè)試,可以及時(shí)發(fā)現(xiàn)系統(tǒng)中存在的問(wèn)題,提高測(cè)試效率,降低開(kāi)發(fā)后期的測(cè)試和維護(hù)成本。軟件的設(shè)計(jì)和實(shí)現(xiàn)需要遵循如下原則:①將復(fù)雜的大模塊拆分成適合于單元測(cè)試的小模塊。對(duì)每個(gè)小模塊進(jìn)行單獨(dú)的單元測(cè)試,有利于缺陷的定位和排除。②將業(yè)務(wù)邏輯與框架做分離。軟件設(shè)計(jì)時(shí)應(yīng)該避免將業(yè)務(wù)邏輯與框架耦合在一起,否則會(huì)造成無(wú)法通過(guò)構(gòu)造模擬數(shù)據(jù)進(jìn)行單元測(cè)試。③基礎(chǔ)模塊的單個(gè)函數(shù)不能實(shí)現(xiàn)過(guò)多功能,復(fù)雜的函數(shù)會(huì)造成測(cè)試粒度過(guò)大,不利于缺陷定位,復(fù)雜函數(shù)應(yīng)拆分為較小的函數(shù)。④消除冗余代碼。冗余代碼會(huì)增加單元測(cè)試的設(shè)計(jì)難度,降低測(cè)試效率,代碼實(shí)現(xiàn)時(shí)需要消除冗余變量、冗余接口,提取冗余代碼的公共部分為獨(dú)立的函數(shù)。
2 自動(dòng)化Qt Test單元測(cè)試方法
2.1 自動(dòng)化Qt Test單元測(cè)試的流程和工具
大型Qt應(yīng)用程序通常包含大量子模塊,本身需要開(kāi)發(fā)大量的單元測(cè)試程序進(jìn)行測(cè)試;在測(cè)試驅(qū)動(dòng)的開(kāi)發(fā)中,需要先設(shè)計(jì)和實(shí)現(xiàn)單元測(cè)試用例,再進(jìn)行功能模塊的開(kāi)發(fā),隨著開(kāi)發(fā)的迭代,單元測(cè)試工程數(shù)量必然逐步增多。所以,在實(shí)際項(xiàng)目中,單元測(cè)試工程的開(kāi)發(fā)和維護(hù)工作量是很大的。為了提高單元測(cè)試的開(kāi)發(fā)和維護(hù)效率,可以使用Qt Creator集成開(kāi)發(fā)環(huán)境(IDE)。Qt Creator可以將多個(gè)的單元測(cè)試工程以子工程(sub project)的形式進(jìn)行管理,有利于單元測(cè)試工程的分類和維護(hù);新建單元測(cè)試工程時(shí),Qt Creator可以為開(kāi)發(fā)者生成框架代碼,避免重復(fù)勞動(dòng)。
隨著單元測(cè)試程序數(shù)量增加,由開(kāi)發(fā)者手動(dòng)運(yùn)行每個(gè)單元測(cè)試程序并查看結(jié)果將是繁重的重復(fù)性勞動(dòng),且不能對(duì)單元測(cè)試中的警告、錯(cuò)誤、失敗率等進(jìn)行有效的統(tǒng)計(jì)和歸檔,項(xiàng)目管理者也無(wú)法及時(shí)了解單元測(cè)試的進(jìn)度、狀況等。持續(xù)集成(Continuous Integration)系統(tǒng)可以自動(dòng)化單元測(cè)試的構(gòu)建、部署、運(yùn)行、結(jié)果統(tǒng)計(jì)過(guò)程。持續(xù)集成是一種軟件工程實(shí)踐,即通過(guò)工具自動(dòng)從源代碼管理系統(tǒng)獲取項(xiàng)目的源代碼,自動(dòng)編譯,部署,運(yùn)行軟件。持續(xù)集成能夠及時(shí)發(fā)現(xiàn)和解決軟件工程中存在的問(wèn)題,減輕開(kāi)發(fā)者的日常工作量,為項(xiàng)目管理者提供可視化的數(shù)據(jù)參考,Jenkins[4]是目前業(yè)內(nèi)廣泛使用的持續(xù)集成系統(tǒng)。Qt Creator和Jenkins系統(tǒng)相結(jié)合可以有效提高Qt Test單元測(cè)試各步驟的自動(dòng)化程度(如圖2),提高Qt Test單元測(cè)試的效率,實(shí)現(xiàn)基于Qt Test的測(cè)試驅(qū)動(dòng)開(kāi)發(fā)。
2.2 使用Qt Creator開(kāi)發(fā)和維護(hù)Qt Test單元測(cè)試工程
Qt Creator是Qt默認(rèn)的集成開(kāi)發(fā)環(huán)境(IDE),使用Qt Creator工程向?qū)Э梢钥焖賱?chuàng)建Qt Test單元測(cè)試工程??梢酝ㄟ^(guò)“文件->新建文件或項(xiàng)目->其他項(xiàng)目->Qt單元測(cè)試”打開(kāi)“Qt單元測(cè)試”向?qū)В▓D3)。
在Qt單元測(cè)試向?qū)Х譃?個(gè)步驟:位置,構(gòu)建套件(Kit),模塊,詳情,匯總,“位置”用于設(shè)置單元測(cè)試工程路徑,“構(gòu)建套件”用于指定目標(biāo)代碼的編譯器或交叉編譯器,“模塊”用于添加單元測(cè)試工程依賴的Qt基礎(chǔ)類庫(kù),詳情用于生成單元測(cè)試框架代碼。向?qū)蓡卧獪y(cè)試工程的框架代碼,開(kāi)發(fā)者只需進(jìn)一步增加和完善測(cè)試代碼。開(kāi)發(fā)者可以在Qt Creator中編譯和運(yùn)行單元測(cè)試代碼,最終完成單元測(cè)試工程的開(kāi)發(fā)。
2.3 使用Jenkins自動(dòng)化構(gòu)建、部署、結(jié)果統(tǒng)計(jì)
在Jenkins系統(tǒng)Web界面的“項(xiàng)目->配置->構(gòu)建”頁(yè)面可編寫(xiě)自動(dòng)化構(gòu)建、部署、運(yùn)行單元測(cè)試程序的腳本,在Linux環(huán)境下采用Shell腳本編寫(xiě)?!绊?xiàng)目->配置->構(gòu)建后操作”頁(yè)面可以添加“Publish xUnit test report”步驟用于呈現(xiàn)Qt單元測(cè)試程序生成的基于XML的測(cè)試結(jié)果。
以自動(dòng)化嵌入式Linux設(shè)備的單元測(cè)試為例,首先需要在Jenkins編譯主機(jī)上交叉編譯生成可執(zhí)行的Qt單元測(cè)試程序,對(duì)于支持輕量級(jí)SSH服務(wù)如dropbear的Linux設(shè)備,可以采用SSH(Secure Shell)協(xié)議進(jìn)行單元測(cè)試程序的部署、運(yùn)行及結(jié)果回收。以下是自動(dòng)化腳本的主要代碼:
# 編譯單元測(cè)試程序
mkdir $WORKSPACE/build
mkdir $WORKSPACE/targetfs
cd build
qmake ..
make INSTALL_ROOT="$WORKSPACE/targetfs" install
cd $WORKSPACE/targetfs/
# 將單元測(cè)試程序部署到遠(yuǎn)程設(shè)備
scp tst_* root@
# 在遠(yuǎn)程設(shè)備上運(yùn)行單元測(cè)試程序
ssh root@
# 將單元測(cè)試結(jié)果收回本地
scp root@
此例中,單元測(cè)試程序可執(zhí)行文件名以tst_開(kāi)頭,remote_run.sh位于待測(cè)Linux設(shè)備上,用于批量運(yùn)行單元測(cè)試程序并輸出XML格式的測(cè)試結(jié)果,remote_run.sh的核心代碼如下:
# 枚舉單元測(cè)試程序
for unittest in tst_*
do
# 執(zhí)行單元測(cè)試程序,并導(dǎo)出XML格式的測(cè)試結(jié)果
bash -c "./${unittest} -xml -o report_{unittest}.xml"
done
Jenkins的“xUnit test report”插件可以根據(jù)XML格式的Qt Test測(cè)試結(jié)果以測(cè)試結(jié)果趨勢(shì)圖(圖4)、詳細(xì)測(cè)試結(jié)果表格(圖5)的形式呈現(xiàn)出來(lái)。測(cè)試結(jié)果趨勢(shì)圖頁(yè)面可以形象地展現(xiàn)近期單元測(cè)試總數(shù)、通過(guò)率、失敗率的變化趨勢(shì);詳細(xì)測(cè)試結(jié)果表格頁(yè)面展示所有測(cè)試?yán)慕Y(jié)果詳情,包括失敗測(cè)試?yán)氖⌒畔?、各測(cè)試?yán)倪\(yùn)行結(jié)果和執(zhí)行時(shí)間等。
3 結(jié)論
在測(cè)試驅(qū)動(dòng)的開(kāi)發(fā)中,需要先完成單元測(cè)試代碼,根據(jù)單元測(cè)試結(jié)果迭代式地設(shè)計(jì)、實(shí)現(xiàn)、改進(jìn)目標(biāo)軟件,最終交付軟件產(chǎn)品。這種以單元測(cè)試為基礎(chǔ)的軟件開(kāi)發(fā)模型,可以對(duì)軟件基礎(chǔ)模塊的基本功能、關(guān)鍵性能等做充分的測(cè)試,可以盡早發(fā)現(xiàn)、定位、解決軟件缺陷,改善軟件質(zhì)量,優(yōu)化軟件架構(gòu)。
隨著軟件規(guī)模的擴(kuò)大,單元測(cè)試的規(guī)模會(huì)逐步增大,構(gòu)建、運(yùn)行、檢查單元測(cè)試結(jié)果的工作量也隨之上升,通過(guò)持續(xù)集成系統(tǒng)進(jìn)行自動(dòng)化單元測(cè)試的實(shí)踐可以減輕開(kāi)發(fā)人員的構(gòu)建、部署、測(cè)試工作量,提高測(cè)試驅(qū)動(dòng)開(kāi)發(fā)的自動(dòng)化程度,在持續(xù)集成系統(tǒng)中實(shí)時(shí)展示單元測(cè)試結(jié)果可以提高開(kāi)發(fā)者定位、修正軟件缺陷的效率,可以為軟件工程的進(jìn)度和質(zhì)量提供定量化的數(shù)據(jù)參考。
參考文獻(xiàn):
[1]吳立金,唐龍利,韓新宇,等.嵌入式軟件GUI自動(dòng)化測(cè)試平臺(tái)研究[J].計(jì)算機(jī)測(cè)量與控制,2015,23(4):1094-1097.
[2]張敏,陳靜,王娟.基于MVC模式的Web系統(tǒng)自動(dòng)化單元測(cè)試方案[J].微型電腦應(yīng)用,2016,32(2):78-80.
[3]賈長(zhǎng)偉,廖建,焉寧,等.基于CppUnit的虛擬試驗(yàn)單元測(cè)試研究[J].計(jì)算機(jī)測(cè)量與控制,2015,23(4):1155-1160.
[4]John Ferguson Smart. Jenkins The Definitive Guide[M]. O'Reilly Media, 2011: 169-180.
[5]Qt Test user manual. http://doc.qt.io/qt-4.8/qtestlib-manual.html 2016.
[6]Nachiappan Nagappan, E. Michael Maximilien, Thirumalesh Bhat, Laurie Williams. Realizing quality improvement through test driven development: results and experiences of four industrial teams[J]. Empir Software Eng 2008,13: 289-302.
[7]Kent Beck. Test-Driven Development By Example[M]. Addison Wesley, 2002.