サイトアイコン てっくぼっと!

Unity 2021 LTS で紹介されたCode Coverage Packageを試してみる

こんにちは、Unityエンジニアの向井です。

Unity 2021 LTSのリリースオーバービューを眺めていると、「Code Coverage package with Test Runner」という機能が追加されていました。

個人的に興味があったので、今回はこのパッケージについて取り上げてみようと思います。

この記事では、下記の流れでCode Coverage Packageの基本的な使い方を紹介します。

  1. コードカバレッジについての簡単な説明
  2. パッケージのインストール方法と各種設定、利用方法の紹介
  3. 実際にコードカバレッジを計測する。テストを記述してコードカバレッジをあげてみる。

今回の検証では、下記環境を用いました。

ちなみにタイトルでUnity 2021 LTSで紹介されたと書いていますが、2019.3以降のUnityでも利用できるようです(未検証)。

コードカバレッジについて

パッケージの説明に入る前に、簡単にコードカバレッジについて触れておきます。

コードカバレッジはテストで用いられる尺度の1つで、プログラム中のソースコードが実行されたかどうかの(多くの場合はテストによってソースコードが実行されたかどうかの)割合を表します。(参考: コード網羅率 – Wikipedia

つまり、このカバレッジが高ければ高いほど、そのソースコードに対してテストが網羅されていると言えます。

ただし、あくまでもソースコードが実行された割合を表すだけなので、コードカバレッジ単体が品質の指標を示すわけではないですし、通常では起こすことが難しいエラーに対する処理は、そもそもテストによって再現しづらいので、単にコードカバレッジが100%を目指せば良い、というものでもありません。(参考: 右手に感情、左手に数値 – カバレッジを味方にしよう – t-wada の日記(旧)

Unityにおいても、たとえばUnity機能に大きく依存する箇所ではそもそもテストが難しいなどといった事情もあるので、カバレッジを上げるのが難しい場合もあります。

そのため、「テストがどのぐらいできているか」とか「このパスにテストが通ってないけど大丈夫なんだっけ?」などと考えるための、指針の1つとして捉えるのが良いかと思います。

インストール

Code Coverage packageは、Unity Package Managerからインストールできます。

メニューの Window > Package Manager でパッケージマネジャーウインドウを開き、次に左上の「+」ボタンをクリックし、下図のように出てくるプルダウンから Add package by name… をクリックします。

Package Managerウインドウ

下図のように出てくるテキストボックスに com.unity.testtools.codecoverage とタイプして、「Add」ボタンをクリックします。

Add package by nameでCode Coverage packageをインストールする

うまくいくとパッケージがダウンロード・インストールされます。Package Managerウインドウ左上を「Packages: In Project」に選択して、下図のように「Code Coverage」パッケージが表示されていれば、インストールは完了です。

正常にCode Coverage packageが追加された様子

Code Coverage packageにはサンプルプロジェクトが用意されています。簡単なシューティングゲームにおけるテストを題材に、このパッケージの使い方と、自分でテストケースを追加してカバレッジを上げる方法について触れています。

Code Coverage packageのサンプルプロジェクトを実行している様子

Code Coverage packageの設定

コードカバレッジを取るためには各種設定が必要です。まずメニューから Window > Analytics > Code Coverage でCode Coverageウインドウを開きます。

Window > Analysis > Code CoverageでCode Coverageウインドウを開ける

Code Coverageウインドウは下図のとおりです。いくつか主要な設定を説明します。

Code Coverageウインドウ

コードカバレッジの結果と実行履歴は「Results Location」と「History Location」でそれぞれ設定できます。指定したフォルダ以下に CodeCoverage というフォルダを掘り、その中にそれぞれ ReportReport-history というフォルダを作成し、その中に結果や履歴が格納されます。

各種Locationの設定

コードカバレッジのレポートでは、カバレッジ率の推移も合わせてレポートされます。例えばCIなどでテストを定期的に実行し、カバレッジの推移をあわせてレポートしたい場合は、この「History Location」で指定したディレクトリを何かしらの方法で保存しておき、またレポートを取り直す時には「History Location」の場所に復元する必要があります。

コードカバレッジは常時結果が計測されるわけではなく、「Enable Code Coverage」チェックボックスを有効にした状態でテストを実行するか、Code Coverageウインドウ右下の「Start Recording」ボタンをクリックしたときのみ計測されます。

カバレッジの計測にはオーバーヘッドがかかるなどの制約もあるため、基本的には必要なときのみ(例えばプロジェクトで定期的にテストを回しているのであれば、そのテスト時のみ)有効にするのが良いでしょう。

「Included Assemblies」と「Included Paths」、「Excluded Paths」ではそれぞれコードカバレッジを計測する対象を指定します。「Included Assemblies」ではアセンブリ単位(asmdef単位)で対象を指定、「Included Paths」ではフォルダ・ファイル単位で対象を指定できます。「Excluded Paths」は指定のフォルダ・ファイルをコードカバレッジの計測対象から外す設定です。

カバレッジ計測対象に含めるアセンブリ・フォルダ・ファイルの設定

ウインドウ下部の設定(下図)は、レポート生成に関するオプションです。「Auto Generate Report」にクリックを入れておけば、テスト実行後やゲームプレイ後に自動的にレポートを生成してくれます。

各種生成に関するオプション

制約

現状では、Burstでコンパイルされたジョブに対してカバレッジを取ることができません。Burstを無効にするには下図のようにメニューの Job > Enable Compilationをクリックしてチェックを外します。

Burstコンパイルをオフにする

また、2020.1から導入された Code Optimizationが有効な状態では正確なカバレッジが取れないようです。カバレッジを取るときは下図のようにエディター右下の「Bug icon」(虫マーク)をクリックして、モードを「Debug」にしておきます。

Code Optimizationの設定確認

上記の設定が行われている場合、Code Coverageウインドウに下図のように警告されます。このウインドウからでも設定を無効にできます。

設定が入っていると、Code Coverageウインドウ上で警告が出る

カバレッジを計測する

前置きが長くなりましたが、実際にカバレッジを計測してみます。下記のような球に対しての内外判定を例に、このコードにテストを追加しつつ、カバレッジを計測してみようと思います。

using UnityEngine;

public class Sample
{
    /// <summary>
    /// 中心が<paramref name="center"/>、半径が<paramref name="radius"/>な球の内外判定を行います。
    /// </summary>
    /// <param name="center">球の中心</param>
    /// <param name="radius">球の半径</param>
    /// <param name="point">内外判定を行う点</param>
    /// <returns>
    /// <paramref name="point"/>が球の内側なら<c>true</c>を、
    /// そうでなければ<c>false</c>を返却します。
    /// </returns>
    public static bool IsInSphere(Vector3 center, float radius, Vector3 point)
    {
        var distance = (center - point).sqrMagnitude;
        if (distance < radius * radius)
        {
            return true;
        }

        return false;
    }
}

まず、このコードを Assets/Scripts 以下に配置します。合わせて今回は、テストスクリプトから上記のコードを参照するために、 Assets/Scripts 以下にasmdefを切ります。

プロジェクトウインドウで Scripts フォルダを右クリックし、Create > Assembly Definition をクリックしてasmdefを作成します。下図のように名前を Sample としておきます。(クラス名と被ってしまって若干ややこしいですが…)

Sampleアセンブリの定義

準備と初回のカバレッジ計測

今回作成した Sample クラスは、前述の通り Sample アセンブリに入ります。

Sample アセンブリをカバレッジの対象に含めるため Code Coverageウインドウを開き、「Included Assemblies」のプルダウンを開き、下図のように Sample にチェックを入れておきます。(もちろん、「Included Paths」で直接上記クラスを追加してもOKです。)

Sampleアセンブリをカバレッジ計測対象に追加する

次に、テストを作成するためのテスト用のasmdefとフォルダを作成します。今回は Assets/Tests というフォルダを作成します。

下図のようにプロジェクトウィンドウで Assets フォルダを右クリックし、Create > Testing > Tests Assembly Folder をクリックしてテストフォルダを作成します。

Test用のフォルダの作成方法

次に、テスト用のスクリプトを作成します。下図のように先程作成した Tests フォルダをクリックし、Create > Testing > C# Test Script をクリックしてスクリプトを作成します。今回は SampleTest という名前で作成します。

テストスクリプトの作成方法

今回は先程作成した Sample アセンブリのクラスをテストしたいので、テスト用アセンブリに Sample アセンブリを参照する必要があります。

作成したasmdefをクリックし、Inspectorウインドウの「Assembly Definition References」右下の「+」ボタンをクリックして枠を追加し、オブジェクトピッカーで Sample アセンブリを指定します。設定が完了すると下図のようになるので、Inspectorウインドウ下の「Apply」ボタンをクリックして設定を反映させます。

テスト用のasmdefの参照にSampleアセンブリを追加

一旦この状態でテストを実行してみます。下図のようにメニューの Window > General > Test Runnerでテストランナーウインドウを開きます。

Test Runnerウインドウの開き方

Test Runnerウインドウの左上「Run All」ボタンをクリックしてテストを実行します。(今回、サンプルプロジェクトをインポートしているので、作成したテスト以外が表示されていますが、こちらは動作に差し支えありません。)

Test Runnerウインドウ

テストが終わると、下図のように自動でレポート出力先のフォルダがエクスプローラで開かれるので、 index.htm (または index.html )を開きます。レポートはHTMLなので、ブラウザが起動します。

テスト終了後に、自動でレポートが格納されたフォルダが開かれる

上記ファイルを開くと、下図のようなレポート画面が出力されます。今回、テストは実行したものの、先程の Sample クラスの処理は1行も実行されていないので、「Line Coverage」は 0%になっています。

カバレッジレポート

また、レポート画面下部の「Coverage」項目から、それぞれのメソッドのカバレッジの詳細を確認できます。

それぞれのメソッド単位でのカバレッジの確認

上記画面からメソッド名をクリックすると、そのメソッドのカバレッジの詳細を確認できます。

特に、一番下の「File(s)」項目では、下図のようにそのテストで各行が何度実行されたか確認できます。また、赤でハイライトされた行は1度も実行されていないことを示しています。(1度でも実行されると緑色でハイライトされます)。

今回実装したIsInSphereの初回のカバレッジ集計結果。どこからも実行されないのでカバレッジは0%

基本的には、この赤い行がなくなるようにテストを追加する計画を立てて実行していくことで、メソッド中の未テストの箇所が減り、テストの品質を上げることができます。(前述の通りテストしづらい処理もあるので、一概にカバレッジを上げることが良いというわけではないのですが。)

テストを書いてカバレッジを上げてみる

テストを追加して、このメソッドに対するカバレッジを上げてみます。下記コードでは球の内側にいるかどうかをテストしています。このコードを、先程作成した SampleTest クラス内に追記します。

[Test]
public void Sample_IsInSphere_CheckIsInside()
{
    // 球の内側かどうかのテスト
    // このパラメータでは、球の内側にいる
    var center = new Vector3(0f, 1f, 0f);
    var radius = 2f;
    var point = new Vector3(0f, 2f, 0f);

    var result = Sample.IsInSphere(center, radius, point);
    // ので、ここはTrue
    Assert.IsTrue(result);
}

ちなみに、 [Test] 属性を [UnityTest] 属性に変えることで、Unityを起動しながらテストもできます。

上記コードを追記したら、再度Test Runnerを実行してレポートを確認してみます。「File(s)」項目は下図のように変化しているはずです。

テストを追加してカバレッジを集計した様子。球の内部判定のコードパスのカバレッジが上がっているのが確認できる。

このように、メソッド中の球の内側を判定するコードのカバレッジを上げることができました。ただし球の外側を判定するテストが抜けているため、上図の23行目は実行されていないことがわかります。

そのため、下記コードを球の外側かを判定するテストとして追加します。

[Test]
public void Sample_IsInSphere_CheckIsOutside()
{
    // 球の外側かどうかのテスト
    // このパラメータでは、球の外側にいる
    var center = new Vector3(0f, 1f, 0f);
    var radius = 0.5f;
    var point = new Vector3(0f, 2f, 0f);

    var result = Sample.IsInSphere(center, radius, point);
    // ので、ここはFalse
    Assert.IsFalse(result);
}

テストを実行して、レポートを確認すると下図のようになります。

球の外部判定のテストを追加してカバレッジを集計した様子。

内側と外側の両方の判定に関するテストを追加したので、このメソッドに対するコードカバレッジが100%になりました。

このように、コードカバレッジをうまく活用することで、テストコードの不足・考慮漏れに視覚的に気づくことができます。

コマンドライン経由でカバレッジ計測を行う

CIでのテスト時に合わせてカバレッジのレポートを出力する場合、通常はカバレッジの設定をオフにしておいて、CIにてコマンドライン経由でテストをするときだけカバレッジの計測を有効にしたいはずです。

テスト時に合わせてカバレッジを計測するには、Windowsの場合は下記コマンドを実行します。

&amp; 'C:\Program Files\Unity\Hub\Editor\2021.3.3f1\Editor\Unity.exe'`
   -projectPath PATH_TO_PROJECT`
   -runTests -batchmode -testPlatform playmode`
   -enableCodeCoverage`
   -debugCodeOptimization -burst-disable-compilation`
   -coverageResultsPath PATH_TO_RESULT`
   -coverageHistoryPath PATH_TO_HISTORY`
   -coverageOptions "generateAdditionalMetrics;generateHtmlReport;generateHtmlReportHistory;generateBadgeReport;"`
   -quit

-enableCodeCoverage を引数として渡すことでカバレッジ計測が有効になります。先述したCode Optimizationのデバッグモードの設定とBurstコンパイルを無効にするためにあわせて -debugCodeOptimization-burst-disable-compilation を渡しています。その他の指定できるオプションについてはこちらのドキュメントを参考にしてください。

ただし、1つのテストも実行していない状態だとカバレッジのレポートが出力されない点に注意が必要です。そのため -runTests をあわせて渡しています。さらにテストの場合は、PlayModeテストかEditModeテストのどちらかを指定する必要があります。今回はPlayModeテストを追加しているので、 -testPlatform playmode を渡しています。

まとめ

Unity 2021 LTSから使えるCode Coverage packageについて基本的な使い方を紹介しました。

導入事例もあまり聞いたことがないですが、Test Runnerでテストさえ書いていれば導入自体はとても簡単なので、テストの品質向上の1つのツールとして導入してみるのも良いのかな、と感じました。

モバイルバージョンを終了