来源:SpringForAll社区

想要学习更多有关Spring Boot项目的功能测试吗?阅读这篇博客可以让您掌握如何利用Docker容器进行测试。

概览

本文重点介绍如何使用Spring Boot进行功能测试的一些最佳实践。我们将演示如何在不设置模拟环境的情况下将服务作为黑盒测试的高级方法。

本文是我之前这篇文章 Native Integration Testing in Spring Boot 的后续。

因此我将参考上一篇文章来介绍这两种测试方法的区别。

我建议你在阅读这篇文章之前先了解上一篇文章。

理论

让我们从功能测试的定义开始(来自于Techopedia):


  1. 功能测试是在软件开发过程中使用的软件测试流程,通过测试来确保软件符合所有的预期需求。

  2. 功能测试也是一种检查软件的方法,通过这种方法来确保它具有指定的所有必须功能。

虽然这个解释看起来有点让人迷惑,但不需要担心——接下来的定义提供了更进一步的解释(来自于Techopedia):


  1. 功能测试主要用于验证一个软件是否提供最终用户或业务所需要的输出。

  2. 通常,功能测试涉及评估和比较每个软件功能与业务需求。

  3. 通过向软件提供一些相关输入进行测试,以便评估软件的输出并查看其与基本要求相比是符合、关联还是变化的。

  4. 此外,功能测试还可以检查软件的可用性,例如确保导航功能能够按要求工作。

在我们的例子中,我们将微服务作为一个软件,这个软件将根据最终用户的要求来提供一些输出。

目的

功能测试应涵盖我们应用程序的以下方面:

  • 上下文启动 - 这可确保服务在上下文中没有冲突,并且可以在没有问题的情况下进行初始化。

  • 业务需求/用户故事 - 包括所请求的功能。

基本上,每个(或大多数)用户的故事都应该有自己的专用功能测试。

我们不需要编写上下文启动测试,因为只要有一个功能要测试,那么上下文启动无论如何都会被测试到的。

实践

为了演示如何使用我们的最佳实践,我们需要编写一些示范服务代码。

就让我们从头开始吧。

任务

我们的新服务有以下要求:

  • 用于存储和检索用户详细信息的REST API。

  • 通过REST检索联系服务中的联系人详细信息,从而检索用户详细信息的REST API。

架构设计

对于此任务,我们将基于Spring平台作为框架,使用Spring Boot作为应用的启动者。

为了存储用户详细信息,我们将使用MariaDB数据库。

由于服务应存储和检索用户详细信息,因此将其命名为用户详细信息服务是合乎逻辑的。

在实现之前,应该使用组件图来更好地理解系统的主要组件:

实现

以下示例代码包含许多Lombok注释。

您可以在网站上的docs文件中找到每个注释的说明。

模型

用户详情模型


  1. @Value(staticConstructor = "of")

  2. public class UserDetails {

  3. String firstName;

  4. String lastName;

  5. public static UserDetails fromEntity(UserDetailsEntity entity) {

  6. return UserDetails.of(entity.getFirstName(), entity.getLastName());

  7. }

  8. public UserDetailsEntity toEntity(long userId) {

  9. return new UserDetailsEntity(userId, firstName, lastName);

  10. }

  11. }

用户联系人模型:


  1. @Value

  2. public class UserContacts {

  3. String email;

  4. String phone;

  5. }

具有汇总信息的用户类:


  1. @Value(staticConstructor = "of")

  2. public class User {

  3. UserDetails userDetails;

  4. UserContacts userContacts;

  5. }

REST API


  1. @RestController

  2. @RequestMapping("user")

  3. @AllArgsConstructor

  4. public class UserController {

  5. private final UserService userService;

  6. @GetMapping("/{userId}") //1

  7. public User getUser(@PathVariable("userId") long userId) {

  8. return userService.getUser(userId);

  9. }

  10. @PostMapping("/{userId}/details") //2

  11. public void saveUserDetails(@PathVariable("userId") long userId, @RequestBody UserDetails userDetails) {

  12. userService.saveDetails(userId, userDetails);

  13. }

  14. @GetMapping("/{userId}/details") //3

  15. public UserDetails getUserDetails(@PathVariable("userId") long userId) {

  16. return userService.getDetails(userId);

  17. }

  18. }

  1. 按ID获取用户汇总数据

  2. 按ID保存用户的用户详细信息

  3. 按ID获取用户详细信息

客户联系人服务


  1. @Component

  2. public class ContactsServiceClient {

  3. private final RestTemplate restTemplate;

  4. private final String contactsServiceUrl;

  5. public ContactsServiceClient(final RestTemplateBuilder restTemplateBuilder,

  6. @Value("${contacts.service.url}") final String contactsServiceUrl) {

  7. this.restTemplate = restTemplateBuilder.build();

  8. this.contactsServiceUrl = contactsServiceUrl;

  9. }

  10. public UserContacts getUserContacts(long userId) {

  11. URI uri = UriComponentsBuilder.fromHttpUrl(contactsServiceUrl + "/contacts")

  12. .queryParam("userId", userId).build().toUri();

  13. return restTemplate.getForObject(uri, UserContacts.class);

  14. }

  15. }

详细信息实体及其存储库


  1. @Entity

  2. @Data

  3. @NoArgsConstructor

  4. @AllArgsConstructor

  5. public class UserDetailsEntity {

  6. @Id

  7. private Long id;

  8. @Column

  9. private String firstName;

  10. @Column

  11. private String lastName;

  12. }

  13. @Repository

  14. public interface UserDetailsRepository extends JpaRepository<UserDetailsEntity, Long> {

  15. }

用户服务


  1. @Service

  2. @AllArgsConstructor

  3. public class UserService {

  4. private final UserDetailsRepository userDetailsRepository;

  5. private final ContactsServiceClient contactsServiceClient;

  6. public User getUser(long userId) {

  7. UserDetailsEntity userDetailsEntity = userDetailsRepository.getOne(userId); //1

  8. UserDetails userDetails = UserDetails.fromEntity(userDetailsEntity);

  9. UserContacts userContacts = contactsServiceClient.getUserContacts(userId); //2

  10. return User.of(userDetails, userContacts); //3

  11. }

  12. public void saveDetails(long userId, UserDetails userDetails) {

  13. UserDetailsEntity entity = userDetails.toEntity(userId);

  14. userDetailsRepository.save(entity);

  15. }

  16. public UserDetails getDetails(long userId) {

  17. UserDetailsEntity userDetailsEntity = userDetailsRepository.getOne(userId);

  18. return UserDetails.fromEntity(userDetailsEntity);

  19. }

  20. }

  1. 从DB检索用户详细信息

  2. 从联系人服务中检索用户联系人

  3. 返回具有汇总数据的用户

应用启动类和它的配置文件

UserDetailsServiceApplication.java


  1. @SpringBootApplication

  2. public class UserDetailsServiceApplication {

  3. public static void main(String[] args) {

  4. SpringApplication.run(UserDetailsServiceApplication.class, args);

  5. }

  6. }

application.properties:


  1. #contact service

  2. contacts.service.url=http://www.prod.contact.service.com

  3. #database

  4. user.details.db.host=prod.maria.url.com

  5. user.details.db.port=3306

  6. user.details.db.schema=user_details

  7. spring.datasource.url=jdbc:mariadb://${user.details.db.host}:${user.details.db.port}/${user.details.db.schema}

  8. spring.datasource.username=prod-username

  9. spring.datasource.password=prod-password

  10. spring.datasource.driver-class-name=org.mariadb.jdbc.Driver

Mavan配置文件pom.xml


  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  4. <modelVersion>4.0.0</modelVersion>

  5. <artifactId>user-details-service</artifactId>

  6. <version>0.0.1-SNAPSHOT</version>

  7. <packaging>jar</packaging>

  8. <name>User details service</name>

  9. <parent>

  10. <groupId>com.tdanylchuk</groupId>

  11. <artifactId>functional-tests-best-practices</artifactId>

  12. <version>0.0.1-SNAPSHOT</version>

  13. </parent>

  14. <dependencies>

  15. <dependency>

  16. <groupId>org.springframework.boot</groupId>

  17. <artifactId>spring-boot-starter-data-jpa</artifactId>

  18. </dependency>

  19. <dependency>

  20. <groupId>org.springframework.boot</groupId>

  21. <artifactId>spring-boot-starter-web</artifactId>

  22. </dependency>

  23. <dependency>

  24. <groupId>org.projectlombok</groupId>

  25. <artifactId>lombok</artifactId>

  26. <scope>provided</scope>

  27. </dependency>

  28. <dependency>

  29. <groupId>org.mariadb.jdbc</groupId>

  30. <artifactId>mariadb-java-client</artifactId>

  31. <version>2.3.0</version>

  32. </dependency>

  33. </dependencies>

  34. <build>

  35. <plugins>

  36. <plugin>

  37. <groupId>org.springframework.boot</groupId>

  38. <artifactId>spring-boot-maven-plugin</artifactId>

  39. </plugin>

  40. </plugins>

  41. </build>

  42. </project>

注意:父级是自定义的functional-tests-best-practices项目,它继承了spring-boot-starter-parent。稍后将对此进行说明。

目录结构

这几乎是我们为满足初始要求所需要的一切:保存和检索用户详细信息,通过联系人检索的用户详细信息。

功能测试

是时候添加功能测试了!对于TDD(测试驱动开发)是什么,您需要在具体实现之前阅读本节。

位置

在开始之前,我们需要选择功能测试的位置;

有两个相对合适的地方:

  • 通过一个独立的文件夹放在单元测试下:

    这是开始添加功能测试最简单,最快速的方法,虽然它有一个很大的缺点:如果你想单独运行单元测试,你需要排除功能测试文件夹。

    那为什么不能每次修改代码时都运行所有测试呢?

    因为功能测试在大多数情况下与单元测试相比具有巨大的执行时间,因此应单独运行以节省开发时间。

  • 做为一个独立项目放置在父项目下:

  1. 父POM(聚合项目)

  2. Service项目

  3. 功能测试项目

    这种方法优于前一种方法 - 我们在服务单元测试中有一个独立的功能测试模块,因此我们可以通过单独运行单元测试或功能测试来轻松验证逻辑。另一方面,这种方法需要一个多模块项目结构,与单模块项目相比,这种结构更加困难。

    您可能已经从service的pom.xml中猜到,对于我们的情况,我们将选择第二种方法。

父pom.xml文件


  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  4. <modelVersion>4.0.0</modelVersion>

  5. <groupId>com.tdanylchuk</groupId>

  6. <artifactId>functional-tests-best-practices</artifactId>

  7. <version>0.0.1-SNAPSHOT</version>

  8. <packaging>pom</packaging>

  9. <name>Functional tests best practices parent project</name>

  10. <parent> <!--1-->

  11. <groupId>org.springframework.boot</groupId>

  12. <artifactId>spring-boot-starter-parent</artifactId>

  13. <version>2.0.4.RELEASE</version>

  14. <relativePath/>

  15. </parent>

  16. <modules> <!--2-->

  17. <module>user-details-service</module>

  18. <module>user-details-service-functional-tests</module>

  19. </modules>

  20. <properties>

  21. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

  22. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

  23. <java.version>1.8</java.version>

  24. </properties>

  25. </project>

  1. spring-boot-starter-parent是父POM的父项目。通过这种方式,我们为Spring提供了依赖管理。

  2. 模块声明。注意:顺序很重要,功能测试应始终放置在最后。

