自動テスト
大規模なプロジェクトや、複数のチームメンバーがシナリオスクリプトやゲームプレイロジックを変更するプロジェクトでは、公開前にゲームが正常に動作するかを確認することが不可欠です。ゲームはインタラクティブであるため、多くの場合、大量の手動テストが必要ですが、比較的単純なビジュアルノベルではテスト工程の大部分を自動化できます。
Naninovelは、ゲームの実行中にシミュレートされたプレイヤーインタラクションのシーケンスを組み合わせることで、エンドツーエンドテストを構築するのに役立つツールを Naninovel.E2E 名前空間で提供します。UnityのTest Framework と組み合わせると、エディター、ターゲットデバイス、またはCIで実行される自動テストスイートを構築できます。
スタートガイド
Unityエディターの Window -> General -> Test Runner から「Test Runner」タブを開き、指示に従ってプレイモードテストをセットアップします。詳細については、UTFガイド を参照してください。必要なAPIを利用できるようにするために、Naninovelのcommon、runtime、およびE2Eアセンブリを参照してください。以下は、テストアセンブリのセットアップ例です。
NaninovelがUPMパッケージとしてインストールされている場合は、プロジェクトの Packages/manifest.json を介して テスト可能にする 必要がある場合もあります。例:
{
"dependencies": {
"com.elringus.naninovel": "...",
"other-packages": "..."
},
"testables": [
"com.elringus.naninovel"
]
}テストは非同期で実行されるため、[UnityTest] 属性を使用し、テストメソッドから IEnumerator を返す必要があります。たとえば、プレイヤーが新しいゲームを開始できることを確認する簡単なメソッドは次のとおりです。
[UnityTest]
public IEnumerator CanStartGame () => new E2E()
.Once(() => Engine.GetService<IUIManager>().GetUI<ITitleUI>().Visible)
.Click("NewGameButton")
.Ensure(() => Engine.GetService<IScriptPlayer>().Playing);コンパイル後、Test Runnerタブを開き、新しく追加されたテストを探します。このテストは、ITitleUI が表示されるまで待機してから、NewGameButton オブジェクトにアタッチされたボタンを探してクリックを試み、スクリプトの再生が開始されたかを確認します。いずれかのステップが失敗すると、テストは停止し、Test Runnerの該当するレコードに赤いバツ印が付きます。
警告
テストを実行する前に、エンジン構成で Initialize On Application Load を無効にしてください。通常の使用時に自動初期化を維持するには、メインシーンのゲームオブジェクトに適用した Runtime Initializer コンポーネントを使用します。エンジンの初期化の詳細については ガイド を参照してください。
ショートカット
簡潔なテストスイートを作成するには、Naninovel.E2E.Shortcuts クラスを 静的インポート すると便利です。このクラスには、テストを短く読みやすく記述するためのショートカットが用意されています。上記のテストをショートカットで書き直すと、次のようになります。
[UnityTest]
public IEnumerator CanStartGame () => new E2E().StartNew().Ensure(Playing);スイート構成
エンドツーエンドテストは可能な限り実際の利用場面に近づけるべきですが、実用的に運用するにはさまざまなパラメーターの調整も必要です。たとえば、読み進めるためのクリックが必要になるたびに、クリック操作のシーケンスを記述するのは避けたいでしょう。同様に、UIのフェードやカメラアニメーションなどのエフェクトには再生時間がかかりますが、テストではそれらの終了を待つ必要はありません。
テストの実行時専用にエンジンを構成するには、E2E インスタンスで使用可能なさまざまな With メソッドを使用します。たとえば、以下のスニペットは、タイムスケールと表示遅延をオーバーライドして効果を非常に高速に実行し、要求されるたびに Continue 入力をアクティブにします。
[UnityTest]
public IEnumerator Test () => new E2E()
.WithConfig<ScriptPlayerConfiguration>(c => c.SkipTimeScale = 999)
.WithConfig<TextPrintersConfiguration>(c => c.MaxRevealDelay = 0)
.With(() => Service<IScriptPlayer>().OnAwaitInput += _ => Input("Continue").Pulse())— これはよく使う構成であるため、WithFastForward 拡張メソッドを介して適用できます。
[UnityTest]
public IEnumerator Test () => new E2E().WithFastForward()各テストをクリーンなエンジン状態で開始するのも、よくある設定です。これにより、グローバル状態、設定状態、ゲーム状態が以前のテスト実行やプレイセッションの影響を受けないようにします。
また、テスト固有のデータはメモリに保持して、ディスクにシリアル化されないようにしたいでしょう。これらはすべて WithTransientState 拡張メソッドで実現できます。さらに、このメソッドでは初期のグローバル状態および設定状態を指定できます。
[UnityTest]
public IEnumerator WhenTrueCompleteTitleBackChanges () => new E2E()
.WithTransientState(GlobalStateMap.With(
new VariableManager.GlobalState {
Variables = new[] {
new Variable("completedX", new(true), VariableKind.Meta),
new Variable("completedY", new(true), VariableKind.Meta)
}
}))— 上記は、ゲームの初回起動をシミュレートしてクリーンな状態でエンジンを初期化しますが、さらに completedX および completedY メタ変数をtrueに設定します。
シーケンスの組み合わせ
分岐シナリオをテストする場合、プレイヤーがクリアに至るさまざまな道筋を記述するために、共通のインタラクションシーケンスを何度も書くことになりがちです。この重複を最小限に抑えるため、シーケンスオブジェクトは、すべてのテストAPIに渡せる ISequence インターフェースを実装しています。共通のシーケンスを変数に格納しておけば、より汎用的な別のシーケンスの中で組み合わせて使えます。
以下は、プレイヤーが共通、X、およびYルートを完了した後にタイトルメニューに「TrueRoute」UIが表示されることを確認するサンプルテストです。
[UnityTest]
public IEnumerator WhenXYRoutesCompleteTrueUnlocks () => new E2E()
.WithTransientState().WithFastForward()
.StartNew().Play(CommonX, RouteX)
.StartNew().Play(CommonY, RouteY)
.Once(InTitle).Ensure(() => UI("TrueRoute").Visible);
ISequence CommonX => Play(D1QuickX, D2TowardX, D3LoseHP);
ISequence CommonY => Play(D1QuickY, D2TowardY, D3LoseX);
ISequence D1QuickX => Once(Choice("d1-qte-x")).Choose("d1-qte-x");
ISequence D1QuickY => Once(Choice("d1-qte-y")).Choose("d1-qte-y");
ISequence D1QuickNone => Once(Choice()).Wait(0.5f);
ISequence D2TowardX => Once(Choosing).Choose("d2-toward-x");
ISequence D2TowardY => Once(Choosing).Choose("d2-toward-y");
ISequence D3LoseHP => Once(Choosing).Choose("d3-lose-hp");
ISequence D3LoseX => Once(Choosing).Choose("d3-lose-x");
ISequence D3LastY => Once(Choosing).Choose("d3-last-y");
ISequence D3LastNah => Once(Choosing).Choose("d3-last-nah");
ISequence RouteX => On(Choosing, Choose(), Var("completedX", false));
ISequence RouteY => On(Choosing, Choose(), Var("completedY", false));— 「X」または「Y」ルートにつながる共通ルートの1〜3日目の選択肢シーケンスが CommonX および CommonY 変数にまとめられ、それらがさらに実際のテストメソッド内で組み合わされている点に注目してください。
選択肢の参照
上記のように、テストでは d1-qte-x などの文字列を介して選択肢を参照できます。これらは、シナリオスクリプトで割り当てられたカスタム テキスト識別子 です。テキスト識別ユーティリティを使用している場合でも、スクリプトでカスタムテキストIDを定義でき、ユーティリティによって保持されます。たとえば、次のシナリオスクリプトを考えてみましょう。
@choice "選択肢1|#my-id-for-choice-1|"
@choice "選択肢2|#my-id-for-choice-2|"— ここでは、最初の選択肢に my-id-for-choice-1 を、2番目の選択肢に my-id-for-choice-2 を割り当てました。実際のIDは何でもかまいませんが、スクリプト内で一意であることを確認してください。これで、割り当てられたIDを介してテストで選択肢を参照できます。
Once(Choosing).Choose("my-id-for-choice-2")例
E2Eサンプル は、利用可能なショートカット、拡張メソッド、およびテストケースのほとんどを示しています。
カバレッジ
テスト中にスクリプト行またはコマンドが実行されたかどうかを確認すると役立つ場合があります。テストを作成するときは、プレイヤーが実際にすべての利用可能なコンテンツを見ることができることを確認したいでしょう。すべてのテストが合格しても実行されなかったコマンドがある場合は、シナリオロジックの問題またはテストスイートの不備を示している可能性があります。
デフォルトでは、すべてのE2Eテストが終了した後、カバレッジレポートがコンソールにログ出力されます。
— 最初の行は、すべてのシナリオスクリプトのコマンド総数に対する、カバーされたコマンド数の割合を示します。続く行はスクリプトごとのカバレッジを示し、カバーされていないコマンドがある場合は、そのコマンドを含む行番号も表示します。
カバレッジを無効にする場合は、E2E コンストラクターで Cover オプションを無効にします。例:
[UnityTest]
public IEnumerator Test () => new E2E(new Options { Cover = false })