單元測試中的單元能夠是一個模塊文件, 測試的內容就是模塊自身的代碼(非導入型代碼)是否正確執行. 其中包含了測試代碼的正反向邏輯是否正確, 異常可否被正常的觸發等程序流. 因此咱們會使用僞數據來替代這個單元中全部導入型代碼的數據集(函數返回值/數據值).css
這裏使用一個 API 接口模塊的單元測試爲例.python
單元測試文件存儲路徑: /opt/stack/keystone/keystone/tests/unit mysql
單元測試代碼文件的命名規則: 「test_moduleName.py」
EXAMPLE:
被測試的模塊爲 vmware_connects.py, 其單元測試的實現爲 test_vmware_connects.py.web
在大多數的單元測試文件中都會涉及到如下幾個類:sql
from serviceName import test # 其中 class test.TestCase 是單元測試類的父類
from serviceName.tests.unit.api import fakes # 主要提供 HTTP 請求的相關數據
from serviceName.tests.unit.api.v1 import stubs # 爲單元測試類提供僞數據
mysql> desc vmware_connects;
+------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
| deleted_at | datetime | YES | | NULL | |
| deleted | tinyint(1) | YES | | NULL | |
| id | varchar(45) | NO | PRI | NULL | |
| ipaddr | varchar(255) | YES | | NULL | |
| username | varchar(255) | YES | | NULL | |
| password | varchar(255) | YES | | NULL | |
| port | int(11) | YES | | NULL | |
| is_vcenter | tinyint(1) | YES | | NULL | |
+------------+--------------+------+-----+---------+-------+
除去基礎字段 created_at/updated_at/deleted_at/deleted 以外, 剩下的字段都是會被 vmware_connect 模塊中的方法返回的, 因此咱們須要在上述的 stubs 模塊中爲這些屬性值設置僞數據.數據庫
# tests/unit/api/v1/stubs.py
DEFAULT_VMWCON_ID = "00000000-0000-0000-0000-000000000001"
DEFAULT_VMWCON_IPADDR = "127.0.0.1"
DEFAULT_VMWCON_USERNAME = "root"
DEFAULT_VMWCON_PASSWORD = "vmware"
DEFAULT_VMWCON_PORT = "443"
DEFAULT_VMWCON_ISVCENTER = None
vmware_connect_api.vmware_connect_get()
的僞方法@wsgi.serializers(xml=VmwareConnectTemplate)
def show(self, req, id):
"""Return data about the given vmware connect."""
context = req.environ['egis.context']
try:
vmware_connect = self.vmware_connect_api.\
vmware_connect_get(context, id)
except exception.NotFound as e:
LOG.exception(_LE("Failed to show vmware_connect. id: %(s)s"
"error: %(err)s"),
{'s': id, 'err': six.text_type(e)})
raise exc.HTTPNotFound(explanation=e.msg)
return self.view_builder.show(req, vmware_connect)
在 stubs 模塊中實現僞方法以前, 咱們先定義一個用於測試 vmware_connects 模塊的單元測試類 FakeVmwareConnect, 而且在該類中咱們會定義一個方法 fake_vmware_connect()
用於返回當咱們正確執行數據庫調用時, 所被返回的僞數據.api
class FakeVmwareConnect(object):
def fake_vmware_connect(self, kwargs=dict()):
vmware_connect = {
'id': DEFAULT_VMWCON_ID,
'ipaddr': DEFAULT_VMWCON_IPADDR,
'username': DEFAULT_VMWCON_USERNAME,
'password': DEFAULT_VMWCON_PASSWORD,
'port': DEFAULT_VMWCON_PORT
}
vmware_connect.update(kwargs)
return vmware_connect
def fake_vmware_connect_get(self, context, vmware_connect_id):
return self.fake_vmware_connect()
固然, 還須要定義 vmware_connect_api.vmware_connect_get()
的僞方法 fake_vmware_connect_get()
.bash
def fake_vmware_connect_get(self, context, vmware_connect_id=None):
return self.fake_vmware_connect(vmware_connect_id)
方法 FakeVmwareConnect:fake_vmware_connect_get()
將會替換方法 vmware_connects.VmwareConnectController:show().vmware_connect_api.vmware_connect_get()
並返回以前已經定義好了的 vmware_connect_get()
.markdown
最後還須要定義一個可以觸發異常的僞數據, 並且咱們能夠看出 show() 方法中的 except 語句捕獲的是 HttpNotFound 異常. 因此繼續在 stubs 模塊中定義一個方法 fake_vmware_connect_get_notfound()
.函數
def fake_vmware_connect_get_notfound(self, context, vmware_connect_id):
raise exc.NotFound(vmware_connect_id)
vmware_connects.VmwareConnectController:show()
來講所須要的僞數據都準備好了. 接下來就能夠實現 test_vmware_connects.py 了.import webob
from serviceName import test
from serviceName.tests.unit.api import fakes
from serviceName.tests.unit.api.v1 import stubs
from serviceName.api.v1 import vmware_connects
from serviceName.recover.virt.drivers.vmware.vmware_connects import api
HTTP_PASH = '/v1/vmware_connects'
class VmwareConnectAPITest(test.TestCase):
def setUp(self):
super(VmwareConnectAPITest, self).setUp()
self.controller = vmware_connects.VmwareConnectController()
self.fake_vmware_connect = stubs.FakeVmwareConnect()
# 將 api.API:vmware_connect_get() 替換成 stubs.FakeVmwareConnect:fake_vmware_connect_get()
# 這一條語句很是重要, 指定了被測單元中的導入數據與僞數據間替換的映射關係.
self.stubs.Set(api.API, 'vmware_connect_get',
self.fake_vmware_connect.fake_vmware_connect_get)
def _vmware_connect_in_request_body( self, id=stubs.DEFAULT_VMWCON_ID, ipaddr=stubs.DEFAULT_VMWCON_IPADDR, username=stubs.DEFAULT_VMWCON_USERNAME, password=stubs.DEFAULT_VMWCON_PASSWORD, port=stubs.DEFAULT_VMWCON_PORT, is_vcenter=stubs.DEFAULT_VMWCON_ISVCENTER):
"""這個方法用於模擬當 HTTP Request 調用 API 時, 所傳入的數據."""
vmware_connect = {'id': id,
'ipaddr': ipaddr,
'username': username,
'password': password,
'port': port,
'is_vcenter': is_vcenter,
'created_at': None,
'updated_at': None}
return vmware_connect
def _expected_vmware_connect_from_controller( self, id=stubs.DEFAULT_VMWCON_ID, ipaddr=stubs.DEFAULT_VMWCON_IPADDR, username=stubs.DEFAULT_VMWCON_USERNAME, password=stubs.DEFAULT_VMWCON_PASSWORD, port=stubs.DEFAULT_VMWCON_PORT, is_vcenter=stubs.DEFAULT_VMWCON_ISVCENTER, created_at=None, updated_at=None):
"""這個方法用於模擬預期但願從 vmware_connects 模塊中返回的數據."""
vmware_connect = {'vmware_connect':
{'id': id,
'ipaddr': ipaddr,
'username': username,
'password': password,
'port': port,
'is_vcenter': is_vcenter,
'created_at': created_at,
'updated_at': updated_at}}
return vmware_connect
def test_vmware_connect_show(self):
# 模擬 Http 請求的所發送的相關信息
req = fakes.HTTPRequest.blank(''.join([HTTP_PASH,
stubs.DEFAULT_VMWCON_ID]))
# 傳入僞數據實參來調用 vmware_connects.VmwareConnectController:show() 方法, 而且該方法中全部的導入型數據都已經使用僞數據來替換了. 因此咱們能夠得出該方法實際返回的結果.
res_dict = self.controller.show(req, stubs.DEFAULT_VMWCON_ID)
# 預期返回的結果, 這個僞數據是由咱們人爲的去限定的
expected = self._expected_vmware_connect_from_controller(
id=stubs.DEFAULT_VMWCON_ID)
# 比較實際返回的結構和預期返回的結構是否相同, 若是相同則經過測試, 反之, 則失敗.
# 因爲不管是預期返回的結果仍是實際返回的結果, 都是以在 stubs 模塊中定義的僞屬性數據爲基礎的, 因此只要在保證 show() 方法的正常執行, 那麼二者應該是相同的.
self.assertEqual(expected, res_dict)
def test_vmware_connect_show_notfound(self):
# 在這一個方法中, 咱們爲了要觸發異常, 因此咱們應該將api.API:vmware_connect_get() 替換成 stubs.FakeVmwareConnect:fake_vmware_connect_get_notfound()
self.stubs.Set(
api.API, 'vmware_connect_get',
self.fake_vmware_connect.fake_vmware_connect_get_notfound)
req = fakes.HTTPRequest.blank(''.join([HTTP_PASH, '/1000']))
# 驗證是否有正確的從新觸發異常, 第二個參數爲實際的 show() 方法, 還須要爲 show() 傳入所需的兩個參數, 不然會觸發錯誤.
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
req, 1000)
sudo tox -e py27
若是經過了單元測試的話, 最後會 Output: Successfully!
NOTE: 編寫單元測試用例的時候, 默認是不能經過 pdb 來調試的. 若是但願經過 pdb 來調試代碼的話須要執行如下步驟:
sudo pip install -e . -r test-requirements.txt -r requirements.txt
在但願 DEBUG 的地方打上斷點以後運行:
python -m testtools.run serviceName.tests.unit.api.v1.test_vmware_connects
就能夠進入調試 console 了.
這只是一個 Openstack 項目中很是簡單的一個 HTTP API 單元測試, 咱們最重要的是要理解單元測試的原理及其存在的意義.
原理: 確保被測試的單元模塊中的導入型數據都被替換成僞數據, 以此來保證單元的獨立性. 並在此獨立的條件下確保單元正確的邏輯和正確的異常處理.
意義: 單元測試可以保證項目中的每個模塊在被修改後還能保持其原始的標準, 若是在修改了一個模塊後不能保證其標準的話, 當我再次執行單元測試時, 就會報錯. 這些標準是很是重要的, 是一個複雜的項目可以正常運行的基礎.