改变了三种方法之后,我们的测试用例运行并通过。现在我们可以开始添加更多的测试,并根据需要将代码添加到我们的模拟对象里。
收益又是什么呢?
在这一环节,值得探讨我们使用模拟对象得到的收益是什么以及花费的代价是什么。让我们先考虑收益。
我们已经编写了检测应用程序代码的单元测试。应用程序代码使用数据库,我们可以不需要数据库就能运行它。
我们的测试代码,特别是模拟对象,使我们能完全控制数据库结果。
测试可以快速运行,不需要依赖真实的数据库,也不需要网络连接。
已有的模拟对象类可以再度使用,稍做修改就可以用于未来的测试。
实现这些解决方案我们需要付出什么?
我们需要创建三个模拟对象类。我们自动完成这一任务,不需要为基础执行编写任何代码。如果我们真的需要通过为每一种方法编写桩模块代码来执行类,那么我们早已独立编写出140多种桩模块方法!
我们大约需要编写50行代码来真正执行我们在模拟对象类中所需的行为。
在我们看来收益远远超出成本。从接口创建模拟对象的过程简单易行,并能帮助我们开发有效的单元测试套件。这一过程与大多数现代化的交互式开发环境(IDEs)工作很好,例如 Eclipse。
如果没有接口如何处理?
设计接口是一个面向对象的极好实践,但是我们没办法总能得到遵循此种设计原则遗留代码。那么怎么办呢?如果我们拥有的代码在执行中使用一个具体的类该怎么办?有两种方法可以处理这一情况。按照上面的解决方案两种方法的成本大体相同。
从类中创建一个接口
如果您只有一个具体的类,那么您可以从类中创建一个接口,您的应用程序代码改写为接口,然后按照前面的描述继续进行。通过引入更多的抽象并对其编程,除了提供特别的类以外,这还提供了提高设计的额外收益。Eclipse 中的重构支持使您能够创建带有单一动作的接口。在 Package 视图中选择类,右击,然后选择 Refactor>Select Interface...。这种方法比选择接口作为开始在执行的工作量方面稍微多花些成本。在大多数交互式开发环境(IDEs)中创建接口很简单,但是如果您决定使用结果替换具体类中的所有源代码,在应用程序代码中改变的数量是十分大的。
创建一个模拟对象类作为具体类中的一个子类
这一解决方法并不像从类中创建一个接口的方法一样令人满意。创建一个子类,并仅仅重载您在测试方法中所使用的那些方法。尽管如此,如果您拥有调用超类构造器的构造器您必须小心一些,因为在您不能控制的外部对象上没有隐藏的依赖关系,例如数据库、网络连接等等。与上面的方法一样,这一点不太令人满意,因为创建接口的确能够再分解代码使其变得更加清楚。这种方法是当我不能改变测试中的代码时所使用的。这种方法所花费的成本与使用接口创建一个模拟对象类所花费的成本一样。
真的没有那么困难
我希望你们能够发现在单元测试中使用模拟对象的益处,也希望这篇简短的指南能够帮您了解如何简单地创建模拟对象。一开始,模拟对象看上去令人生畏。不可否认,一些执行是相当复杂的——超过他们所要求的。尽管如此,这里所展示的方法并不是最终的文字。有时您可能需要开发更复杂的模拟对象类,因为他们会带给您额外的收益。我的建议仅仅是抛转引玉,使用这里所讲述的一些方法并按照您的需要增加难度。使用其他工具或者那些预先打包好的模拟对象。一些模拟对象可以在模拟对象的网站 4 上中找到。
还有另外一个我正在研究的可能解决方案。我想这一方案有希望引导单元测试的新发展,这个方案是方面的一个应用,它能够模拟您的测试需要的能力。 5 我们将另行进行这一讨论。