UDN-企业互联网技术人气社区

板块导航

浏览  : 1127
回复  : 0

[资源分享] 微服务场景下的自动化测试

[复制链接]
哥屋恩的头像 楼主
发表于 2016-10-12 14:09:26 | 显示全部楼层 |阅读模式
  新的挑战

  微服务和传统的单块应用相比,在测试策略上,会有一些不太一样的地方。简单来说,在微服务架构中,测试的层次变得更多,而且对环境的搭建要求更高。比如对单块应用,在一个机器上就可以setup出所有的依赖,但是在微服务场景下,由于依赖的服务往往很多,要搭建一个完整的环境非常困难,这对团队的DevOps的能力也有比较高的要求。

  相对于单块来说,微服务架构具有以下特点:

  每个微服务在物理上分属不同进程

  服务间往往通过RESTful来集成

  多语言,多数据库,多运行时

  网络的不可靠特性

  不同的团队和交付周期

  上述的这些微服务环境的特点,决定了在微服务场景中进行测试自然会面临的一些挑战:

  服务间依赖关系复杂

  需要为每个不同语言,不同数据库的服务搭建各自的环境

  端到端测试时,环境准备复杂

  网络的不可靠会导致测试套件的不稳定

  团队之间的沟通成本测试的分层

  相比于常见的三层测试金字塔,在微服务场景下,这个层次可以被扩展为5层(如果将UI测试单独抽取出来,可以分为六层)。

  单元测试

  集成测试

  组件测试

  契约测试

  端到端测试

6.png

  和测试金字塔的基本原则相同:

  越往上,越接近业务/最终用户;越往下,越接近开发

  越往上,测试用例越少

  越往上,测试成本越高(越耗时,失败时的信息越模糊,越难跟踪)

  单元测试

  单元测试,即每个微服务内部,对于领域对象,领域逻辑的测试。它的隔离性比较高,无需其他依赖,执行速度较快。

  对于业务规则:

  商用软件需要License才可以使用,License有时间限制

  需要License的软件在到期之前,系统需要发出告警

  1.   @Test

  2.   public void license_should_expire_after_the_evaluation_period() {

  3.   LocalDate fixed = getDateFrom("2015-09-03");

  4.   License license = new License(fixed.toDate(), 1);

  5.   boolean isExpiredOn = license.isExpiredOn(fixed.plusYears(1).plusDays(1).toDate());

  6.   assertTrue(isExpiredOn);

  7.   }

  8.   @Test

  9.   public void license_should_not_expire_before_the_evaluation_period() {

  10.   LocalDate fixed = getDateFrom("2015-09-05");

  11.   License license = new License(fixed.toDate(), 1);

  12.   boolean isExpiredOn = license.isExpiredOn(fixed.plusYears(1).minusDays(1).toDate());

  13.   assertFalse(isExpiredOn);

  14.   }
复制代码


  上面这个例子就是一个非常典型的单元测试,它和其他组件基本上没有依赖。即使要测试的对象对其他类有依赖,我们会Stub/Mock的手段来将这些依赖消除,比如使用mockito/PowerMock。

  集成测试

  系统内模块(一个模块对其周边的依赖项)间的集成,系统间的集成都可以归类为集成测试。比如

  数据库访问模块与数据库的集成

  对外部service依赖的测试,比如对第三方支付,通知等服务的集成

  集成测试强调模块和外部的交互的验证,在集成测试时,通常会涉及到外部的组件,比如数据库,第三方服务。这时候需要尽可能真实的去与外部组件进行交互,比如使用和真实环境相同类型的数据库,采用独立模式(Standalone)的WireMock来启动外部依赖的RESTful系统。

  通常会用来做模拟外部依赖工具包括:

  WireMock

  mountebank

  其中,mountbank还支持Socket级别的Mock,可以在非HTTP协议的场景中使用。

5.jpg

  组件测试

  贯穿应用层和领域层的测试。不过通常来说,这部分的测试不会访问真实的外部数据源,而是使用同schema的内存数据库,而且对外部service的访问也会使用Stub的方式:

  内存数据库

  Stub外部服务(WireMock)

  RestAssured

  比如使用h2来做内存数据库,并且自动生成schema。使用WireMock来Stub外部的服务等。

 
  1.  @Test

  2.   public void should_create_user() {

  3.   given().contentType(ContentType.JSON).body(prepareUser()).

  4.   when().post("/users").

  5.   then().statusCode(201).

  6.   body("id", notNullValue()).

  7.   body("name", is("Juntao Qiu")).

  8.   body("email", is("juntao.qiu@gmail.com"));

  9.   }

  10.   private User prepareUser() {

  11.   User user = new User();

  12.   user.setName("Juntao Qiu");

  13.   user.setEmail("juntao.qiu@gmail.com");

  14.   user.setPassword("password");

  15.   return user;

  16.   }
