【问题标题】:Unit test a void method with Mock?使用 Mock 对 void 方法进行单元测试?
【发布时间】:2015-06-19 20:50:05
【问题描述】:

我想用 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);
    }

请指引我正确的方向。

【问题讨论】:

  • 最简单的方法是将 ConsoleTarget 类注入到方法中。然后你可以在模拟上设置期望。更熟悉 Moq 框架的人可以帮助您编写代码
  • 你在你想测试的类的接口上创建一个模拟。为什么 ?然后您正在实例化 ConsoleTargetBuilder 但构造函数中没有方法调用。你在测试什么?你的模拟永远不会被调用。
  • 您是要测试 AddNLogConfigurationTypeTagret 内的代码还是只是调用 AddNLogConfigurationTypeTagret 的事实?
  • @sleepwalker,我想要两件事。 A) 方法被调用。 B) 添加了两个目标。所以预期的计数是 2。
  • 没有人会调用那个方法。得自己调用,或者改构造函数调用AddNLogConfigurationTypeTagret();

标签: c# unit-testing moq xunit


【解决方案1】:

在我看来,您根本不需要模拟。您正在测试 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);
}

然后类构造函数应该是:

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

【讨论】:

  • System.NullReferenceException : 对象引用未设置为对象的实例
  • 不要按原样使用此代码。我试图通过提供示例代码和解释来为您提供帮助。关键是你不需要模拟 dut 正在实现的接口。
  • @Love 你真的需要说明你从哪一行得到对象引用错误。从表面上看,它可能只是您的 loggerModel.layout 返回 null 例如..
【解决方案2】:

请指引我正确的方向,我想要两件事。 A) 方法被调用。 B) 添加了两个目标。所以预期的计数是 2。

A) 如果您想验证方法AddNLogConfigurationTypeTagret 是否被调用,那么您需要在调用此方法的代码上对其进行测试。例如。类似于ConsoleTargetBuilderClient 的类,它是使用ITargetBuilder 的类的一个假设示例(在您自己的代码中,您应该有一些使用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);
}

注意:谷歌基于价值与基于状态与交互测试。

【讨论】:

  • 我将使用你的代码。 ConsoleTargetBuilderClient 是否必须继承 ITargetBuilder 接口?作为我的代码继承...
  • ConsoleTargetBuilderClient 只是一个使用ConsoleTargetBuilder 的类的假设示例。在您自己的代码中,您必须在某个地方使用ConsoleTargetBuilder,并且此代码可以像ConsoleTargetBuilderClient 一样使用。所以它不是从接口继承的,它有一个实现它的类的实例。 HTH
  • 好的。如何在ConsoleTargetBuilderClient 类中实现AddNLogConfigurationTypeTagret?您在代码中有_builder.AddNLogConfigurationTypeTagret(),但只有一行。我的意思是这个方法的细节。
  • AddNLogConfigurationTypeTagret 在类ConsoleTargetBuilder : ITargetBuilder 中实现。客户端类只是在构造函数中接收构建器并使用它。这只是使用构建器的类的一个示例。
【解决方案3】:

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

AddNLogConfigurationTypeTagret 方法是您正在测试的类的公共方法,因此正如@Philip 所说,您可能应该只是从测试中调用它,或者应该从构造函数中调用它。如果您的目标是从您的构造函数中调用它,那么我建议它可能应该是一个私有方法,除非有理由从类外部调用它。

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

【讨论】:

    【解决方案4】:
    // Assert
    targetBuilderMock.Verify(x => x.AddNLogConfigurationTypeTagret(), Times.Once());
    

    【讨论】:

    • 添加您的代码后,我得到了以下内容。结果消息:Moq.MockException:预期在模拟上调用一次,但为 0 次:x => x.AddNLogConfigurationTypeTagret() 配置的设置:x => x.AddNLogConfigurationTypeTagret(), Times.Never 未执行任何调用。结果 StackTrace:在 Moq.Mock.VerifyCalls 处 Moq.Mock.ThrowVerifyException(MethodCall 预期,IEnumerable1 setups, IEnumerable1 actualCalls,Expression 表达式,Times 次,Int32 callCount)(Interceptor targetInterceptor,MethodCall 预期,Expression 表达式,Times 次)跨度>
    • 那么你的单元测试失败了,你需要让它通过。您确定正在调用 AddNLogConfigurationTypeTagret() 方法吗?起订量说不是。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-16
    相关资源
    最近更新 更多