APP下载

单元测试方法在GIS开发中的应用研究

2012-12-21尤晓洺蔡先华

科技视界 2012年1期
关键词:单元测试多边形许可

尤晓洺 蔡先华

(东南大学交通学院地理信息工程系 江苏 南京 210096)

单元测试方法在GIS开发中的应用研究

尤晓洺 蔡先华

(东南大学交通学院地理信息工程系 江苏 南京 210096)

单元测试是在软件测试中要进行的最低级别的测试活动,是保证软件质量的第一环。然而由于GIS开发的一些复杂特征,很多GIS开发者放弃了编写单元测试。本文针对GIS开发的特点,探讨了GIS开发中的编写单元测试的一些问题。包括如何在单元测试中加载第三方GIS组件许可,如何改善设计使单元测试易于编写,如何在单元测试中和复杂组件对象交互等。通过对这些方法的应用,解决GIS开发中编写单元测试的一些问题。

单元测试;GIS开发;NUnit

0 引言

随着社会对GIS的需求越来越多,GIS软件规模的不断扩大,GIS软件设计的复杂程度不断提高,软件开发中出现错误或缺陷的几率也越来越大,如何保证GIS软件的质量,使GIS软件为社会提供稳定而且正确的服务,是一个值得研究的问题。软件测试是验证软件质量的有效手段,而单元测试是保证软件质量的第一环。然而据2008年的一份统计,超过48%的GIS开发者不编写任何单元测试,这所导致的是他们的项目有超过50%成本都消耗在了系统维护上[1]。本文就如何在GIS开发中编写单元测试进行讨论。

1 单元测试简介

单元测试是开发者编写的一段可以自动执行的代码,用于证明被测试代码的行为和开发者所期望的一致[2]。所以单元测试应该是由程序员自己来完成的,它往往是对一个个基本的功能单元进行的测试。

举一个简单的例子,现在有一个编写好的函数,函数的接受一个整型数组作为参数,返回这个数组中值最大的数。可以编写这样一个单元测试用例:把数组{1,2,3,4}作为参数传递给这个函数,判断这个函数的返回值的是否等于4,如果不是,就说明这个函数的代码是错误的。可以使用单元测试框架,如NUnit[3],编写单元测试,断言(Assert)这个函数在接受上述参数时的返回值为4,单元测试框架可以使这个测试和其它的单元测试自动执行,并报告哪些测试用例没有通过。

好的单元测试测试应该是自动的、全面的、可重复的、独立的和专业的[2]。正确的使用单元测试可以起到如下作用:验证代码行为;通过编写单元测试,解除代码中的耦合;单元测试可以作为软件开发的文档,它是可编译、可运行的,与代码同步;自动化的单元测试避免了代码出现回归。

本文以使用ArcGIS Engine for.Net进行GIS开发为例,使用为. Net开发而设计的自动测试框架NUnit,探讨一下GIS开发中单元测试编写的一些问题。如果使用Visual Studio进行开发,结合使用免费的TestDriven.Net Personal Version,可以非常方便快捷地在开发环境中执行和调试单元测试,帮助提高开发效率[5]。

2 在单元测试中加载第三方组件许可

在使用ArcGIS Engine中的对象(可以看作是ArcObjects的子集)时是需要加载许可的。同样,在使用到ArcObjects的单元测试,也是需要加载许可的。单元测试不同于普通的应用程序,没有显式的程序入口,没有明确的执行顺序,它是被Test Runner加载并执行的,那如何在单元测试中加载许可是GIS开发中编写单元测试所要解决的一个问题。如果只有少数几个Test Fixture使用到了ArcObjects,可以在这些Test Fixture中标记了TestFixtureSetUp属性的方法中加载许可,在标记了TestFixtureTearDown中卸载ArcObjects,如下:

如果有很多Text Fixture中都使用到了ArcObjects,这样做会降低测试执行的效率,可以采用另外一种更通用的方法,在标注了SetUpFixture属性的类中的SetUp方法加载许可,TearDown方法中卸载ArcObjects,如下:

