从无到有:京东持续集成实践分享

2018-04-08 13:24:47 浏览数 (1)

讲师 | 潘晓明 编辑 | 黄晓轩

讲师简介

潘晓明 目前就职于京东商城平台产品研发部,主要从事测试开发一职,擅长测试工具的设计与开发。先后就职于惠普,腾讯,在测试领域奋斗了 10 多年,对黑盒测试,白盒测试,性能测试,专项测试有一定的了解。《移动 App 测试实战》作者之一。京东移动 app 持续集成项目负责人,主要负责持续集成项目的整体架构设计和维护的工作。

前言

我今天主要分享三个主题,首先是京东在持续集成的历程,是怎么从无到有的。其次是京东现有持续集成设计的架构以及提供的服务能解决哪些痛点。最后是我们在使用持续集成以后,给京东APP测试流程带来的收益。

一、持续集成在京东的发展历程

这是我刚进京东的时候,京东开发的流程,大家看到这是一个典型的瀑布流程,开发和测试正好是一个对接的关系,所有的测试是在开发这块的下游。测试必须等待开发工作全部完成后才能开始测试工作。

这样会产生的问题是:测试团队各自为战。

大家知道我们测试都是有自己的测试团队,每个测试团队都会有自己的测试模块,测试模块就对应不同的开发,造成的问题就是每个测试团队都是各自为战的。每个模块都由开发打包提测。这种情况下就带来了一些效率的问题:

  • 测试干等,无法工作。以前我们的测试包都是等开发打,开发这边如果说工作很忙,或者说本地代码出问题了,测试就只能等着。
  • 新bug,追溯源困难。测试过程中发现一些BUG,但今天一天开发给你许多提测包,开发说这个BUG什么时候引发的,上午测试的时候有没有发现。那你只能根据你自己本地文件时间排序,大概找一下上午第几个包出问题,这个包引发的代码变更,包括一些东西你都不知道,你完全就是一个黑盒的状态去开发那边拿包。
  • 测试自己本地打包测试,熟悉成本太高。有的测试人员等不及了,我自己本地拉代码编译打包吧。肯定也有这样的测试,这种测试可能会插入一些自己需要的代码的测试插桩的东西,包括做一些埋点的东西。这样做测试的话,对测试要求比较高。特别是安卓和IOS的环境,IOS涉及到一些证书及配置的管理。测试同学如果对这块不是特别了解的话,会经常造成编译失败,编译失败的错误日志还看不懂,得找开发问这个错误是什么原因导致的,是不是某个编译的环境或者某一条编译的设置是需要改的。
  • 模块1测试完了,模块2还没提测。我们某个测试团队已经完成测试了,但还有测试团队的包还没拿到,还没有测完,就会造成集成项目的delay。整个项目的集成就被另外一个模块拖住了。

诸如此类还有很多的问题,都是因为分散测试管理模式导致的问题。

那我们期待怎样的改变:

我们期待能够通过持续集成统一出日常提测包,这样就不需要找开发进行对接了。我们需要有一个统一的持续集成平台来拿到提测包。同时我们能够通过持续集成追溯哪个包或哪个构建的时候出的问题。我们也希望有特殊的插桩测试包能够通过持续集成帮我们做到,而不需要我们自己每个人打自己特定的包,最后还有更多需求的介入。

以前的流程走不下去了,我们要做持续集成的团队,我了解的这个团队有的公司是运维做,有的公司是开发做,有的公司是测试做,具体哪一个团队做其实没有关系,关键的点是能够把持续集成这块流程和这个部署的东西满足现有公司的需要。

二、京东持续集成服务设计及解决的痛点

