使用 Mock 对 void 方法进行单元测试?

Unit test a void method with Mock?

我想用 Mock 测试一个 void 方法。

 public class ConsoleTargetBuilder : ITargetBuilder
{
    private const string CONSOLE_WITH_STACK_TRACE = "consoleWithStackTrace";
    private const string CONSOLE_WITHOUT_STACK_TRACE = "consoleWithoutStackTrace";
    private LoggerModel _loggerModel;
    private LoggingConfiguration _nLogLoggingConfiguration;

    public ConsoleTargetBuilder(LoggerModel loggerModel, LoggingConfiguration nLogLoggingConfiguration)
    {
        _loggerModel = loggerModel;
        _nLogLoggingConfiguration = nLogLoggingConfiguration;
    }

    public void AddNLogConfigurationTypeTagret()
    {
        var consoleTargetWithStackTrace = new ConsoleTarget();
        consoleTargetWithStackTrace.Name = CONSOLE_WITH_STACK_TRACE;
        consoleTargetWithStackTrace.Layout = _loggerModel.layout + "|${stacktrace}";
        _nLogLoggingConfiguration.AddTarget(CONSOLE_WITH_STACK_TRACE, consoleTargetWithStackTrace);

        var consoleTargetWithoutStackTrace = new ConsoleTarget();
        consoleTargetWithoutStackTrace.Name = CONSOLE_WITHOUT_STACK_TRACE;
        consoleTargetWithoutStackTrace.Layout = _loggerModel.layout;
        _nLogLoggingConfiguration.AddTarget(CONSOLE_WITHOUT_STACK_TRACE, consoleTargetWithoutStackTrace);
    }

问题是我不确定如何测试它。我有我的主要代码。

public class ConsoleTargetBuilderUnitTests
{
    public static IEnumerable<object[]> ConsoleTargetBuilderTestData
    {
        get
        {
            return new[]
            {
                 new object[]
                {
                    new LoggerModel(),
                    new LoggingConfiguration(),
                    2
                }
            };
        }
    }
    [Theory]
    [MemberData("ConsoleTargetBuilderTestData")]
    public void ConsoleTargetBuilder_Should_Add_A_Console_Target(LoggerModel loggerModel, LoggingConfiguration nLogLoggingConfiguration, int expectedConsoleTargetCount)
    {
        // ARRANGE
        var targetBuilderMock = new Mock<ITargetBuilder>();
        targetBuilderMock.Setup(x => x.AddNLogConfigurationTypeTagret()).Verifiable();
        // ACT
        var consoleTargetBuilder = new ConsoleTargetBuilder(loggerModel, nLogLoggingConfiguration);
        // ASSERT
        Assert.Equal(expectedConsoleTargetCount, nLogLoggingConfiguration.AllTargets.Count);
    }

请指引我正确的方向。

// Assert
targetBuilderMock.Verify(x => x.AddNLogConfigurationTypeTagret(), Times.Once());

在我看来,您根本不需要模拟。您正在测试 AddNLogConfigurationTypeTagret。不是构造函数。

[Theory]
[MemberData("ConsoleTargetBuilderTestData")]
public void ConsoleTargetBuilder_Should_Add_A_Console_Target(LoggerModel loggerModel, LoggingConfiguration nLogLoggingConfiguration, int expectedConsoleTargetCount)
{
    // ARRANGE
    var consoleTargetBuilder = new ConsoleTargetBuilder(loggerModel, nLogLoggingConfiguration);
    // ACT
    consoleTargetBuilder.AddNLogConfigurationTypeTagret();
    // ASSERT
    Assert.Equal(expectedConsoleTargetCount, nLogLoggingConfiguration.AllTargets.Count);
}

如果不想自己调用AddNLogConfigurationTypeTagret,应该在构造函数中调用。这会将测试更改为:

[Theory]
[MemberData("ConsoleTargetBuilderTestData")]
public void ConsoleTargetBuilder_Should_Add_A_Console_Target(LoggerModel loggerModel, LoggingConfiguration nLogLoggingConfiguration, int expectedConsoleTargetCount)
{
    // ARRANGE
    // ACT
    var consoleTargetBuilder = new ConsoleTargetBuilder(loggerModel, nLogLoggingConfiguration);
    // ASSERT
    Assert.Equal(expectedConsoleTargetCount, nLogLoggingConfiguration.AllTargets.Count);
}

并且 class 构造函数应该是:

public ConsoleTargetBuilder(LoggerModel loggerModel, LoggingConfiguration nLogLoggingConfiguration)
{
    _loggerModel = loggerModel;
    _nLogLoggingConfiguration = nLogLoggingConfiguration;
    AddNLogConfigurationTypeTagret();
}

一般来说,您不应该模拟要测试的 class,您应该模拟它的依赖项。当您开始模拟 class 时,您尝试测试的事情通常会交织在一起,导致脆弱的测试,并且很难确定您是在测试代码,还是在模拟设置,或两者兼而有之。

方法 AddNLogConfigurationTypeTagret 是您正在测试的 class 上的一个 public 方法,所以正如@Philip 所说,您应该只是从测试中调用它,或者它应该从你的构造函数中调用。如果您的目标是从您的构造函数调用它,那么我建议它应该是一个私有方法,除非有理由从 class.

外部调用它

就测试 void 方法调用的效果而言,您对该方法引起的状态变化很感兴趣。在这种情况下,_nLoggingConfiguration 是否有两个添加了正确属性的目标。您没有向我们展示 nLoggingConfiguration.AllTargets 属性,但根据您在测试中使用 Count 属性 的事实,我认为您可以简单地检查列表中的项目以确认正确的名称和布局已添加到正确的目标类型。

Please guide me to the right direction, I want two things. A) the method was called. B) two targets were added. So the expected count is 2.