案例

对于挑选案例以涵盖功能测试,我们需要考虑两件大事:

  • 功能需求 - 基本上,每个需求都应有自己的功能测试。

  • 执行时间长 - 案例应该侧重于应用程序的关键部分,这与单元测试相反的是它还要涵盖每个次要案例。否则,构建时间将是巨大的。

构建

是的,测试还需要考虑构建文件,尤其是那些受执行时间影响程度比较大的功能测试,比如一些业务逻辑可能会随着时间的推移变得过于复杂。

此外,功能测试应该是可维护的。这意味着,在功能转换的情况下,功能测试对开发人员来说不是一件令人头疼的问题。

步骤

步骤(也称为固定节点)是一种封装每个通信通道逻辑的方法。

每个通道都应有自己的步骤对象,与其他步骤隔离。

在我们的例子中,我们有两个通信渠道:

  • 用户详细信息服务的REST API(输入频道)

  • 联系人服务的REST API(输出频道)

对于输入频道的REST,我们将使用名为REST Assured的库。

与我们之前使用MockMvc进行验证REST API的集成测试相比,这里我们使用更多的黑盒测试来避免测试模拟对象破坏Spring的上下文。

至于REST输出频道,我们将使用WireMock。

我们不会让Spring用REST模板替换模拟的模板。

相反,WireMock引擎使用的jetty服务器将与我们的服务一起启动,以模拟真正的外部REST服务。

用户详情步骤


  1. @Component

  2. public class UserDetailsServiceSteps implements ApplicationListener<WebServerInitializedEvent> {

  3. private int servicePort;

  4. public String getUser(long userId) {

  5. return given().port(servicePort)

  6. .when().get("user/" + userId)

  7. .then().statusCode(200).contentType(ContentType.JSON).extract().asString();

  8. }

  9. public void saveUserDetails(long userId, String body) {

  10. given().port(servicePort).body(body).contentType(ContentType.JSON)

  11. .when().post("user/" + userId + "/details")

  12. .then().statusCode(200);

  13. }

  14. public String getUserDetails(long userId) {

  15. return given().port(servicePort)

  16. .when().get("user/" + userId + "/details")

  17. .then().statusCode(200).contentType(ContentType.JSON).extract().asString();

  18. }

  19. @Override

  20. public void onApplicationEvent(@NotNull WebServerInitializedEvent webServerInitializedEvent) {

  21. this.servicePort = webServerInitializedEvent.getWebServer().getPort();

  22. }

  23. }

就像你从steps对象中看到的,每个API都有自己的方法。

默认情况下,REST Assured将访问localhost的API,但需要指定端口号否则我们的服务将使用随机端口来启动。

为了区分端口号,我们应该从WebServerInitializedEvent中去获取端口。

注意:@LocalServerPort注解不能在此处使用,因为在Spring Boot-embedded容器启动之前就初始化了步骤的bean。

联系人服务步骤


  1. @Component

  2. public class ContactsServiceSteps {

  3. public void expectGetUserContacts(long userId, String body) {

  4. stubFor(get(urlPathMatching("/contacts")).withQueryParam("userId", equalTo(String.valueOf(userId)))

  5. .willReturn(okJson(body)));

  6. }

  7. }

在这里,我们需要以与从我们的应用程序调用远程服务时相同的方式来模拟服务器的端口、参数等。

数据库

我们的服务是将数据存储在Maria DB中,但就功能测试而言,数据的存储位置并不重要,因此按照黑盒测试的要求,我们不需要在测试中提及数据库。

假设在未来,我们考虑将Maria DB更改为某些NoSQL解决方案,那么功能测试应不需要做出改动。

那么,为此解决方案是什么?

当然,我们可以使用嵌入式解决方案,就像在集成测试中使用H2数据库一样,但在生产时,我们的服务又将使用Maria DB,这可能会导致某些地方出错。