刚开始做持续集成第一版的时候,就暴露了一些问题:

  • 持续集成环境没有备份容灾。我们持续集成服务是单点的,这是一个内部的系统,给内部用的,首先没有大量的流量,也没有大量的并发访问,根本不用考虑性能方面的问题,满足日常的构建就可以了。
  • 没有统一的入口。一开始是直接使用到Jenkins,Jenkins我们针对不同项目部署不同Jenkins的服务,大大小小各种项目进来以后,不同的服务是不可能让公司内的员工自己去登到服务上做触发,这样不现实。这个服务太多,而且暴露给用户也不太现实。
  • 手动部署环境。当有新项目进来或者新的东西要做发布,靠手工去堆这样的构建,效率也比较低。
  • 缺乏有效的配置管理。配置管理一开始做的时候没有想到这么多,配置管理这块是有所忽视的。
  • 未关联测试工作。一开始持续集成的目的只是为了出包给大家做测试。给大家做测试可能没有关联到后续测试的东西。
  • 无法满足敏捷项目的需求。现在各个公司都在推敏捷,敏捷怎么做,持续集成怎么跟上敏捷的步伐。

这是安卓app构建的架构图,就是简单的四层架构的结构。前端用户进到我们的平台(简单的Nginx PHP搭建的平台),中间是我们持续集成微服务的中间件,底层是Jenkins给所有项目的服务,持久层使用到了MySQL的DB 京东云上的云服务。所有的构建包都是上传到京东云。这个图可以关注的重点,所有服务、所有机器都是做的双备份,就是多机的备份,做到这样容灾和负载均衡的工作。

这是持续集成中间微服务的架构。主要是服务网关进来的,是按照功能模块做服务划分的,涉及到Jenkins的基础服务,Job服务。这个涉及到Jenkins基础服务,我们的节点管理就会通过使用一些基础服务做我们自己节点的调度管理工作。

这样我就解决了前面说的第一个问题,备份容灾的问题。无论是从平台、微服务、Jenkins容器和数据库,所有都是做了双机、多机的备份。

同时这边的Jenkins容器服务,甚至还设计了跨机房的服务。这样保证在极端的情况下即使是一个机房出问题了,机器全挂了,另外一个机房可以保证还能使用。

这样就满足了7*24不间断提供服务,还能做到负载均衡,满足极端的情况下服务的可用性。大家不要忽视公司内部的服务单点的问题,好像觉得内部服务这个没什么人用,不太会坏,一定要考虑到它是需要有备份的,所有机器你想不到就是会莫名其妙出问题。

关于统一平台,我们说我们需要怎样的平台。刚刚提到这么多Jenkins服务,我们是不是需要一个统一的平台,把所有项目统一起来。让用户能够直观的以黑盒的方式去使用。

这个是持续集成平台的手动构建的界面,左边的菜单是现有加入到持续集成的项目,比如说京东商城、金融、京东冰箱。

右边是每个项目具体的构建类型,大家可以选IOS构建也可以选安卓构建,每一种构建都有相应分支给你选择,还有相应的构建类型。这里点击开始构建,大家就可以等待构建完成以后,到构建历史里面拿包。

这就是构建历史的界面,可以拿到历史上的和你上一次构建完的测试包。这里有包名、上传的时间,包的大小,是在哪一个分支、哪一个用户触发的,这些信息都可以看到。

我们做这个东西是有时间的搜索,大家可以通过历史的时间搜索过去某一个时间的包。我们做到所有的历史都是可追溯的

这是系统构建的统计页面。这个主要是用来做每日构建或者每月构建数据的统计。这些数据可以告诉我们哪些项目,哪些平台构建的数量是怎么样的,它以后是否要扩容或者缩容提供强大的数据保障。

这是配置管理页面,主要是做前端的Job和后台Jenkins Job做映射,同时提供了平台的配置、用户管理。提到用户管理这里所有的平台,在每个用户进来的时候都可以配置一套权限,当你被赋于一种权限的时候,例如仅赋予你安卓权限,你是不能打IOS包的;你是构建打Release包,你不能打Debug包。主要是在这里做权限管理。