在上述代码中,标记了SetUp属性的LoadLicense方法会在执行这个类AeLicenseLoader所在的命名空间中所有测试前执行,而标记了TearDown属性的UnloadAoObjects方法会在执行完这个类所在的命名空间中所有的测试后执行。如果要为整个程序集(Assamble)提供运行许可,把类AeLicenseLoader置于全局命名空间即可,这样在执行这个程序集任何测试前,会加载许可,执行完测试后,卸载ArcObjects。

3 优化设计使便于测试

有时候会发现很难对一些功能代码编写单元测试,这往往暗示着设计需要修改,不应该放弃编写单元测试,需要做的是修改设计,分离代码的关注点,直到代码易于测试。易于测试的代码往往有更好的结构和可维护性[2]。

例如有些程序员习惯将业务逻辑的代码混杂在用户界面的代码中,这本身并不是一种优秀的设计,也使单元测试编写起来变得非常困难。应该将业务逻辑的代码和用户界面的代码进行分离,使用诸如三层架构(表示层、业务逻辑层和数据访问层三层架构)或MVC(模型、视图和控制器)模式等方法对代码进行重构。这样既改善了设计,也使单元测试的编写变得容易。类似的,要避免编写出职能过多的类,这样的类不仅很难测试,也很难维护,是违背面向对象设计的原则的,应用一些模式,改善设计,使之易于测试。也可以通过实践驱动测试开发,改善代码的设计。

4 在单元测试中和复杂对象交互

然而ArcObjects是一个庞大而且复杂的系统,ArcObjects中的很多对象甚至是很难构建的,如果在功能代码中使用到这些对象,真的需要为了编写单元测试而构建这些对象吗?

首先需要注意的是单元测试所测试的目标。编写的单元测试是要测试自己编写的功能代码,而不是ESRI的ArcObjects。比如对一个面的缓冲区操作,对两个几何对象求交,或者是修改了Geodatabase中的一个要素的属性值,这些类似的代码只是对ArcObjects的简单调用,如果对这些代码的行为不确定,需要做的是参考ArcObjects的帮助文档,当然如果对这些代码的行为还是不确定,也可以编写单元测试进行验证,但是这样做的代价往往会很高。

如果确定了ArcObjects对象的行为,要测试和ArcObjects中复杂对象进行交互的功能代码时,我们可以使用Mock对象的技术[6],对功能模块进行测试。这种测试方法的思想是用模拟的对象替代真实的对象,模拟出一个测试环境,对功能代码进行测试,Mock对象就是真实对象在调试期的替代品。而且由于ArcObjects是基于接口的架构,正适合使用这种技术。可以自己编写Mock对象,也可以使用现有的Mock对象框架,如NMock2、DotNetMock和RhinoMocks。

ArcObjects中一个经常到对象的类型是Feature类,而它的对象又是很难用代码从头构建的。下文中的CountyFeature类就使用到了表示一个县域要素的对象,它对一个县域要素进行了强类型化的封装。部分代码如下:

CountyFeature类的构造方法接受一个表示县域要素的参数,县域要素有一个字段的值是该县域的政区代码,这个代码是符合中华人民共和国行政区划代码标准(GB2260-1995)的。根据这个县域的政区代码可以判断这个要素是否是省直辖县级市,CountyFeature类的一个属性就实现了这个功能,它返回该县域是否是省直辖县级市。现在编写单元测试对这个功能代码进行测试。那么就需要实例化CountyFeature类的对象,而CountyFeature类的构造方法需要一个IFeature类型的县域要素作为参数。编写测试时需要这个参数,获得这个参数可以使用真实的数据,也就是说,使用包含县域要素类的Geodatabase,假设这个数据是容易获取的,但是仍然需要在单元测试代码中编写许多根本不是这个测试所需要关注的代码,而且由于这些代码访问外部资源,测试运行的速度也会变慢,所以不应该使用这种方法。

