如何避免或跳过 XUnit 测试的一些私有方法?

How to avoid or skip some private method for XUnit test?

假设我编写了一个单元测试来测试 XUnit 中的 public 方法。

[Fact]
public void MethodA_WhenSomething_ThenReturnNull()
{
    // Code removed for brevity.

    // Assert
}

这是方法 A。

public void MethodA() {
  MethodOne();
  MethodTwo();
  MethodThree();
}

MethodOne、MethodTwo、MethodTree都是私有方法。有没有办法在 运行 我对 MethodA 进行单元测试时跳过私有方法(即 MethodTwo)?我想跳过 methodTwo 的原因是因为 methodTwo 调用存储过程并导致 Xunit 出错。但是我知道存储过程 运行 没问题,所以我可以跳过这个方法。

而现在,我正在使用这种方式。

public void MethodA() {
  MethodOne();

  #if DEBUG == false
    MethodTwo();
  #endif

  MethodThree();
}

如果有更好的方法,希望不放If DEBUG

如果您的私有方法依赖于某些外部服务,那么您可以创建它的模拟并将其标记为可验证。

[Fact]
public void MethodA_WhenSomething_ThenReturnNull()
{
    var barService = new Mock<Bar>();
    barService.Setup(x => x.DoSomething()).Verifiable();
    ///
}

public class Foo
{
    public void MethodA()
    {
        MethodOne();
        MethodTwo();
        MethodThree();
    }

    private void MethodThree() => System.Console.WriteLine();

    private void MethodTwo() => new Bar().DoSomething();

    private void MethodOne() => System.Console.WriteLine();
    
}

public class Bar
{
    public void DoSomething() => System.Console.WriteLine("....");
}

您可以使用多种方法来替换私有方法。让我们使用 MethodRedirect 库。

假设有以下 class:

public class SomeClass
{
    public void MethodA()
    {
        MethodOne();
        MethodTwo();
        MethodThree();
    }
    private void MethodOne() { }
    private void MethodTwo() =>
        throw new NotImplementedException();
    private void MethodThree() { }
}
var sc = new SomeClass();
sc.MethodA(); // An exception will be thrown here.

以上库没有nuget包。因此,我们简单地从sources复制三个文件:Extensions.cs、MethodOperation.cs、MethodToken.cs.

然后编写如下单元测试:

[Fact]
public void MethodA_WhenCalled_NotThrow()
{
    var sut = new SomeClass();
    var fake = new Fake();

    MethodInfo privateMethod = sut.GetType().GetMethod("MethodTwo", BindingFlags.Instance | BindingFlags.NonPublic);
    MethodInfo redirectMethod = fake.GetType().GetMethod("Redirect", BindingFlags.Static | BindingFlags.NonPublic);

    var token = privateMethod.RedirectTo(redirectMethod);

    sut.MethodA(); // should not throw

    token.Restore();
}

辅助class其方法将被用作替代

public class Fake
{
    static void Redirect() { }
}

这类问题通常通过Moq.Protected解决。

因此,您至少需要将 private 访问器更改为 protected MethodTwo
但我建议将所有访问器更改为 protected.

public class SomeClass
{
    public void MethodA()
    {
        MethodOne();
        MethodTwo();
        MethodThree();
    }
    protected void MethodOne() { ... }
    protected void MethodTwo() { ... }
    protected void MethodThree() { ... }
}

这样,模拟设置将如下所示:

using Moq.Protected;

...
var mockSomeClass = new Mock<SomeClass>();

mockSomeClass.Protected()
             .Setup("MethodTwo")
             .Verifiable();

另外你可以设置一个Callback来写出测试输出

const string toBeMockedMethodName = "MethodTwo";
mockSomeClass.Protected()
             .Setup(toBeMockedMethodName)
             .Callback(() => TestContext.Progress.Writeline($"{toBeMockedMethodName} has been called."))
             .Verifiable();

参考文献: