Unity 2021 LTS で紹介されたCode Coverage Packageを試してみる
こんにちは、Unityエンジニアの向井です。
Unity 2021 LTSのリリースオーバービューを眺めていると、「Code Coverage package with Test Runner」という機能が追加されていました。
個人的に興味があったので、今回はこのパッケージについて取り上げてみようと思います。
この記事では、下記の流れでCode Coverage Packageの基本的な使い方を紹介します。
- コードカバレッジについての簡単な説明
- パッケージのインストール方法と各種設定、利用方法の紹介
- 実際にコードカバレッジを計測する。テストを記述してコードカバレッジをあげてみる。
今回の検証では、下記環境を用いました。
- Unity 2021.3.3f1
- Windows 11、ARM Ryzen 7 5800X
ちなみにタイトルで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… をクリックします。
下図のように出てくるテキストボックスに com.unity.testtools.codecoverage
とタイプして、「Add」ボタンをクリックします。
うまくいくとパッケージがダウンロード・インストールされます。Package Managerウインドウ左上を「Packages: In Project」に選択して、下図のように「Code Coverage」パッケージが表示されていれば、インストールは完了です。
Code Coverage packageにはサンプルプロジェクトが用意されています。簡単なシューティングゲームにおけるテストを題材に、このパッケージの使い方と、自分でテストケースを追加してカバレッジを上げる方法について触れています。
Code Coverage packageの設定
コードカバレッジを取るためには各種設定が必要です。まずメニューから Window > Analytics > Code Coverage でCode Coverageウインドウを開きます。
Code Coverageウインドウは下図のとおりです。いくつか主要な設定を説明します。
コードカバレッジの結果と実行履歴は「Results Location」と「History Location」でそれぞれ設定できます。指定したフォルダ以下に CodeCoverage
というフォルダを掘り、その中にそれぞれ Report
と Report-history
というフォルダを作成し、その中に結果や履歴が格納されます。
コードカバレッジのレポートでは、カバレッジ率の推移も合わせてレポートされます。例えば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をクリックしてチェックを外します。
また、2020.1から導入された Code Optimizationが有効な状態では正確なカバレッジが取れないようです。カバレッジを取るときは下図のようにエディター右下の「Bug icon」(虫マーク)をクリックして、モードを「Debug」にしておきます。
上記の設定が行われている場合、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
アセンブリをカバレッジの対象に含めるため Code Coverageウインドウを開き、「Included Assemblies」のプルダウンを開き、下図のように Sample
にチェックを入れておきます。(もちろん、「Included Paths」で直接上記クラスを追加してもOKです。)
次に、テストを作成するためのテスト用のasmdefとフォルダを作成します。今回は Assets/Tests
というフォルダを作成します。
下図のようにプロジェクトウィンドウで Assets
フォルダを右クリックし、Create > Testing > Tests Assembly Folder をクリックしてテストフォルダを作成します。
次に、テスト用のスクリプトを作成します。下図のように先程作成した Tests
フォルダをクリックし、Create > Testing > C# Test Script をクリックしてスクリプトを作成します。今回は SampleTest
という名前で作成します。
今回は先程作成した Sample
アセンブリのクラスをテストしたいので、テスト用アセンブリに Sample
アセンブリを参照する必要があります。
作成したasmdefをクリックし、Inspectorウインドウの「Assembly Definition References」右下の「+」ボタンをクリックして枠を追加し、オブジェクトピッカーで Sample
アセンブリを指定します。設定が完了すると下図のようになるので、Inspectorウインドウ下の「Apply」ボタンをクリックして設定を反映させます。
一旦この状態でテストを実行してみます。下図のようにメニューの Window > General > Test Runnerでテストランナーウインドウを開きます。
Test Runnerウインドウの左上「Run All」ボタンをクリックしてテストを実行します。(今回、サンプルプロジェクトをインポートしているので、作成したテスト以外が表示されていますが、こちらは動作に差し支えありません。)
テストが終わると、下図のように自動でレポート出力先のフォルダがエクスプローラで開かれるので、 index.htm
(または index.html
)を開きます。レポートはHTMLなので、ブラウザが起動します。
上記ファイルを開くと、下図のようなレポート画面が出力されます。今回、テストは実行したものの、先程の Sample
クラスの処理は1行も実行されていないので、「Line Coverage」は 0%になっています。
また、レポート画面下部の「Coverage」項目から、それぞれのメソッドのカバレッジの詳細を確認できます。
上記画面からメソッド名をクリックすると、そのメソッドのカバレッジの詳細を確認できます。
特に、一番下の「File(s)」項目では、下図のようにそのテストで各行が何度実行されたか確認できます。また、赤でハイライトされた行は1度も実行されていないことを示しています。(1度でも実行されると緑色でハイライトされます)。
基本的には、この赤い行がなくなるようにテストを追加する計画を立てて実行していくことで、メソッド中の未テストの箇所が減り、テストの品質を上げることができます。(前述の通りテストしづらい処理もあるので、一概にカバレッジを上げることが良いというわけではないのですが。)
テストを書いてカバレッジを上げてみる
テストを追加して、このメソッドに対するカバレッジを上げてみます。下記コードでは球の内側にいるかどうかをテストしています。このコードを、先程作成した 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の場合は下記コマンドを実行します。
& '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つのツールとして導入してみるのも良いのかな、と感じました。
この記事へのコメントはありません。