例如,我们有一个名为MAXVALUE的列,并针对H2运行测试,一切正常。但是,在生产中,服务失败了,因为这是MariaDB中的一个预留关键字,这意味着我们的测试不如预期的那么好,并且在将服务发布之前可能浪费大量时间来解决这个问题。

避免这种情况的唯一方法是在测试中使用真正的Maria DB。同时,我们需要确保我们的测试可以在本地执行,而无需设置Maria DB的任何其他临时环境。

为了解决这个问题,我们使用testcontainers项目,该项目提供常见的轻量级数据库实例:可以在Selenium Web浏览器或Docker容器中运行的。

但testcontainers库不支持Spring Boot的开箱即用。因此,我们将使用另一个名为testcontainers-spring-boot的库,而不是为MariaDB编写自定义的Generic Container并将其注入Spring Boot。testcontainers-spring-boot支持最常用的技术,并可以直接在您的服务中使用:MariaDB,Couchbase,Kafka,Aerospike,MemSQL,Redis,neo4j,Zookeeper,PostgreSQL,ElasticSearch等等。

要将真正的Maria DB注入我们的测试,我们只需要将相应的依赖项添加到我们的user-details-service-functional-tests项目pom.xml文件中,如下所示:


  1. <dependency>

  2. <groupId>com.playtika.testcontainers</groupId>

  3. <artifactId>embedded-mariadb</artifactId>

  4. <version>1.9</version>

  5. <scope>test</scope>

  6. </dependency>

如果您的服务不使用Spring Cloud,则应把下述依赖跟上述依赖一并添加:


  1. <dependency>

  2. <groupId>org.springframework.cloud</groupId>

  3. <artifactId>spring-cloud-context</artifactId>

  4. <version>2.0.1.RELEASE</version>

  5. <scope>test</scope>

  6. </dependency>

它需要在Spring Boot上下文启动之前为dockerized资源进行引导。

这种方法显然有很多优点。

由于我们拥有“真实”资源,因此如果无法对所需资源进行真正的连接测试,则无需在代码中编写解决办法。

不幸的是,这个解决方案带来了一个巨大的缺点 - 测试只能在安装Docker的环境中运行。

这意味着您的工作站和CI工具应该安装Docker。

此外,您应该准备好更多的时间来执行您的测试。

父测试类

因为执行时间很重要,所以我们需要避免对每个测试进行多个上下文加载,因此Docker容器将对所有测试仅启动一次。

Spring默认启用了上下文缓存功能,但我们需要小心使用:通过添加简单的@MockBean注解,我们将强制Spring为模拟的bean创建一个新的上下文而不是重用现有的上下文。

此问题的解决方案是创建单个父抽象类,该类将包含所有需要的Spring注解,以确保所有测试套件重用单个上下文:


  1. @RunWith(SpringRunner.class)

  2. @SpringBootTest(

  3. classes = UserDetailsServiceApplication.class, //1

  4. webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)//2

  5. @ActiveProfiles("test")//3

  6. public abstract class BaseFunctionalTest {

  7. @Rule

  8. public WireMockRule contactsServiceMock = new WireMockRule(options().port(8777)); //4

  9. @Autowired //5

  10. protected UserDetailsServiceSteps userDetailsServiceSteps;

  11. @Autowired

  12. protected ContactsServiceSteps contactsServiceSteps;

  13. @TestConfiguration //6

  14. @ComponentScan("com.tdanylchuk.user.details.steps")

  15. public static class StepsConfiguration {

  16. }

  17. }

  1. 指定Spring Boot的测试注解以加载我们服务的主要配置类。

  2. 引导使用Web生产环境(默认情况下将使用模拟的环境)。

  3. 测试环境的配置项被加载在了application-test.properties文件中,其中包含了一些生产环境的属性,例如URL,用户,密码等。

  4. WireMockRulet通过启动jetty服务器以便在提供的端口上进行连接。

  5. 步骤是自动加载为protected属性的,这样每一个步骤会在每一个测试得到访问。

  6. @TestConfiguration注解会通过扫描包名来加载上下文的步骤。

