在软件开发中,无论是将第三方API集成到应用程序中,还是开发API供其他开发者使用,API的开发都变得越来越重要。随着这一重要性的增长,也逐渐意识到,能够正确地测试这些API是一项至关重要的任务。API的测试可以采取多种方式和层次,这里将讨论三种测试层次,它们结合在一起可以提供全面的覆盖,并在最近的实践中效果非常好。
单元测试已经存在很长时间了,如果做得正确,它非常有帮助。这种测试层次适用于验证应用程序的单元,对应于面向对象语言中的一个类。非常重要的一点是,单元测试应该测试一个单元,并且与所有依赖项隔离。软件模块相互依赖,但由于单元测试旨在测试一个单元而不是它的依赖项,通常会模拟依赖项并给它们预定义的行为,以验证被测试的单元是否按照期望的方式工作。
模拟是单元测试中的关键概念。在Java中,有许多模拟框架可以使用,如JMockit、Mockito和EasyMock。个人更喜欢JMockit,但其他框架也应该提供相同的功能。这些框架通常使用注解来标记依赖对象,并提供工具,在单元测试中调用时给它们预定义的行为。
以下是一个JMockit的示例:
@Test
public void doBusinessOperationXyz(@Mocked final Dependency mockInstance) {
// ...
new NonStrictExpectations() {{
// ...
// 实例方法的期望:
mockInstance.someMethod(1, "test");
result = "mocked";
// ...
}};
// ...
// 被测试单元的调用发生在这里,导致模拟调用
// 可能或可能不匹配指定的期望。
}
在这个单元测试中,使用@Mocked注解将Dependency对象标记为依赖项,并指定当someMethod被调用时,参数分别为1和"test",依赖对象应该返回"mocked"字符串值。当被测试的单元调用指定的参数时,JMockit会接管控制,并在运行时返回"mocked"字符串,忽略Dependency类的someMethod的实际实现。这样就可以隔离单元与其依赖项,并专注于被测试单元的行为。
当处理数据库交互、外部服务或I/O操作时,模拟是至关重要的。当单元测试一个依赖于DAO模块从任何数据源返回数据的类时,应该模拟这种交互,以防止实际调用数据源。
作为一个原则,单元测试不使用真实的数据库连接,不使用网络访问外部服务,也不执行I/O操作。编写单元测试时,需要记住不是在测试正在测试的单元与其依赖项之间的交互。因此,最好使用模拟交互来保持测试的简单性、快速性和可靠性。
编写单元测试被认为是软件开发的一部分。应该尽可能频繁地运行它们,并将其与应用程序的构建阶段联系起来。值得花时间保持单元测试覆盖率高,应该意识到代码有多少百分比被单元测试覆盖。
有很多优秀的工具,如Cobertura,可以帮助跟踪应用程序中的单元测试覆盖率。它可以为源代码生成报告,并为类提供有关行和分支覆盖率的有价值信息,甚至可以显示哪些行没有被测试覆盖。
更高级的工具,如Sonar,除了代码覆盖率外,还会分析代码并给出提高代码质量的建议,指出可能有bug的地方。
集成测试,顾名思义,旨在测试软件不同组件之间的集成。与单元测试相反,集成测试不模拟依赖项。假设暴露了一个RESTful端点,它以JSON格式返回从数据库获取的数据,可以编写一个集成测试来击中这个端点并验证响应数据以及HTTP响应代码。在这个测试中,Restful服务将连接到一个真实的数据库并执行适当的SQL查询。这里的关键是确保软件组件之间的交互是正确的。
集成测试为应用程序提供了更广泛的覆盖范围,因为它们测试了软件的多个组件。它们也更脆弱,因为它们可能会受到环境变化的影响,并且必须依赖于系统的其他部分的可访问性,如数据源或外部Web服务。而在单元测试中,只依赖于纯Java代码,这使得可以在每次构建或更改一行代码时运行它们,并想知道否破坏了任何东西。
此外,运行一个集成测试套件需要更多的时间,因为之前提到的原因。因此,将集成测试与单元测试分开,并不像单元测试那样频繁地运行它们是有意义的。一个好的做法可能是在每次部署到测试环境后运行它们。
编写集成测试的一个经验法则是定义它们,以便它们可以多次一致地执行。软件测试的魅力在于当一遍又一遍地运行测试并看到在更改代码时破坏了什么。弄清楚测试失败是因为它们写得不正确并不是一件有趣的事情。
目标应该是编写可以多次运行的集成测试,这可以通过确保个别测试不会相互影响,并且它们不依赖于前一个测试的结果来实现。
API的集成测试可以通过简单地编写服务端点的客户端来完成。在RESTful API的情况下,任何HTTP客户端框架都可以很好地工作。这里的好处是客户端可以用任何语言编写,因为它基本上是发送HTTP请求并验证响应。像Ruby基础的Rspec这样的框架正变得流行,用于这种类型的测试。此外,JAX-RS实现,如Jersey,提供了内置的测试框架,可以用于这个目的。
当单元测试和集成测试结合在一起时,可能会给足够的信心,让认为API已经准备好生产了,但不要被愚弄!如何确保API在生产条件下仍然可以正常工作?
在确信所有功能都正常工作之后,进行性能测试,但也希望了解API在高负载下的表现。许多开发人员忽略了性能测试,但如果打算让多个消费者同时使用API,进行性能测试是非常重要的,以检测潜在的并发错误,这些错误在生产后将非常难以重现和排除故障。这里的想法是从多个线程以随机顺序进行API调用,并在一定时间内监控应用程序的行为。
Apache JMeter是这个目的的完美工具。人们可以很容易地为服务端点创建一个性能测试脚本,并用大量的请求冲击测试机器。性能测试不仅可以很好地揭示应用程序中的并发问题,还可以让有机会监控应用程序在机器上使用CPU和内存资源的情况。在性能测试期间监控这些资源将揭示内存泄漏、硬件资源不足或Web服务器、负载均衡器等的配置不当。可以说,性能测试是构建可扩展应用程序的关键。
监控服务器行为被认为是性能测试的一部分,使用监控工具总是一个好主意。虽然像JMeter这样的工具可以给一个详细的报告,说明服务器对每个请求的响应,但它不知道性能测试时服务器的状态。对于Web请求、数据库操作、服务器资源等的全面监控,New Relic是一个伟大的产品。