草庐IT

《微服务架构设计模式》读书笔记 | 第9章 微服务架构中的测试策略(上)

多氯环己烷 2023-03-28 原文


前言

传统测试在微服务架构中有两大缺点:手动测试效率极低、在交付流程中才进行测试为时已晚;应该采取新的测试策略提高微服务架构的可测试性;

进行自动化测试是缩短交付周期的唯一方法;

这是一本关于微服务架构设计方面的书,这是本人阅读的学习笔记。以下对一些符号做些说明:

()为补充,一般是书本里的内容;
[]符号为笔者笔注;


1. 微服务架构中的测试策略概述

本章重点在用于验证应用程序或服务的功能的自动化测试;

1.1 编写自动化测试


图解

  • 自动化测试通常使用测试框架编写,如JUnit;
  • 自动化测试通常包括四个阶段:设置环境、执行测试、验证结果、清理环境;
  • 清理环境很容易被忽略,在涉及数据库的测试可能需要在测试后将数据库状态回滚到设置环境阶段的初始状态;
  • 测试套件是一组测试类的集合,测试由测试运行器执行;

1.2 使用模拟和桩进行测试

用来解决被测系统与其他系统之间的一些麻烦依赖关系;


图解

  • 使用测试替身来消除被测系统的依赖性;
  • 测试替身是一个对象,该对象负责模拟依赖项的行为;
  • 有两种类型的测试替身:桩(stub)模拟(mock)
    • 是一个测试替身,它代表依赖项来向被测系统发送调用的返回值;
    • 模拟也是一个测试替身,用来验证被测系统是否正确调用依赖项;此外,模拟通常也扮演桩的角色;

1.3 使用范围对测试进行分类

  • 单元测试:测试服务的一小部分,如类;
  • 集成测试:验证服务是否可以与基础设施服务(如数据库)或其他应用程序服务进行交互;
  • 组件测试:单个服务的验收测试;
  • 端到端测试:整个应用程序的验收测试;

1.4 使用测试象限对测试进行分类


图解

  • 两个维度:
    • 测试是面向业务还是面向技术:使用领域专家的术语来描述面向业务的测试,使用开发人员的术语和实现来描述面向技术的测试;
    • 测试的目标是协助开发还是寻找产品缺陷:开发人员使用协助开发的测试作为日常工作的一部分;寻找产品缺陷的测试旨在确定需要改进的部分;
  • 四种不同测试类别:
    • Q1协助开发 / 面向技术:单元和集成测试;
    • Q2协助开发 / 面向业务:组件和端到端测试;
    • Q3寻找产品缺陷 / 面向业务:易用性和探索性测试;
    • Q4寻找产品缺陷 / 面向技术:非功能性验收测试,如性能测试;

1.5 使用测试金字塔对测试进行分类


图解

  • 测试范围越大,其构成部件越多,可靠性越低;
  • 强调要对服务的每一个细分元素进行测试,能最大限度地减少测试整个服务的组件测试数量;

1.6 微服务架构中的测试挑战

进程通信是微服务架构的核心,应用程序模块之间的任何交互都是通过编程语言级别的API进行的;因此测试验证API的服务是否能与其依赖关系和客户端进行正常交互尤为重要;


图解

  • 图中的进程间通信方式:
    • REST客户端 - 服务端:API Gateway将请求路由到服务并实现API组合;
    • 领域事件使用者 - 发布者:Order History Service使用Order Service发布的事件;
    • 命令式消息请求方 - 回复方:Order Service将命令式消息发送到各种服务并使用回复;
  • 一对服务之间的交互代表了这两个服务之间的契约或协议,契约要求双方就事件消息结构及其发布的通道达成一致;
  • 作为开发人员,需要确保服务具有稳定的API,做出的改动尽量不要破坏原有的API;
  • 测试两个服务可以交互的一种方法是同时运行两个服务,调用触发通信的API,并验证是否有预期结果;这会遇到集成的问题,解决方案是消费者驱动的契约测试;

1.7 消费者驱动的契约测试


图解

  • 消费者驱动的契约测试模式:验证服务是否满足它的消费者期望;
  • 消费者契约测试模式:验证服务的客户端是否可以与服务通信;
  • 消费者驱动的契约测试通常使用样例测试,消费者和提供者之间的交互由一组样例定义,称为契约;每个契约都包含在一次交互期间交换的样例消息;
  • 消费者契约测试侧重于验证提供者API的参数定义是否符合消费者的期望;
  • 对于REST接口,契约测试将验证提供者程序实现的接口是否:
    • 具有预期的HTTP方法和路径;
    • 接受预期的HTTP头部;
    • 接受请求主体;
    • 返回预期中的响应,包括状态代码、头部和主体等;
  • REST API的消费者契约测试实际上是通过模拟控制器进行的测试;

1.8 使用Spring Cloud的契约测试服务


图解

  1. API Gateway团队(消费者)编写的契约定义API Gateway如何与Order Service进行交互;
  2. 编写的契约包括API Gateway可能发送给Order Service的HTTP请求和预期的HTTP响应;
  3. Order Service团队(提供者)使用这些契约来测试Order Service,并使用它们来测试API Gateway;测试代码Spring Cloud Contract生成;
  4. Order Service团队(提供者)将测试Order Service的契约打包成jar包发布到Maven存储库;
  5. API Gateway团队(消费者)使用已发布的契约为API Gateway编写测试;

  • 一个契约示例;
  • 请求元素是REST接口 GET/orders/{ orderId } 的一个HTTP请求;
  • 响应元素是一个该接口对应的HTTP响应,它描述了API Gateway所期望的Order;

1.9 部署流水线


