Qtテストの概要
Qt Test は Qt ベースのアプリケーションやライブラリを単体テストするためのフレームワークです。Qt Test は、グラフィカルユーザーインターフェースのテスト用の拡張機能だけでなく、ユニットテストフレームワークで一般的に見られる全ての機能を提供します。
Qt Test は、Qt ベースのアプリケーションやライブラリのユニットテストを簡単に書くことができるように設計されています:
機能 | 詳細 |
---|---|
軽量 | Qt Test は約 6000 行のコードと 60 個のエクスポートされたシンボルで構成されています。 |
自己完結型 | Qt Test は、gui 以外のテストのために Qt Core モジュールからわずかなシンボルしか必要としません。 |
迅速なテスト | Qt Test は特別なテストランナーを必要とせず、テストのための特別な登録も必要ありません。 |
データ駆動型テスト | 異なるテストデータで複数回テストを実行できます。 |
基本的な GUI テスト | Qt Test はマウスとキーボードのシミュレーション機能を提供します。 |
ベンチマーク | Qt Test はベンチマークをサポートし、いくつかの測定バックエンドを提供します。 |
IDE フレンドリー | Qt Test は Qt Creator、Visual Studio、KDevelop で解釈可能なメッセージを出力します。 |
スレッドセーフ | エラー報告はスレッドセーフでアトミックです。 |
型安全性 | テンプレートを多用することで、暗黙の型キャストによるエラーを防ぎます。 |
簡単に拡張可能 | カスタム型をテストデータとテスト出力に簡単に追加できます。 |
Qt Creator ウィザードを使用して Qt テストを含むプロジェクトを作成し、Qt Creator から直接ビルドして実行できます。詳細については、Qt Creator を参照してください:テストのビルドと実行 を参照してください。
テストの作成
テストを作成するには、QObject をサブクラス化し、1 つ以上のプライベート・スロットを追加します。各プライベートスロットは、テスト内のテスト関数です。QTest::qExec() を使うと、テストオブジェクト内のすべてのテスト関数を実行することができます。
さらに、テスト関数として扱われない以下のプライベートスロットを定義することができます。これらのスロットが存在すると、テストフレームワークによって実行され、 テスト全体あるいは現在のテスト関数を初期化したりクリーンアップしたりするために使われます。
initTestCase()
は、最初のテスト関数が実行される前に呼び出されます。initTestCase_data()
グローバルテストデータテーブルを作成するために呼び出されます。cleanupTestCase()
最後のテスト関数が実行された後に呼び出されます。init()
各試験関数が実行される前にコールされます。cleanup()
各テスト関数の実行後に呼び出されます。
テストの準備にはinitTestCase()
を使用する。すべてのテストは、繰り返し実行できるように、システムを使用可能な状態にする必要があります。クリーンアップ処理はcleanupTestCase()
で処理する。
テスト関数の準備にはinit()
を使用する。すべてのテスト関数は、繰り返し実行できるように、システムを使用可能な状態にしておく必要があります。クリーンアップ操作はcleanup()
で処理する必要があり、テスト関数が失敗して早期に終了しても実行されます。
別の方法として、RAII(リソース取得は初期化)を使って、デストラクタでクリーンアップ操作を呼び出すこともできます。
initTestCase()
が失敗した場合、テスト関数は実行されません。init()
が失敗した場合、次のテスト関数は実行されず、テストは次のテスト関数に進みます。
例
class MyFirstTest: public QObject { Q_OBJECT private: bool myCondition() { return true; } private slots: void initTestCase() { qDebug("Called before everything else."); } void myFirstTest() { QVERIFY(true); // check that a condition is satisfied QCOMPARE(1, 1); // compare two values } void mySecondTest() { QVERIFY(myCondition()); QVERIFY(1 != 2); } void cleanupTestCase() { qDebug("Called after myFirstTest and mySecondTest."); } };
最後に、テストクラスに static publicvoid initMain()
メソッドがある場合、QApplication オブジェクトがインスタンス化される前に、QTEST_MAIN マクロによって呼び出されます。これは5.14で追加されました。
より多くの例については、Qt Test Tutorialを参照してください。
テスト関数のタイムアウトを増やす
QtTest 無限ループや類似のバグを検出するために、各テストの実行時間を制限します。デフォルトでは、テスト関数の呼び出しは 5 分後に中断されます。データ駆動型テストの場合、これは個別の data タグを持つ各呼び出しに適用されます。このタイムアウトは、 環境変数に、1回の呼び出しで許容できる最大ミリ秒数を設定することで設定できます。テストが設定されたタイムアウトより長くかかった場合、テストは中断され、 が呼び出される。その結果、テストはクラッシュしたかのようにデフォルトで中止される。QTEST_FUNCTION_TIMEOUT
qFatal()
LinuxまたはmacOSのコマンドラインからQTEST_FUNCTION_TIMEOUT
を設定するには、次のように入力する:
QTEST_FUNCTION_TIMEOUT=900000 export QTEST_FUNCTION_TIMEOUT
Windowsの場合:
SET QTEST_FUNCTION_TIMEOUT=900000
この環境でテストを実行する。
あるいは、テスト・クラスのinitMain()メソッドから呼び出すなどして、テスト・コード自体にプログラム的に環境変数を設定することもできます:
qputenv("QTEST_FUNCTION_TIMEOUT", "900000");
タイムアウトの適切な値を計算するには、テストに通常どれくらいの時間がかかるかを確認し、 問題の兆候なしにどれくらいの時間がかかるかを判断します。その長い時間をミリ秒に変換して、タイムアウトの値を求めます。例えば、遅いマシンなどで、数分かかるテストに20分かかると判断した場合、20 * 60 * 1000 = 1200000
を掛け、環境変数に900000
の代わりに1200000
を設定します。
テストの構築
通常、プロダクションコードの1クラスをテストするテストクラスを含む実行ファイルをビルドすることができます。しかし、通常は1つのコマンドを実行することで、プロジェクト内の複数のクラスをテストしたいと思うでしょう。
単体テストの書き方を参照ください。
CMake と CTest によるビルド
Build with CMake と CTestを使ってテストを作成することができます。CTestでは、テスト名とマッチする正規表現に基づいてテストを含めたり除外したりすることができます。さらにLABELS
プロパティをテストに適用すると、CTest はそれらのラベルに基づいてテストを含めたり除外したりすることができます。test
target がコマンドラインで呼び出されると、ラベル付けされたすべてのターゲットが実行されます。
注: Androidでは、接続されているデバイスまたはエミュレータが1つの場合、テストはそのデバイス上で実行されます。複数のデバイスが接続されている場合は、環境変数ANDROID_DEVICE_SERIAL
にテストを実行したいデバイスのADB シリアル番号を設定してください。
CMakeには他にもいくつかの利点がある。例えば、テスト実行の結果をCDashを使ってウェブサーバーに公開することができます。
CTest は、さまざまなユニットテストフレームワークに対応し、QTest ですぐに動作します。
以下は、プロジェクト名と使用言語(ここではmytestと C++)、テストのビルドに必要な Qt モジュール(Qt5Test)、テストに含まれるファイル(tst_mytest.cpp)を指定した CMakeLists.txt ファイルの例です。
project(mytest LANGUAGES CXX) find_package(Qt6 REQUIRED COMPONENTS Test) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) enable_testing(true) qt_add_executable(mytest tst_mytest.cpp) add_test(NAME mytest COMMAND mytest) target_link_libraries(mytest PRIVATE Qt::Test)
オプションの詳細については、CMake でビルドするを参照してください。
qmakeでビルドする
ビルドツールとしてqmake
を使っている場合は、プロジェクトファイルに以下を追加するだけです:
QT += testlib
make check
を使ってテストを実行したい場合は、次の行を追加する:
CONFIG += testcase
テストがターゲットにインストールされないようにするには、次の行を追加する:
CONFIG += no_testcase_installs
make check
の詳細については、qmakeのマニュアルを参照してください。
他のツールでのビルド
他のビルドツールを使用している場合は、Qt Test のヘッダーファイルの場所をインクルードパス(通常、Qt のインストールディレクトリの下にあるinclude/QtTest
)に追加してください。Qt のリリースビルドを使用している場合は、テストをQtTest
ライブラリにリンクしてください。デバッグビルドの場合は、QtTest_debug
を使用してください。
Qt Test コマンドライン引数
構文
自動テストを実行するための構文は、次のようなシンプルな形式です:
testname [options] [testfunctions[:testdata]]...
testname
に実行ファイル名を代入する。testfunctions
には、実行するテスト関数の名前を含めることができる。testfunctions
が渡されなければ、すべてのテストが実行される。testdata
に項目名を追加すると、テスト関数はそのテストデータでのみ実行されます。
例えば
/myTestDirectory$ testQString toUpper
toUpper
というテスト関数を、利用可能なすべてのテストデータで実行します。
/myTestDirectory$ testQString toUpper toInt:zero
利用可能なすべてのテスト・データを使用してtoUpper
テスト関数を実行し、zero
というテスト・データ行を使用してtoInt
テスト関数を実行します(指定されたテスト・データが存在しない場合、関連するテストは失敗し、利用可能なデータ・タグが報告されます)。
/myTestDirectory$ testMyWidget -vs -eventdelay 500
testMyWidget
関数テストを実行し、すべての信号発光を出力し、マウス/キーボード イベントをシミュレートするたびに 500 ミリ秒待機する。
オプション
ロギングオプション
以下のコマンドラインオプションは、テスト結果の報告方法を決定します:
-o
filename,format
指定されたフォーマット( 、 、 、 、 、 、 のいずれか)で、指定されたファイルに出力を書き込みます。標準出力にログ出力するには、特別なファイル名 (ハイフン)を使用する。txt
csv
junitxml
xml
lightxml
teamcity
tap
-
-o
filename
指定されたファイルに出力を書き込みます。-txt
結果をプレーン・テキストで出力する。-csv
結果をスプレッドシートへのインポートに適したカンマ区切り値(CSV)として出力する。このモードでは、通常の合否メッセージが抑制されるため、ベンチマークにのみ適しています。-junitxml
結果をJUnit XMLドキュメントとして出力します。-xml
結果を XML ドキュメントとして出力します。-lightxml
結果を XML タグのストリームとして出力します。-teamcity
結果をTeamCity形式で出力します。-tap
Test Anything Protocol(TAP) 形式で結果を出力します。
-o
オプションの最初のバージョンは、複数の形式でテスト結果を記録するために繰り返すことができま すが、このオプションのインスタンスを 1 つ以上使用して、テスト結果を標準出力に記録することはできま せん。
-o
オプションの最初のバージョンを使用する場合、-o
オプションの 2 番目のバージョンも、-txt
、-xml
、-lightxml
、-teamcity
、-junitxml
、-tap
オプションも使用しないこと。
-o
オプションのいずれのバージョンも使用しない場合、テスト結果は標準出力に記録される。format オプションを使用しない場合、テスト結果はプレーンテキストで記録される。
テストログ詳細オプション
以下のコマンドラインオプションは、テストログで報告される詳細情報を制御します:
-silent
サイレント出力:致命的なエラー、テストの失敗、および最小限のステータスメッセージのみを表示します。-v1
冗長出力:各テスト関数が入力されたときに表示します。(このオプションはプレーンテキスト出力にのみ影響します)。-v2
各QCOMPARE() とQVERIFY() を表示します。(このオプションはすべての出力形式に影響し、プレーン・テキスト出力では-v1
を意味する)。-vs
出力されるすべてのシグナルと、それらのシグナルに起因するスロットの起動を表示します。(このオプションはすべての出力フォーマットに影響する)。
テストオプション
以下のコマンドラインオプションは、テストの実行方法に影響します:
-functions
テストで使用可能なすべてのテスト関数を出力し、終了します。-datatags
テストで使用可能なすべてのデータタグを出力します。グローバル・データ・タグの前には ' __global__ ' が付きます。-eventdelay
ms
キーボードまたはマウス・シミュレーションに遅延が指定されていない場合 ( ()、 () など)、このパラメータの値 (ミリ秒単位) が代入されます。QTest::keyClick QTest::mouseClick-keydelay
ms
-eventdelayと同様だが、キーボード・シミュレーションにのみ影響し、マウス・シミュレーションには影響しない。-mousedelay
ms
-eventdelay と同様だが、マウス・シミュレー ションにのみ影響し、キーボード・シミュレーション には影響しない。-maxwarnings
number
出力する警告の最大数を設定します。0 で無制限、デフォルトは 2000 です。-nocrashhandler
Unixプラットフォームではクラッシュハンドラを無効にします。Windowsでは、デフォルトではオフになっているWindows Error Reportingダイアログを再度有効にします。これはクラッシュのデバッグに便利です。-repeat
n
testsuite を n 回、あるいはテストが失敗するまで実行する。不安定なテストを見つけるのに便利である。否定された場合、テストは永遠に繰り返される。これは開発者向けのツールであり、プレーンテキストのロガーでのみサポートされています。-skipblacklisted
ブラックリストに載ったテストをスキップする。このオプションは、ブラックリストに登録されたテストがカバレッジの統計量を増加させないようにすることで、 テストカバレッジをより正確に計測できるようにするためのものです。テストカバレッジを測定しない場合は、ブラックリストに登録されたテストを実行し、新しいクラッシュやブラックリスト登録の原因となった問題が解決されたなど、テスト結果の変化を明らかにすることを推奨します。-platform
name
このコマンドライン引数はすべての Qt アプリケーションに適用されますが、自動テストのコンテキストでは特に有用です。offscreen" platform plugin (-platform offscreen) を使うことで、 や を使うテストを、画面に何も表示せずに実行することができます。現在のところ、offscreen platform プラグインは X11 でのみ完全にサポートされています。QWidget QWindow
ベンチマークオプション
以下のコマンドラインオプションはベンチマークテストを制御します:
-callgrind
Callgrind を使用してベンチマークを計時します(Linux のみ)。-tickcounter
CPUティックカウンタを使用してベンチマークを計時する。-eventcounter
ベンチマーク中に受信したイベントをカウントする。-minimumvalue
n
許容可能な測定値の最小値を設定します。-minimumtotal
n
テスト関数の繰り返し実行の最小許容合計を設定します。-iterations
n
累積反復回数を設定します。-median
n
中央値の反復回数を設定します。-vb
ベンチマーク情報を冗長に出力します。
その他のオプション
-help
可能なコマンドライン引数を出力し、便利なヘルプを表示します。
Qt テスト環境変数
自動テストの実行に影響を与えるために、特定の環境変数を設定することができます:
QTEST_DISABLE_CORE_DUMP
この変数をゼロ以外の値に設定すると、コアダンプファイルの生成が無効になります。QTEST_DISABLE_STACK_DUMP
この変数をゼロ以外の値に設定すると、自動テストがタイムアウトまたはクラッシュした場合に、Qt Test がスタックトレースを表示しないようにします。QTEST_FATAL_FAIL
この変数を 0 以外の値に設定すると、自動テストに失敗した場合、自動テスト全体が直ちに中止されます。これは、デバッガでテストを起動して、テストの不安定な失敗や断続的な失敗をデバッグするときなどに便利です。この変数のサポートは Qt 6.1 で追加されました。QTEST_THROW_ON_FAIL
(6.8 以降) この変数を 0 以外の値に設定すると、 ()/ () などが失敗したときにスローされます(直後の関数スコープから戻るだけではありません)。
QCOMPAREQVERIFYQTEST_THROW_ON_SKIP
(6.8以降) ()に影響を与える以外は、 と同じ。
QSKIPQTEST_THROW_ON_FAIL
ベンチマークの作成
ベンチマークを作成するには、テストの作成手順に従って、QBENCHMARK マクロまたはQTest::setBenchmarkResult() をベンチマーク対象のテスト関数に追加します。以下のコードでは、マクロを使用しています:
class MyFirstBenchmark: public QObject { Q_OBJECT private slots: void myFirstBenchmark() { QString string1; QString string2; QBENCHMARK { string1.localeAwareCompare(string2); } } };
パフォーマンスを測定するテスト関数には、QBENCHMARK
マクロを 1 つ、またはsetBenchmarkResult()
を 1 回呼び出す必要があります。複数回呼び出すのは意味がありません。なぜなら、テスト関数ごと、あるいはデータ駆動型セットアップではデータタグごとに、1 つのパフォーマンス結果しか報告できないからです。
QBENCHMARK
マクロのボディを形成する(あるいは影響を与える)テストコード、あるいはsetBenchmarkResult()
に渡される値を計算するテストコードの変更は避けてください。連続した性能結果の相違は、理想的には、テストしている製品の変更によってのみ発生するはずです。テストコードを変更すると、性能の変化について誤解を招くような報告がなされる可能性があります。テストコードを変更する必要がある場合は、コミットメッセージにその旨を明記してください。
性能テスト関数では、QBENCHMARK
またはsetBenchmarkResult()
の後に、QCOMPARE()、QVERIFY() などの検証ステップを記述します。そして、意図したコードパスとは別のコードパスが測定された場合、性能結果が無効であるとフラグを立てることができます。性能解析ツールはこの情報を使って、無効な結果をフィルタリングすることができます。例えば、予期しないエラー状態が発生すると、通常、プログラムは正常なプログラム実行から早々にベイルアウトするため、性能の劇的な向上を偽って示すことになります。
測定バックエンドの選択
QBENCHMARK マクロ内のコードは測定され、正確な測定を行うために数回繰り返されることもあります。これは、選択した測定バックエンドに依存します。いくつかのバックエンドが利用可能です。コマンドラインで選択できます:
名前 | コマンドライン引数 | 可用性 |
---|---|---|
壁時間 | (デフォルト) | すべてのプラットフォーム |
CPUティックカウンター | -ティックカウンター | Windows、macOS、Linux、多くのUNIX系システム。 |
イベント・カウンター | -イベント・カウンター | すべてのプラットフォーム |
コールグラインド | -コールグラインド | Linux(インストールされている場合) |
Linux Perf | -perf | Linux |
要するに、walltimeはいつでも利用できるが、有用な結果を得るためには多くの繰り返しが必要である。ティックカウンターは通常利用可能で、より少ない繰り返しで結果を得ることができるが、CPU周波数のスケーリングの問題の影響を受けやすい。Valgrindは正確な結果を提供するが、I/O待ちを考慮しないため、限られたプラットフォームでしか利用できない。イベントカウントはすべてのプラットフォームで利用可能で、対応するターゲットに送信される前にイベントループによって受信されたイベントの数を提供します(これには Qt 以外のイベントが含まれる場合があります)。
Linux パフォーマンス・モニタ・ソリューションは Linux でのみ利用可能で、多くの異なるカウンターを提供します。これらのカウンターは、追加オプション-perfcounter countername
を渡すことで選択できます(-perfcounter cache-misses
、-perfcounter branch-misses
、-perfcounter l1d-load-misses
など)。デフォルトのカウンターはcpu-cycles
です。カウンターの完全なリストは、ベンチマーク実行ファイルにオプション-perfcounterlist
を付けて実行することで取得できます。
- パフォーマンス・カウンターを使用するには、非特権アプリケーションへのアクセスを有効にする必要がある場合があります。
- 高分解能タイマーをサポートしていないデバイスのデフォルトは1ミリ秒単位である。
ベンチマークの例については Qt Test Tutorial のWriting a Benchmarkを参照してください。
グローバルテストデータの使用
initTestCase_data()
を定義して、グローバルテストデータテーブルをセットアップすることができます。各テストは、グローバルテストデータテーブルの各行に対して 1 回ずつ実行されます。テスト関数自体がデータ駆動型である場合、各ローカルデータ行に対して、各グローバルデータ行に対して実行されます。したがって、グローバル・データ・テーブルにg
行があり、テスト自身のデータ・テーブルにd
行がある場合、このテストの実行回数はg
回d
となります。
グローバル・データは、QFETCH_GLOBAL ()マクロを使用してテーブルから取得されます。
以下は、グローバルテストデータの典型的な使用例です:
- QSql テストで使用可能なデータベースバックエンドを選択し、すべてのデータベースに対してすべてのテストを実行する。
- SSL(HTTP 対 HTTPS)およびプロキシの有無にかかわらず、すべてのネットワークテストを実行する。
- タイマーを高精度クロックでテストするか、粗いクロックでテストするか。
- パーサがQByteArray から読み込むか、QIODevice から読み込むかを選択する。
例えば、roundTripInt_data()
が提供する各数字を、initTestCase_data()
が提供する各ロケールでテストする:
void TestQLocale::roundTripInt() { QFETCH_GLOBAL(QLocale, locale); QFETCH(int, number); bool ok; QCOMPARE(locale.toInt(locale.toString(number), &ok), number); QVERIFY(ok); }
テストのコマンドラインで関数名(test-class-name という接頭辞はつけない)を渡すと、その関数のテストだけを実行することができます。テストクラスがグローバルデータを持っている場合、あるいは関数がデータ駆動型である場合は、コロンの後に data タグを追加して、そのタグのデータセットだけを関数に対して実行させることができます。グローバルなタグとテスト関数固有のタグの両方を指定するには、グローバルなデータタグを先頭に置いて、それらの間にコロンを挟みます。例
./testqlocale roundTripInt:zero
はinitTestCase_data()
で指定されたロケールのそれぞれで、上記のroundTripInt()
テスト(そのTestQLocale
クラスが実行可能なtestqlocale
にコンパイルされていると仮定)のzero
テストケースを実行します。
./testqlocale roundTripInt:C
はroundTripInt()
の3つのテストケースすべてをCロケールでのみ実行し
./testqlocale roundTripInt:C:zero
はzero
のテストケースのみを C ロケールで実行します。
どのテストを実行するかをこのように細かく制御することで、問題のデバッグがかなり簡単になります。
ここに含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。