从0到1:搭建基于Travis CI和GitHub的自动化测试工作流

在上个月的博客中我们讨论了如何将博客Docker化([译文链接](http://dockone.io/article/961))。但是那篇文章我没有讲我是怎么使用Docker Hub的自动构建功能来在包含了本博客源码的GitHub仓库有改动的时候自动地构建一个新的镜像。 自动构建非常有用。因为我只需要简单地改动仓库中的代码或者文章,然后一旦push,这些改动都会触发Docker Hub使用我们前一个文章中创建的`Dockerfile`来构建一个镜像。另外一个好处是Docker镜像也会在Docker Hub中获取到,这意味任何安装了Docker的系统都可以通过执行`docker run -d madflojo/blog`这样简单的命令来部署最新的博客版本。 唯一的问题在于,假如这些改动具有破坏性怎么办?假如一次构建让整体构建都无法完成,或者更糟的,让静态网站生成器不再正确的生成页面。我需要的是一个方法在他们被合并到`master`分支并部署到生产环境之前,提前知道是否一个改动将会造成问题。 要达到这一点,我们可以利用持续集成的原则和工具。 什么是持续集成 持续集成(Continuous Integration)或者CI,是一个已经在软件开发中已经流行好一阵子的东西了,但是最近逐渐在运维界中获得了越来越多的拥趸。CI提出来是为了解决多个开发者在同一个代码库开发的时候造成的集成问题。基本上,两个开发者在同一样的代码上进行开发就会产生冲突,并且只有在之后很久才会发现这些冲突。 而基本的规律是,越往后发现代码中的问题,就要为修复这些问题付出越昂贵的代价,不管是时间还是金钱。要解决这个问题,可以让开发者更加频繁地将代码提交到版本控制中,一天平均提交多次。随着代码更频繁的提交到推到代码库,代码集成的问题就会减少,并且即使真正在发生了问题,这些问题也能更加容易的被解决。 然而一天多次的提交代码本身无法解决集成问题。还需要一个方法来保证被提交的代码质量过关且能良好运行。这就引出了CI的另外一个概念,每当代码提交的时候,代码需要进行自动构建和测试。 在本博客的这个场景中,这个构建将会包含构建一个Docker镜像,并且测试将会包含多个我编写的测试,用来保证支撑本博客的代码是能正常工作的。要执行这些自动化的构建和测试,我们需要一个工具能检测更改发生,并且执行必要的操作;我们需要一个类似Travis CI的工具。 Travis CI Travis CI是一个持续构建工具,集成了GitHub并且能执行自动化的构建和测试操作。它对于公有的GitHub仓库免费,比如我的这个博客。 在这篇文章中,我将走一遍如何配置Travis CI来自动构建和测试为这个博客生成的Docker镜像,这会教会你如何使用Travis CI来测试你自己的Docker构建的基础知识。 使用Travis CI来自动化Docker构建 这个博客将会假设你已经注册了Travis CI,并且将其连接到我们我们的公有仓库。这个过程十分的简单,这是一个Travis CI新手上路的一部分。如果你需要更加详细的教程,请查看Travis CI的新手上路指南。 因为我们将对我们的构建进行测试,我们不希望影响主要的`master`分支,那第一件要做的事情就是创建一个新的`git`分支用于我们后续的实验。
$ git checkout -b building-docker-with-travis
当我们像这个分支做改动的时候,我们可以将内容提交到Github上仓库的同一名称分支下,然后验证Travis CI的构建结果,而不需要让更改影响到`master`分支。 # 配置Travis CI 首先在我们新创建分支中,创建一个`.travis.yml`文件,这个文件主要包含Travis CI需要用到的配置和指令。在这个文件中我们能告诉Travis CI构建的环境需要使用什么编程语言环境,需要运行什么服务,以及进行构建需要需要执行什么指令。 # 定义构建环境 在开始任何的构建之前我们需要定义好构建环境是什么样子的。比如,因为`hamerkop`应用和相关的测试脚本是用Python编写的,我们需要在测试环境中安装Python。 尽管安装好Python只需要使用几个`apt-get`命令就能搞定,但是因为Python是唯一我们在这个环境中需要的语言,将其在`.travis.yml`文件中用`language: python`参数来将其定义为基础语言是更好的做法。
language: python
python:
  - 2.7
  - 3.5
上面的配置让Travis CI来将构建环境设置成为一个Python的环境;且明确要求需要Python的2.7和3.5的版本安装好并且能提供支持。 上面使用的语法是用YAML文件格式指定的,这是一种十分流行的配置格式。在上面我们主要将`language`参数定义为python并且将`python`参数设置为一个包含值2.7和3.5的列表。如果我们需要添加额外的版本,那只需要简单地在这个列表下添加该版本,如下边这样。
language: python
python:
  - 2.7
  - 3.2
  - 3.5
在上边这个例子我们仅仅通过将3.2添加到这个列表完成了就添加这个版本支持。 ## 必需的服务 因为我们将要构建一个Docker镜像我们也需要Docker安装好,并且需要Doker服务在环境中运行,我们可以通过使用`services`参数来让Travis CI来安装Docker并且启动Docker服务。
services:
  - docker
与`python`参数类似`services`参数是一个需要在我们环境中启动的服务的列表。这意味着通过向这个列表添加条目就能添加额外的服务。假如我们需要Docker和Redis服务,那只需要指定Docker服务下面那一行再添加一行。
services:
  - docker
  - redis-server
在这个例子中我们不需要除Docker之外的任何服务,然而知道Travis CI支持很多服务是很有用处的。 # 进行构建 现在我们已经定义好的我们想要的构建环境,我们可以执行构建步骤了。因为我们想验证一个Docker的构建我们基本上需要执行两个步骤:构建一个Docker的镜像,并且启动一个基于该镜像的容器。 我们可以依靠指定在前一篇文章中使用的`docker`命名来一步步的执行这些步骤。
install:
  - docker build -t blog .
  - docker run -d -p 127.0.0.1:80:80 --name blog blog
在上面我们可以看到在`install`参数下面两个`docker命令。这个`install`参数实际上是Travis CI的一个定义好的构建步骤。 Taravis CI有多个在构建中预定义的步骤,在`.tarvis.yml`文件中可以对这些步骤进行指定。在上面的例子中我们定义了那两个`docker`命名令是安装这个应用的两个必需步骤。 # 测试构建 Travis CI不仅仅是一个简单的构建工具,它还是一个持续构建工具,这意味着它的主要功能是进行测试。这也意味着我们需要在构建步骤中添加一个测试;暂时我们可以简单地验证是否Docker容器是否能运行,这可以通过执行命令`docker ps`命令。
script:
  - docker ps | grep -q blog
在上面,我们通过使用`script`参数定义了我们基本的测试。这是另外一个我们可调用测试用例的构建步骤。`script`步骤是一个必需的步骤,如果省略了那构建就会失败。 ## Push到GitHub 定义上面的步骤之后,我们有了一个能发送给Travis CI的最小化的构建;要完成这一点我们只需要将我们的改动push到GitHub仓库。
$ git add .travis.yml 
$ git commit -m "Adding docker build steps to Travis"
[building-docker-with-travis 2ad7a43] Adding docker build steps to Travis
 1 file changed, 10 insertions(+), 32 deletions(-)
 rewrite .travis.yml (72%)
$ git push origin building-docker-with-travis
在Travis CI的注册过程中,你会被询问到将你的仓库和Travis CI进行连接。这能让它监控仓库发生的任何改动。当改动发生的时候,Travis CI会自动的拉取这些改动并执行在`.travis.yml`文件中定义好的步骤。在这个例子中,意味着它会执行我们的Docker构建然后验证是否一切工作正常。 就在我们将我们新的改动推到仓库中的时候,Travis CI应该已经检测到了这些更改。我们可以到Travis CI中验证时候这些改动的结果是否能够构建成功。 Travis CI将会为每一个构建显示一个构建日志,在这个特定构建日志的末尾,我们可以看到构建成功了。
Removing intermediate container c991de57cced
Successfully built 45e8fb68a440
$ docker run -d -p 127.0.0.1:80:80 --name blog blog
45fe9081a7af138da991bb9e52852feec414b8e33ba2007968853da9803b1d96
$ docker ps | grep -q blog

The command "docker ps | grep -q blog" exited with 0.

Done. Your build exited with 0.
Travis CI的一个很重要的一点是,绝大多的构建步骤需要命令成功执行才能让构建被标记为成功。 步骤`script`和`install`就是两个例子,如果这两个步骤中的任何命令失败了,没有返回0的退出码那么整个构建将被标记为失败。 如果这发生在`install`步骤阶段,构建将会终止在该步骤发生的那一个点。然而对于`script`步骤,构建将不会停止。这背后的想法是,如果一个`install`步骤失败了,构建无论如何也不会成功;然而假如一个单个测试用例失败了,只有一部分功能会失败。最终将所有的测试结果显示给用户,用户能自己辨别出哪些没有正常工作,哪些是符合预期工作正常。 添加额外的测试 尽管现在我们Travis CI能验证是否Docker构建成功与否,仍然有很多其他的可能让我们不经意地破坏这个博客的运行。比如我们可能做了一个改动,让网站静态生成器无法继续正常生成页面,这会破坏容器中的网站但不一定会破坏容器自身。要避免类似这样的情形发生,我们需要添加一些额外的测试。 在我们的仓库中,有一个目录叫做`tests`,这个目录包含三个目录,`unit`,`integration`和`functional`。这些目录包含了这个环境的多种自动化测试。前两个测试类型`unit`和`intergration`是被设计来测试在`hamerkop.py`应用中的代码的。尽管其很有用但是这些测试对于测试Docker容器却帮不上忙。然后最后一个目录`functional`,包含了可以用来测试运行中的Docker容器的自动化测试。
$ ls -la tests/functional/
total 24
drwxr-xr-x 1 vagrant vagrant  272 Jan  1 03:22 .
drwxr-xr-x 1 vagrant vagrant  170 Dec 31 22:11 ..
-rw-r--r-- 1 vagrant vagrant 2236 Jan  1 03:02 test_broken_links.py
-rw-r--r-- 1 vagrant vagrant 2155 Jan  1 03:22 test_content.py
-rw-r--r-- 1 vagrant vagrant 1072 Jan  1 03:13 test_rss.py
这些测试是用来连接到运行的Docker容器并且验证静态网站的内容。 比如,`test_broken_links.py`将会爬取由Docker容器服务的网站,然后检测每一个页面HTTP的返回状态码。如果状态码不是`200 OK`那么测试就会失败。`test_content.py`也会爬取网站并且验证返回的内容,看其是是否与一个特定的模式匹配。如果不匹配,那么这些测试还是会失败。 这些测试有用之处在于,即使静态网站在Docker容器中运行,我们仍然能够测试网站的功能性。如果我们能像Travis CI的配置添加这些测试,它们也会在每一次构建的时候运行;可以给我每一次发生的更改更多信心。 # 用`before_scriot`安装测试的必须条件 要通过Travis CI来运行这些测试,我们只需要简单地将他们添加到`script`部分,正如我们添加`docker ps`命令一样。然而在它们可以被执行之前,这些测试需要安装好一些Python的库。要安装这些库我们可以将安装步骤放在`before_script`的构建步骤里面:
before_script:
  - pip install -r requirements.txt
  - pip install mock
  - pip install requests
  - pip install feedparser
`before_script`构建步骤会在`script`步骤之前,但在`install`步骤之后执行。这让`before_script`是一个完美的放置`script`的预先条件,但是不属于整个安装过程的地方。因为`before_script`不像`install`步骤一样会执行安装操作,它也要求所有的命令都成功执行才会继续`script`构建步骤。如果`before_script`构建的一个命令失败了,那么构建将会失败。 # 运行额外的测试 在必须的Python库安装好我们可以添加测试添加到`script`步骤中:
script:
  - docker ps | grep -q blog
  - python tests.py
这些测试可以通过通过执行`tests.py`来启动,然后执行所有的是哪个自动化测试:`unit`,`intergration`和`functional`。 # 再次进行测试 在测试添加好我们可以再一次将我们的更改提交到GitHub。

$ git add .travis.yml
$ git commit -m "Adding tests.py execution"
[building-docker-with-travis 99c4587] Adding tests.py execution
 1 file changed, 14 insertions(+)
$ git push origin building-docker-with-travis
当把更新提交到仓库之后我们可以坐下来然后静静等待Travis CI构建并测试我们的应用了。
###################################################################[size=16]#[/size]
Test Runner: Functional tests
###################################################################[size=16]#[/size]
runTest (test_rss.VerifyRSS)
Execute recursive request ... ok
runTest (test_broken_links.CrawlSite)
Execute recursive request ... ok
runTest (test_content.CrawlSite)
Execute recursive request ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.768s

OK
一旦构建完成我们可以在构建日志中看到如上的消息,显示Travis CI已经真正地运行了我们的测试。 总结 当我们我们构建成功处理完成,然后我们最后看看我们`.travis.yml`文件的样子:
language: python
python:
  - 2.7

services:
  - docker

install:
  - docker build -t blog .
  - docker run -d -p 127.0.0.1:80:80 --name blog blog

before_script:
  - pip install -r requirements.txt
  - pip install mock
  - pip install requests
  - pip install feedparser

script:
  - docker ps | grep -q blog
  - python tests.py
在上面我们可以看到Travis CI配置包含三个构建步骤:`install`,`before_script`和`script`。`install`步骤用来构建并且启动我们的Docker容器。`before_script`步骤只是用来安装测试脚本所必需的库,然后`script`用来执行我们的测试脚本。 整体上,这个设置是非常简单的,即使不用Travis CI我们也可以手动的就行测试。然而,使用Travis C的好处在于所有这些步骤会在每一次改动的时候被自动执行,不管这些改动多么细微。 并且,因为我们使用Github将会将构建的状态通知附加到每一个拉取请求中,比如这个。有了这些提醒,在合并这种类型的拉取请求的时候我就有信心它们不会对生产环境造成破坏。 # 构建一个持续集成和部署的流水线 在上个月的博文里,我们探索了如何使用Docker打包并发布运行本博客的应用。在这个文章中,我们讨论了利用Travis CI来自动构建Docker镜像并且对其做功能性测试。 在接下来的博文里,我们将会更进一步,自动地将这些更改使用SaltStack部署到多个服务器。在下一个文章结束的时候,我们就有了一个完整的持续集成和部署工作流,这会让更改经过测试,并且不需要人为的干预就能部署到生产环境。 原文链接:Using Travis CI to test Docker builds(翻译:钟最龙)

0 个评论

要回复文章请先登录注册