QTestLib是Qt提供的一種針對基於Qt編寫的程序或庫的單元測試框架。QTestLib提供了單元測試框架的基本功能,並提供了針對GUI測試的擴展功能。express
QTestLib是爲了簡化QT程序或庫的單元測試工做而設計的。
QTestLib特性以下:
A、輕量級:QTestlib只包含6000行代碼和60個導出符號
B、自包含:對於非GUI測試,QTestlib只須要Qt核心庫的幾個符號。
C、快速測試:QTestlib不須要特殊的測試執行程序,不須要爲測試而進行特殊的註冊。
D、數據驅動測試:一個測試程序能夠在不一樣的測試數據集上執行屢次。
E、基本的GUI測試:QTestlib提供了模擬鼠標和鍵盤事件的功能。
F、基準測試:QTestLIB支持基準測試並提供多種測量後端。
G、IDE友好:QTestlib的輸出信息能夠被Visual Studio和KDevelop解析。
H、線程安全:錯誤報告是線程安全的、原子性的。
J、類型安全:對模板進行了擴展使用,防止由隱式類型轉換引發的錯誤。
K、易擴展:用戶自定義類型能夠容易地加入到測試數據和測試輸出中。後端
全部公有的方法都在QTest命名空間中。另外,QSignalSpy類爲Qt的信號和槽提供了簡單的內省機制。安全
默認測試結果以純文本形式顯示在控制檯(應用程序輸出標籤),不夠直觀,可以使用AutoTest插件實現可視化效果。
經過Help->About Plugins->Utilities,選中AutoTest,重啓Qt Creator,而後在下方會多出TestResults的標籤,可直接在此標籤點擊上方的運行按鈕運行全部測試,同時在「Tools-Tests-Run All Tests」也可運行全部測試。
此插件能夠在運行單元測試後以紅、綠色表示明確標記處運行結果,而且以Case爲單位顯示,能夠展開看到具體每個測試用例的結果。
框架
原理:輸入測試數據表和結果數據表,與實際值比較。ide
測試類須要從QObject類繼承,類中須要加入一個或者多個私有槽。每個私有槽都是一個測試函數,但有4種私有槽不能做爲測試函數,它們由測試框架執行,可爲整個測試程序或當前測試函數進行初始化和清除操做。
initTestCase():在第一個測試函數執行前調用。
cleanupTestCase():在最後一個測試函數執行後調用。
init():在每個測試函數執行前調用。
cleanup():在每個測試函數執行後調用。
若是initTestCase()函數執行失敗,任何測試函數都不會執行。若是init()函數執行失敗,緊隨其後的測試函數不會被執行,測試會繼續處理下一個測試函數。
QTest::qExec(QObject* testClassObject)函數用於執行測試對象中全部的測試函數。函數
對於一個要測試的目標函數,須要使用兩個函數進行測試:testFunctionName()和testFunctionName_data()。
testFunctionName_data:數據提供,在函數體中寫入測試數據。
testFunctionName:測試的實體,讀取testFunctionName_data函數中的數據表,並逐行進行測試。若是測試結果與數據表中的結果不一樣,則認爲測試失敗。單元測試
數據由QTest::addColumn < T > (name)和QTest::newRow(name) << input << result來構建一個數據表,其中的列能夠被獲取,而後將表中對應的數據按行測試,並與表中的結果列進行對比。測試
對於GUI交互操做的測試,則將數據設置爲事件列表,供模擬測試。QTestlib提供了模擬鼠標和鍵盤事件的功能。ui
QTest提供一系列宏來進行數據的通訊。編碼
QBENCHMARK QBENCHMARK_ONCE QCOMPARE(actual, expected) QEXPECT_FAIL(dataIndex, comment, mode) QFAIL(message) QFETCH(type, name) QFINDTESTDATA(filename) QSKIP(description) QTEST(actual, testElement) QTEST_APPLESS_MAIN(TestClass) QTEST_GUILESS_MAIN(TestClass) QTEST_MAIN(TestClass) QTRY_COMPARE(actual, expected) QTRY_COMPARE_WITH_TIMEOUT(actual, expected, timeout) QTRY_VERIFY2(condition, message) QTRY_VERIFY(condition) QTRY_VERIFY2_WITH_TIMEOUT(condition, message, timeout) QTRY_VERIFY_WITH_TIMEOUT(condition, timeout) QVERIFY2(condition, message) QVERIFY(condition) QVERIFY_EXCEPTION_THROWN(expression, exceptiontype) QWARN(message)
QTest提供了QTEST_MAIN()做爲測試的啓動宏,構建一個main函數,在main函數內調用QTest::qExec(QObject testClassObject),也能夠直接調用QTest::qExec(QObject testClassObject)來啓動測試。
假設要測試QString類的行爲。首先,須要一個用於包含測試函數的必須從QObject繼承的類:
#include <QtTest/QtTest> class TestQString: public QObject { Q_OBJECT private slots: void toUpper(); };
注意:須要包含QTest頭文件,而且測試函數必須聲明爲私有槽,便於測試框架找到並執行它們。
void TestQString::toUpper() { QString str = "Hello"; QVERIFY(str.toUpper() == "HELLO"); }
QVERIFY()宏將計算傳入的表達式的值。若是爲真,則測試函數繼續進行;不然會向測試日誌中增長一條描述錯誤的信息,而且該測試函數會中止執行。
可是若是須要向測試日誌中增長更詳細的輸出信息,應該使用QCOMPARE()宏:
void TestQString::toUpper() { QString str = "Hello"; QCOMPARE(str.toUpper(), QString("HELLO")); }
寫完測試程序後就須要執行測試程序。假設將測試程序命名爲testqstring.cpp並保存在一個空目錄中,可使用qmake生成一個工程文件和一個Makefile文件。
myTestDirectory$ qmake -project "QT += qtestlib" myTestDirectory$ qmake myTestDirectory$ make
執行自動測試的語法形式:testname [options] [testfunctions[:testdata]]...
testname:測試項目的可執行文件
testfunctions:包含要執行的測試函數名,若是不指定testfunctions,全部的測試函數都會執行。若是測試函數名以後加上了測試數據行的名字,則測試函數執行時只會使用該行測試數據。
列如:/myTestDirectory# StringTest toUpper
使用全部的測試數據執行toUpper測試函數。/myTestDirectory$ StringTest toUpper toInt:zero
使用全部的測試數據執行toUpper測試函數,使用行名爲zero的測試數據執行toInt測試函數(若是對應的測試數據不存在,相關的測試執行時就會失敗)。/myTestDirectory$ WidgetTest -vs -eventdelay 500
執行WidgetTest測試程序,輸出每個信號發射信息,在每次模擬鼠標/鍵盤事件以後等待500毫秒。
選項
下列命令行參數能夠被接受:
-help
輸出命令行參數的幫助信息。
-functions
輸出測試中的全部測試函數。
-o filename
將輸出信息寫入到執行文件中,而不是打印到標準輸出上。
-silent
沉默地輸出,只顯示警告、錯誤和最少的狀態信息。
-v1
詳細輸出;輸出每次進入或離開測試函數的信息。
-v2
詳細輸出;也輸出每一個QCOMPARE()和QVERIFY()信息。
-vs
輸出發出的全部信號。
-xml
將輸出格式化成XML格式,而不是普通文本
-lightxml
輸出成XML標籤流。
-eventdelay ms
若是鍵盤或鼠標模擬(QTest::keyClick(),QTest::mouseClick()等)不指定延遲時間,則使用該參數(以毫秒爲單位)做爲延遲時間。
-keydelay ms
與-eventdelay的做用同樣,但隻影響鍵盤模擬的延遲時間,不影響鼠標模擬的延遲時間。
-mousedelay ms
與-eventdelay的做用同樣,但隻影響鼠標模擬的延遲時間,不影響鍵盤模擬的延遲時間。
-keyevent-verbose
詳細輸出鍵盤模擬信息。
-maxwarnings numberBR
設置警告信息的最大數量,0表示不限制,默認值爲2000。
目前爲止,採用硬編碼的方式將測試數據寫到測試函數中。若是增長更多的測試數據,那麼測試函數會變成:
QCOMPARE(QString("hello").toUpper(), QString("HELLO")); QCOMPARE(QString("Hello").toUpper(), QString("HELLO")); QCOMPARE(QString("HellO").toUpper(), QString("HELLO")); QCOMPARE(QString("HELLO").toUpper(), QString("HELLO"));
爲了避免使測試函數被重複的代碼弄得凌亂不堪, QTestLib支持向測試函數增長測試數據,僅須要向測試類增長另外一個私有槽:
class TestQString: public QObject { Q_OBJECT private slots: void toUpper_data(); void toUpper(); };
爲測試函數提供數據的函數必須與測試函數同名,並加上_data後綴。爲測試函數提供數據的函數相似這樣:
void TestQString::toUpper_data() { QTest::addColumn<QString>("string"); QTest::addColumn<QString>("result"); QTest::newRow("all lower") << "hello" << "HELLO"; QTest::newRow("mixed") << "Hello" << "HELLO"; QTest::newRow("all upper") << "HELLO" << "HELLO"; }
首先,使用QTest::addColumn()函數定義測試數據表的兩列元素:測試字符串和在該測試字符串上調用QString::toUpper()函數指望獲得的結果。
而後使用 QTest::newRow()函數向測試數據表中增長一些數據。每組數據都會成爲測試數據表中的一個單獨的行。
QTest::newRow()函數接收一個參數:將要關聯到該行測試數據的名字。若是測試函數執行失敗,名字會被測試日誌使用,以引用致使測試失敗的數據。而後將測試數據加入到新行:首先是一個任意的字符串,而後是在該行字符串上調用 QString::toUpper()函數指望獲得的結果字符串。
能夠將測試數據看做是一張二維表格。在這個例子裏,它包含兩列三行,列名爲string 和result。另外,每行都會對應一個序號和名稱:
index name string result
0 all lower "hello" HELLO
1 mixed "Hello" HELLO
2 all upper "HELLO" HELLO
測試函數須要被重寫:
void TestQString::toUpper() { QFETCH(QString, string); QFETCH(QString, result); QCOMPARE(string.toUpper(), result); }
TestQString::toUpper()函數會執行兩次,對toUpper_data()函數向測試數據表中加入的每一行都會調用一次。
首先,調用QFETCH()宏從測試數據表中取出兩個元素。QFETCH()接收兩個參數: 元素的數據類型和元素的名稱。而後用QCOMPARE()宏執行測試操做。
使用這種方法能夠不修改測試函數就向該函數加入新的數據。
像之前同樣,爲使測試程序可以單獨執行,須要加入下列代碼:
QTEST_MAIN(TestGui)
QTEST_MAIN()宏將擴展成一個簡單的main()函數,該main()函數會執行全部的測試函數。
QTestlib單元測試提供GUI操做函數,可對控件發送消息後檢測執行結果,好比QTest::keyClick(),QTest::mouseClick()等等
QTestlib具備測試GUI的一些特性。QTestLib發送內部Qt事件,而不是模擬本地窗口系統事件,所以運行測試程序不會對機器產生任何反作用。
#include <QtGui> #include <QtTest/QtTest> class TestGui: public QObject { Q_OBJECT private slots: void testGui(); };
惟一的區別是除了要加入QTest命名空間以外,須要包含QtGui類的定義。
void TestGui::testGui() { QLineEdit lineEdit; QTest::keyClicks(&lineEdit, "hello world"); QCOMPARE(lineEdit.text(), QString("hello world")); }
在測試函數實現中,建立一個QLineEdit,使用QTest::keyClicks()函數模擬在行編輯框中輸入「hello world」字符串。
注意: 爲了正確測試快捷鍵,控件必須顯示出來。
QTest::keyClicks()在控件上模擬一連串的鍵盤敲擊操做。另外,每次鍵盤敲擊後,能夠指定延遲時間(以毫秒爲單位)。一樣,也能夠用 QTest::keyClick()、QTest::keyPress()、QTest::keyRelease()、QTest::mouseClick()、QTest::mouseDClick()、QTest::mouseMove()、QTest::mousePress() 和QTest::mouseRelease()函數來模擬相應的GUI事件。
最後,使用QCOMPARE()宏來檢驗行編輯框的文本是否與預期的一致。
像前面同樣,爲使測試程序可以單獨執行,須要加入下列代碼:QTEST_MAIN(TestGui)
QTEST_MAIN()宏將擴展成一個簡單的main()函數,該main()函數會執行全部的測試函數。
在本節中,將展現如何模擬GUI事件,以及如何存儲一系列GUI事件以及如何在組件上重複這些GUI事件。
將一系列GUI事件保存起來並重復觸發的方法與數據驅動測試程序的方法很相似。所要作的只是向測試類增長一個提供測試數據的函數:
class TestGui: public QObject { Q_OBJECT private slots: void testGui_data(); void testGui(); };
像前面同樣,爲測試函數提供數據的函數必須與該測試函數同名,並加上_data後綴。
void TestGui::testGui_data() { QTest::addColumn<QTestEventList>("events"); QTest::addColumn<QString>("expected"); QTestEventList list1; list1.addKeyClick('a'); QTest::newRow("char") << list1 << "a"; QTestEventList list2; list2.addKeyClick('a'); list2.addKeyClick(Qt::Key_Backspace); QTest::newRow("there and back again") << list2 << ""; }
首先,用QTest::addColumn()函數定義測試數據表的元素:一個GUI事件列表,以及在控件上應用該事件列表預期獲得的結果。注意第一個元素的類型是QTestEventList。
QTestEventList能夠保存未來要使用的GUI事件,並能夠在任意控件上重複觸發這些事件。
在目前的提供測試數據的函數中,建立了兩個QTestEventLists。第一個鏈表包括了一個敲擊「a「鍵事件,調用QTestEventList::addKeyClick()函數向鏈表中加入該事件。而後用QTest::newRow()函數給該行數據指定一個名字,並把事件隊列和指望結果輸入到測試數據表中。
第二個鏈表包括兩次鍵盤敲擊:一個「a「,而後是一個「backspace「。一樣用 QTestEventList::addKeyClick()函數將事件加入隊列,用QTest::newRow()將事件隊列和指望的結果加入測試數據表中,併爲該行指定一個名字。
void TestGui::testGui() { QFETCH(QTestEventList, events); QFETCH(QString, expected); QLineEdit lineEdit; events.simulate(&lineEdit); QCOMPARE(lineEdit.text(), expected); }
TestGui::testGui()函數會執行兩次,對在TestGui::testGui_data()函數中建立的每一行測試數據都執行一次。
首先,用QFETCH()宏從測試數據集中取出兩個元素。QFETCH()宏接收兩個參數:元素的數據類型和元素的名字。而後建立了一個QLineEdit,調用 QTestEventList::simulate()函數在控件上觸發事件隊列。
最後,用QCOMPARE()宏檢測行編輯框的內容是否與指望的一致。
像之前同樣,爲使測試程序可以單獨執行,須要加入下列代碼:QTEST_MAIN(TestGui)
QTEST_MAIN()宏將擴展成一個簡單的main()函數,該main()函數會執行全部的測試函數。
爲了編寫一個基準測試程序,須要使用QBENCHMARK宏來擴展測試函數。一個基準測試函數一般由初始化代碼和一個QBENCHMARK宏組成,QBENCHMARK宏包含了須要被測試的代碼。
測試函數會對QString::localeAwareCompare()函數進行基準測試。
void TestBenchmark::simple() { QString str1 = QLatin1String("This is a test string"); QString str2 = QLatin1String("This is a test string"); QCOMPARE(str1.localeAwareCompare(str2), 0); QBENCHMARK { str1.localeAwareCompare(str2); } }
初始化部分將在函數的開頭被完成,但時鐘並不在這點運行。內嵌在QBENCHMARK宏中的代碼將被估量,而且爲了得出精確的測量將會被重複數次。
當建立對多個數據輸入進行比較的基準測試時,數據函數是有用的。
void TestBenchmark::multiple_data() { QTest::addColumn<bool>("useLocaleCompare"); QTest::newRow("locale aware compare") << true; QTest::newRow("standard compare") << false; }
測試函數使用輸入數據決定什麼被基準測試。
void TestBenchmark::multiple() { QFETCH(bool, useLocaleCompare); QString str1 = QLatin1String("This is a test string"); QString str2 = QLatin1String("This is a test string"); int result; if (useLocaleCompare) { QBENCHMARK { result = str1.localeAwareCompare(str2); } } else { QBENCHMARK { result = (str1 == str2); } } }
「if(useLocaleCompare)」開關放在QBENCHMARK宏外部避免測量開銷。每一個基準測試函數能夠有一個在用的QBENCHMARK宏。
A、單元測試類中建議不要出現私有成員,尤爲是指針,同時不建議在測試函數中創建被測類的指針,而是直接創建被測類的對象,在測試結束後容易遺忘指針。若須要指針,在initTestCase函數中new,在cleanupTestCase函數中delete。B、若某個測試函數中出現了new,必定記着delete,且務必讓delete在第一個斷言前出現,由於斷言失敗函數就回馬上結束,並把當前函數標記爲測試失敗。若delete在第一個斷言以後,而第一個斷言失敗則不會執行以後的delete。C、若測試類必須有私有成員,必須注意一個測試類中的全部函數公用私有成員,不會在每一個測試以前刷新狀態。D、若被測類爲單例,欲對其內全部函數作單元測試,會出現測試第一個函數能夠保證測試環境爲初始狀態,後續測試會由於單例的緣由,致使測試時創建在以前操做後的環境下。欲解決此問題,須要刪除單例。