Spring Boot微服务的集成测试策略

单元测试是清理代码的必要条件,但是今天的Spring-over-configuration框架(如Spring Boot)通常用于构建由多个服务组成的应用程序。您需要某种方法来确保部件适合放在一起并且您正在使用框架。

自动化测试的优点和重要性

1.测试确保代码中某处的更改不会在其他位置导致意外行为。

2.它们验证代码的行为是否符合设计要求。

3.他们维护良好的编码原则。

4.他们确保产品的各个部分组合在一起。

简而言之:测试可以降低您的产品让客户生气或不快的风险。

有许多方法可以测试,并非上面列出的所有目标都适用于(或根本不适用于)我们使用的每种方法。

单元测试:单独测试并受设计限制

单元测试的规范目的是严格地关注单个代码,每个单元外部代码的调用都被模拟出来,最好是通过mockito或jmockit这样的模拟框架。抽象出所有依赖关系可能是进行单元测试的正确方法,也意味着这些测试绝不能保证整个产品按设计工作。没关系:它们是编码质量的必要条件。

像Spring Boot这样的现代容器环境可以通过配置约定的方式进行设置,提供具有JPA持久性,Web安全性和JSON序列化的完全成熟的服务器应用程序,所有这些都具有最少的样板代码。这很好,但是将所有这些职责交给框架会使检查你正确使用框架变得更加重要,通过单元测试无法保证这一点。一个很好的例子:

@OneToMany(mappedBy="paremt")

private List children

mappedBy属性中的拼写错误将导致持久性框架中的运行时错误

@RequestMapping(value="/my/unique/path", method = RequestMethod.GET)

如果另一个静态控制器中的另一个方法尝试使用与GET http动词相同的/ my / unique / path,则Spring上下文将无法启动。

端到端测试的问题

我们应该测试我们的应用程序,因为它实际上将在生产中运行,没有任何依赖项被模拟或存根。很公平,但在典型的Web应用程序中,这意味着服务于前端和后端,设置和填充数据库,更不用说团队外部的其他服务,无论是已经存在还是正在其他地方开发。网上商店的典型端到端测试将启动浏览器,完成表单中的订单(例如,使用Selenium)点击订单按钮并检查支付服务器,仓库和快递是否得到适当的通知。这些测试在设置和运行方面都很昂贵。

更重要的是,从第一天开始就无法构建它们:在一个庞大的分布式团队中,组件不会以相同的速度开发。在后端团队已经完成其REST端点后,可能没有“PLACE ORDER”按钮可以点击几个月。

使用SpringRunner进行集成测试

通过Spring中的集成测试,您可以在产品组件准备好进行端到端测试之前,超越单元测试的缺点。事实上,集成测试可以而且应该。

在此处下载示例项目:

git clone git @ gitlab.com:jsprengers / springboot-testing-tips.git

该项目包括四个子项目。Weatherstation是网络温度计的实现,atdd包含Cucumber测试。这篇文章的第二部分将介绍端到端测试。

api项目包含WeatherReportDTO传输对象和由weaterstation实现的接口。实际上,这是一个RESTful服务,不需要使用Spring甚至Java来开发weatherstation,但我们假设它是。还有其他方法可以在具体的情况下使用REST api,但是请不要去那里。拥有一个单独的api项目使得weaterstation开发人员可以在不参考weatherserver项目的情况下导入规范,这是一件好事。

请注意,单元测试可能不合适:它无法检查在运行时是否注入了正确的值,它无法检查是否有另一个静态控制器映射到相同/天气网址,而且最不能检查其余客户端是否实际连接到weatherstation服务器并检索正确的结果。所有这些都可以在Spring测试中完成。为简洁起见,省略了实际的@Test方法; 你可以在源代码中找到它们。

@RunWith(SpringRunner.class)

@SpringBootTest(classes = {

WeatherServerApplication.class,

NorthWeatherStation.class, SouthWeatherStation.class},

webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)

@TestPropertySource(properties = {

"weather.host.1000-3000=http://localhost:8090/north/weather",

"weather.host.3001-6000=http://localhost:8090/south/weather",

"weather.host.6001-9999=http://localhost:8090/south/weather"})

public class WeatherServerIntegrationTest {

private RestTemplate restTemplate = new RestTemplate();

private void assertWeatherForPostcode(String postcode, double temperature, String unit, int humidity) {

String url = String.format("http://localhost:8090/weather?postCode=%s&unit=%s", postcode, unit);

WeatherReportDTO temperatureObject = restTemplate.getForObject(url, WeatherReportDTO.class);

assertThat(temperatureObject.getTemperature()).isEqualTo(temperature);

assertThat(temperatureObject.getUnit().name()).isEqualTo(unit);

assertThat(temperatureObject.getHumidity()).isEqualTo(humidity);

}

}

我们的WeatherServerIntegrationTest与任何其他JUnit测试一样运行,但它会启动Spring Boot应用程序并将您的测试类转换为可以使用@Autowired注释注入任何组件或服务的组件。该@SpringBootTest 类属性指向我们的生产应用WeatherServerApplication,你可以添加任意数量的特定测试管理组件或其他的@Configuration类。忍受我,我会去NorthWeatherStation。

我们可以注入WeatherServerEndpoint并调用它的getTemperatureForPostalCode:

@Autowired

WeatherStationRestClient client;

@Test

public void getTemperatureForPostalCodeInFahrenheitShouldBe42(){

WeatherReportDTO report = client.getTemperatureByPostalCode("1234","F");

}

但是,由于SpringRunner已经启动了一个实际的REST服务器,为什么不立即查询端点,使用RestTemplate,一个围绕http客户端和JSON序列化官僚机构的有用包装器。

虽然不是那么快:给定一个有效的邮政编码,我们的服务器将要查询另一个REST端点,即我们迷人但隐居的物联网朋友一起焊接的端点。但那还没有。有两种方法可以解决它。第一种是在我们的服务器中使用模拟REST客户端。我们可以在WeatherStationRestClient类中添加@Profile(“!test”)注释,将带有@Profile(“test”)注释的测试实现添加到Spring测试配置中,上下文将实例化我们的模拟实现而不是生成模拟实现。

凭借很少的设置代码和一些简洁的服务存根,我们现在有一个完全成熟的集成测试,不仅涵盖了我们所有的应用程序逻辑,还测试了我们的REST服务器和天气的测试站客户实际上工作。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181205A17TP500?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券