以前已经讨论了很多次,但下面例子中的优点并不明显,所以请耐心等待。
我正在尝试决定是否在我的单元测试中使用模拟实现,并且考虑到以下两个示例尚未确定,第一个使用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;
}
干杯
答案 0 :(得分:3)
这两种测试方法对我来说都很奇怪。使用第一种方法,您似乎没有测试任何东西(或者我可能误解了NSubstitute的功能),因为您只是模拟ISocketBase
接口(没有要测试的行为)并开始测试该模拟对象真正的实施。
第二种方法也很糟糕,因为你应该 NOT 在单元测试中使用任何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之前Disposed
应为false,这不是运行该测试的前提条件;这是系统工作的要求。换句话说,你需要明确这一点,需要进行第二次测试。
另请注意使用在多个测试中重复使用的CreateValidSocket
工厂方法。当其他测试检查需要更多特定伪造或模拟对象的类的其他部分时,此方法可能有多个重载(或可选参数)。
答案 1 :(得分:1)
你太在意了。此测试是测试天气与否,给定的实现正确处理,因此您的测试应该反映出来。请参阅下面的伪代码。非脆性测试的技巧是仅测试满足测试所需的绝对最小值。
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
的所有对象。我可能不得不嘲笑他们或做其他事情,但这不是测试的关注点。最终,它只关注验证当处理某些东西时,它实际上应该被处理掉。