微服务测试的不同策略之测试Java微服务,看完秒懂
测试Java 微服务
在开发新应用程序时,我们也不应该忘记自动化测试。如果考虑使用基于微服务的架构,这些则特别重要。测试微服务所需要采用的方法与测试一体化应用程序所采用的方法不同。就一体化应用程序而言,主要关注的是单元测试和集成测试,以及数据库层。
而在微服务的情况下,最重要的是以尽可能最细的粒度为每个通信提供覆盖。虽然每个微服务都是独立开发和发布的,但其中一个微服务的更改可能会影响与该服务交互的所有其他服务。它们之间的通信是通过消息实现的。一般来说,这些是通过REST或AMQP协议发送的消息。
本章将要讨论的主题包括:
口Spring的自动化测试支持。
口SpringBoot微服务的组件和集成测试之间的差异。
口使用Pact实现契约测试。
口使用Spring Cloud Contract实现契约测试。
口使用Gatling实现性能测试。
测试策略
有5种不同的微服务测试策略,前3个策略与一体化应用程序相同。
口单元测试(UnitTest) :通过单元测试,开发人员可以测试最小的代码片段,例如,单个方法或组件,并模拟其他方法和组件的每次调用。有许多流行的框架均支持Java中的单元测试,如JUnit、TestNG和Mockito (用于模拟)。此类测试的主要任务是确认实现符合要求。单元测试可以是一个强大的工具,尤其是与测试驱动开发模式相结合时。
口集成测试 (Integration Test) :仅使用单元测试并不能保证开发人员可以验证整个系统的行为。集成测试采用模块并尝试一起测试它们。 这种方法使开发人员有机会在子系统中测试通信路径。我们将测试组件之间的交互和通信,这些组件基于与模拟的外部服务的接口。在基于微服务的系统中,可以使用集成测试以包括其他微服务、数据源或高速缓存。
口端到端测试 (End-to-End Test) :端到端测试也称功能测试(Function Test)。这些测试的主要目标是验证系统是否满足外部要求。这意味着我们应该设计测试方案来测试参与该过程的所有微服务。良好的端到端测试设计并非易事。由于我们需要测试整个系统,因而特别强调测试的方案设计非常重要。
口契约测试(ContractTest):契约测试用于确保微服务的显式和隐式契约按预期工作。当使用者与组件的接口集成以便使用它时,总是形成契约。通常,在基于微服务的系统中,存在单个组件的许多使用者。他们每个人通常都需要一份满足其要求的不同契约。遵循这些假设,每个使用者都负责源组件的接口行为。
口组件测试 (Component Test) :在完成微服务中所有对象和方法的单元测试之后,开发人员应该单独测试整个微服务。为了单独运行测试,开发人员需要模拟(Mock)或桩(Stub) 其他微服务的调用。外部数据存储应替换为等效的内存数据存储,这也提供了显著的测试性能改进。
契约测试和组件测试之间的差异是显而易见的。图13.1说明了我们的示例order-service微服务中契约测试和组件测试之间的差异。
现在,有一个问题是:我们是否确实需要两个额外的策略来测试基于微服务的系统?
通过适当的单元和集成测试,开发人员就可以确认组成微服务的各个组件的实现是否正确。但是,如果没有针对微服务的更具体的测试策略,开发人员将无法确定它们如何协同工作以满足业务需求。
因此,我们增加了组件测试和契约测试。这是一个非常重要的变化,它可以帮助我们理解组件测试、契约测试和集成测试之间的差异。由于组件测试是与外界隔离进行的,因而集成测试将负责验证与该世界的交互。这就是为什么我们应该为组件测试提供桩以进行集成测试。契约测试很像集成测试,强调微服务之间的交互,但是契约测试会将微服务视为黑盒(Black Box)并仅验证响应的格式。
一旦为微服务提供功能测试,开发人员还应该考虑性能测试(PerformanceTest)。我们将区分以下性能测试策略。
口负载测试(Load Test) :这些测试用于确定系统在正常和预期负载条件下的行为。这里的主要思想是识别一些弱点,如响应时间延迟、异常中断,或者如果未正确设置网络超时,则重试次数过多等。
口压力测试(Stress Test) :这些测试将检测系统的上限,以检查它在极重负载下的行为。除负载测试外,它还检查内存泄漏、安全问题和数据损坏。它可能使用与负载测试相同的工具。
图13.2说明了在系统上执行所有测试策略的逻辑顺序。我们将从最简单的单元测试开始,它将验证软件的各个小段,然后逐步经过下一个阶段,直至最终完成压力测试,将整个系统推向极限。
测试 Spring Boot应用程序
正如第13.1节所述,在应用程序测试中有一些不同的测试策略和方法。前文已经简要提到了所有这些理论知识,所以现在我们可以进入实际环节。Spring Boot 提供了一组有助于实现自动化测试的实用程序。为了在项目中启用这些功能,必须将spring-boot-starter-test启动器包含在依赖项中。它不仅会导入spring- test和spring-boot-test工件,还会导入一些其他有用的测试库,如JUnit. Mockito 和AssertJ。
<dependency>
<groupId>org .springf ramework . boot</groupId>
<artifactId>spring-boot-starter-test</atifactId>
<scope>test</ scope>
</dependency>
构建示例应用程序
在开始进行自动化测试之前,需要出于测试目的而准备一个示例业务逻辑。我们可以使用本书前面章节中的相同示例系统,但必须对其进行一些修改。到目前为止,我们从未使用外部数据源来存储和收集测试数据。在本章中,为了说明不同策略如何处理持久性测试问题,则有必要这样做。现在,每个服务都应该有自己的数据库,当然,在通常情况下,选择哪个数据库并不重要。Spring Boot支持多种解决方案,包括关系数据库和NoSQL数据库。我们决定使用的数据库是Mongo.我们先来了解一下 示例系统的架构。图13.3显示的当前示例系统架构的模型就已经考虑了在每个服务中都采用专用数据库的假设。
与数据库集成
为了对我们的Spring Boot应用程序启用Mongo支持,可以将springoboot-starter-data-mongo启动器包含在依赖项中。该项目提供了一些有趣的功能来简化与MongoDB的集成。在这些功能中,值得一提的是特定的富对象映射( Rich Object Mapping) 一MongoTemplate,当然还有对其他Spring Data项目众所周知的存储库编写风格的支持。
以下是pom.xml中所需的依赖项声明。
<dependency>
<groupId>org .springframework. boot</groupId>
<artifactId>spring-boot-starter -data -mongodb</arti factid>
</ dependency>
可以使用Docker镜像轻松启动MongoDB的实例。运行以下命令即可启动Docker容器并在端口27017上公开Mongo数据库。
docker run --name mongo -P 27017:27017 -d mongo
为了将应用程序与先前启动的数据源连接,开发人员应该覆盖aplication.yml中的一些自动配置设置。这可以通过spring .data .mongodb.*属性来实现。
spring:
application:
name: account-service
data:
mongodb :
host: 192.168.99.100
port: 27017
database: micro
username: micro
password: micro123
前文已经提到了对象映射功能。Spring Data Mongo提供了一些可用于此的注解。 存储在数据库中的每个对象都应该使用@Document注解。目标集合的主键是一个12字节的字符串,应该使用Spring Data @Id在每个映射的类中指示。以下是Account对象实现的代码片段。
@ Document
public class Account {
@Id
private String id;
private String number ;
private int balance ;
private String customerId;
// ...
}
单元测试
前面花了较多时间来描述与MongoDB的集成。但是,测试持久性是自动化测试的关键点之一,因而正确配置它非常重要。现在,我们可以继续进行测试的实现。Spring Test可以为最典型的测试方案提供支持,如通过REST 客户端与其他服务集成或与数据库集成。我们有一组库可以让开发人员轻松模拟与外部服务的交互,这对于单元测试来说尤为重要。
以下测试类是Spring Boot应用程序的典型单元测试实现。我们使用了JUnit 框架,它是Java的事实标准。这里使用Mockito库来替换真实的存储库和控制器及其桩。这种方法使我们可以轻松验证@Contoller类实现的每个方法的正确性。测试与外部组件隔离进行,这是单元测试的主要假设。
@RunWith (SpringRunner.class)
@WebMvcTest (AccountController .class)
public class AccountControllerUnitTest {
ObjectMapper mapper = new objectMapper();
@Autowired
MockMvC mvc;
@MockBean
AccountRepository repository;
@Test
public void testAdd() throws Exception {
Account account = new Account ("1234567890", 5000, "1");
when (repository. save (Mockito. any (Account.class))) . thenReturn (new
Account ("1", "1234567890", 5000, "1") );
mvc .perform (post ("/") .contentType (MediaType .APPLICATION JSON) .
content (mappe r.writeValueAsString (account) ) )
.andExpect (status() .isOk( ) );
}
@Test
public void testwithdraw() throws Exception {
Account account - new Account ("1", "1234567890", 5000, "1") ;
when (repository. findOne ("1")) . thenReturn (account) ;
when (repository . save (Mockito. any (Account.class))) . thenAnswer (new
Answer<Account>() {
@Override
public Account answer (InvocationOnMock invocation) throws
Throwable {
Account a = invocation. getArgumentAt (0,Account.class) ;
return a;
}
} ) ;
mve. perform (put ("/withdraw/1/1000"))
. andExpect (status() . isOk())
. andExpect (content () . contentType (MediaType。APPLICATION JSON UTF8) )
. andExpect (jsonPath("$ .balance", is (4000));
好消息是,开发人员可以轻松地模拟Feign客户端通信(特别是在微服务环境中)。以下示例测试类将通过调用account-service服务公开的端点来验证用于提取资金的order-service服务的端点。你可能已经注意到,该端点又由先前引入的测试类进行了测试。以下是order-service服务的单元测试实现的类。
@RunWith (SpringRunner .class)
@WebMvcTest (OrderController.class)
public class OrderControllerTest {
@Autowired
MockMve mvc ;
@MockBean
OrderRepository repository;
@MockBean
AccountClient accountClient ;
@Test
public void testAccept () throws Exception {
Order order = new Order ("1", Orderstatus . ACCEPTED, 2000,"1", "1",
null) ;
when (repository. findone ("1")) . thenReturn(order) ;
when (accountClient .Wi thdraw (order . getAccountId(),
order .getPrice())) . thenReturn (new Account("1", "123", 0));
when (repository . save (Mockito.any (Order .class))) . thenAnswer (new
Answer<order>( ) {
@Override
public Order answer (InvocationOnMock invocation) throws
Throwable {
Order O= invocation . getArgumentAt (0,Order.class) ;
return O;
}
} ) ;
mvc . perform(put("/1"))
. andExpect (status( ) .isOk( ) )
. andExpect (content ( ).contentType (MediaType .APPLICATION_JSON_UTF8) )
. andExpect (jsonPath("s.status", is ("DONE") ) );
}
}
本文给大家讲解的内容是测试Java微服务
- 下篇文章给大家讲解的是使用内存数据库运行测试;
- 觉得文章不错的朋友可以转发此文关注小编,有需要的可以私信小编获取资料;
- 感谢大家的支持!