hol*_*see 9 .net c# unit-testing moq .net-4.0
我正在尝试通过被测系统(SUT)对依赖性调用方法进行单元测试/验证.
单元测试(Moq):
[Test]
public void StartBar_ShouldCallStartOnAllFoo_WhenFoosExist()
{
//ARRANGE
//Create a foo, and setup expectation
var mockFoo0 = new Mock<IFoo>();
mockFoo0.Setup(foo => foo.Start());
var mockFoo1 = new Mock<IFoo>();
mockFoo1.Setup(foo => foo.Start());
//Add mockobjects to a collection
var foos = new List<IFoo>
{
mockFoo0.Object,
mockFoo1.Object
};
IBar sutBar = new Bar(foos);
//ACT
sutBar.Start(); //Should call mockFoo.Start()
//ASSERT
mockFoo0.VerifyAll();
mockFoo1.VerifyAll();
}
Run Code Online (Sandbox Code Playgroud)
IBar作为酒吧的实施:
class Bar : IBar
{
private IEnumerable<IFoo> Foos { get; set; }
public Bar(IEnumerable<IFoo> foos)
{
Foos = foos;
}
public void Start()
{
foreach(var foo in Foos)
{
Task.Factory.StartNew(
() =>
{
foo.Start();
});
}
}
}
Run Code Online (Sandbox Code Playgroud)
Moq例外:
*Moq.MockVerificationException : The following setups were not matched:
IFoo foo => foo.Start() (StartBar_ShouldCallStartOnAllFoo_WhenFoosExist() in
FooBarTests.cs: line 19)*
Run Code Online (Sandbox Code Playgroud)
@dpurrington和@StevenH:如果我们开始在我们的代码中加入这种东西
sut.Start();
Thread.Sleep(TimeSpan.FromSeconds(1));
Run Code Online (Sandbox Code Playgroud)
我们有成千上万的"单位"测试然后我们的测试开始运行到分钟而不是几秒钟.如果您有1000个单元测试,如果有人已经离开并使用Thread.Sleep乱丢测试代码库,那么很难让测试在5秒内运行.
我建议这是不好的做法,除非我们明确地进行集成测试.
我的建议是使用System.CoreEx.dll的System.Concurrency.IScheduler接口并注入TaskPoolScheduler实现.
这是我对如何实施这一建议的建议
using System.Collections.Generic;
using System.Concurrency;
using Moq;
using NUnit.Framework;
namespace StackOverflowScratchPad
{
public interface IBar
{
void Start(IEnumerable<IFoo> foos);
}
public interface IFoo
{
void Start();
}
public class Bar : IBar
{
private readonly IScheduler _scheduler;
public Bar(IScheduler scheduler)
{
_scheduler = scheduler;
}
public void Start(IEnumerable<IFoo> foos)
{
foreach (var foo in foos)
{
var foo1 = foo; //Save to local copy, as to not access modified closure.
_scheduler.Schedule(foo1.Start);
}
}
}
[TestFixture]
public class MyTestClass
{
[Test]
public void StartBar_ShouldCallStartOnAllFoo_WhenFoosExist()
{
//ARRANGE
TestScheduler scheduler = new TestScheduler();
IBar sutBar = new Bar(scheduler);
//Create a foo, and setup expectation
var mockFoo0 = new Mock<IFoo>();
mockFoo0.Setup(foo => foo.Start());
var mockFoo1 = new Mock<IFoo>();
mockFoo1.Setup(foo => foo.Start());
//Add mockobjects to a collection
var foos = new List<IFoo>
{
mockFoo0.Object,
mockFoo1.Object
};
//ACT
sutBar.Start(foos); //Should call mockFoo.Start()
scheduler.Run();
//ASSERT
mockFoo0.VerifyAll();
mockFoo1.VerifyAll();
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在,这允许测试在没有任何Thread.Sleep的情况下全速运行.
请注意,合同已被修改为接受Bar构造函数中的IScheduler(用于依赖注入),IEnumerable现在传递给IBar.Start方法.我希望这是有道理的,为什么我做了这些改变.
测试速度是这样做的第一个也是最明显的好处.这样做的第二个也可能是更重要的好处是当你为代码引入更复杂的并发时,这使得测试变得非常困难.即使面对更复杂的并发性,IScheduler接口和TestScheduler也允许您运行确定性的"单元测试".