这样我们平台的问题也解决了。我们平台主要是三个大方面:

  • 统一性。集中所有的项目和使用,把所有的东西集中起来。
  • 可用性。通过前端的注册,包括使用以及操作的话,对用户来说是完全黑盒和简易的。所有的用户不需要去直接触发后台Jenkins服务,那些非常复杂的东西已经都帮他封装好了。
  • 信息化。所有信息都可以直接展示,包括构建的数据,每个月的构建数量,这样我们整套构建数据可以做到闭环。

接下来我讲一下节点管理。Jenkins自带节点管理,其做的也非常好。他的master /slave机制允许你可以对Job做并发构建。京东这一块主要是做混合的节点管理,有用到master/slave,也有用到我们自己的一套东西。我们这边的节点主要是Docker加Jenkins的环境。

使用Docker的好处是每一台容器可以做备份,Docker image可以做环境的快速部署,就是说IOS或者说安卓底层的构建需要依赖的构建的东西,包括安卓SDK的构建环境,这些东西都是通用的,可以通过Docker image快速的生成,方便新项目的接入,如果有新的项目,可以直接帮他生成一个新的节点,这个底层环境就可以直接用了。

同时这么一个Docker化的节点管理,就可以支持一个并发的构建。并发的构建前提是有持续集成的微服务,微服务可以通过前端调度每台节点的使用情况,做一个动态的负载均衡。

最终Docker这块的部署,包括Docker这块的管理,对运维同事来说,这块的管理就可以做到非常统一的管理,不像线下的机器大家手动管理,所有的都可以通过系统或者平台统一管理这样Docker的信息。

节点的监控,也是节点管理的一部分。节点监控我们是保持在分钟级别的,能够在第一时间发现如果某一个节点出现问题,会有第一时间的微信告警,责任人收到这个告警以后,我们就会修复出问题的节点,保证所有的服务是可用的。第一时间解决问题,不能让掉线的节点或者出问题的节点一直在那边不知道。

配置化管理是比较重要的,我认为是持续集成核心的东西。我们环境配置化可以通过Docker实现,我们Job的配置化,一些Job参数化的东西,以及各种插件、各种本地编写的脚本,这些东西全部可以通过版本管理工具做这样的配置。

这样做有什么好处呢?如果我们所有的东西实现了全配置化,我们的配置化管理可以使我们的部署完全的自动化。我们说一个配置,或者一个Job,如果说出现了更新,大家在Jenkins服务上有更新,或者需要更新某些东西,使后面的构建不稳定导致的问题,怎么办?回滚就可以了。

我们所有东西都是有配置管理的,同样我们不同的Job可以拉不同的分支,做不同策略的管理。

这样每一次构建的项目我们可以通过不同配置化的管理,应用到对应不同的分支上面的配置信息,这样就可以做一个非常灵活的按照配置管理部署,就做到非常简便。

第一能自动化,第二能做到非常灵活,所有手动信息就不需要你手动输了,而且你手动输入之前的信息根本没办法记录,这是配置化管理带来最大的好处。

全平台的支持。我们这边是IOS和安卓两个平台的构建,需要依赖的一些环境支持。我们编译的类型也是支持Debug和Release的包,也包括发布包、企业包、预发布包。

参数化的构建。我们每一个Job的构建,是尽量用到参数化,参数化最大的好处是可以做到Job的复用。大家在新建Job的时候大部分的配置是一样的,在大部分配置一样的情况下,如果在一个Jenkins list里面有成百上千个Job,这些Job是不是可以做这个参数化的合并。大部分的Job是不是可以通过参数化合并在一起。

合并的好处有很多,一个是快速可以接入项目;还有一个是灵活的配置,按需产出。例如在构建完一个包以后,上传到京东云的时候,京东云的节点是可以选择不是上传到固定的点,而是上传到另外一个点,这个节点的信息就可以动态的来修改。

同样,我们gradle编译的信息,在项目过程中开发需要调试,调试时我需要在2.14版本上编译一次,在2.2版本上编译一次,你不能在环境变量中固定死,这就可以直接通过参数化的方式实现就行了。这样就可以实现一个按需的产出。

