经过demo学习OpenStack开发所需的基础知识 -- 单元测试

本文将进入单元测试的部分,这也是基础知识中最后一个大块。本文将重点讲述Python和OpenStack中的单元测试的生态环境。html

单元测试的重要性

github上有我的画了一些不一样语言的学习曲线图:Learning Curves (for different programming languages),虽然有些恶搞的倾向,不过确实说明了问题。这里贴一下Python的部分:python

Python Learning Curve

这个图说明了,会单元测试对于提升Python生产力的重要性,这主要是由于Python是个动态语言,不少问题都没法经过静态编译检查来发现,所以单元测试就成了一个重要的确保质量的手段。OpenStack的核心项目都对单元测试有极高的要求,以保证项目的高质量。ios

单元测试工具

Python的单元测试工具不少,为单元测试提供不一样方面的功能。OpenStack的项目也基本把如今流行的单元测试工具都用全了。单元测试能够说是入门OpenStack开发的最难的部分,也是最后一千米。本章,咱们就介绍一下在OpenStack中会用到的单元测试的工具。因为数量不少,不可能详细介绍,所以主要作一些概念和用途上的介绍。git

unittest

unittest是Python的标准库,提供了最基本的单元测试功能,包括单元测试运行器(简称runner)和单元测试框架。项目的单元测试代码的测试类能够继承unittest.TestCase类,这样这个类就可以被runner发现而且执行。同时,unittest.TestCase这个类还定义了setUp()tearDown()setUpClass()tearDownClass()方法,是用来运行单元测试前的设置工做代码和单元测试后的清理工做代码,这个也是全部Python代码遵照的规范,因此第三方的单元测试库和框架也都遵循这个规范。github

unittest库也提供了一个runner,可使用$ python -m unittest test_module的命令来执行某个模块的单元测试。另外,在Python中指定要运行的单元测试用例的完整语法是:path.to.your.module:ClassOfYourTest.test_methodweb

unittest是学习Python单元测试最基本也最重要的一个库,完整的说明请查看官方文档:https://docs.python.org/2.7/library/unittest.htmlsql

mock

mock也是另外一个重要的单元测试库,在Python 2中是做为一个第三方库被使用的,到Python 3时,就被归入了标准库,可见这个库的重要性。简单的说,mock就是用来模拟对象的行为,这样在进行单元测试的时候,能够指定任何对象的返回值,便于测试对外部接口有依赖的代码。关于mock的使用,能够查看我以前写的这篇文章Python Mock的入门mongodb

testtools

testtools是个unittest的扩展框架,主要是在unittest的基础上提供了更好的assert功能,使得写单元测试更加方便。具体能够查看文档:http://testtools.readthedocs.org/en/latest/数据库

fixtures

fixture的意思是固定装置,在Python的单元测试中,是指某段能够复用的单元测试setUptearDown代码组合。一个fixture通常用来实现某个组件的setUp和tearDown逻辑,好比测试前要先建立好某些数据,测试后要删掉这些数据,这些操做就能够封装到一个fixture中。这样不一样的测试用例就不用重复写这些代码,只要使用fixture便可。fixtures模块是一个第三方模块,提供了一种简单的建立fixture类和对象的机制,而且也提供了一些内置的fixture。具体的使用方法能够查看官方文档:https://pypi.python.org/pypi/fixtures/segmentfault

testscenarios

testscenarios模块知足了场景测试的需求。它的基本用法是在测试类中添加一个类属性scenarios,该属性是一个元组,定义了每一种场景下不一样的变量的值。好比说你测试一段数据访问代码,你须要测试该代码在使用不一样的驱动时,好比MongoDB、SQL、File,是否都能正常工做。咱们有三种办法:

  1. 最笨的办法是为不一样的驱动把同一个测试用例编写3遍。

  2. 比较好的办法是,编写一个统一的非测试用例方法,接收driver做为参数,执行测试逻辑,而后再分别编写三个测试用例方法去调用这个非测试用例方法。

  3. 更好的办法就是使用testscenarios模块,定义好scenarios变量,而后实现一个测试用例方法。

testscenarios模块在OpenStack Ceilometer中被大量使用。更多的信息能够查看文档:https://pypi.python.org/pypi/testscenarios/

subunit

