持续集成 基于集成 jenkins 的测试平台

夏日鸣虫 · 2016年11月29日 · 最后由 夏日鸣虫 回复于 2016年12月02日 · 2517 次阅读

(一)先看测试业务的情况:

有各种各样的任务需要通过 jenkins 调度执行,这里包括代码构建、部署搭建、单元测试、功能自动化测试(包括许多模块的功能自动化测试,有十几个居多),性能测试、正确性验证;复杂一点的是这些任务在不同的测试阶段中都必须部署一套,一般测试至少都需要有三套环境:dailyrun 环境、两套 test 环境用来测试不同版本。要做到持续集成,则每天晚上都需要运行所有的构建、部署、ut、ft、性能、正确性,这些任务达到五十五个,而且彼此之间存在依赖关系的,总是要先编译再部署,功能测试则由于资源有限不得不也做成前后依赖模式,占用资源比较多的分开运行。

问题有:
(1)原本使用 jenkins 默认的任务依赖模式,修改任务调度次序的时候就会很悲剧,每个回归都需要从头到尾的修改一次;

(2)当前一个任务由于各种原因超时或者是执行失败的时候后面所有任务都等待,到了第二天要手工全部杀掉,否则无法运行;

(3)任务过多以后查找也很麻烦,难以找到对应任务的链接,每次翻都翻到手痛;遇到版本发布更痛苦,每个 jenkins 任务中 git 地址的版本都需要修改。

(4)测试结果用邮件发送的,没有地方可以看到全部回归的整体情况,比如某天挂了,并不能立即知道是 build 阶段、deploy 阶段、ut 阶段或者是 ft 的具体某个阶段的问题,还是要一层层去调查;

(二)我们的解决方案:

首先,这个系统要能够替代 jenkins 的任务依赖关系,在修改任务依赖的时候可以通过全局配置,这样解除掉问题 1;

其次,每个任务设置超时时间去监控 jenkins 执行情况,当超时的时候杀死掉当前任务,同时不影响下面任务的执行,这就解决问题 2;

第三,把 jenkins 信息和每个阶段信息存放到 mysql 中,其中配置修改直接通过调用 jenkins api,修改 git 地址,修改配置相关信息,这样就解决问题 3;

第四,当第一个任务做完以后,调度独立以后,可以获取到每个阶段执行是否成功的情况,测试报告同时上传到 mysql 数据库中,前端再用个 web 系统展示。

jenkins 提供了一整套 api 体系,可以触发任务,获取任务状态,可修改任务配置,将这些调用串起来。
为了能有效利用回归机器资源,设置机器池子,运行 jenkins 任务的时候从池子中随机获取机器进行下发,这样全部配置信息都来自于数据库中,规避测试过程中各种配置不对的情况。
一个 tip 对于 jenkins slave 机器本身,使用 ssh 模式启动,jenkins master 会通过监控手段确保 slave failover 操作,当 slave 挂的时候 master 会启动,不需要人工操作。
下面是任务依赖的配置

[
        [{"jobname":"Cupid","slave":"TEST3-JK-10"}],
        [{"jobname":"Cupid-HiveTest","slave":"TEST3-JK-1"}],
        [{"jobname":"git-console","slave":"TEST3-JK-10.1"}],
        [{"jobname":"git-console-public","slave":"TEST3-2"}],
        [{"jobname":"OpenMrLocal","slave":"TEST3-JK-10"}],
        [{"jobname":"OpenMrOnLot","slave":"TEST3-JK-10"}],
        [{"jobname":"Graph","slave":"TEST3-JK-1222"}],
        [{"jobname":"MR","slave":"TEST3-JK-101"}],
        [{"jobname":"SqlTask-Finance","slave":"TEST3-JK-1180"}],
        [{"jobname":"SqlTask-Lot","slave":"TEST3-JK-1010"}],
        [{"jobname":"Moye","slave":"TEST3-JK-10"}],
        [{"jobname":"Security","slave":"TEST3-JK-1"}],
        [{"jobname":"SqlTask-Chinese","slave":"TEST3-JK-1"}],
        [{"jobname":"SqlTask-ServiceMode","slave":"TEST3-JK-1"}],
        [{"jobname":"SqlTask-Taobao","slave":"TEST3-JK-1"}],
        [{"jobname":"XLib","slave":"TEST3-JK-1"}],
        [{"jobname":"RESTFulAPI","slave":"TEST3-JK-1"},{"jobname":"RESTFulAPI-AdminTask","slave":"TEST3-JK-1"},{"jobname":"RESTFulAPI-Event","slave":"TEST3-JK-1"}],
        [{"jobname":"CopyTask","slave":"Test-1"}],
        [{"jobname":"ReplicationTask","slave":"Test-10"}],
        [{"jobname":"MetaTest-FromFinance","slave":"TEST3-JK-1"}],
        [{"jobname":"Console-UT","slave":"TEST3-JK-1693"}],
        [{"jobname":"SDK-UT","slave":"TEST3-JK-100"}],
        [{"jobname":"PL-492","slave":"1"}],
        [{"jobname":"FT-gcc492","slave":"Test-vm-13"}],
        [{"jobname":"RESTApi-FT","slave":"Test-vm-13"}],
        [{"jobname":"FT","slave":"Test-vm-10"}],
        [{"jobname":"Api-FT-gcc492","slave":"Test-vm-1"}],
        [{"jobname":"CppSdk-FT-gcc492","slave":"Test-vm-1"}],
        [{"jobname":"OldSdk-FT-gcc492","slave":"Test-vm-1"}],
        [{"jobname":"Lot","slave":"TEST3-JK-1"}]
]