还有一个好处是节省磁盘空间。一个Job最大的占空间的点就是workspace。现在虽然说跟十年前比起来,磁盘每兆的价钱单量已经不值钱了,但如果在有限的空间里面,我们能够节省这样空间的话,为什么不去做呢。

而且这么多的Job累积的代码量对实际最终构建的结果,或者说总结的数量,其实占用空间还是非常大的。

这是构建状态实时获取。我们用户刚刚在平台上手动触发了构建,这个构建不是一会儿就好的,这个时候有一个构建状态告诉你,你的构建已经开始了。

然后大家可以等待构建完成,会有一个状态记录告诉你构建成功了,相当于一个通知的功能,你可以知道你刚刚的构建是成功还是失败,失败的原因是什么,构建时间多长,这些信息都是一目了然的。

然后是构建的历史,刚刚讲到可以到构建历史里面拿测试的包。这些构建历史信息最大的好处是追溯过去发生BUG的信息。如果每次都是从开发那边拿包的话,这块的历史包括这块的构建记录不清楚,通过构建历史可以做到BUG的追溯,包括做数据的分析。

这是构建版本的差异信息,我认为这是非常重要。某一个构建或者每一次构建当中,引入一个新的BUG,就可以通过这个信息追溯,这个BUG是什么原因导致的。上一次在拿到这个包的时候没有出现这个BUG,这次拿到这个包出BUG了。

我们通过提交记录和changelog信息,就可以知道开发在什么时候,在哪一个类上做了哪些改动,这些信息就一目了然,通过这些信息这个BUG的定位和追溯就简单了很多。

这是一个构建日志的获取,构建日志的获取这是Jenkins本身自带的,我们把这个信息就无缝地结合到我们平台上。这个好处是,我们可以通过构建信息判断本次构建失败的原因是什么,或者说构建成功还好,主要是构建失败。

构建失败的话,失败的原因是什么,为什么会失败,我们可以通过这些信息追溯这些构建失败的原因,同时可以验证一些相关参数的使用,是不是正确的。

这个就是数据的统计。数据统计基本上是围绕着平台各个项目和模块按时间的纬度,它构建的数据。这个构建数据包括它的成功率,这些数据对我们开发有着鞭策作用,它的成功率低的话是什么原因导致的,为什么会经常出现编译失败的情况。

如果成功率不是因为编译的问题,是我们自己本身的原因,是否我们可以回去找一下,或者本身定位一下我们这边构建长期不稳定的原因是什么。其次也是作为后续CI这块是否需要扩容,我们可以根据这个数据做依据。

有些项目一年下来或者一个月下来,构建次数很低,有些很高。这些数据可以作为我们参考的依据。

新需求的接入到我们这个持续集成的系统,怎么方便的接入新需求。我们持续集成的服务和平台是针对整个大部门的,一定会有其他的部门同事找你,问一下能不能接入我们持续集成平台。

接入新需求,要么是新项目,要么就是新的工程,在已有的工程下接入新的子工程,要么是在已有的基础上需要增加他的节点。

所有的信息我们只要知道每一个项目需要依赖的东西,通过我们前面的配置化管理和Docker管理,我们可以非常快速的帮他生成一个Jenkins服务,而不需要手动或者人工的核实、排查或者手工建立这个东西。

测试的介入,有测试准入标准、单元测试和UI自动化测试。每日的自动测试还会涉及到一些代码扫描和接口自动化。

测试准入流程是自动或者手动的时候触发创建包的时候,如果发现这个包是编译失败的情况,这种编译失败的情况会第一时间邮件告警到相关开发责任人。

这个告警信息会包括本次构建和上次成功之间代码的差异。还有这次构建是谁触发的,开发是谁提交的。开发的老板是谁,这些信息都会告警出来。

