单元测试中的单元能够是一个模块文件, 测试的内容就是模块自身的代码(非导入型代码)是否正确执行. 其中包含了测试代码的正反向逻辑是否正确, 异常可否被正常的触发等程序流. 因此咱们会使用伪数据来替代这个单元中全部导入型代码的数据集(函数返回值/数据值).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 单元测试, 咱们最重要的是要理解单元测试的原理及其存在的意义.
原理: 确保被测试的单元模块中的导入型数据都被替换成伪数据, 以此来保证单元的独立性. 并在此独立的条件下确保单元正确的逻辑和正确的异常处理.
意义: 单元测试可以保证项目中的每个模块在被修改后还能保持其原始的标准, 若是在修改了一个模块后不能保证其标准的话, 当我再次执行单元测试时, 就会报错. 这些标准是很是重要的, 是一个复杂的项目可以正常运行的基础.