单元测试的性能
单元测试必须运行得非常快。如果单元测试组运行得太久,开发者就会停止运行它们,然后我们需要它们一直在运行。事实上,如果测试运行的时间超过了0.1秒,测试可能太慢了。现在,如果你在过去运行过单元测试,你知道任何单元测试(要求访问一个现场数据库)运行的时间会更久。有NUnit时,你可以把测试放在categories里,每次运行测试的不同组时就会更加容易,同时大部分时间就会用在排除测试(此测试与数据库连接)。但是至少,这些“慢”测试应该在Continuous Integration环境里每晚运行。这里有一个已经分类的单元测试的例子。
[TestFixture] [Category("Database Tests")] public class SomeTests { [Test] public void TestSomethingThatDependsOnDb() { ... } }
领域层单元测试
为了简单化,这个应用程序的领域层很简单,至少可以这样说。但是即使在最简单的领域层,在最小程度上,拥有每一个非异常途径(被领域层覆盖)。在命名空间BasicSample.Tests.Domain里有领域层测试。(一方面,CustomerTests.CanCreateCustomer测试每一个属性的getter/setter时,它是不是非常具有杀伤力?在这个过程中你抓住了几个琐碎的bug)。在阅读这篇文章时,你可能会注意到类型DomainObjectIdSetter.cs;下面将讨论建立这个类型的动机。
为了运行单元测试,打开NUnit,转到File/Open Project,打开BasicSample.Tests/bin/Debug/BasicSample.Tests.dll.为了阻止消耗时间的测试继续运行,转到NUnit的Categories t键,双击“数据库测试”和"Web Smoke Tests."此外,点击底部的“删除这些类型”。现在,当你运行单元测试时,只有域名逻辑测试将运行,而且不会被HTTP和数据库访问测试减速。对于这样一个小的应用程序,补充的顶端的“慢”测试是可以忽略的,但是要增加时间来运行更大的应用程序的单元测试。
为数据访问层使用Test Doubles 。
在进入模拟数据库层之前,要注意这里有一个命名法来描述模拟服务的不同类型。Dummies,fakes, stubs and mocks都用来描述模拟行为的不同类型。对这些区别的总看法引起了一些东西,这些东西将包含在Gerard Meszaros' 即将出版的XUnit Test Patterns里。Meszaros提供了“双重测试”,一般用来描述任何这些行为。Stubs和mocks就是例子代码里显示的这样两个双重测试。
除非你明确测试DAO类别,通常你不需要运行单元测试,这些单元测试依靠现场数据库。本质上,它们非常慢,而且不稳定。例如:如果数据改变了,测试就崩溃了。当测试域名逻辑时,如果数据库改变,单元测试就不会崩溃。但是主要的障碍就是域名对象它们自己可能依靠DAOs。使用抽象的工厂模式(在例子中存在(以后将讨论))和连接的DAO接口,我们能把DAO双重测试注入到域名对象中,以此来模拟与数据库通信。在CustomerTests.CanGetOrdersOrderedOnDateUsingStubbedDao中包括了一个例子。下面的片段,从单元测试创建了DAO stub,并且通过公共的安装员,把它注入到customer中。因为安装员仅仅希望执行IOrderDao接口,stub DAO就会简单地代替所有现场数据库行为。
Customer customer = new Customer("Acme Anvils"); customer.ID = "ACME"; customer.OrderDao = new OrderDaoStub();
编写stub DAO 的另一个选择,可以静态地大量生成“无需实现接口”的代码,而这些可以通过如Rhino Mocks 或NMock工具模拟DAO。无论哪个都是非常好的选择,但是Rhino Mocks 以一种强而有力的方式调用方法,而不是使用数字符,而NMock 就是使用数字符。这使得能检查它的使用编译时间,协助重命名属性和方法。演示示例CustomerTests.CanGetOrdersOrderedOnDateUsingMockedDao 告诉人们使用Rhino Mocks 3.0来创建模拟的IOrderDao 。尽管看起来建立一个模拟的对象比建立一个stub更复杂。补充的灵活性和很大程度上减少的“没有执行”代码是有力的好处。下面的代码,在类型MockOrderDaoFactory.cs中找到的,展示了IOrderDao是怎样和Rhino Mocks一起模拟的。本质上,它建立了一个“静态”模拟,或者一个stub,事实上,在变元中经过了什么并不重要:它总是返回同一个例子命令(由TestOrdersFactory创建)。但是和Rhino Mocks模拟并没有限制在dumb reflexes上,例如:这个可以像要求的一样有响应。
public IOrderDao CreateMockOrderDao() { MockRepository mocks = new MockRepository(); IOrderDao mockedOrderDao = mocks.CreateMock<IOrderDao>(); Expect.Call(mockedOrderDao.GetByExample(null)).IgnoreArguments() .Return(new TestOrdersFactory().CreateOrders()); mocks.Replay(mockedOrderDao); return mockedOrderDao; }