我非常幸运能跟几个聪明的小伙儿们一起工作(让我伤感的是他们中有几个人比我年轻很多,这使我找不到存在感,但是好的一面是,我总能够学到新东西)。
前一天我就学到了一个新玩意儿,工作时一个公子哥给我展示了MSpec来进行TDD/BDD,我不得不说这东西真是吊爆了。其中,我最喜欢的就是它的“AutoMocking”特性。
那么这里所说的“AutoMocking”是指什么呢,为什么说它很酷呢?原因很简单,因为它在一开始就缓和了代码和测试之间的棘手的关系,使得测试可以自动的创建通过构造器注入的参数。
应当指出的是这个模型尽管有一些变化,仍然非常适合新的代码库,不确定的是对于已投入使用的代码库有多大的用处。
需要注意的是这种模型可应用于任何的IOC/依赖注入,这纯粹是一个测试问题。
因此,问它是什么?你只能说你有这个类
public class Autobooker { private readonly ILogger logger; private readonly IRateProvider rateProvider; private readonly IBooker booker; public Autobooker(ILogger logger, IRateProvider rateProvider, IBooker booker) { this.logger = logger; this.rateProvider = rateProvider; this.booker = booker; } public void BookDeal(string ccy1, string ccy2, decimal amount) { logger.Log(string.Format("Booked deal for {0}/{1}", ccy1, ccy2)); decimal rate = rateProvider.GetRate(string.Join("", ccy1, ccy2)); booker.Book(ccy1, ccy2, rate, amount); } }
你可能已经写了一个类似这样的测试用例(作者本人使用的是NUnit和Moq,你可能有自己的工具)
[TestFixture] public class TestCases { private IWindsorContainer container; [SetUp] public void SetUp() { container = new WindsorContainer(); container.Install(new BookerInstaller()); } [TestCase("EUR", "GBP", 1500)] [TestCase("GBP", "EUR", 2200)] public void TypicalTestCase(string ccy1, string ccy2, decimal amount) { //setup string ccyPair = string.Join("", ccy1, ccy2); var rate = 1; var loggerMock = new Mock(); var rateProviderMock = new Mock(); var bookerMock = new Mock(); rateProviderMock .Setup(x => x.GetRate(ccyPair)).Returns(1); var autobooker = new Autobooker(loggerMock.Object, rateProviderMock.Object, bookerMock.Object); autobooker.BookDeal(ccy1, ccy2, amount); //assert loggerMock .Verify(x => x.Log(string.Format("Booked deal for {0}/{1}", ccy1, ccy2)), Times.Exactly(1)); rateProviderMock .Verify(x => x.GetRate(string.Join("", ccy1, ccy2)), Times.Exactly(rate)); bookerMock .Verify(x => x.Book(ccy1, ccy2, rate, amount), Times.Exactly(1)); } }
问题是这段代码极度耦合与设计。 如果Autobooker类需要一个像下面似的新构造函数依赖,将会发生什么问题.
public class Autobooker { private readonly ILogger logger; private readonly IRateProvider rateProvider; private readonly IBooker booker; private readonly IPricer pricer; public Autobooker(ILogger logger, IRateProvider rateProvider, IBooker booker, IPricer pricer) { this.logger = logger; this.rateProvider = rateProvider; this.booker = booker; this.pricer = pricer; } }
需要警惕的是,你现存的测试用例即将被打破,但是我们能为此做些什么呢?这便是"AutoMocking"所能做的。让我们继续往下看看。
所以第一步是来确定一个适合这工作的IOC容器,对于我来说Castle Windsor是不二人选。所以就这么决定吧,接下来我们要做什么呢?现在我们真正需要做的是为我们自动化创建Mocks。那像什么呢?它将如下所示:
public class AutoMoqServiceResolver : ISubDependencyResolver { private IKernel kernel; public AutoMoqServiceResolver(IKernel kernel) { this.kernel = kernel; } public bool CanResolve( CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency) { return dependency.TargetType.IsInterface; } public object Resolve( CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency) { var mock = typeof(Mock<>).MakeGenericType(dependency.TargetType); return ((Mock)kernel.Resolve(mock)).Object; } }
下一步是为该系统在测试(SOT)下创建IOC,这对于我们来说意味着是Autobooker的类型。因此我们做了如下操作:
public class BookerInstaller : IWindsorInstaller { public void Install( IWindsorContainer container, IConfigurationStore store) { container.Kernel.Resolver.AddSubResolver(new AutoMoqServiceResolver(container.Kernel)); container.Register(Component.For(typeof(Mock<>))); container.Register(Classes .FromAssemblyContaining() .Pick() .WithServiceSelf() .LifestyleTransient()); } }
我现在可以在所有地方写非脆弱的代码,我可以从IOC容器中得到我想测试的对象,而所有的依赖都将尝试用Automocking IOC容器来满足。所以我的测试用例就会如下所示:
[TestCase("EUR", "GBP", 1500)] [TestCase("GBP", "EUR", 2200)] public void TestBooking(string ccy1, string ccy2, decimal amount) { var autobooker = container.Resolve(); string ccyPair = string.Join("", ccy1, ccy2); var rate = 1; //arrange container.Resolve>() .Setup(x => x.GetRate(ccyPair)).Returns(1); autobooker.BookDeal(ccy1,ccy2,amount); //assert container.Resolve>() .Verify(x => x.Log(string.Format("Booked deal for {0}/{1}", ccy1, ccy2)), Times.Exactly(1)); container.Resolve>() .Verify(x => x.GetRate(string.Join("", ccy1, ccy2)), Times.Exactly(rate)); container.Resolve>() .Verify(x => x.Book(ccy1,ccy2,rate,amount), Times.Exactly(1)); }
你瞧,我现在不用再为定义mocks而烦恼,我只是定义了他们的行为,而这些在我看来非常酷。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务