經過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項目等。

相關文章
相關標籤/搜索