subunit是一个用于传输单元测试结果的流协议。通常来讲,运行单元测试的时候是把单元测试的结果直接输出到标准输出,可是若是运行大量的测试用例,这些测试结果就很难被分析。所以就可使用python-subunit模块来运行测试用例,而且把测试用例经过subunit协议输出,这样测试结果就能够被分析工具聚合以及分析。python-subunit模块自带了一些工具用来解析subunit协议,好比你能够这样运行测试用例:$ python -m subunit.run test_module | subunit2pyunitsubunit2pyunit命令会解析subunit协议,而且输出到标准输出。关于subunit的更多信息,请查看官方文档:https://pypi.python.org/pypi/python-subunit/

testrepository

OpenStack中使用testrepository模块管理单元测试用例。当一个项目中的测试用例不少时,如何更有效的处理单元测试用例的结果就变得很重要。testrepository的出现就是为了解决这个问题。testrepository使用python-subunit模块来运行测试用例,而后分析subunit的输出并对测试结果进行记录(记录到本地文件)。举例来讲,testrepository容许你作这样的事情:

  • 知道哪些用例运行时间最长

  • 显示运行失败的用例

  • 从新运行上次运行失败的用例

testrepository的更多信息,请查看官方文档:http://testrepository.readthedocs.org/en/latest/

coverage

coverage是用来计算代码运行时的覆盖率的,也就是统计多少代码被执行了。它能够和testrepository一块儿使用,用来统计单元测试的覆盖率,在运行完单元测试以后,输出覆盖率报告。具体的使用方法能够查看官方文档:http://coverage.readthedocs.org/en/latest/

tox

tox是用来管理和构建虚拟环境(virtualenv)的。对于一个项目,咱们须要运行Python 2.7的单元测试,也须要运行Python 3.4的单元测试,还须要运行PEP8的代码检查。这些不一样的任务须要依赖不一样的库,因此须要使用不一样的虚拟环境。使用tox的时候,咱们会在tox的配置文件tox.ini中指定不一样任务的虚拟环境名称,该任务在虚拟环境中须要安装哪些包,以及该任务执行的时候须要运行哪些命令。更多信息,请查看官方文档:https://testrun.org/tox/latest/

单元测试工具小结

本章介绍了OpenStack中经常使用的单元测试工具的基本用途,但愿你们对这些工具备个大概的认识。这里咱们能够按照类别总结一下这些工具:

  • 测试环境管理: tox
    使用tox来管理测试运行的虚拟环境,而且调用testrepository来执行测试用例。

  • 测试用例的运行和管理: testrepository, subunit, coverage
    testrepository调用subunit来执行测试用例,对测试结果进行聚合和管理;调用coverage来执行代码覆盖率的计算。

  • 测试用例的编写: unittest, mock, testtools, fixtures, testscenarios
    使用testtools做为全部测试用例的基类,同时应用mock, fixtures, testscenarios来更好的编写测试用例。

The Hacker's Guide to Python(《Python高手之路》)一书中,也有专门的一章介绍了各类单元测试工具及其用法,读者也能够参考一下。下一章,咱们来分析Keystone项目的单元测试框架,可让你看到在OpenStack的实际项目中,这些工具是如何被使用的。

Keystone的单元测试框架

如今,咱们以Keystone项目为例,来看下真实项目中的单元测试是如何架构的。咱们采用自顶向下的方式,先从最上层的部分介绍起。

使用tox进行测试环境管理

大部分状况下,咱们都是经过tox命令来执行单元测试的,而且传递环境名称给tox命令:

➜ ~/openstack/env/p/keystone git:(master) ✗ $ tox -e py27

tox命令首先会读取项目根目录下的tox.ini文件,获取相关的信息,而后根据配置构建virtualenv,保存在.tox/目录下,以环境名称命名:

➜ ~/openstack/env/p/keystone git:(master) ✗ $ ls .tox
log  pep8  py27

除了log目录,其余的都是普通的virtualenv环境,你能够本身查看一下内容。咱们来看下py27这个环境的相关配置(在tox.ini)中,我直接在内容上注释一些配置的用途:

[tox]
minversion = 1.6
skipsdist = True
# envlist表示本文件中配置的环境都有哪些
envlist = py34,py27,pep8,docs,genconfig,releasenotes