复制代码


  如果使用Spring,还可以通过profile来切换不同的数据库。比如下面这个例子中,默认的profile会连接数据库jigsaw,而integration的profile会连接jigsaw_test数据库:

 
  1.  spring:

  2.   datasource:

  3.   url: jdbc:mysql://localhost:3306/jigsaw

  4.   driver-class-title: com.mysql.jdbc.Driver

  5.   username: root

  6.   password: password

  7.   ---

  8.   spring:

  9.   profiles: integration

  10.   datasource:

  11.   url: jdbc:mysql://localhost:3306/jigsaw_test

  12.   driver-class-title: com.mysql.jdbc.Driver

  13.   username: root

  14.   password: password
复制代码


  组件测试会涉及到的组件包括:

  URL路由

  序列化与反序列化

  应用对领域层的访问

  领域层对数据的访问

  数据库访问层

  前后端分离

  除了后端的测试之外,在目前的前后端分离场景下,前端的应用越来越复杂,在这种情况下,前端的组件测试也是一个测试的重点。

  一个前端应用至少包括了这样一些组件:

  前端路由

  模板

  前端的MVVM

  拦截器

  事件的响应

  要确保这些组件组合起来还能如预期的执行,相关测试必不可少。这篇文章详细讨论了前后端分离之后的测试及开发实践。

  契约测试

  在微服务场景中,服务之间会有很多依赖关系。根据消费者驱动契约,我们可以将服务分为消费者端和生产者端,通常消费者自己会定义需要的数据格式以及交互细节,并生成一个契约文件。然后生产者根据自己的契约来实现自己的逻辑,并在持续集成环境中持续验证。

  Pact已经基本上是消费者驱动契约(Consumer Driven Contract)的事实标准了。它已经有多种语言的实现,Java平台的可以使用pact-jvm及相应的maven/gradle插件进行开发。

  pact/pact-jvm

  pact-broker

4.png
  (图片来源:Why you should use Consumer-Driven Contracts for Microservice integration tests)

  通常在工程实践上,当消费者根据需要生成了契约之后,我们会将契约上传至一个公共可访问的地址,然后生产者在执行时会访问这个地址,并获得最新版本的契约,然后对着这些契约来执行相应的验证过程。

  一个典型的契约的片段是这样的(使用pact):

  
  1. "interactions": [

  2.   {

  3.   "description": "Project Service",

  4.   "request": {

  5.   "method": "GET",

  6.   "path": "/projects/11046"

  7.   },

  8.   "response": {

  9.   "status": 200,

  10.   "headers": {

  11.   "Content-Type": "application/json; charset=UTF-8"

  12.   },

  13.   "body": {

  14.   "project-id": "004c97"

  15.   },

  16.   "matchingRules": {

  17.   "$.body.project-id": {

  18.   "match": "type"

  19.   }

  20.   }

  21.   },

  22.   "providerState": "project service"

  23.   }

  24.   ]
复制代码


  端到端测试

  端到端测试是整个微服务测试中最困难的,一个完整的环境的创建于维护可能需要花费很大的经历,特别是当有多个不同的团队在独立开发的场景下。

  另一方面,从传统的测试金字塔来看,端到端测试应该覆盖那些业务价值最高的Happy Path。也就是说,端到端测试并不关注异常场景,甚至大部分的业务场景都不考虑。要做到这一点,需要在设计测试时,从最终用户的角度来考虑,通过用户画像和User Journey来确定测试场景。

  在端到端测试中,最重要的反而不是测试本身,而是环境的自动化能力。比如可以通过一键就可以将整个环境provision出来:

  安装和配置相关依赖

  自动将测试数据Feed到数据库

  自动部署

  服务的自动重启

  随着容器技术和容器的编排技术的成熟,这部分工作已经可以比较好的自动化,依赖的工具包括:

  Docker

  Rancher

  一个典型的流程是:

  搭建持续发布流水线

  应用代码的每一次提交都可以构建出Docker镜像

  将docker镜像发布在内部的docker-hub上

  触发部署任务,通过rancher的upgrade命令将新的镜像发布

  执行端到端测试套件

  端到端测试还可以细分为两个不同的场景:

  没有用户交互的场景,如一系列的微服务组成了一个业务API

  有用户交互的场景

  UI测试

  最顶层的UI测试跟传统方式的UI测试并无二致。我们可以使用BDD与实例化需求(Specification By Example)的概念,从用户使用的角度来描述需求,以及相关的验收条件。这里我们会使用WebDriver来驱动浏览器,并通过诸如Capybara等工具来模拟用户的操作。

  BDD工具:Cucumber

  Web应用验收测试工具:Capybara

  Page Object的DSL工具:Site_prism

原文作者:佚名  来源:开发者头条
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部