.Net 核心库:如何使用 xUnit 测试私有方法

.Net core library: How to test private methods using xUnit

最新的 xunit 框架不允许在使用 .Net Core 框架编译时在库代码中测试 运行ners(这对于正常的 Visual Studio 代码是允许的)。解决方案是为 运行 您的测试用例创建一个单独的测试项目。

我遇到的问题是我要测试的某些方法是 'private' 方法。 如何在不指定范围的情况下从我的测试项目中调用这些方法 'public'?

更一般的问题是:当使用私有方法的public方法不能使用时,如何测试私有方法?(因为它的交互式性质 - 也就是说,它有一个 ReadLine(); 包含在其中)


可能的解决方案:
1) 制作私有方法 public 并以表明它们不应在外部使用的方式重命名它们。 (使用 'private' 或 'internal' 作为名称的一部分)
2) 创建一个可以设置为 true 的“public static bool Testflag”字段,以绕过 public 方法的交互部分,确保对其所有部分进行测试。

(虽然我已经使用了上述两种方法 - 我真的不喜欢它,因为私有方法应该保持私有并且标志增加了很多额外的复杂性。有人遇到过同样的问题吗?你是如何解决的?

一个快速的解决方案是让 private 个您想要测试的成员 internal

然后您可以将 InternalsVisibleTo 属性添加到主图书馆的 AssemblyInfo.cs。这将允许您的测试库(没有其他库,没有反射)访问主库中的内部 methods/classes。

例如

[assembly: InternalsVisibleTo("Library.Tests")]

为了测试内部 类, 是正确的。

据推测,私有方法是私有的是有充分理由的。他们不应该成为内部人士,这也是有充分理由的。

(有些人认为我们不应该为私有方法编写单元测试。为什么?好吧,因为它们是 "private"!但是如果你真的要为私有方法编写单元测试:)

这是测试私有方法的更好方法:

public class Hello
{
  private string _firstName { get; set; }
  private string _lastName { get; set; }

  public Hello(string firstName, string lastName)
  {
     _firstName = firstName;
     _lastName = lastName;
  }

  public string HelloMan()
  {
    if (string.IsNullOrEmpty(_firstName))
       throw new Exception("Missing First Name.");
    return this.HelloMan(_firstName, _lastName);
  }

  private string HelloMan(string firstName, string lastName)
  {
     return $"Hello {firstName} {lastName}!";
  }
}

测试过程如下:

public class HelloTests
{
  [Fact]
   public void PrivateHelloManShouldBeWellFormated()
   {
      // Arrange
      var firstName = "John";
      var lastName = "Doe";

      Type type = typeof(Hello);
      var hello = Activator.CreateInstance(type, firstName, lastName);
      MethodInfo method = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
      .Where(x => x.Name == "HelloMan" && x.IsPrivate)
      .First();

      //Act
      var helloMan = (string)method.Invoke(hello, new object [] {firstName, lastName});

     //Assert
     helloMan
     .Should()
     .StartWith("Hello")
     .And
     .EndWith("!")
     .And
     .Contain("John")
     .And
     .Contain("Doe");
   }
 }

参考:

http://anthonygiretti.com/2018/08/26/how-to-unit-test-private-methods-in-net-core-applications-even-if-its-bad/

如果您需要测试私有方法,那么您就是代码异味。您应该检查您的体系结构,而没有理由测试私有属性或方法。如果你需要这个 - 它不是私有方法,它是另一个 class.

的 public 方法

特别针对仇恨者阅读单元测试艺术第 2 版中的第 8.2.1 段。只有 public 方法(契约)应该是测试,仅此而已。

老式的逐步优化使得测试私有方法很有用,即使不是纯粹主义者,也适用于实用主义者。

将@Tohid 的出色回答更进一步,这个class 可用于调用静态或实例私有方法

public static class TestPrivate
{
    public static T StaticMethod<T>(Type classType, string methodName, object[] callParams)
    {
        var methodList = classType
            .GetMethods(BindingFlags.NonPublic | BindingFlags.Static);

        if (methodList is null || !methodList.Any()) 
            throw new EntryPointNotFoundException();
        
        var method = methodList.First(x => x.Name == methodName && !x.IsPublic && x.GetParameters().Length == callParams.Length);

        var output = (T)method.Invoke(null, callParams);

        return output;
    }

    public static T InstanceMethod<T>(object instance, string methodName, object[] callParams)
    {
        var classType = instance.GetType();
        var methodList = classType
            .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);

        if (methodList is null || !methodList.Any()) 
            throw new EntryPointNotFoundException();
        
        var method = methodList.First(x => x.Name == methodName && !x.IsPublic && x.GetParameters().Length == callParams.Length);

        var output = (T)method.Invoke(instance, callParams);

        return output;
    }
}

然后这样使用:

        {
            var result = TestPrivate.InstanceMethod<bool>(InstanceOfClass, "privateMethod", new object[] { param1, param2 });

            Assert.True(result);

            var actual = TestPrivate.StaticMethod<myReturnType>(typeof(StaticClass), "privateMethod", new object[] { param1, param2, param3 });

            Assert.True(actual.prop == expected.prop);
        }