チェックを忘れてしまった場合は、Qt > Qt Project Settings メニューを選び、Qt Module タブを選び、 Test Library にチェックを入れて、【OK】を押す。
テスト用クラスは QObject 派生クラスとする
テスト用メソッドは private slots: スコープで宣言しておく
#include <QtCore/QCoreApplication>
#include <QTest>
#include "TestBuffer.h" // テスト用クラス
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
TestBuffer tc; // テスト用クラスインスタンス生成
return QTest::qExec(&tc, argc, argv); // 単体テスト実行
//return a.exec();
}
上記コードは、下記の様に、IDE が生成した main() 部分を削除し、QTEST_MAIN(テスト用クラス名) を追加してもよい
// 自動生成された部分は削除する // 代わりに以下を追加。(TestBuffer.cpp の方に記述してもよい) #include <QTest> #include "TestBuffer.h" QTEST_MAIN(TestBuffer)
テスト用クラスのインスタンス生成と qExec() の部分は、下記のように1行にすることも出来る。
return QTest::qExec(&TestBuffer(), argc, argv);
テスト用クラスをローカル変数ではなく、ヒープ上に new することも可能である。
return QTest::qExec(new TestBuffer(), argc, argv);
VS の出力は文字が小さいし、メッセージと混ざって見づらいという場合は、以下のようにすることも出来る。
bool rc = QTest::qExec(new TestBuffer(), argc, argv);
getchar();
return rc;
********* Start testing of TestBuffer ********* Config: Using QTest library 4.8.2, Qt 4.8.2 PASS : TestBuffer::initTestCase() PASS : TestBuffer::testBuffer() PASS : TestBuffer::cleanupTestCase() Totals: 3 passed, 0 failed, 0 skipped ********* Finished testing of TestBuffer *********
QCOMPARE( 1, 2 ) を実行した場合:
********* Start testing of TestBuffer *********
Config: Using QTest library 4.8.2, Qt 4.8.2
PASS : TestBuffer::initTestCase()
FAIL! : TestBuffer::testBuffer() Compared values are not the same
Actual (1): 1
Expected (2): 2
TestBuffer.cpp(94) : failure location
RESULT : TestBuffer::benchmark():
103 msecs per iteration (total: 103, iterations: 1)
PASS : TestBuffer::benchmark()
PASS : TestBuffer::cleanupTestCase()
Totals: 3 passed, 1 failed, 0 skipped
********* Finished testing of TestBuffer *********
F4 またはエラー表示行をダブルクリックすることで、テストに失敗した行にタグジャンプすることが出来る。← 便利だよ
これに対して、テストされるクラス・関数がテスト用クラスとは独立に存在する。
配布するアプリケーションが単体テストコードを含まないようにしたい場合は、テストを別プロジェクトとして作成する。
被テストクラス・関数は、スタティックライブラリ・DLL またはソースコードを直接インクルードするようにする。
VS Qt-Addin でスタティックライブラリプロジェクトを追加し、依存関係を設定しても、lib が自動的にリンクされない。
プロジェクトのプロパティ > 構成プロパティ > 入力 > 追加の依存ファイル に lib を手動で追加する必要がある。
class TestBuffer : public QObject
{
Q_OBJECT
public:
TestBuffer(QObject *parent = 0);
~TestBuffer();
private slots:
void testBuffer(); // isEmpty(), clear(), size(), push_back() などの基本テスト
void benchmark();
};
********* Start testing of TestBuffer ********* Config: Using QTest library 4.8.2, Qt 4.8.2 PASS : TestBuffer::initTestCase() FAIL! : TestBuffer::testBuffer() Compared values are not the same Actual (0): 0 Expected (1): 1 TestBuffer.cpp(142) : failure location PASS : TestBuffer::testBuffer2() .....
独自に定義した構造体・クラスの値を表示させたい場合は template<typename T> char *toString(T val) を下記のように特殊化する。
namespace QTest
{
template<>
char *toString(const TextPos &pos)
{
QByteArray ba = "TextPos(";
ba += QByteArray::number(pos.m_line) + ", " + QByteArray::number(pos.m_offset);
ba += ")";
return qstrdup(ba.data());
}
}
{ } 内を複数回繰り返し、ベンチマークを取る。QBENCHMARK_ONCE を使用した場合は、{ } 内を1回のみ実行する。
ひとつのテストメソッド内に複数の QBENCHMARK を記述することは意味がないようだ(結果が最後のもので上書きされてしまう)。
class TestBuffer : public QObject
{
.....
private slots:
void testToInt_data();
void testToInt();
}
// テスト用データセットの定義と作成
void TestBuffer::testToInt_data()
{
QTest::addColumn<QString>("aString"); // データセット定義にカラム追加
QTest::addColumn<int>("expected");
QTest::newRow("positive value") << "42" << 42; // データセットを作成
QTest::newRow("negative value") << "-42" << -42;
QTest::newRow("zero") << "0" << 0;
}
void TestBuffer::testToInt()
{
QFETCH(QString, aString); // データセットを取得
QFETCH(int, expected);
QCOMPARE(aString.toInt(), expected); // テスト
}
上記は、testToInt(const QString &, int) という関数を用意し、testToInt() を以下のように記述しても良さそうに思える。
void testToInt(const QString &str, int expected)
{
QCOMPARE(str.toInt(), expected);
}
void TestBuffer::testToInt()
{
testToInt("42", 42);
testToInt("-42", -42);
testToInt("0", 0);
}
class TestBuffer : public QObject
{
.....
private slots:
void testReadBuffer(); // マルチスレッドによる遅延リードのテスト
private:
QThread m_thread;
};
void TestBuffer::testReadBuffer()
{
m_thread.start();
....
while( m_thread.isRunning() ) { // スレッドの終了を待つ
QCoreApplication::processEvents(); // イベントループを回し、スレッド間シグナル/スロット処理
m_thread->wait(1); // メインスレッドのCPU負荷を下げるためにコール
}
}
上記コードは、被テストコードが m_thread のコンテキストで動作し、処理が終了した場合は、m_thread を終了させるものと仮定している。
m_thread を終了させないのであれば、ループ中に被テストオブジェクトの状態をチェックし、処理が終了すればループを抜けるようにすると良い。
メッセージループもちゃんと回るので、スレッド間シグナル/スロット通信も大丈夫
void TestBuffer::testLasyRead()
{
TextDocument doc;
doc.read("/data/bigFile.txt"); // 巨大ファイルを遅延リード
while( doc.isLasyReading() ) // 遅延リード中
QTest::qWait(1);
QCOMPARE( ... ); // 遅延リードが正しく行われたかテスト
.....
}
コンソールアプリケーションの場合は、使用モジュールにGUIを追加する。
main() 関数を以下のように修正する
#include <QApplication> // for GUI Application
#include <QTest> // テスト用クラス
#include "Test.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv); // GUI Application
Test test;
QTest::qExec(&test);
return app.exec();
}
QWidget 等が表示されるまで待つ場合は bool QTest::qWaitForWindowShown ( QWidget * ) を使用する
操作をエミュレートしたい場合は QTest::mouseClick ( QWidget * widget, ... ) 等を使用する
イベントループを回したい場合は、QTest::qWait(int) を利用する
#include <QSound>
#include <QDir>
#include <time.h>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
TestBuffer tc;
bool rc = QTest::qExec(&tc, argc, argv);
if( QSound::isAvailable() ) {
qsrand((uint)time(0));
QDir soundDir(!rc ? "/sound/succeeded" : "/sound/failed"); // テスト成功 or 失敗
QStringList filter; filter << "*.wav";
QStringList lst = soundDir.entryList(filter);
if( !lst.isEmpty() ) {
int r = qrand() % lst.size(); // 再生ファイルをランダムに選ぶ
QString wavFile = soundDir.absolutePath() + "/" + lst[r];
QSound s(wavFile);
s.play();
getchar(); // 音声が再生されるのを待ってあげる
}
}
return rc;
}