现在越来越多的开发组织在程序开发中使用单元测试的方式,甚至有些外包工程要求开发者交货的时候提供完整的单元测试代码。单元测试不仅仅是在编码的时候需要考虑,在程序设计的时候就应该充分考虑测试的需要,要设计和编写出“可测试”的代码。
为什么一些代码难以测试
在进行单元测试的时候,会发现程序中某些部分很难进行自动测试,比如耦合程度比较高的类、用户界面、数据库、Servlets和EJB类、等等。本文主要说明程序中这些“难以测试”的部分应该采用什么样的方式去测试。是什么因素使得这些代码难以测试呢?首先是不知道测什么,其次是一些代码之间互相依赖严重,在测试环境中要建立起这些类的实例都很难。
应该测什么
我们首先假设某些部分是不会有问题的:假设Swing或者MFC的底层类是没问题的;假设Oracle、Sql Server等等数据库的操作是没问题的。我们需要做的就是把我们自己写的代码部分和这些代码从物理上尽量的分离开,这样一来我们写的代码就可以测试了。至于Servlets这样的程序,可以使用HttpUnit或者使用一段程序调用其中的业务代码进行测试。
测试高耦合的类
我们看一下下面的程序:
程序中有一个CoffeeMaker类,我们想使用CoffeeMakerTest类对其进行测试。CoffeeMaker类工作的时候需要调用Warmer类和Boiler类,假设这两个类很难在测试环境中创建出来,这样的情况应该怎么进行测试呢?对于高耦合的类,事实上总是可以通过某种编程方法减少类之间的耦合。请看下面的例子:
在左边的图中,A和B紧密的耦合在一起。为了减弱这种耦合,在右侧的图中,在程序中引入了IB接口,类A只要了解IB接口就可以工作了。实际运行环境中可以使用B类实现IB接口进行实际的工作,测试环境下,可以实现一个虚拟的IB就口,就可以方面的对A进行测试了。
测试的时候情况如上图,真实运行情况下,IB的实现类是B。在测试的时候类B很难创建实例,因此在测试程序中创建BStub类的实例。BStub是专门为了测试而创建的,ATest类控制他的输入和输出。
如果简化上面的情况,可以更进一步:测试类本身就是IB接口的一个实现,这样就不用写一个新的类了,如下:
使用上面介绍的方式,减少类之间的耦合,下面就是Coffee Maker的程序经过改进的情况,类之间的耦合程度大大下降,便于测试了。
类似上面介绍的Stub类的方法在单元测试中有很广泛的应用。这种方法要求在设计时充分考虑类之间的联系,类与类之间的关系要基于接口,而不是基于实现。这样的方式也会为程序带来最大的灵活性。
一般说来,下列模块都应该通过接口的方式进行调用:
1:硬件;
2:数据库;
3:还没有完成的类。
测试用户界面
测试用户界面是一个在设计的时候就应该考虑的问题(又强调了一遍,要在设计的时候考虑)。对用户界面进行单元测试,要求程序具有较好的层次性,用户界面和其背后的业务逻辑要是分离的。也就是说:用户界面只是薄薄的一层,并且用户界面和业务逻辑之间的耦合程度很小,模块间联系较为松散。这样的实现进行单元测试就很容易了。后台的业务逻辑可以独立进行测试,前台的界面可以写一个假的后台接口的实现进行测试。基本上所有的东西都是可以进行自动测试的。当然在实际上没有必要测试的这么具体,测试过多细节要花很多成本,并且随着需求的细微变更(比如某个控件位置的改变)都要修改测试程序也是不实际的。
程序设计一般遵循层次原则,一般说来,上层的程序可以调用下层的程序,同层之间也可能存在互相的调用。具体到UI的设计,如下所示:UI和下面各个层次之间的调用是单方向的,UI调用业务代码得到返回值,业务代码只能通过消息、事件和抛出错误等方式与UI发生联系,不能够在业务代码中直接调用UI,否则业务代码将不可测试。
一般说来,程序应该尽力减少UI和UI模块之间的互相调用,比如,窗口之间的迁移逻辑不应该写在窗体的代码中;业务代码之间的互相调用则是比较常见的,比如一个公司的管理系统,财务模块调用人员管理模块的接口,这是很正常的结构。层次之间原则上坚持单向调用。如果存在层次之间的双向调用,程序中的层次划分基本上就名存实亡了。
很多JSP开发者很熟悉STRUTS框架,这是MVC的一种实现。使用这个框架,可以使JSP页面成为程序的一种单纯的数据表示,不用进行任何业务功能。JSP画面之间的迁移、JSP对底层业务代码的调用都不是在UI层次上进行的。微软为应用程序的界面设计也创建了这样一种技术,叫做User Interface Process (UIP) Application Block。根据这样的技术,UI实际上简化为一个薄薄的层,UI之间的迁移、UI对后台层次的调用是在UIP层次上(User Interface Process)实现的。类似STRUTS的struts.config,程序也使用一个配置文件反映用户界面之间的迁移关系。有兴趣的朋友可以自己去MSDN看看。