Qt高級——QTestLib單元測試框架

Qt高級——QTestLib單元測試框架

1、QTestLib簡介

一、QTestLib簡介

QTestLib是Qt提供的一種針對基於Qt編寫的程序或庫的單元測試框架。QTestLib提供了單元測試框架的基本功能,並提供了針對GUI測試的擴展功能。express

二、QTestLib特性

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、易擴展:用戶自定義類型能夠容易地加入到測試數據和測試輸出中。後端

三、QTestLib API

全部公有的方法都在QTest命名空間中。另外,QSignalSpy類爲Qt的信號和槽提供了簡單的內省機制。安全

四、AutoTest插件

默認測試結果以純文本形式顯示在控制檯(應用程序輸出標籤),不夠直觀,可以使用AutoTest插件實現可視化效果。
經過Help->About Plugins->Utilities,選中AutoTest,重啓Qt Creator,而後在下方會多出TestResults的標籤,可直接在此標籤點擊上方的運行按鈕運行全部測試,同時在「Tools-Tests-Run All Tests」也可運行全部測試。
此插件能夠在運行單元測試後以紅、綠色表示明確標記處運行結果,而且以Case爲單位顯示,能夠展開看到具體每個測試用例的結果。
Qt高級——QTestLib單元測試框架框架

2、QTestLib單元測試原理

一、QTestLib單元測試原理簡介

原理:輸入測試數據表和結果數據表,與實際值比較。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測試

對於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)來啓動測試。

3、簡單測試程序

一、編寫測試程序

假設要測試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

Qt高級——QTestLib單元測試框架

三、QTestlib命令行參數

執行自動測試的語法形式:
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。

4、數據驅動測試程序

一、數據驅動測試簡介

目前爲止,採用硬編碼的方式將測試數據寫到測試函數中。若是增長更多的測試數據,那麼測試函數會變成:

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()函數會執行全部的測試函數。

5、GUI測試

QTestlib單元測試提供GUI操做函數,可對控件發送消息後檢測執行結果,好比QTest::keyClick(),QTest::mouseClick()等等

一、模擬GUI事件

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事件。
將一系列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()函數會執行全部的測試函數。

6、Benchmark測試

爲了編寫一個基準測試程序,須要使用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宏。

7、注意事項

A、單元測試類中建議不要出現私有成員,尤爲是指針,同時不建議在測試函數中創建被測類的指針,而是直接創建被測類的對象,在測試結束後容易遺忘指針。若須要指針,在initTestCase函數中new,在cleanupTestCase函數中delete。B、若某個測試函數中出現了new,必定記着delete,且務必讓delete在第一個斷言前出現,由於斷言失敗函數就回馬上結束,並把當前函數標記爲測試失敗。若delete在第一個斷言以後,而第一個斷言失敗則不會執行以後的delete。C、若測試類必須有私有成員,必須注意一個測試類中的全部函數公用私有成員,不會在每一個測試以前刷新狀態。D、若被測類爲單例,欲對其內全部函數作單元測試,會出現測試第一個函數能夠保證測試環境爲初始狀態,後續測試會由於單例的緣由,致使測試時創建在以前操做後的環境下。欲解決此問題,須要刪除單例。

相關文章
相關標籤/搜索