该配置文件中用逗号分隔的表示任务是并行执行的关系,放在一个中括号里面用大括号分隔表示其前后存在依赖关系,执行完毕前面的才是后面的。下面是调用这个配置的主程序入口,这里的 startbuilds 会去读取任务配置,API 是对各种 jenkins api 的包装

if __name__ == "__main__":
    print "start job dependencies"
    if len(sys.argv) < 2:
        print "please input the jobName"
        exit(1)

    args = sys.argv[1]
    print "jobName===>"+args
    api = jenkins.API()

    stime= common.getNow()
    api.startbuild(args)

python 对 jenkins api 有专门支持的包 jenkinsapi,在此基础上面进行集成是非常的方便的。
下面的函数对 jenkins 里面的 git 配置进行修改

def modify_branch(jobname,new_branch):
    print(jobname)
    if jobname=="None" or jobname is None : 
        return 
    jkserver=jenkins.API().get_jenkins_instance()
    job=jkserver.get_job(jobname)
    try:
        branch=job.get_scm_branch()
    except Exception,e:
        print e
        return
    print(branch)
    job.modify_scm_branch(new_branch)
    branch=job.get_scm_branch()
    print(branch)

下面这段代码可以直接获取到 jenkins 机器信息

def get_job_slave(groupname,jobname):
    jkserver=jenkins.API().get_jenkins_instance()
    job=jkserver.get_job(jobname)
    jobconfig = job.get_config()
    tree = Etree.fromstring(jobconfig)
    roamnode = tree.findall("./canRoam")
    if roamnode != None:
        roamnode[0].text="false"
    nodes = tree.findall("./assignedNode")
    print nodes
    if nodes == None or len(nodes)==0:
        tree.append(Etree.fromstring('<assignedNode>'+slave+'</assignedNode>'))
    else:
        node = nodes[0]
        print(node.text)
    return node.text

(三)运行结果收集
测试运行结果收集这块,测试平台提供一个 restful 接口,每个模块的报告最后调用一下这个接口,首先把自己的报告上传到远程的一个 ftp 上面,然后再把模块名称、环境名称、成功失败用例个数,这些信息上报上去,接口会存放这些信息到数据库中,在前端 webserver 予以展示。

共收到 8 条回复 时间 点赞

如果团队有比较强的开发能力,我个人建议别用 Jenkins 了。这个东西我用下来的感觉是比较挫(也可能是我用的版本比较老)。
主要有:

  1. 设计疑问:master slave 在执行过程中是保持长连接的。一旦网络抖动,长时间运行的测试用例就会中断,无法获得测试结果。这块完全可以用异步来做,不清楚为啥这么设计。
  2. 二次开发问题。如果基于 Jenkins 进行二次开发来修正他的一些底层奇葩设计,修复 BUG 或者实现自己定制化需求,一旦 Jenkins 版本升级,很难为二次开发版本添加新特性。而且 Jenkins 现在的体量太大,二次开发复杂度也比较高。说不定改着改着就踩另一个坑了。
  3. 各种版本有各种奇怪 BUG。遇到了除了尝试升级外,一般只能干瞪眼。代码量太大,很难排查。
  4. 如果不是直接对 Jenkins 二次开发,而是在上层再封装一层做定制化的需求,这样会导致链路过长。越长链路的系统越难维护,稳定性也越差。

从头做一个分布式持续集成测试系统其实并不那么难。你们现在的系统强耦合 Jenkins,后面维护的人可能会遇到各种坑。特别是需要升级 Jenkins 的时候。如果你们有能力的话,个人建议先将你们现在耦合 Jenkins 的地方抽象出来,把目前的依赖 Jenkins 的逻辑作为一个特殊的实现,同时并行做一个分布式持续集成系统来自己实现这些功能,完成后替换 Jenkins 的实现即可。

对于第一点,确实可以改进,先提交过去再轮询,jenkins api 都已经提供,再去调用一下就可以;
难和易是一方面,使用习惯上现在大多都依赖于 jenkins,不少大厂自己都做了 jenkins 替代产品,基本上能满足自己需要都不错了,能拿出来的很少,开源自有其生命力;
我们当时也遇到过一个 jenkins 的 bug,自己绕过去都能解决,最后升级 jenkins 影响面也还好,一年升级一次也不麻烦;
目前暂时能满足业务需求,我觉得 jenkins 最大的问题是没有中心数据库,master 上面的数据都保留在本地 master 的磁盘上面,这样导致很多时候不得不再通过 resful api 绕一层,如果有更好的东西当然欢迎

请问楼主,你们持续集成的中心调度,用的是什么工具呢?

jenkins api 的确是一条新的方式,我这边还没用过这个。

不错的

#3 楼 @yunbin_7 就是用 jenkins 自己的定时调度就可以,设置一个虚拟的头结点,然后触发我们真实的调度作业,里面再通过 jenkins api 驱动,jenkins 的 python 版本 api 很完善,可以通过 buildWithParameter 进行任务触发。其中有获取 text 的配置,可以获取到全部参数信息。

#6 楼 @cobbxia 楼主,我的意思是,因为我们现在的项目管理平台用 jira 来管理,包括需求、设计、开发、测试的任务都在上面处理,并可以邮件通知对应的指派处理人,我在想,能不能把 jira 关联到我的 jenkins 上来,用 jira 来调度处理,但是好像 jira 不支持这种持续集成的调度。如果用 QC,又不好用。

#7 楼 @yunbin_7 jira 里面能定义执行 shell cmd 的话就可以,jenkins api 可以直接支持 resul api 格式

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册