(一)使用 NUnit, NUnitAsp, Rhino Mocks 和 Fit 创建BasicSample.Tests

发表于:2007-10-19 15:56

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:未知    来源:网络转载

        我估计你已经能猜到这个项目有什么用途。在文章的第一版本,只是随便讨论了单元测试。行业和个人经验都证明:在生产高质量产品(这些产品更能适应做改变)测试驱动开发是一个关键的因素。而且,使用测试驱动方法往往会生产更好的设计,不好的方面就是会创建许多完美的有效的技术文件。看测试发展中一天的介绍对初学者非常有好处,例如,阅读Kent Beck's的《Test-Driven Development: By Example》。检查示例应用程序的单元测试给人们提供了:应用程序是怎样构建的,有什么可用的性能。当看了单元测试后,我们将进一步研究它们测试的代码。

单元测试的性能

        单元测试必须运行得非常快。如果单元测试组运行得太久,开发者就会停止运行它们,然后我们需要它们一直在运行。事实上,如果测试运行的时间超过了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; }

        很不幸地,而且通常大都是不幸的,你正在维护一个legacy 代码,这个legacy code没有完美的"代码-接口"形式,双重测试注入允许存在完美的"代码-接口"。通常,这里在具体对象上有许多详细的依赖项,很难用test doubles代替数据访问对象,以此来模拟现场数据库。在这些情况中,你的选择要么在测试中,refactor legacy code来安装,要么使用一个对象模拟工具(例如:TypeMock)。有了TypeMock,就可能模拟密封的和单一的类型-没有这样一个工具却来实现一个感人的宴会。
    
        Albeit很有力,除非非得需要,不然TypeMock就被放在旁边;过早地使用TypeMock使得不编译接口非常诱人。当操作遗留代码-时间和预算允许时,更正确的方法就是refactor the code,取得更大的灵活性。Michael Feathers的《有效地与遗留代码工作》是包含了很多关于如何在测试工作中重构遗留代码的好办法的书。

NHibernateDAO的单元测试

        在这篇文章的前一版本,Nhibernate的ISession通过System.Runtime.Remoting.Messaging.CallContext单例保存和读取。尽管非常适合WinForms 和单元测试,对ASP.NET 来说,却是一个不好的方法,因为ISession 可能在载入时会丢失(这两篇文章给予了进一步的解释:为什么在ASP.NET应用程序里使用CallContext是一个坏的方法)。为了正确地与ASP.NET 应用程序连接,应该将ISession 储存在HttpContext.Current.Items里,但是这样做,当运行单元测试时,会强迫你模拟一个HTTP文本。它同样阻止将结构简单地传递到WinForms。一个更好的方法就是在它正确的时候,使用正确的repository。因此如果一个网页文本可用,然后使用HttpContext;然而,使用CallContext. 将在后面讨论这个结合方式的执行细节。(要感谢许多对这篇文章给予评论,从而引起了这个关注)。阅读与怎样管理ISession有关的

        BasicSample.Tests/Data/CustomerDaoTests.cs这篇文章,因为单元测试HTTP-agnostic。一方面,你将在单元测试看到,除非你像在你的测试中改变数据,来服从于数据库,反转处理才是一个好的方法。

        正如在示例中显示的,可能创建一个通用的DAO,这个一般的DAO为任何持久稳固的对象工作。(后面将详细讨论)这就引起了应该测试什么以及怎样测试它的争论。每一个具体的DAO都要全面测试吗?怎样维护测试数据?个人经验给出了这些建议: 
        • 保证每个泛型DAO中的每一个方法都有一个单元测试,例如,如果你有10个实现了泛型Dao的Dao,只有他们其中的一个需要被完全测试。单元测试中的实现了泛型DAO其他9个Dao只会提供很少的附加值。 
        • 确保每个从泛型Dao扩展的每个方法都有一个单元测试。例如,如果你有一个从泛型DAO继承的CustomerDao 类并且增加了一个方法。如GetActiveCustomers(),那么这个单元测试应该测试这个扩展方法。 
        • 保证存在一个单元测试来完完全全测试每一个“专业” DAO。例如,DAO BasicSample.Data/HistoricalOrderSummaryDao.cs不是从一般DAO 继承来的,将被看成是一个专业DAO。因此,存在一个单元测试来测试每一个方法。 
        • 在DAO单元测试被运行之前或者以后,都使用一个工具,例如NDbUnit,把测试数据库放到一个已知的状态。 
        • 使用如NDbUnit的工具测试数据库在单元测试运行请后的状态。 永远记着,单元测试永远不要依赖另外一个单元测试!他们应该是独立并且可以独立运行。例如一个删除测试不应该依赖上一个插入测试的成功。注意TestFixtureSetUp和其他setup、teardown的方法,保证包含了这个能够独立运行的测试也能够正常运行。

《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号