使用Mock对象就可以解决上述这个问题。判断这个县域是否是省直辖县级市的功能代码关注的只是县域要素区域政区代码字段的值,所以可以编写一个带有政区代码的仿真县域要素的对象,其他至于县域要素到底有没有其它字段等都不是这个测试所关心的内容。IFeature接口有9个属性,4个方法,如果自己编写这个Mock对象的话同样会浪费很多时间,解决方法就是使用动态Mock对象,动态Mock对象会在运行时构建一个指定类型的 Mock对象。本文以NMock2中的动态Mock对象为例,编写的对上述功能的测试代码如下:

测试方法的第三行代码表示,使当以指定参数调用 feature.get_Value方法时,返回regionCode的值。上述测试代码隔离了和测试目标无关的内容,使测试专注于需要测试的功能。

Mock对象是一项很实用的技术,但由于需要编写更多的代码,特别是自己编写Mock对象,无疑会加重项目的负担,有时我们可以通过一些重构消除对Mock对象的依赖。

尽管使用了如上所述的方法,但在GIS开发中还是不可避免地需要使用到一些复杂的对象。典型的例子是ArcObjects中的几何数据类型。例如需要自己实现两个多边形求交的功能,那么如何对这个功能进行测试。在测试代码中需要构建两个多边形对象,还有这两个多边形求交后的正确结果,然后将真实的求交结果和正确望的结果进行比较,如果不相同,就说明我自己实现的这个功能是错误的。按照单元测试应该是全面的原则,需要编写多个测试用例。对于那些简单的多边形对象,可以编写代码进行构建(新建一个多边形对象,定义它的空间参考和所有节点)。如果是复杂的多边形对象,这种方法会给编写单元测试带来较大的负担,我们可以从一个文件中(如Shapefile)读取一个多边形,但是这样做会牵扯到更多的与测试不相关的对象(如工作空间,要素类等对象),降低了单元测试编写和执行的效率。ArcObjects中有个IXMLSerialize接口,ArcObjects中有超过340个类实现了这个接口,其中也包括几何数据类,实现了这个接口的类,可以将这些类的对象序列化为xml,也可以从xml中构建这些对象。可以使用这个功能在编写单元测试时从xml中构建一些复杂的对象。一个名为ArcUnit的开源项目,实现了一个ArcMap的工具扩展,它可以将选中要素的Shape保存成xml文件,方便在编写单元测试时构建复杂的几何对象[7]。

5 结论

编写单元测试本身是需要时间的,但是从长远角度来看,单元测试对提高团队开发的效率和软件质量都有较大的作用,同时还能改善设计,提升bug修复的效率。因此,很有必要在GIS开发中编写单元测试。在发现很难对一些功能代码编写单元测试时,优先考虑改善设计,再考虑使用Mock对象等其他技术方法,编写单元测试。解决了GIS开发中编写单元测试这些细节问题,实践测试驱动开发,以单元测试作为开发过程的开端并且将测试引入到系统开发的全过程,保证了代码的阶段正确性,避免了没有单元测试的GIS开发中后期集中测试产生问题时的举步维艰,同时单元测试代码这份可以运行的文档可以指导其他开发者如何使用相应的功能代码。

[1]Dave Bouwman.Developer Survey[EB/OL].http://blog.davebouwman.com/2008/ 06/04/developer-survey-unit-testing-other-tools/,2008.

[2]Andy Hunt,Dave Thomas,Matt Hargett.Pragmatic Unit Testing in C#with Nunit,Second Edition[M].The Pragmatic Bookshelf,2007.

[3]NUnit.org.NUnit Documentation[EB/OL].http://www.nuint.org/.

[4]NCover LLC[EB/OL].http://www.ncover.com/.

[5]TestDriven.Net.Quickstart[EB/OL].http://www.testdriven.net/quickstart.aspx.

[6]Roy Osherove.The Art of Unit Testing with Examples in.NET[M].Greenwich:Manning,2009.

[7]Brian Noyle,David Bouwman.Unit Testing for Esri Developers[J].ArcUser,2010,Winter:38-41.

王静]

猜你喜欢

单元测试多边形许可
多边形中的“一个角”问题
版权许可声明
版权许可声明
版权许可声明
本期作者介绍
多边形的艺术
解多边形题的转化思想
多边形的镶嵌