测试准入标准:

  • 构建成功是测试最基本的标准。如果这个构建连成功都不行,那就不要谈后面的测试了。
  • 不负责任的提交代码导致的构建失败是非常低级的问题。是需要严重跟开发团队交涉的,因为这个严重影响我们的测试效率。
  • 持续集成需要时刻保证项目的输出是可测试的。持续集成是保证我们测试包是随时随地可以测的。我们输出是随时随地可以测试的。

单元测试,我们这边在构建的时候同时完成单元测试的构建。可以通过覆盖率的工具完成这块的单元测试覆盖率报告的数据。

单元测试其实是跟集成测试最方便集成的东西,这一定要做。因为他的投入产出比非常高,很多问题不通过黑盒的方式,通过单元测试就能够发现。单元测试是整个测试金字塔最核心底层的东西。

单元测试也可以从覆盖率的角度、业务异常输出角度做。我这里重点提一下行覆盖率和方法覆盖率,大家不要特别依赖这个东西,如果只是从行覆盖率角度来做,测试一定是不全面的。

举个例子,我们有个开发写的代码,他的方法是判断一个字符串是否为数字,他的实现很简单,就是做了一个正则表达式。如果你测这个方法,正向的输入和反向输入,这个方法和行的覆盖数一定是100%,这个100%对你来说没有意义。

还有很多异常场景没有测到,负数的情况就没有考虑到,小数点的情况他也没有考虑到。这种异常的情况,是我们测试需要关注的,而不是单单考虑到分支覆盖、行覆盖、方法覆盖的数就认为测试是完成的。

单元测试和UI自动化测试比起来,其最大特点就是很快。除去之前构建环境的时间不算,几百条单元测试可能在几秒之内就执行完了。他的速度很快,他就可以作为每天回归测试重要的步骤,每天持续的构建,持续的测试,这样可以第一时间发现我们代码变更导致的问题,这是单元测试这块的重要性。

单元测试讲完了以后,可以跟构建信息集成起来了,构建代码提交次数、构建成功率、每次构建花的时间,这是从构建的角度做的数据统计。

测试就可以从测试代码覆盖率、圈复杂度、缺陷数量和代码风格的问题,通过整个测试和构建最终的数据获取。我们可以把这个报告,每天发给同事或老板看,这是非常重要的数据度量的指标。

UI自动化测试,这是老生常谈的问题,这个我不想说的更细,UI自动化要做吗?一定要做,但UI自动化为什么做起来这么让人讨厌呢?它很不稳定,用例维护起来成本非常高。

其次就是执行起来非常慢。如果把它放在每天的构建当中做的话,这个时间太长了。

可以考虑一个策略,每天晚上做一个持续集成,做一个每日自动回归,可以考虑做这样的UI自动化,当然我们持续集成平台是可以支持UI自动化选择的,支持选择本次构建要不要执行UI自动化测试,还可以支持选择对应的模块用例。

因为本次代码变更如果只是在购物车这个模块的话,就没有必要把其它模块的自动化再去跑一遍。只是做一个增量的测试

。同时我们UI自动化的执行,也是在线下多台机器做了这样分布式的执行,因为它真的很慢,如果不去实现分布式的话,这么多用例执行完不知道等到猴年马月了。

同时我们支持用例在线编辑,就是当你刚刚选择一个模块用例,我也可以选择这个模块下部分用例执行,部分用例不执行,这样可以更小缩小UI自动化执行的范围。

最后我们还有一个自动化执行的报表产出,这样所有的东西,所有UI自动化和持续集成平台相结合。

京东这块敏捷怎么走,京东选的是Scrum的方式去做,现在已经从一个月一版变更到了两周一版。两周一版的迭代,造成的问题就是产品、研发和测试,各个团队压力都非常大。

对测试来说,根本没有时间对每个包都做测试。我们做持续集成怎么同步跟进,让持续集成的脚步能够跟上敏捷的脚步呢?

先说一下构建时机。平台上手动触发的构建,这是按照用户需要。还有是自动构建。可能有同学想到说构建触发机制是怎样的,是按时触发,还是按照开发提交代码后立即触发。我觉得还是根据实际需要,我的建议是按时触发,因为你没有办法判断或更改开发提交代码的频次。

