Web API からレスポンスを取得してごにょごにょするクラスをテストしたくなったので、「API からレスポンスを取得する」部分をスタブにすることにした。 Unity Test Tools に同梱される NSubstitute を少し使ってみたので、その記録を残す。

概要

  • NSubstitute を使って interface のスタブを作る
    • 引数に渡された Action を指定した引数を与えて呼び出すスタブ
  • テスト対象クラスのインスタンスに、スタブを DI してテストする

環境

  • Unity 5.3.4f1
  • Unity Test Tools 1.5.8

基本的なこと

NSubstitute: A friendly substitute for .NET mocking libraries を読む。

NSubstitute をプロジェクトに追加する

Unity Test Tools に入っている、 NSubstitute.dll を使う。

テストするものを準備する

Web API にリクエストを投げて、レスポンスを Action でコールバックするようなクラスを用意する。

UserProvider.cs

このクラスの Get メソッドをテストしたい。

Get の実装では、「API から JSON 形式文字列を取得するクラス」に移譲するようにして、移譲先のクラスはコンストラクタで DI するようにする。

IUsersAPI.cs

テストする

ということで早速テストを書いていく。方針としては、IUsersAPI のスタブを作って、ダミーのレスポンス(JSON 形式文字列)を返すこととする。

先に結果を示すと、以下のようになった :white_check_mark:

UserProviderTest.cs

手順 1: NSubstitute でスタブを作る

CreateStub メソッドでは、IUsersAPI のスタブを作っている。まず、以下のように IUsersAPI 型のインスタンスを作る。

var api = Substitute.For<IUsersAPI>();

続いて、メソッドのスタブを定義する。まずは、第 1 引数の id が 1 のときを定義する。

api.GetUser(Arg.Is(1),
    Arg.Invoke<string>("{ \"id\":1, \"name\":\"Aimee\" }"),
    Arg.Any<Action<string>>());

第 2 引数の onSuccess では Arg.Invoke<T> を使って、「指定した値を引数として渡されて実行される Action」を定義している。 第 3 引数の onFailure は気にしないので、Arg.Any<T> で適当なものを入れておく。

要するに、このインスタンスの GetUser メソッドを id = 1 で呼び出せば、onSuccess"{ \"id\":1, \"name\":\"Aimee\" }" が渡される。 よってめでたく、リクエスト成功のスタブができあがる。

あとは、このスタブを DI してテスト対象クラスのインスタンスを作る。

sut = new UserProvider(api);

準備完了 :ok_woman:

手順 2: リクエスト成功をテストする

作ったスタブに対応する id = 1 でテストする。

sut.Get(1, (User user) =>
    {
        result = user;
    }, (string error) =>
    {
        Assert.Fail("Failure callback should not be called");
    });

:warning: メソッドの API は非同期だが、スタブは非同期でない定義にしたので、テストは非同期にはならない。

スタブでは、GetUser が呼ばれたら onSuccess を実行するように定義したので、onSuccess は同期的に実行される。 async に対応していない Unity の Editor Test でもテストが書けるのは良いが、非同期呼び出しによるバグを見つけることはできないのが難点。

手順 3: リクエスト失敗をテストする

手順 1 で作ったスタブに id = 2 の場合を追加して、テストする。特に手順 1 & 2 と変わらない。

api.GetUser(Arg.Is(2),
    Arg.Any<Action<string>>(),
    Arg.Invoke<string>("User not found"));

まとめ・感想

  • Unity Test Tools に付属する NSubstitute を使って、interface のスタブを作った
    • Arg.Invoke<T> で「指定した値を引数として渡されて実行される Action」を定義できた
  • テスト対象クラスのインスタンスに、スタブを DI してテストした
    • メソッドの API は非同期でも、(今回作った)スタブは同期的に実行される
    • Editor Test でテストを書ける利点がある一方で、非同期のバグは見つけられない難点がある

非同期の API でも、真に非同期ではないとしても一応実行できてテストできるのが良いと思った。 しかし実は、非同期プログラミング能力が低いためか、ユニットテストの範囲において、今回のような同期的な実行をされると困るケースがまだ浮かんでいない。

本当に非同期でテストするのは、Integration テストで良いんじゃないか。

:point_right: Unity で ユニットテストをする(非同期編) でやったような。

参考