xUnitでクラスのプライベート メソッドのテストをする - C#

xUnitでクラスのプライベート メソッドのテストをするコードを紹介します。

概要

xUnitテストプロジェクトでテストを実施する場合、テストされるクラスのあるプロジェクトをxUnitテストプロジェクトに参照で追加し、 クラスのインスタンスをテストプロジェクトで作成するか、静的クラスのメソッドをテストプロジェクトから呼び出す必要があります。 しかし、クラスのメソッドがプライベートメソッドで定義されている場合、クラスのメソッドをテストプロジェクトから呼び出すことができません。
この記事ではクラスのプライベートメソッドをxUnitテストプロジェクトでテストするコードを紹介します。

実装例

コード

以下のコードを記述します。
MyClass.cs (テストされるクラス)
using System;

namespace xUnitPrivateMethod
{
  public class MyClass
  {
    public int Func(int a, int b, int c)
    {
      int sa = subProc1(a, c);
      int sb = subProc2(b, c);
      int sc = subProc3(a, b);

      return (sa + sb) * sc;
    }

    private int subProc1(int a, int b)
    {
      return a + b;
    }

    private int subProc2(int a, int b)
    {
      return a * b;
    }

    private int subProc3(int a, int b)
    {
      return a - b;
    }
  }
}
UnitTestMain.cs (テストプロジェクト)
using System;
using Xunit;
using xUnitPrivateMethod;

namespace xUnitPrivateMethodTest
{
  public class UnitTestMain
  {
    [Fact]
    public void Test1()
    {
      MyClass mc = new MyClass();
      Assert.Equal(5, mc.Func(1, 2, 3));
    }
  }
}

解説

テストプロジェクトの UnitTestMain.cs から、MyClassのインスタンスを作成し、MyClassのFuncメソッドのテストを実行します。

テストは失敗しますが、テストは実行できます。

Privateメソッドのテスト

上記の状態から、テストプロジェクトのテストクラスに新しいテストメソッドを作成し、以下のコードを記述します。 MyClassのインスタンスを作成し、プライベートメソッドを呼び出すコードを記述します。
UnitTestMain.cs
using System;
using Xunit;
using xUnitPrivateMethod;

namespace xUnitPrivateMethodTest
{
  public class UnitTestMain
  {
    [Fact]
    public void Test1()
    {
      MyClass mc = new MyClass();
      Assert.Equal(5, mc.Func(1, 2, 3));
    }

    [Fact]
    public void Test2()
    {
      MyClass mc = new MyClass();
      Assert.Equal(5, mc.subProc1(1, 2, 3));
    }
  }
}

クラス外からプライベートメソッドを呼び出すことはできないため、コンパイルエラーとなり、次のエラーメッセージが表示されます。
メッセージ
エラー CS0122
'MyClass.subProc1(int, int)' はアクセスできない保護レベルになっています

対処法1:設計の見直し

クラスのプライベートメソッドをテストせずに、Publicメソッドをテストするよう、設計を変更します。
オブジェクト、関数に対するinとout をテストすることがテストの基本のため、内部のプライベートメソッドのテストは冗長である可能性があります。

対処法2:リフレクションを利用した対応

通常の呼び出しでは、プライベートのメソッドは呼び出せないため、リフレクションを利用してクラスのプライベートメソッドにアクセスします。
リフレクションの動作の詳細についてはこちらの記事を参照してください。
UnitTestMain.cs
using System;
using Xunit;
using xUnitPrivateMethod;
using System.Reflection;

namespace xUnitPrivateMethodTest
{
  public class UnitTestMain
  {
    [Fact]
    public void Test1()
    {
      MyClass mc = new MyClass();
      Assert.Equal(5, mc.Func(1, 2, 3));
    }

    [Fact]
    public void Test2()
    {
      var obj = Activator.CreateInstance(typeof(MyClass));
      MethodInfo method = typeof(MyClass).GetMethod("subProc1", BindingFlags.NonPublic | BindingFlags.Instance);

      int result = (int)method.Invoke(obj, new object[] { 1, 2});
      Assert.Equal(3, result);
    }
  }
}

または、下記コードの Test3() メソッドの記述方式でも同様の処理が実装できます。
UnitTestMain.cs
using System;
using Xunit;
using xUnitPrivateMethod;
using System.Reflection;

namespace xUnitPrivateMethodTest
{
  public class UnitTestMain
  {
    [Fact]
    public void Test1()
    {
      MyClass mc = new MyClass();
      Assert.Equal(5, mc.Func(1, 2, 3));
    }

    [Fact]
    public void Test3()
    {
      Type t = typeof(MyClass);
      //インスタンス作成
      object obj = t.InvokeMember("MyClass", System.Reflection.BindingFlags.CreateInstance, null, null, null);

      //メソッド呼び出し作成
      int result = (int)t.InvokeMember("subProc1", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, obj, new object[] { 1, 2 });
      Assert.Equal(3, result);
    }
  }
}

解説

リフレクションでのメソッド呼び出しで InvokeAttr のパラメーターに BindingFlags.NonPublic BindingFlags.Instance を指定すると、 クラスのプライベートメソッドの呼び出しやメソッドオブジェクトの取得ができます。 この機能を利用してテストプロジェクトから、プライベートメソッドの呼び出しができます。

このページのキーワード
  • xUnit プライベート メソッド
  • xUnit プライベート メソッド テスト
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2021-09-03
iPentec all rights reserverd.