gpt4 book ai didi

C# 单元测试 - 模拟、 stub 或使用显式实现

转载 作者:行者123 更新时间:2023-11-30 15:34:33 25 4
gpt4 key购买 nike

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

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

本质上,两者都在测试同一件事,即在调用 .Dispose() 方法时将 Disposed 成员设置为 true(请参阅本文底部的方法实现)。

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

为什么您会建议我选择一个而不是另一个来验证该方法是否按预期运行? IE。调用了 .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 接口(interface)(没有要测试的行为)并开始测试模拟对象而不是真正的实现。

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

  1. 您现在使用所有测试使用的共享状态,这使得所有测试都相互依赖(测试应该独立运行)。
  2. 容器 Bootstrap 逻辑将变得非常复杂,因为您要为不同的测试插入不同的模拟,而且测试之间没有共享对象。
  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 个测试。 Disposed 在调用 dispose 之前应该为 false 不是该测试运行的先决条件;这是系统工作的要求。换句话说,您需要对此明确说明并需要进行第二次测试。

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

关于C# 单元测试 - 模拟、 stub 或使用显式实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16057885/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com