読者です 読者をやめる 読者になる 読者になる

ASP.NET Core MVC と xUnit.NET でユニットテストを行う Part.1

ASP.NET ASP.NET Core MVC xUnit

前回までのあらすじ

ねんがんの
xUnitを
どうにゅうしたぞ!

mrgchr.hatenablog.com

最初の記事で後回しにしていたことを回収する

最初の記事で、ControllerにNameプロパティを生やしたときに「その整合性はユニットテストで検証する」としたので、それをチェックしようと思います。

Nameプロパティはコントローラーの名前を返すようにしておきます。 このNameプロパティが返す文字列は"HomeController"の接尾語"Controller"を覗いた部分と常に一致させる必要があります。 力ずくの解決策なので、整合性チェックは単体テストですることにします。

ASP.NET Core MVC で、コントローラー名とアクション名とかをインテリセンスに無理矢理対応させる Part.1 - 時が癒す

テスト手順

  1. リフレクションを用いて、テストターゲットのアセンブリ情報を読み込む
  2. 1より、Controller型を継承しているコントローラーの情報を全て取得する
  3. 2で取得したコントローラーに、Nameプロパティがあるか調べる。(今回は無い場合もエラーとする)
  4. 3のNameプロパティの値(+"Controller")が、2で取得したコントローラーの名前と同じか調べる。

アセンブリ情報を読み込む

「Assembly.LoadFrom()でアセンブリ情報読み込めばええやろー」と思っていたら、なんと.NET COREには"Assembly.LoadFrom()"がありません!

「AssemblyLoadContextを継承したAssemblyLoaderを作りたまえ!」という事らしいので、適当に作りました。

public class AssemblyLoader : AssemblyLoadContext
{
  protected override Assembly Load(AssemblyName assemblyName)
  {
    return Assembly.Load(assemblyName);
  }
}

それと、次のようなUtiityも併せて作りました。

public static class TestUtils
{
  public static IEnumerable<Type> FindAllDerivedTypes<T>(string name)
  {
    var loader = new AssemblyLoader();
    var asm = loader.LoadFromAssemblyName(new AssemblyName(name));
    return FindAllDerivedTypes<T>(asm);
  }

  public static IEnumerable<Type> FindAllDerivedTypes<T>(Assembly assembly)
  {
    var derivedType = typeof(T);
    return
      assembly.GetTypes()
      .Where(t => t != derivedType &&
        derivedType.IsAssignableFrom(t));
  }
}

で、実行してみたら動作しました。やったね!

テストする

次に新しいテストクラス"ControllerTest"を作成して、テストを実装します。
手順は上記に書いた通りなので、ソースを張り付けておきます。

public class ControllerTest
{
  public IEnumerable<Type> Controllers { get; set; }

  public ControllerTest()
  {
    Controllers = TestUtils.FindAllDerivedTypes<Controller>("PracticeWorld1");
  }

  [Fact]
  public void AllControllersShouldHaveNamePropety()
  {
    foreach (var controller in Controllers)
    {
      var pi = controller.GetProperty("Name", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
      Assert.True(pi != null, $"controller: {controller.FullName}");
      var value = pi.GetValue(controller);
      var expected = controller.Name;
      var actual = value + "Controller";
      Assert.Equal(expected, actual);
    }
  }
}

これで、こんな風にNameプロパティを実装し忘れたり、 f:id:mrgchr:20161026195924p:plain

コピペしたまま直し忘れたり、 f:id:mrgchr:20161026195934p:plain

というヒューマンエラーを検出できます。

ここまですれば、cshtmlにcontroller="Home"と文字列で記述するよりも便利になると思います。
見た目はかっこよくはないかもしれませんが。
WebAPIのコントローラー?知らんよ。

ところで、xUnit.NETでは、Assert.TrueとAssert.FalseしかAssert失敗時のユーザーメッセージを与えられないのでしょうかね?