在这里,我们试图不修改上下文,这样的好处时在后期用于生产环境时,只需要往上下文添加一些util项就可以了,例如步骤和属性覆盖。

使用@MockBean注解是不好的做法,因为它会用mock替换部分应用程序,所以这部分程序将是没有经过测试的。

在不可避免的情况下 - 例如在业务中获取当前时间System.currentTimeMillis(),这样的代码应该被重构,最好使用Clock对象:clock.millis()。并且,在功能测试中,应该模拟Clock对象以便验证结果。

测试属性

application-test.properties:


  1. #contact service                                          #1

  2. contacts.service.url=http://localhost:8777

  3. #database                                                 #2

  4. user.details.db.host=${embedded.mariadb.host}

  5. user.details.db.port=${embedded.mariadb.port}

  6. user.details.db.schema=${embedded.mariadb.schema}

  7. spring.datasource.username=${embedded.mariadb.user}

  8. spring.datasource.password=${embedded.mariadb.password}

  9. #3

  10. spring.jpa.hibernate.ddl-auto=create-drop

  1. 使用WireMock jetty服务器节点而不是生产环境下的联系人服务URL。

  2. 数据库属性的重载。注意:这些属性由spring-boo-test-containers库提供。

  3. 在测试中,数据库的表将由Hibernate创建。

自我测试

为了进行这项测试,我们做了很多准备工作,让我们先瞅一眼:


  1. public class RestUserDetailsTest extends BaseFunctionalTest {

  2. private static final long USER_ID = 32343L;

  3. private final String userContactsResponse = readFile("json/user-contacts.json");

  4. private final String userDetails = readFile("json/user-details.json");

  5. private final String expectedUserResponse = readFile("json/user.json");

  6. @Test

  7. public void shouldSaveUserDetailsAndRetrieveUser() throws Exception {

  8. //when

  9. userDetailsServiceSteps.saveUserDetails(USER_ID, userDetails);

  10. //and

  11. contactsServiceSteps.expectGetUserContacts(USER_ID, userContactsResponse);

  12. //then

  13. String actualUserResponse = userDetailsServiceSteps.getUser(USER_ID);

  14. //expect

  15. JSONAssert.assertEquals(expectedUserResponse, actualUserResponse, false);

  16. }

  17. }

在以前,打桩和断言都是通过JSON文件来创建使用的。这种方式,把请求和响应的格式同时进行了验证。但在功能测试里,我们最好不要使用确定格式的测试数据,而是使用生产环境中请求/响应数据的副本。

由于整个逻辑封装在步骤、配置和JSON文件中,如果改动与功能无关,那么测试将保持不变。例如:

  • 响应数据格式的更改 - 只应修改JSON测试文件。

  • 联系人服务节点更改 - 应修改ContactsServiceSteps对象。

  • Maria DB替换为No SQL DB - 应修改pom.xml和test properties文件。

功能测试项目的POM文件


  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  4. <modelVersion>4.0.0</modelVersion>

  5. <artifactId>user-details-service-functional-tests</artifactId>

  6. <version>0.0.1-SNAPSHOT</version>

  7. <name>User details service functional tests</name>

  8. <parent>

  9. <groupId>com.tdanylchuk</groupId>

  10. <artifactId>functional-tests-best-practices</artifactId>

  11. <version>0.0.1-SNAPSHOT</version>

  12. </parent>

  13. <dependencies>

  14. <dependency> <!--1-->

  15. <groupId>com.tdanylchuk</groupId>

  16. <artifactId>user-details-service</artifactId>

  17. <version>${project.version}</version>

  18. <scope>test</scope>

  19. </dependency>

  20. <dependency>

  21. <groupId>org.springframework.boot</groupId>

  22. <artifactId>spring-boot-starter-test</artifactId>

  23. <scope>test</scope>

  24. </dependency>

  25. <dependency>

  26. <groupId>org.springframework.cloud</groupId>

  27. <artifactId>spring-cloud-context</artifactId>

  28. <version>2.0.1.RELEASE</version>

  29. <scope>test</scope>

  30. </dependency>

  31. <dependency>

  32. <groupId>com.playtika.testcontainers</groupId>

  33. <artifactId>embedded-mariadb</artifactId>

  34. <version>1.9</version>

  35. <scope>test</scope>

  36. </dependency>

  37. <dependency>

  38. <groupId>com.github.tomakehurst</groupId>

  39. <artifactId>wiremock</artifactId>

  40. <version>2.18.0</version>

  41. <scope>test</scope>

  42. </dependency>

  43. <dependency>

  44. <groupId>io.rest-assured</groupId>

  45. <artifactId>rest-assured</artifactId>

  46. <scope>test</scope>

  47. </dependency>

  48. </dependencies>

  49. </project>

用户详细信息服务作为依赖项添加,因此它可以由SpringBootTest进行加载。

目录结构

总而言之,我们有了下一个项目的目录结构。

向服务添加功能不会改变当前目录结构,只会扩展它。

通过添加额外的步骤,比如添加了更多的通信渠道,那么utils文件夹下可以添加很多常用方法;

比如带有测试数据的新文件; 当然还有针对每个功能要求的附加测试。

总结

在本文中,我们基于给定的要求构建了一个新的微服务,并通过功能测试来满足这些要求。

在测试中,我们使用黑盒类型的测试,我们尝试不改变应用程序的内部部分,而是作为普通客户端从外部与它进行通信,以尽可能多地模拟生产行为。

同时,我们奠定了功能测试架构的基础,因此未来的服务更改不需要重构现有测试,并且尽可能将添加新的测试简单化。

这个项目的源代码都可以在GitHub上找到。

原文链接:https://dzone.com/articles/advanced-functional-testing-in-spring-boot-by-usin

作者:Taras Danylchuk

译者:liumapp

·END·

近期热文:

  • Spring Cloud Stream同一通道根据消息内容分发不同的消费逻辑

  • Git 版本控制之 GitFlow

  • 彻底搞懂 Git-Rebase

  • 我说分布式事务之最大努力通知型事务

  • 我说分布式事务之TCC

  • 让你的系统“坚挺不倒”的最后一个大招——「降级」

  • 不可错过的CMS学习笔记

  • 致敬| 她永远地离开了,但我们依然每天收益于您的伟大发明!

  • 在生产中使用Java 11:需要了解的重要事项

  • 如何在到处是“雷”的系统中「明哲保身」?这是第一招

  • 可能是最全面的G1学习笔记

看完,赶紧点个“好看”鸭

点鸭点鸭

↓↓↓↓

在Docker中运行Spring Boot的高级功能测试相关推荐

  1. 8s 使用本地打包镜像_在Docker环境构建、打包和运行Spring Boot应用

    为何考虑采用Docker? Docker是提供用户构建镜像的一种容器化技术,所构建的镜像包含了主要的应用程序和运行应用所需的所有依赖项.该镜像可在任何虚拟机或物理机器上的Docker容器上运行.它的强 ...

  2. docker build -t_在Docker环境构建、打包和运行Spring Boot应用

    为何考虑采用Docker? Docker是提供用户构建镜像的一种容器化技术,所构建的镜像包含了主要的应用程序和运行应用所需的所有依赖项.该镜像可在任何虚拟机或物理机器上的Docker容器上运行.它的强 ...

  3. eclipse中run运行不了_Springboot专辑:运行 Spring Boot 应用的 3 种方式!

    一:在 IDE 中运行 在 Eclipse.IDEA 中直接运行,又有以下两种方式. jar 包方式 Spring Boot 默认采用 jar 包内嵌 Tomcat.Jetty 等 Server 的方 ...

  4. Docker容器及Spring Boot微服务应用

    2019独角兽企业重金招聘Python工程师标准>>> Docker容器及Spring Boot微服务应用 1 什么是Docker 1.1 Docker的出现 问题一:项目实施环境复 ...

  5. 如何在STS中创建Spring Boot项目

    你好朋友, 在本教程中,我们将逐步介绍如何在STS(Spring工具套件)中创建Spring Boot项目. 步骤1: 如果您尚未从相应的操作系统的以下链接中下载Spring工具套件,请下载: htt ...

  6. Spring boot(6)---在Eclipse中搭建Spring boot 项目

    Spring boot入门:在Eclipse中搭建Spring boot 项目 Eclipse中的STS插件 打开Eclipse-Help-Eclipse Marketplace-popular 下载 ...

  7. 第64节:Java中的Spring Boot 2.0简介笔记

    Java中的Spring Boot 2.0简介笔记 spring boot简介 依赖java8的运行环境 多模块项目 打包和运行 spring boot是由spring framework构建的,sp ...

  8. docker保护python源码_Tensorflow在Docker中运行和源码编译

    本文分享在在Docker中运行Tensorflow和进行源码编译的方法和步骤,包括:编译.构建docker镜像.创建和运行Docker容器.部署完的容器可以通过Jupyter Notebook进行访问 ...

  9. ASP.NET Core 网站在Docker中运行

    Docker作为新一代的虚拟化方式,未来肯定会得到广泛的应用,传统虚拟机的部署方式要保证开发环境.测试环境.UAT环境.生产环境的依赖一致性,需要大量的运维人力,使用Docker我们可以实现一次部署, ...

最新文章

  1. 我专严新闻小偷之心得与大家交流
  2. 鱼油账号记录程序 - 零基础入门学习Delphi38
  3. c#创建可以为空类型
  4. Java后端:Linux的基本使用学习笔记
  5. 开篇词 | 别说你没被安全困扰过
  6. 世界杯千万级直播高稳定的挑战和实践
  7. 计算机的主机主要由什么和什么不同,台式电脑主机由什么硬件组成?
  8. 【报告分享】2020年上半年中国直播电商行业发展分析报告.pdf(附下载链接)...
  9. GPS定位系统及协议介绍
  10. python中的散点图还可以这么画
  11. 去除nginx.conf文件中注释和空格行方法
  12. OPTIONS预检请求
  13. 中国探月计算机考试时间,关于选拔2020年波兰罗兹大嫦娥三号登月时间 学暑期实习实训项目学员的通知...
  14. Android前台服务讲解二之自定义通知视图(RemoteViews)及数据UI更新
  15. 什么是市场?市场营销基础入门
  16. CSS浮动,平铺,鼠标状态
  17. Oracle 11g RAC 搭建详细步骤
  18. mybatis反序列化,自动生成代码
  19. WEB前端--SEO优化学习笔记(9)
  20. pylon保存图片_pylon界面中文说明-德国basler工业相机

热门文章

  1. java反序列化漏洞的一些gadget
  2. python3 pymysql 查询结果转字典dict
  3. 关于STL中的map和hash_map
  4. 穿透Socks5 代理的UDP编程
  5. linux insmod命令参数及用法详解--linux加载模块命令
  6. ipconfig的C语言实现
  7. Android实现手机手电筒
  8. Ubuntu安装google拼音输入法
  9. c语言strcat_s用于调用的参数太少_为什么系统调用会消耗较多资源呢
  10. leetcode10 为什么p[j-1] == '*'的时候,不能用递推公式dp[i][j] = dp[i][j-1] || dp[i][j-2] || dp[i-1][j]