图解

  • 其包含一系列执行测试套件的阶段,后面是一个发布或部署服务的阶段;
  • 理想情况下流水线是完全自动化的,也可能包含手动步骤;
  • 部署流水线包含以下几个阶段:
    • 提交前测试阶段:执行单元测试。这是由开发人员在提交代码更改之前执行的;
    • 提交测试阶段:编译服务,执行单元测试,并执行静态代码分析;
    • 集成测试阶段:执行集成测试;
    • 部署阶段:将服务部署到生产环境中;

2. 为服务编写单元测试

2.1 两种类型的单元测试


图解

  • 独立型单元测试:使用针对类的依赖性的模拟对象隔离测试;
  • 协作型单元测试:测试一个类及其依赖项;

2.2 类的职责决定使用哪种单元测试


图解

  • 每个类的典型策略如下的:
    • Order等具有持久化身份对象的实体,使用协作型单元测试;
    • Money等作为值集合对象的实体,使用协作型单元测试;
    • Sages等需要维护服务之间的数据一致性,使用协作型单元测试;
    • OrderService等实现不属于实体或值对象的业务逻辑的类,使用独立型单元测试;
    • OrderController等处理HTTP请求的控制器类,使用独立型单元测试;
    • 入站和出站等消息网关,使用独立型测试;

2.3 为实体编写单元测试

  • 单元测试可以彻底测试业务逻辑;

2.4 为值对象编写单元测试

  • 对值对象的测试通常会创建特定状态的值对象,调用其中一个方法,并对返回值进行断言;
  • 这里使用独立型单元测试,因为此时Money类没有任何依赖;前文说使用协作型单元测试,是针对FTGO中Money对象的实际情况;

2.5 为Saga编写单元测试

  • Saga会实现重要的业务逻辑,其是一个持久化对象,向Saga参与方发送命令式消息并处理他们的回复;
  • 对Saga的测试除了要为正常执行的场景编写测试单元,还必须为Saga回滚的各种场景编写测试;
  • 一种方法是编写使用真实数据库和消息代理以及桩服务的测试,以此来模拟各种Saga参与方;这种测试非常缓慢;
  • 一种更有效的方法是编写模型与数据库和消息代理交互的类的测试;这样可以专注测试Saga的核心职责;
  • 上述代码使用Eventuate Tram Saga测试框架编写,框架提供一个易于使用的DSL,其抽象出于Saga相互作用的细节;
  • DSL可以创建一个Saga并验证其是否发出正确的消息;
  • 事实上,Saga测试框架使用数据库和消息传递基础设施的模拟来配置Saga框架;

2.6 为领域服务编写单元测试


  • 服务的大多数业务逻辑通过实体类、值对象和Saga实现;领域服务类实现业务逻辑的其余部分;
  • 测试领域服务类使用独立型单元测试,它可以模拟储存库和消息传递类等依赖项;
  • 每个测试按如下方法完成各自测试阶段:
    • 设置:配置服务依赖项的模拟对象;
    • 执行:调用服务方法;
    • 验证:验证服务方法返回的值是否正确,以及是否已正确调用依赖项;

2.7 为控制器编写单元测试


  • 服务类通常具有一个或多个控制器,用于处理来自其他服务和API Gateway的HTTP请求;
  • 控制器类由一组请求处理程序方法组成,每个方法实现一个REST API端点;
  • 使用某些框架,如Spring Mock Mvc编写的测试会产生模拟的HTTP请求,并对HTTP响应进行断言;
  • 这些框架在测试HTTP请求路由以及Java对象与JSON之间的转换时,无须进行真正的网络调用;
  • 上述代码是一个独立单元测试,利用模拟对象来解决OrderController的依赖项;
  • 上述代码使用REST Assured Mock MVC编写,提供一个DSL,抽象出与控制器交互的细节;其可以轻松地模拟HTTP请求发送到控制器并验证响应;

2.8 为事件和消息处理程序编写单元测试

  • 与控制器类似,消息适配器往往是调用领域服务的简单类,因此可以使用类似针对控制器进行单元测试的方法;
  • 每个测试示例都是消息适配器,向消息通道发送消息,并验证是否正确调用了服务模拟;
  • 使用Eventuate Tram Mock Messaging框架进行测试,框架提供了一个易于使用的DSL,用于编写模拟消息测试;
  • 为了验证服务是否与正确地与其他服务交互,必须编写集成测试;还需要编写单独测试整个服务的组件测试;这点在下一章进行讨论;

3. 本章小结

  • 自动化测试是快速、安全地交互软件的重要基石。更重要的是,由于微服务架构固有的复杂性,要从微服务架构中充分受益,必须实现自动化测试;
  • 测试的目的是验证被测试系统(SUT)的行为。在这个定义中,系统是一个泛指,意味着被测试的软件元素。它可能像一个类一样小,也可能像整个应用程序一样大,或者是介于两者之间,例如一组类或一个单独的服务。测试套件是一组相关测试的集合;
  • 简化和加快测试的一个好方法是使用测试替身。测试替身是一个模拟被测系统依赖项的行为的对象。有两种类型的测试替身:桩和模拟。桩是一个测试替身,它将值返回给被测系统。模拟也是一个测试替身,由测试用来验证被测系统是否正确调用依赖;
  • 使用测试金字塔可确定将测试工作重点放在服务的哪个部分。大多数测试应该是快速、可靠且易于编写的单元测试。必须尽量减少端到端测试的数量,因为它们写入速度慢、脆弱且耗时;


最后

新人制作,如有错误,欢迎指出,感激不尽!
欢迎关注公众号,会分享一些更日常的东西!
如需转载,请标注出处!

有关《微服务架构设计模式》读书笔记 | 第9章 微服务架构中的测试策略(上)的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  3. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  4. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  5. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  6. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  7. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  8. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  9. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  10. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

随机推荐