可以第一个开发提交代码触发了构建,第二个开发又提交了,这是他的提交可能就没有办法立即触发构建,他可能会被并到下一个构建当中。

还有一种可能是在很长一段时间内开发都没有提交,这时候就会出现空档期,出现空转期这时候再有开发提交,那这个空档期就会把之前所有的提交都拉出来,这样时间跨度比较大,同时增量内容比较多。

所以我这边建议是考虑用定时的方式触发。定时的方式有很多好处,定时触发构建,测试就不用再去拿包了,也不用再去等了。

我们这边平台是支持扫码安装的,第一时间拿包,这个包一定是保证几分钟之内是最新的,这样就免去了等待时间,提升了整块的测试效率。

敏捷的出包策略,我想说代码分支管理每家公司都不一样,如果走敏捷的话,大部分都会通过Feature分支做常规的user story开发,Intergration分支做每日集成代码的开发,Master分支是发版的一个稳定的分支。

我们说不管哪一个分支,Feature分支一定是每天定时Merge到Intergration分支,做每日的集成回归测试。所以Intergration分支每天的合并量是非常大的,一定要保证它的回归是正确的,是没有问题的。

它的每一个user story被合进来以后,是需要做一个tag标签来指定这个story合并进来并且是没有问题的。我们项目经理说这次发版出哪个版本,就可以从Intergration分支选择ABC三个需求,D不上了,可以选择在这个tag下面直接合并到Master分支。所以Master分支基本上是随时随地都能上线的分支。

通过这样的方式,我们要做到Master分支、Integration、Feature分支都能及时的为测试团队出测试包。最频繁的是Integration和Feature分支。每天的回归集成一定会通过Intergration分支去做,包括每天Feature分支的功能测试,这块自动出包的策略完全是跟着我们项目的策略来走的。

问题总结

这里总结一下刚刚提到所有的问题。没有备份环境就通过多点部署。没有统一入口是使用持续集成平台,通过平台做统一功能封装和项目管理。

手动部署环境和缺乏有效的配置管理是通过数据全配置化做到自动部署,还有按需版本管理的部署,包括后面的测试和敏捷项目的需求都有通过出包的策略解决。

三、持续集成在京东的价值体现

这是三个提升点

  • 沟通成本。在最开始的时候,没有持续集成平台怎么办?就是产品、测试、开发,这三个人到处跑,到处沟通,说什么时候出包,什么时候测,项目会盯着,产品会盯着测试说这个东西做的怎么样了,能测吗。开发会盯着测试说完成了没有,有问题吗。测试会盯着开发什么时候出包,什么时候功能是否解决了。这些大部分的时间都是一些无效的沟通,或者说无意义的沟通,我们说统一使用持续集成平台的话,可以过滤这样大部分的沟通。
  • 测试效益。我们引入单元测试和自动化测试概念,通过每日持续的构建,持续的测试,就可以帮助我们节省大量人力测试的投入,单元测试可以帮助我们非常快,同时非常高效的去发现代码当中的问题。
  • 时间成本。这些实践可以节省有效人力资源的东西,为我们节省整个项目的进度。

带来的成果

7×24小时提供构建和编译的服务。支撑了超过10万次有效的构建。每日触发400多条单元测试用例,100多条UI自动化测试用例,300多条接口自动化测试用例,超过300万行代码扫描(这是IOS和安卓代码加起来的总量)。这个是属于每天一次惯例的每日执行。成功的帮助了京东大大小小版本按时发版超过15个。

总结

最后总结一下持续集成的方案,京东持续集成可能不是业界最好的,每个公司内部开发的限制,包括网络运营限制、测试的流程,每家公司不一样,一套方案肯定不能适用于所有公司,所以只有最适合自己公司的东西才是最好的持续集成方案。我今天的分享就到这里。谢谢大家!

0 人点赞