之前已经对此进行了多次讨论,但是以下示例中的优点并不明显,因此请耐心等待.
我正在尝试确定是否在单元测试中使用模拟实现,并且给出以下两个示例,我不确定,第一个使用NSubstitute进行模拟,第二个使用SimpleInjector(Bootstrapper对象)解决的实现.
本质上,两者都在测试同一件事,即当调用.Dispose()方法时,将Disposed成员设置为true(请参阅本文底部的method的实现).
在我看来,第二种方法对于回归测试更有意义,因为模拟代理在第一个示例中将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容器.这只会使事情变得更复杂,因为:
>现在,您使用所有测试都使用的共享状态,这会使所有测试相互依赖(测试应独立运行).
>容器引导逻辑将变得非常复杂,因为您想为不同的测试插入不同的模拟,并且再次,在测试之间不共享任何对象.
>您的测试对根本不存在的框架或外观有额外的依赖.从这个意义上讲,您只是在使测试变得更加复杂.它可能会稍微复杂一点,但是仍然是一个额外的复杂性.
相反,您应该做的是始终在单元测试(或测试工厂方法)本身内部创建被测类(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之前Dispose应该为false并不是该测试运行的前提;这是系统正常运行的要求.换句话说,您需要对此进行明确说明,并需要进行第二次测试.
还要注意使用CreateValidSocket工厂方法,该方法可在多个测试中重复使用.当其他测试检查类的其他部分需要更具体的伪造或模拟对象时,此方法可能会有多个重载(或可选参数).