A) 如果您希望验证调用了方法 AddNLogConfigurationTypeTagret,那么您需要在调用此方法的代码上对其进行测试。例如。类似于 class ConsoleTargetBuilderClient 假设示例 class 使用 ITargetBuilder (在你自己的代码中你应该有一些地方使用 class ConsoleTargetBuilder).

public class ConsoleTargetBuilderClient
{
    private ITargetBuilder _builder;

    public ConsoleTargetBuilderClient(ITargetBuilder builder)
    {
        _builder = builder;
    }

    public void DoSomething()
    {
        _builder.AddNLogConfigurationTypeTagret();
    }
}

然后您可以进行测试,验证方法 DoSomething 是否调用了方法 AddNLogConfigurationTypeTagret。为此,您可以使用 ConsoleTargetBuilder 的模拟,因为您测试了与它的交互。测试可能如下所示:

    public void DoSomething_WhenCalled_AddNLogConfigurationTypeTagretGetsCalled()
    {
        // Arrange
        bool addNLogConfigurationTypeTagretWasCalled = false;
        Mock<ITargetBuilder> targetBuilderMock = new Mock<ITargetBuilder>();
        targetBuilderMock.Setup(b => b.AddNLogConfigurationTypeTagret())
            .Callback(() => addNLogConfigurationTypeTagretWasCalled = true);
        ConsoleTargetBuilderClient client = new ConsoleTargetBuilderClient(targetBuilderMock.Object);

        // Act
        client.DoSomething();

        // Assert
        Assert.IsTrue(addNLogConfigurationTypeTagretWasCalled);
    }

B) 如果您想验证目标是否已添加,您需要测试代码本身(而不是模拟代码)。测试可能如下所示:

public void AddNLogConfigurationTypeTagret_WhenCalled_ConsoleTargetsAdded()
{
    // Arrange
    const int expectedConsoleTargetCount = 2;
    var loggerModel = new LoggerModel();
    var nLogLoggingConfiguration = new LoggingConfiguration();
    var consoleTargetBuilder = new ConsoleTargetBuilder(loggerModel, nLogLoggingConfiguration);

    // Act
    consoleTargetBuilder.AddNLogConfigurationTypeTagret();

    // Assert
    Assert.AreEqual<int>(expectedConsoleTargetCount, nLogLoggingConfiguration.AllTargets.Count);
}

注意:google基于值与基于状态与交互测试。