NUnit 3.x で Private メソッドをテストできるようにした

PrivateObjectクラスってすげー。MSTestって地味に頑張ってるんだなぁと思った。

やりたかったこと。とあるクラスのPrivateメソッドをテストしたかった。

MSTestの場合、PrivateObjectという大変使える?クラスがいたので、それを使ってテスト対象クラスインスタンスからInvokeすれば解決してたんだよね。

https://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.testtools.unittesting.privateobject.aspx

まぁ、これに同等の機能クラスが実はNUnit3.xに存在しないってことを知ってしまったわけだ。

なので、作ったw

internal static class CommonModule
{
  static public Object PrivateMethodInvoke(Object testClass, String methodName, Object[] parameters)
  {
    if (string.IsNullOrWhiteSpace(methodName))
        Assert.Fail("No Name");

    MethodInfo method = testClass.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);

    if (method == null)
        Assert.Fail(string.Format("{0} is not found", methodName));

    try
    {
        return method.Invoke(testClass, parameters);
    }
    catch (TargetInvocationException ex)
    {
        throw ex.InnerException;
    }
  }
}

例外についても今まで通りの形でテストしたかったんで、ちょいテク。Try-Catch でInvoke例外とってその原因の例外を実行時例外に代替。ILコスト? まーいいんじゃない? テストコードだし、実行じゃないしw

で、テスト用にサンプルクラス作る。Privateメソッドでパラメータの文字列を数字にして「倍返し」。

public class ForPrivate
{
  private int TwoTimeString(String BaseNum)
  {
    if (string.IsNullOrWhiteSpace(BaseNum))
        throw new InvalidCastException();

    int ret = int.MinValue;

    if (int.TryParse(BaseNum, out ret))
    {
        return ret * 2;
    }

    throw new InvalidCastException();
  }
}

そして、テストコードを書いてみる。正常ケースと例外ケースを。

public class ForPrivateTest
{
  [Test, Sequential]
  public void TwoTimeStringTest([Values("1", "4", "88", "-4", "0")] String prm1,
                                [Values(2, 8, 176, -8, 0)] int Exp1)
  {
    ForPrivate forPrivate = new ForPrivate();
    Assert.NotNull(forPrivate);

    int act = (int)CommonModule.PrivateMethodInvoke(forPrivate, "TwoTimeString", new object[] { prm1 });
    Assert.AreEqual(Exp1, act);
  }

  [Test, Sequential]
  public void TwoTimeStringExceptionTest([Values("", " ", "0.00004", null)] String prm1)
  {
    ForPrivate forPrivate = new ForPrivate();
    Assert.Throws<InvalidCastException>(() => CommonModule.PrivateMethodInvoke(forPrivate, "TwoTimeString", new object[] { prm1 }));
  }
}

実行してみた。

image

グリーンだよv