繁体   English   中英

C#单元测试 - 模拟,存根或使用显式实现

[英]C# Unit Test - To mock, stub or use explicit implementation

之前已经讨论了很多次,但下面例子中的优点并不明显,所以请耐心等待。

我正在尝试决定是否在我的单元测试中使用模拟实现,并且考虑到以下两个示例尚未确定,第一个使用NSubstitute进行模拟,第二个使用SimpleInjector(Bootstrapper对象)解析实现。

基本上两者都在测试相同的东西,当调用.Dispose()方法时,Disposed成员被设置为true(请参阅本文底部的方法实现)。

在我看来,第二种方法对于回归测试更有意义,因为模拟代理在第一个示例中显式地将Disposed成员设置为true,而它是由注入的实现中的实际.Dispose()方法设置的。

为什么你会建议我选择一个来验证方法是否符合预期? 即调用.Dispose()方法,并通过此方法正确设置Disposed成员。

    [Test]
    public void Mock_socket_base_dispose_call_is_received()
    {
        var socketBase = Substitute.For<ISocketBase>();
        socketBase.Disposed.Should().BeFalse("this is the default disposed state.");

        socketBase.Dispose();
        socketBase.Received(1).Dispose();

        socketBase.Disposed.Returns(true);
        socketBase.Disposed.Should().BeTrue("the ISafeDisposable interface requires this.");
    }

    [Test]
    public void Socket_base_is_marked_as_disposed()
    {
        var socketBase = Bootstrapper.GetInstance<ISocketBase>();
        socketBase.Disposed.Should().BeFalse("this is the default disposed state.");
        socketBase.Dispose();
        socketBase.Disposed.Should().BeTrue("the ISafeDisposable interface requires this.");
    }

作为参考,.Dispose()方法就是这样的:

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Releases unmanaged and - optionally - managed resources.
    /// </summary>
    /// <param name="disposeAndFinalize"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
    protected void Dispose(bool disposeAndFinalize)
    {
        if (Disposed)
        {
            return;
        }

        if (disposeAndFinalize)
        {
            DisposeManagedResources();
        }

        DisposeUnmanagedResources();

        Disposed = true;
    }

干杯

这两种测试方法对我来说都很奇怪。 使用第一种方法,你似乎没有测试任何东西(或者我可能误解了NSubstitute的作用),因为你只是模拟ISocketBase接口(没有行为可以测试)并开始测试那个模拟对象而不是真正的实现。

第二种方法是坏的为好,因为你应该使用任何DI容器单元测试中。 这只会使事情变得更复杂,因为:

  1. 您现在使用所有测试都使用的共享状态,这使得所有测试都相互依赖(测试应该单独运行)。
  2. 容器引导逻辑将变得非常复杂,因为您希望为不同的测试插入不同的模拟,并且在测试之间不会共享任何对象。
  3. 您的测试得到了对框架或外观的额外依赖,而这种框架或外观完全不存在。 从这个意义上说,你只是让你的测试更复杂。 它可能只是稍微复杂一点,但它仍然是一个额外的复杂功能。

相反,您应该做的是始终在单元测试(或测试工厂方法)本身内创建测试中的类(SUT)。 您可能仍希望使用模拟框架创建SUT依赖项,但这是可选的。 所以,IMO测试看起来应该是这样的:

[Test]
public void A_nondisposed_Socket_base_should_not_be_marked_dispose()
{
    // Arrange
    Socket socket = CreateValidSocket();

    // Assert
    socketBase.Disposed.Should().BeFalse(
        "A non-disposed socket should not be flagged.");
}

[Test]
public void Socket_base_is_marked_as_disposed_after_calling_dispose()
{
    // Arrange
    Socket socket = CreateValidSocket();

    // Act
    socketBase.Dispose();

    // Assert
    socketBase.Disposed.Should().BeTrue(
        "Should be flagged as Disposed.");
}

private static Socket CreateValidSocket()
{
    return new Socket(
        new FakeDependency1(), new FakeDependency2());
}

请注意,我将您的单个测试分成2个测试。 在调用dispose之前, Disposed应该是false,这不是运行该测试的先决条件; 这是系统工作的要求。 换句话说,您需要明确这一点,并需要进行第二次测试。

还要注意使用在多个测试中重用的CreateValidSocket工厂方法。 当其他测试检查需要更多特定伪造或模拟对象的类的其他部分时,此方法可能有多个重载(或可选参数)。

你太在意了。 此测试是测试天气与否,给定的实现正确处理,因此您的测试应该反映出来。 请参阅下面的伪代码。 非脆性测试的技巧是仅测试满足测试所需的绝对最小值。

 public class When_disposed_is_called()
 {
    public void The_object_should_be_disposed()
    {
       var disposableObjects = someContainer.GetAll<IDisposable>();
       disposableObjects.ForEach(obj => obj.Dispose());
       Assert.False(disposableObject.Any(obj => obj.IsDisposed == false));
    }
 }

正如您所看到的,我在一个依赖容器中填充了我所关注的实现IDisposable所有对象。 我可能不得不嘲笑他们或做其他事情,但这不是测试的关注点。 最终,它只关注验证当处理某些东西时,它实际上应该被处理掉。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM