当前位置:首页 > 技术知识 > 正文内容

微服务测试的不同策略之测试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微服务

  1. 下篇文章给大家讲解的是使用内存数据库运行测试
  2. 觉得文章不错的朋友可以转发此文关注小编,有需要的可以私信小编获取资料;
  3. 感谢大家的支持!

相关文章

Objective C interface(objective什么意思)

在Objective C里面,interface基本可以理解为其他语言里面的class。当然也有些不同。首先我们可以新建一个Objective-C的file。这里我们添加一个MyClass.m和一个M...

Windows 加密盘BitLocker爆锁屏绕过严重漏洞

BitLocker Windows内置现代设备级数据加密保护功能,BitLocker与Windows内核深度集成。有大量的企业和个人使用BitLocker加密自己关键数据,以防止数据泄密。BitLoc...

Flutter 之 ListView(flutter框架)

在 Flutter 中,ListView 可以沿一个方向(垂直或水平方向)来排列其所有子 Widget,常被用于需要展示一组连续视图元素的场景ListView 构造方法ListView:仅适用于列表中...

Android让视图折叠(安卓叠加视图设置)

Android UI Libs之ExpandableLayout1. 说明ExpandableLayout,顾名思义,可扩展的布局,是一个可以帮助我们实现折叠功能的第三方库,折叠时,只显示头部,打开时...

CPU「离奇」飙到 100%!开发者挖出 Linux 内核 16 年老 Bug:这么多年竟无人发现?

【CSDN 编者按】每一次对旧设备的升级都仿佛是一场跨越时代的冒险。本文作者致力于将基于 PXA166 的 Chumby 8 设备从 Linux 2.6.28 版本升级到现代 6.x 版本,然而,在看...

从 async/await 到虚拟线程:Python 并发的再思考

演进之路:从async/await到线程的反思首先必须明确的是,async/await对Python并非全无裨益:它最大的价值,是让更多人接触到了并发编程。通过在编程语言中嵌入语法元素,并发编程的门槛...