チェックを忘れてしまった場合は、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; }