# testenv是默认配置,若是某个配置在环境专属的section中没有,就从这个section中读取
[testenv]
# usedevelop表示安装virtualenv的时候,本项目本身的代码采用开发模式安装,也就是不会拷贝代码到virtualenv目录中,只是作个连接
usedevelop = True
# install_command表示构建环境的时候要执行的命令,通常是使用pip安装
install_command = pip install -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
# deps指定构建环境的时候须要安装的依赖包,这个就是做为pip命令的参数
# keystone这里使用的写法比较特殊一点,第二行的.[ldap,memcache,mongodb]是两个依赖,第一个点'.'表示当前项目的依赖,也就是requirements.txt,第二个部分[ldap,memcache,mongodb]表示extra,是在setup.cfg文件中定义的一个段的名称,该段下定义了额外的依赖,这些能够查看PEP0508
# 通常的项目这里会采用更简单的方式来书写,直接安装两个文件中的依赖:
#    -r{toxinidir}/requirements.txt
#    -r{toxinidir}/test-requirements.txt
deps = -r{toxinidir}/test-requirements.txt
       .[ldap,memcache,mongodb]
# commands表示构建好virtualenv以后要执行的命令,这里调用了tools/pretty_tox.sh来执行测试
commands =
  find keystone -type f -name "*.pyc" -delete
  bash tools/pretty_tox.sh '{posargs}'
whitelist_externals =
  bash
  find
passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY PBR_VERSION

# 这个section是为py34环境定制某些配置的,没有定制的配置,从[testenv]读取
[testenv:py34]
commands =
  find keystone -type f -name "*.pyc" -delete
  bash tools/pretty_tox_py3.sh

上面提到的PEP-0508是依赖格式的完整说明。setup.cfg的extra部分以下:

[extras]
ldap =
  python-ldap>=2.4:python_version=='2.7' # PSF
  ldappool>=1.0:python_version=='2.7' # MPL
memcache =
  python-memcached>=1.56 # PSF
mongodb =
  pymongo!=3.1,>=3.0.2 # Apache-2.0
bandit =
  bandit>=0.17.3 # Apache-2.0

使用testrepository管理测试的运行

上面咱们看到tox.ini文件中的commands参数中执行的是tools/pretty_tox.sh命令。这个脚本的内容以下:

#!/usr/bin/env bash

set -o pipefail

TESTRARGS=$1
# testr和setuptools已经集成,因此能够经过setup.py testr命令来执行
# --testr-args表示传递给testr命令的参数,告诉testr要传递给subunit的参数
# subunit-trace是os-testr包中的命令(os-testr是OpenStack的一个项目),用来解析subunit的输出的。
python setup.py testr --testr-args="--subunit $TESTRARGS" | subunit-trace -f
retval=$?
# NOTE(mtreinish) The pipe above would eat the slowest display from pbr's testr
# wrapper so just manually print the slowest tests.
echo -e "\nSlowest Tests:\n"
# 测试结束后,让testr显示出执行时间最长的那些测试用例
testr slowest
exit $retval

tox就是从tools/pretty_tox.sh这个命令开始调用testr来执行单元测试的。testr自己的配置是放在项目根目录下的.testr.conf文件:

[DEFAULT]
test_command=
    ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./keystone/tests/unit} $LISTOPT $IDOPTION

test_id_option=--load-list $IDFILE
test_list_option=--list
group_regex=.*(test_cert_setup)


# NOTE(morganfainberg): If single-worker mode is wanted (e.g. for live tests)
# the environment variable ``TEST_RUN_CONCURRENCY`` should be set to ``1``. If
# a non-default (1 worker per available core) concurrency is desired, set
# environment variable ``TEST_RUN_CONCURRENCY`` to the desired number of
# workers.
test_run_concurrency=echo ${TEST_RUN_CONCURRENCY:-0}

这个文件中的配置项能够从testr官方文档中找到。其中test_command命令表示要执行什么命令来运行测试用例,这里使用的是subunit.run,这个咱们在上面提到过了。

到目前为止的流程就是:

  1. tox建好virtualenv

  2. tox调用testr

  3. testr调用subunit来执行测试用例

每一个OpenStack项目基本上也都是这样。若是你本身在开发一个Python项目,你也能够参考这个架构。

单元测试用例的代码架构

