测试开发

《JUnit实战》第6章 使用stub进行粗粒度测试(续2)

6.3 使用stub替换 We服务器资源

现在你已经知道了如何简单地启动和设置Jetty了,所以接下来我们来关注一下HTTP连接的单元测试。你会首先编写一个测试,以证明你可以调用一个有效的URL,并获取它的内容。

6.3.1 建立第一个stub 测试

为了证实webclient可以使用一个有效的URL 正常工作,你就需要在测试用例的setUp方法中实现测试之前,启动Jetty服务器。你也可以在tearDown 方法中停止服务器的运行。代码6.3给出了具体的做法。

image.png

为了实现@Before和@After方法,你有两个选择。一种方法是,你可以准备一个包含“It works”文本的静态页面,该静态页面被放置在代码6.2中的文档根目录(在代码6.2中由对context.setResourceBase (string)的调用所控制)中。另一种方法是,配置Jetty使用你自己自定义的处理器,它可以直接返回字符串“It works”,而不用在文件系统的文件中获取字符串。这是一个更强大的技术,即便远程HTTP服务器在你的WebClient 客户端应用程序上抛出了一个错误代码,你还是能够对这个测试用例进行单元测试。

创建一个 Jetty处理器

代码6.4显示了如何创建一个返回“1t works”字符串的 Jetty 处理器。

image.png

这个类通过继承Jetty的 AbstractHandler类和实现单个的handle方法来创建一个处理器。Jetty调用handle方法将收到的请求转发给处理器。然后,我们使用Jetty的ByteArrayISo8859writer类来返回字符串“It works”,该字符串被写人HTTP响应中。最后一步设置响应内容的长度为写入输出流(这在 Jetty中是必需的)的字符串长度,然后发送响应。

既然处理器已经被编写好了,你就可以让Jetty通过调用context.setHandler(newTestGetContentOkHandler())来使用该处理器。你现在距离运行你的测试还有一步之遥。最后要解决的问题涉及了@Before和@After方法。如代码6.3所示的解决办法并不是最佳的,因为JUnit将为了每一个测试方法启动和关闭服务器。即使Jetty处理速度很快,但是也没有必要反复启动和关闭服务器。一个更好的解决方法是,使用我们在本书第2章中介绍的JUnit注释:@BeforeClass和GAfterClass,这样对于所有的测试我们只要启动服务器一次就可以了。这些注释可以使你在类中的所有@Test方法之前/之后执行代码。

隔离每个测试与性能考虑

在前面的章节中,我们花费了很长的篇幅来解释为什么每个测试应该运行在一个干净的环境中(甚至到了每次使用一个新的类加载实例的地步)。但是,有时候还有其他方面的考虑。性能就是一个典型的考虑因素。在 Jetty 的例子中,即使启动服务器只花1秒钟,一旦你有300个测试,总共加起来就有300秒(即5分钟)。测试集的执行要花费很长一段时间,这的确是个缺陷。你不会想要经常执行这些耗时的测试,这就抵消了单元测试反复运行的优点。你必须注意这种平衡。根据具体的情况,你也许会选择具有较长的执行时间,但运行在一个干净环境中的测试,或者基于性能的考虑,选择复用环境中的某些部分以调整测试。在手头的这个例子中,你可以针对不同的测试使用不同的处理器,并且你能够确信它们之间不会相互影响。

编写测试类

现在我们能够使用@Beforeclass注释简单地编写出测试类,如代码6.5所示。

image.png

这个测试类已经变得非常简单了。@BeforeClass setUp方法以与代码6.2中同样的方式构建了Server对象。接下来的是@Test方法,并且我们故意将@Afterclass方法留为空,因为我们已经通过编程使服务器在关闭时停止。

如果你在Eclipse中运行测试,那么你就能看到如图6.4所示的结果——我们的测试通过了。

到目前为止,所有的事情都非常顺利——我们一直在测试的代码也很正常。但是,如果服务器崩溃了或者配置在服务器中的应用程序崩溃了,那么测试什么将会发生的行为看起来就非常符合逻辑了。在下一节中我们将详细讨论这些问题。

image.png

图6.4 第一个使用Jetty stub正常工作的测试的结果。JUnit在开始第一个测试前

启动了服务器,并且在结束最后一个测试后服务器关闭了它自己。

6.3.2 针对故障情况进行测试

既然第一个测试已经正常工作了,我们来看一下如何针对服务器故障情况进行测试。当故障发生时,webclient.getcontent (URL)方法就返回了一个null值。你也需要测试这种可能性。有了前面建立的基础,你仅仅需要创建一个新的 Jetty Handler类,来返回一个错误码并且将它注册在TestwebclientSetupl类的@Before方法中。

我们为了无效的URL (URL 指向一个不存在的文件)添加了一个测试。这个例子非常简单,因为Jetty已经为这种情况提供了一个NotFoundHandler处理器。你只需要按照以下方式修改TestWebClient setUp方法即可(变化部分已加粗显示);

image.png

以下是实现TestGetcontentNotFoundHandler类的具体代码:

image.png

在 TestWebClient中添加一个新的测试也极其简单:

image.png

采用类似的方法,你可以轻松地添加一个测试,来模拟服务器出现故障的情况。如果返回一个5xx的 HTTP 响应代码就表示出现了这个问题。为了做到这一点,你需要使用HttpServletResponse.sc_SERVICE_UNAVAILABLE来编写一个 Jetty Handler类,并将它注册在TestwebClientSetup1类的@Before方法中。

如果你没有选择一个嵌入式 Web服务器(如Jetty),那么要执行诸如此类的测试将会十分困难。

6.3.3 回顾第一个stub测试

你现在已经能够通过使用stub替换Web资源,对解决方案中的getContent方法进行全面的单元测试了。你已经测试了什么?你完成的是哪种类型的测试?事实上,你已经完成了一些十分重要的事情:你已经对方法进行了单元测试,但同时,你也已经执行了一个集成测试。此外,你不仅已经测试了代码逻辑,也测试了代码之外的连接部分(通过Java的HttpURLConnection类)。

这个做法的缺点是它非常复杂。一个 Jetty 初学者为了正确地建立测试,可能需要花费半天的工夫才能掌握足够多的Jetty知识。在一些实例中,你将必须对stub进行调试才能使它们正常工作。要时刻牢记:stub必须保持简单,不要使它们成为需要测试和维护的、功能完备的应用程序。如果你在调试stub上花费了太多的时间,那就要考虑采用另一种不同的解决方法。

在这些示例中,你都需要用到Web服务器——但是在另一个示例中,stub将会有所不同,并且也需要不同的设置。虽然经验会带来一些帮助,但是不同的情况通常还是需要不同的替换方案。

以上示例测试都很完美,因为你既可以对代码进行单元测试,同时也可以执行一些集成测试。但是,这个功能性是以复杂性为代价的。大部分轻量级的解决方案只专注于代码的单元测试,而不执行集成测试。理由是虽然集成测试很有必要,但是可以在单独的测试集中执行或者作为功能测试的一部分来完成。

在下一节中,我们来看一下另一个仍可以称作stub的解决方法。这个方法更简单,更容易理解,因为它不需要你替换整个Web服务器。它可以使你更加接近mock object策略,关于mock object我们将在下一章中详细介绍。

相关内容

文章评论

表情

共 0 条评论,查看全部
  • 这篇文章还没有收到评论,赶紧来抢沙发吧~