持续集成

持续集成是敏捷开发流程中重要的一环,其存在的价值包括:

  • 通过每天多次的集成,尽早的发现bug
  • 减少测试,部署等过程中大量的重复性操作
  • 快速反馈,能让团队即时的跟踪到产品的状态

在做项目的持续集成时,一般会依赖版本控制工具、自动化测试工具以及自动构建工具等等。

本文将结合flask框架和Jenkins来描述整个持续集成的过程。

准备工作

需要的准备工作如下:

首先需要至少一台支持外网访问的服务器,并在这个服务器上搭建了Flask和Jenkins CI。可以参考Flask Installation以及Jenkins CI安装

版本控制

持续集成中的自动化测试和自动构建一般都是在版本库的某些分支发生push操作时,进行自动进行测试和构建。所以,在持续集成中,一个版本控制工具是必要的。

本文中使用的是<bitbucket.org>的私有库,因为bitbucket.org的代码库支持标准的webhook,可以在项目分支发生push,merge时,自动向webhook中填写的api地址POST更新。

有了版本库之后,还需要考虑的问题是项目开发过程中的分支模型,目前比较流行的有git flow和github flow。由于项目的人数比较少,所以这里用了比较简单的分支模型:

  • Production,该分支的代码是可以部署到生产服务器上运行的
  • Staging,该分支的代码是可以部署到开发服务器上来预览开发效果的
  • 多个Feature分支,这些分支用来开发各种各样的新功能,在通过测试之后,会被集成到Staging分支中

自动化测试

要在Flask项目中构建自动化测试的环境需要两方面的支持,首先是需要一个自动化测试的脚本或者框架,其次是在Jenkins CI中创建自动化测试的任务。

自动化测试的框架

自动化测试的框架我选择了nose,因为在Flask中用的是Flask_testing测试组件,而nose可以自动化的查找项目目录中可以执行(文件名中包含test或Test的文件)的测试:

Any python source file, directory or package that matches the testMatch regular expression (by default: (?:^|[b_.-])[Tt]est) will be collected as a test (or source for collection of tests).

当项目中有多个测试的时候,我们希望能通过一条nose命令来自动的执行所有的测试并生成测试结果,为了达到这个目标,我们先来看看项目的结构

project_folder----app    ----resources        ----users            ----model.py            ----view.py        ----XXXXX    ----config        ----default.py        ----development.py        ----production.py     ----tests        ----test_users.py        ----test_****.py

其中,在test_users.py中会import app/resouces/users/view.py中的类来进行测试,因为tests目录下的文件都是以test_XXXX.py形式命名的,所以,可以通过在项目中执行一条nosetests指令来执行。

通常和nosetests命令搭配使用的还有coveragecoverage是用来生成测试代码覆盖率的工具,其配合nosetests的方法如下:

nosetests --with-xunit --with-coverage --cover-package=app && coverage xml

在开发过程中,在提交到项目的代码库之前,需要在项目文件夹中执行了上面的测试命令并且通过后,才能正式把代码签入代码库。代码签入代码库后,通过在coding.net配置的webhook来自动发起POST请求到Jenkins CI服务器的任务api接口,接下来就说说怎么在Jenkins CI中创建一个这样的任务

Jenkins CI自动测试任务

首先,为了让Jenkins CI支持自动构建任务,需要安装下面几个插件

   

其中CobererturaJUint是测试中需要的插件,而其他的插件则是基本的插件。

接着就在Jenkins CI中新建一个任务

然后就是配置项目的信息

首次在Jenkins CI中使用这个库时,还需要到bitbucket中配置部署SSH公钥(保证Jenkins CI可以访问代码库),如下:

其中SSH-RSA公钥key需要填写的是你的Jenkins CI服务器的SSH公钥。

接着继续配置测试任务需要监听的分支,因为我们希望提交到版本库里面的代码都是能通过测试的,所以,这里我们监听了所有的代码分支:

选择构建触发器

参考自stackoverflow

然后配置测试任务构建具体要执行的命令

if [ ! -d "venv" ]; then    virtualenv -p /usr/bin/python2.6 venvfi. venv/bin/activatepip install -i http://pypi.douban.com/simple -r requirements.txtnosetests --with-xunit --with-coverage --cover-package=app && coverage xml

参考自Hustlzp-jenkins

接着选择构建后操作,分别是生成测试报告和测试覆盖率的报告

最后,最最重要的一步,在bitbucket的webhook中添加我们的测试任务的api接口

其中URL的格式为:

http://yourserverurl/git/notifyCommit?url=your_repo_url

自动构建

因为上面已经配置好了bitbucket,所以这里直接说明如何自动构建staging分支的代码。

先来看看自动构建的过程

  • 开发人员往staging分支push了代码
  • 版本库的webhook发起POST请求到自己搭建的Jenkins CI服务器
  • Jenkins服务器监听到是来自staging分支的代码变化,将发起自动构建
  • 执行用户定义的自动构建的脚本

前面三个步骤都很好理解,所以自动构建的关键是写自动构建的脚本。

staging分支自动构建的脚本因为包含以下操作:

  • 自动pull 版本库的staging分支
  • 重新启动flask app

第一点很好实现,直接用git pull命令即可,第二个涉及到了进程的管理,在这里用到了一个python进程管理的工具supervisor。

[program:project_stage]command=/home/stage/env/bin/python /home/stage/run.pydirectory=/home/stagestartsecs=0stopwaitsecs=0autostart=falseautorestart=falsestdout_logfile=/home/stage/log_stdoutstderr_logfile=/home/stage/log_stdin environment = MODE="STAGING"

在supervisor,我们可以为flask app的启动定义一个program,然后就可以方便的使用 start, restart命令来启动和重启我们的app了。

从上面的配置信息可以看出,我们的staging分支是放在/home/stage目录的,由于git pull命令需要在本机有版本库的信息的时候才能使用,所以,我们还需要做以下准备工作

  • 首先,git clone版本库到stage目录
  • 在stage目录下使用virtualenv env命令生成virtual env环境
  • git checkout develop

同样地,还需要在Jenkin CI中定义个任务,其中最主要的就是要监听来自staging分支,并且构建的脚本如下:

cd /home/stagegit reset --hard HEADgit pull origin staging -fsource env/bin/activatepip install -r requirements.txtsupervisorctl restart project_stage

上面的具体构建脚本的步骤的具体含义是

  1. git pull最新的代码
  2. 切换到develop分支
  3. 激活virtualenv环境
  4. 安装requirements.txt里面要求的python库
  5. 重启project_stage

对于production分支,主要的区别就在于采用手动触发的方式(即触发器不选择,人工点执行构建)。

参考文献

  • 在Python Web项目中使用Jenkins进行持续集成
  • Flask Config实践
  • CentOS-6.3安装配置JDK-7
  • 在centos中安装jenkins master测试环境
  • Continuous Web Development with Flask & Jenkins
  • git flow
  • github flow