下面咱们来看一下Keystone的单元测试代码是如何写的,主要是看一下其层次结构。每一个OpenStack项目的单元测试代码结构可能都不同,不过你了解完Keystone的结构以后,看其余项目的就会比较快了。

咱们以一个测试类为例来分析测试代码的结构:keystone.tests.unit.test_v3_assignment:AssignmentTestCase。下面是这个类的继承结构,同一级别的缩进表示多重继承,增长缩进表示父类,这里删掉了没必要要的路径前缀(从unit目录开始),以下所示:

# 这个测试类是测RoleAssignment的API的
unit.test_v3_assignment.RoleAssignmentBaseTestCase
-> unit.test_v3.AssignmentTestMixin  这个类包含了一下测试Assignment的工具函数
-> unit.test_v3.RestfulTestCase      这个类是进行V3 REST API测试的基类,实现了V3 API的请求发起和校验
  -> unit.core.SQLDriverOverride     用于修改各个配置的driver字段为sql
  -> unit.test_v3.AuthTestMixin      包含建立认证请求的辅助函数
  -> unit.rest.RestfulTestCase       这个类是进行RESP API测试的基类,V2和V3的API测试都是以这个类为基类,这个类的setUp方法会初始化数据库,建立好TestApp。
    -> unit.TestCase                 这个类是Keystone中全部单元测试类的基类,它主要初始化配置,以及初始化log
      -> unit.BaseTestCase           这个类主要是配置测试运行的基本环境,修改一些环境变量,好比HOME等。
        -> oslotest.BaseTestCase     这个是在oslotest中定义的基类,原来全部的OpenStack项目的单元测试都继承自这个基类。
                                     不过,这个继承在Keystone中已经被删除了,Keystone本身在unit.BaseTestCase中作了差很少的事情。
                                     这个是2016-02-17作的变动,具体的能够查看这个revision 262d0b66c3bcb82eadb663910ee21ded63e77a78。
          -> testtools.TestCase      使用testtools做为测试框架
            -> unittest.TestCase     testtools自己是unittest的扩展

从上面的层次结构能够看出,OpenStack中的大项目,因为单元测试用例不少(Keystone如今有超过6200个单元测试用例),因此其单元测试架构也会比较复杂。要写好单元测试,须要先了解一下整个测试代码的架构。

总结

本文咱们了解了Python中的单元测试的概念和工具,而且经过Keystone项目了解了实际项目中的单元测试的架构,但愿有助于各位读者更好的掌握OpenStack项目的单元测试基础。webdemo项目目前没有单元测试的代码,有兴趣的读者能够本身fork而后参考Keystone的架构为其增长完整的单元测试架构。

系列后记

这个系列我打算就此结束,到目前为止一共写了8篇文章,写写停停,先后写了9个月。这里也作个小结。

一开始写这个系列的文章是由于我本身在学习OpenStack开发的过程当中遇到不少困难,很难找到所需的入门文章。因此打算写点文章,既能做为本身的总结,也能为其余人提供些帮助。若是这些文章能帮到你,我就很是的开心。固然,这些文章的质量确定有好有坏,欢迎你们提意见,若是有时间,我会继续修改。

而后,我想说一下写这类文章的难点,主要是要保证细节都是正确的,而后又不能太啰嗦。

  • 细节都是正确的。举个例子,大学的不少数据结构教材中的代码,你直接贴到电脑上,而后编译,大部分是编译不经过的。这个会让初学者很是沮丧。因此我但愿可以保证这些文章里的细节都是正确的,包括一些工具的配置,若是以为有必要,我也会描述下配置的做用,以及要去哪里找更多的信息。若是这方面有遗漏,请和我说。

  • 不能太啰嗦。这8篇文章里涉及的库有好几十个,每一个库若是都讲仔细了,那就会让文章显得很是啰嗦。可是又不能直接让读者去看库的官方文档,因此权衡内容也是很麻烦的。若是各位有这方面的建议,也请和我说。

这个系列的文章是关于OpenStack的基础知识,其实OpenStack开发还要涉及到不少其余的知识,好比消息队列、非阻塞IO等,并且还要了解整个OpenStack的开发生态,包括Gerrit评审系统、Zuul持续集成、devstack开发环境、oslo项目等。

相关文章
相关标签/搜索