このドキュメントでは、以下のことについて説明します。
低レベル テストを記述する
このページでは、主に Unreal Engine (UE) のコンテキストで Catch2 を使用して 低レベル テスト (LLT) を記述するための構造、ガイドライン、およびベスト プラクティスについて説明します。Catch2 に固有の情報については、Catch2 の GitHub リポジトリ を参照してください。テストの記述に関するガイドの全文については、Catch2 のリファレンス を参照してください。
テストのための Unreal C++ コーディング規約を使用するようにしてください。詳細については、「Unreal のコーディング規約」を参照してください
開始する前に
次の項目について、低レベル テストの種類 のドキュメントを確認してください。
- 明示的 テストと 暗黙的 テストのどちらが具体的なユース ケースに適しているかを判断する。
- 使用するディレクトリが、ドキュメントの手順に記載されているディレクトリ構造と一致するようにする。
テスト .cpp ファイルの記述の開始準備が完了したら、次の手順に従います。
.cppテスト ファイルにわかりやすい名前を付けます (<NAME_OF_FILE>Test.cppなど)。- 詳細については、このドキュメントの 命名規則 のセクションを参照してください。
- 暗黙的テスト を記述する場合は、
.cppファイルを#if WITH_LOW_LEVEL_TESTS-#endifディレクティブで囲みます。- 以下の「BDD テスト例」を参照してください。
- 必要なすべてのヘッダ ファイルをインクルードします。
- 少なくとも、任意指定の
#include "CoreMinimal.h"と#include "TestHarness.h"を指定する必要があります。 - 最低限のヘッダをインクルードしたら、テストを完了するために必要なヘッダのみをインクルードします。
- 少なくとも、任意指定の
- これで、TDD (テスト駆動開発) パラダイムまたは BDD (ビヘイビア駆動開発) パラダイムのいずれかを使用してテストを記述できます。
ビヘイビア駆動開発テスト例
SCENARIO を使用した BDD スタイルのフォーカス テストファイルには複数のシナリオを含めることができます。コアとなる構造のシナリオは、次のとおりです。
GIVEN:設定条件WHEN:アクションが実行されるTHEN:予測される結果の内容。
GIVEN および WHEN セクションには、内部状態に対する追加の初期化および変更を含めることができます。THEN セクションは、チェックを実行して目的の結果が真であるかどうかを判定する必要があります。REQUIRE が単一のテストの実行を停止する一方で、CHECK のエラーでは実行が継続されます。
以下のコード例は、UE の低レベル テスト フレームワークにおける、BDD スタイル テストの一般的なアウトラインを示しています。
#if WITH_LOW_LEVEL_TESTS // 暗黙的テストにのみ必要です!
#include "CoreMinimal.h"
#include "TestHarness.h"
// その他のインクルードは CoreMinimal.h および TestHarness.h 以降に配置し、スコープごとにグループ化 (std ライブラリ、UE モジュール、サードパーティなど) する必要があります。
/* BDD スタイル テスト*/
SCENARIO("Summary of test scenario", "[functional][feature][slow]") // タグはブラケット [] 内に配置します
{
GIVEN("Setup phase")
{
// 変数を初期化し、テストの前提条件などを設定します
[...]
WHEN("I perform an action")
{
// 内部状態を変更します
[...]
THEN("Check for expectations")
{
REQUIRE(Condition_1);
REQUIRE(Condition_2);
// 前のものが失敗する必要がある場合は到達しません
CHECK(Condition_3);
}
}
}
}
#endif //WITH_LOW_LEVEL_TESTS
テスト駆動開発テスト例
TEST_CASE を使用した TDD スタイルのフォーカス テスト各 TEST_CASE には、テスト対象のケースを設定するためのコードをインクルードすることができます。続いて、実際のテスト ケースを複数の SECTION ブロックに分割できます。各 SECTION ブロックは、チェックを行って目的の結果が真であるかどうかを判定します。SECTION ブロックですべてのチェックが実行されると、TEST_CASE に必要なティアダウン コードをインクルードすることができます。
以下のコード例は、UE の低レベル テスト フレームワークにおける、TDD スタイル テストの一般的なアウトラインを示しています。
#if WITH_LOW_LEVEL_TESTS // 暗黙的テストにのみ必要です!
#include "CoreMinimal.h"
#include "TestHarness.h"
// その他のインクルードは CoreMinimal.h および TestHarness.h 以降に配置し、スコープごとにグループ化 (std ライブラリ、UE モジュール、サードパーティなど) する必要があります。
/* 典型的な TDD スタイル テスト*/
TEST_CASE("Summary of test case", "[unit][feature][fast]")
{
// このテスト ケースのための設定コード
[...]
// テストはセクションに分割できます
SECTION("Test #1")
{
REQUIRE(Condition_1);
}
...
SECTION("Test #n")
{
REQUIRE(Condition_n);
}
// このテスト ケース用のティアダウン コード
[...]
}
#endif //WITH_LOW_LEVEL_TESTS
#if WITH_LOW_LEVEL_TESTS の条件付きチェックは、暗黙的テストのコンパイル用に予約されています。明示的テストではこのチェックは必須ではありませんが、ターゲット クラスで bWithLowLevelTestsOverride = true を設定することで、依存関係から暗黙的テストをインクルードすることができます。このフラグによって、WITH_LOW_LEVEL_TESTS が強制的に 1 になります。
テスト ケースでは、2 つのコロン :: の表記を使用して、テスト内に階層を作成することもできます。
TEST_CASE("Organic::Tree::Apple::Throw an apple away from the tree") { ... }
このセクションに含まれる例は、Unreal Engine の低レベル テストや Catch2 のあらゆる機能に対して包括的なものではありません。ジェネレータ、ベンチマーク、浮動小数点数の近似ヘルパー、マッチャー、変数キャプチャ、ロギングなどについては、すべて外部の Catch2 のドキュメント に詳細が記載されています。
その他の例
エンジンのディレクトリである Engine/Source/Runtime/Core/Tests には、複数の UE 固有の低レベル テストの例があります。低レベル テストのタイプ の例を継続する場合は、Engine/Source/Programs/LowLevelTests/Tests にある「ArchiveReplaceObjectRefTests.cpp」ファイル内の TDD スタイル テストの例を参照することができます。
追加の低レベルテスト機能
テストのグループおよびライフサイクル イベント
テストのグループ化は、UE の拡張 Catch2 ライブラリの機能です。デフォルトでは、すべてのテスト ケースが空の名前のグループ名の下にグループ化されます。テストをグループに追加するには、最初のパラメータとしてその名前を指定し、GROUP_* バージョンのテスト ケースを使用します。
GROUP_TEST_CASE("Apples", "Recipes::Baked::Pie::Cut slice", "[baking][recipe]")
GROUP_TEST_CASE_METHOD("Oranges", OJFixture, "Recipes::Raw::Juice Oranges", "[raw][recipe]")
GROUP_METHOD_AS_TEST_CASE("Pears", PoachInWine, "Recipes::Boiled::Poached Pears", "[desert][recipe]")
GROUP_REGISTER_TEST_CASE("Runtime", UnregisteredStaticMethod, "Dynamic", "[dynamic]")
各グループには、わかりやすい 6 つのライフサイクル イベントがあります。次のコード セクションは、これらのイベントを示しています。
GROUP_BEFORE_ALL("Apples") {
std::cout << "Called once before all tests in group Apples, use for one-time setup.\n";
}
GROUP_AFTER_ALL("Oranges") {
std::cout << "Called once after all tests in group Oranges, use for one-time cleanup.\n";
}
GROUP_BEFORE_EACH("Apples") {
std::cout << "Called once before each test in group Apples, use for repeatable setup.\n";
}
GROUP_AFTER_EACH("Oranges") {
std::cout << "Called once after each tests in group Oranges, use for repeatable cleanup.\n";
}
GROUP_BEFORE_GLOBAL() {
std::cout << "Called once before all groups, use for global setup.\n";
}
GROUP_AFTER_GLOBAL() {
std::cout << "Called once after all groups, use for global cleanup.\n";
}
GROUP_TEST_CASE("Apples", "Test #1") {
std::cout << "Apple #1\n";
}
GROUP_TEST_CASE("Apples", "Test #2") {
std::cout << "Apple #2\n";
}
GROUP_TEST_CASE("Oranges", "Test #1") {
std::cout << "Orange #1\n";
}
GROUP_TEST_CASE("Oranges", "Test #2") {
std::cout << "Orange #2\n";
}
これにより、次の出力が生成されます。
Called once before all groups, use for global setup.
Called once before all tests in group Apples, use for one-time setup.
Called once before each test in group Apples, use for repeatable setup.
Apple #1.
Called once before each test in group Apples, use for repeatable setup.
Apple #2.
Orange #1.
Called once after each tests in group Oranges, use for repeatable cleanup.
Orange #2.
Called once after each tests in group Oranges, use for repeatable cleanup.
Called once after all tests in group Oranges, use for one-time cleanup.
Called once after all groups, use for global cleanup.
テストを記述および整理するためのガイドライン
ファイルおよびフォルダの命名規則
- テスト ファイルにわかりやすい名前を付けます。
SourceFile.cppがテスト対象のソース ファイルである場合、テスト ファイルに「SourceFileTest.cpp」または「SourceFileTests.cpp」という名前を付けます。
- テスト対象のモジュールのフォルダ階層をミラーリングします。
Alpha/Omega/SourceFile.cppは、暗黙的テストの場合はTests/Alpha/Omega/SourceFileTests.cppに、明示的テストの場合はAlpha/Omega/SourceFileTests.cppにマッピングされます。
- ユニット テストでない場合、テスト ファイル名のユニット テストから派生した用語を使用することは避けてください。このように定義すると、制限があり、誤った名前を付けると混乱が発生します。
ユニット テストでは最小のテスト可能ユニットをターゲットとする必要があります (クラスまたはメソッド)。また、秒単位未満で実行する必要があります。統合、機能、スモーク、エンド ツー エンド、パフォーマンス、ストレス、またはロード テストなど、特定のテストのその他のタイプにも同じ原則が適用されます。すべてのユニット テストを「Unit」サブフォルダに配置することもできます。
明示的テストのリソース フォルダ
明示的テストの場合、任意のバイナリ ファイル、アセット ファイル、その他のファイルシステム ベースのリソースなど、テスト ファイルは「resource folder」に配置する必要があります。このフォルダを、次のようにして .Build.cs モジュールに設定します。
SetResourcesFolder("TestFilesResources");
Unreal Build Tool (UBT) によってプラットフォームのデプロイ手順が実行されると、UBT はこのフォルダとそのコンテンツ全体をバイナリ パスにコピーし、そこからテストが相対的に位置を特定し、リソースをロードできるようにします。
ベスト プラクティス
- テスト ケースおよびシナリオには、タグを追加します。
- 一貫性のある名前を使用し、短いものにします。
- タグを活用します。たとえば、
[unit]でタグ付けされたテストの実行を並列化したり、実行が低速なすべてのテストをナイトリー ビルドで実行したりすることができます。
- 各
SECTIONまたはTHENブロックには、必ず少なくともREQUIREまたはCHECK.- 期待がないテストは役立つものになりません。
- テストの前提条件を満たす必要がある場合は
REQUIREを使用します。REQUIREはエラー発生時に即座に停止しますが、CHECKは停止しません。
- 設計テストは決定論的であり、特定のタイプに適合します。
- ユニット、統合、機能、ストレスなど、タイプ別にテストを作成およびグループ化します。
- パフォーマンステストを目的とする場合は、低速なテストを
[slow]または[performance]でタグ付けします。- これは、継続的インテグレーション/継続的デリバリー (CI/CD) パイプラインでナイトリー ビルドからフィルタリングで除外するために利用できます。
- テスト対象のモジュールが必要とするすべてのプラットフォームを、テスト コードがサポートするようにします。
- たとえば、プラットフォーム ファイル システムでの作業時には
FPlatformFileManagerクラスを使用し、デスクトップ プラットフォームのみでテストが実行されるわけではないと想定します。
- たとえば、プラットフォーム ファイル システムでの作業時には
- テスト グループやライフサイクル イベントを使用し、特定のテストを他のものとは別に初期化します。
- 「テストのグループおよびライフサイクル イベント」セクションを参照してください。
- 各テスト タイプのベスト プラクティスに従います。
- これは、ユニット テストではモックを利用し、外部依存関係 (他のモジュールやローカル データベースなど) に頼らず、他の依存関係があってはならない場合などです。
- 異なるタイプのテストやその特徴の詳細については、「低レベル テストの概要」を参照してください。
次のステップ
テストの記述が完了したら、「低レベル テストをビルドして実行する」のドキュメントを参照し、それらのテストを実行します。