目录

单元测试的原理

单元测试中的单元可以是一个模块文件, 测试的内容就是模块自身的代码(非导入型代码)是否正确执行. 其中包含了测试代码的正反向逻辑是否正确, 异常能否被正常的触发等程序流. 所以我们会使用伪数据来替代这个单元中所有导入型代码的数据集(函数返回值/数据值).

单元测试的实现

这里使用一个 API 接口模块的单元测试为例.

  • 单元测试文件存储路径: /opt/stack/keystone/keystone/tests/unit

  • 单元测试代码文件的命名规则: “test_moduleName.py”

    EXAMPLE:

    被测试的模块为 vmware_connects.py, 其单元测试的实现为 test_vmware_connects.py.

在大多数的单元测试文件中都会涉及到以下几个类:

from serviceName import test    # 其中 class test.TestCase 是单元测试类的父类
from serviceName.tests.unit.api import fakes # 主要提供 HTTP 请求的相关数据
from serviceName.tests.unit.api.v1 import stubs # 为单元测试类提供伪数据
  • 首先, 需要查看 vmware_connects.py 模块中所需要的伪属性数据

    因为 vmware_connects.py 是一个 HTTP API 模块, 所以我们可以从数据库中的 vmware_connects 表得知其返回的数据集.
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_connects 模块中含有那些需要被替换的伪方法数据

    EXAMPLE: 在 vmware_connects.VmwareConnectController:show() 中调用了外来模块 vmware_connect_api 的 vmware_connect_get() 方法. 除此之外还实现了 try-catch 语句. 所以我们仍要在 stubs 模块中实现 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() 用于返回当我们正确执行数据库调用时, 所被返回的伪数据.

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() .

    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().

最后还需要定义一个能够触发异常的伪数据, 而且我们可以看出 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 单元测试, 我们最重要的是要理解单元测试的原理及其存在的意义.

原理: 确保被测试的单元模块中的导入型数据都被替换成伪数据, 以此来保证单元的独立性. 并在此独立的条件下确保单元正确的逻辑和正确的异常处理.

意义: 单元测试能够保证项目中的每一个模块在被修改后还能保持其原始的标准, 如果在修改了一个模块后不能保证其标准的话, 当我再次执行单元测试时, 就会报错. 这些标准是非常重要的, 是一个复杂的项目能够正常运行的基础.

Openstack_单元测试的更多相关文章

  1. Openstack_单元测试工具 tox

    目录 目录 扩展阅读 Openstack 的单元测试工具 单元测试工具使用流程 tox toxini 参考文章 扩展阅读 Python Mock的入门 Openstack 的单元测试工具 unitte ...

  2. Intellij idea添加单元测试工具

    1.idea 版本是14.0.0 ,默认带有Junit,但是不能自动生成单元测试,需要下载JunitGererator2.0插件 2.Settings -Plugins,下载 JunitGenerat ...

  3. Python的单元测试(二)

    title: Python的单元测试(二) date: 2015-03-04 19:08:20 categories: Python tags: [Python,单元测试] --- 在Python的单 ...

  4. Python的单元测试(一)

    title: Python的单元测试(一) author: 青南 date: 2015-02-27 22:50:47 categories: Python tags: [Python,单元测试] -- ...

  5. javascript单元测试框架mochajs详解

    关于单元测试的想法 对于一些比较重要的项目,每次更新代码之后总是要自己测好久,担心一旦上线出了问题影响的服务太多,此时就希望能有一个比较规范的测试流程.在github上看到牛逼的javascript开 ...

  6. 使用NUnit为游戏项目编写高质量单元测试的思考

    0x00 单元测试Pro & Con 最近尝试在我参与的游戏项目中引入TDD(测试驱动开发)的开发模式,因此单元测试便变得十分必要.这篇博客就来聊一聊这段时间的感悟和想法.由于游戏开发和传统软 ...

  7. 我这么玩Web Api(二):数据验证,全局数据验证与单元测试

    目录 一.模型状态 - ModelState 二.数据注解 - Data Annotations 三.自定义数据注解 四.全局数据验证 五.单元测试   一.模型状态 - ModelState 我理解 ...

  8. ABAP单元测试最佳实践

    本文包含了我在开发项目中经历过的实用的ABAP单元测试指导方针.我把它们安排成为问答的风格,欢迎任何人添加更多的Q&A's,以完成这个列表. 在我的项目中,只使用传统的ABAP report. ...

  9. python_单元测试unittest

    Python自带一个单元测试框架是unittest模块,用它来做单元测试,它里面封装好了一些校验返回的结果方法和一些用例执行前的初始化操作. 步骤1:首先引入unittest模块--import un ...

随机推荐

  1. 机器学习-回归中的相关度和R平方值

    1. 皮尔逊相关系数(Pearson Correlation Coefficient) 1.1 衡量两个值线性相关强度的量 1.2 取值范围[-1, 1] 正相关:>0, 负相关:<0, ...

  2. 安装最新版本的kubernets(+安装插件dashboard)

    一.安装docker // 安装系统工具: sudo yum install -y yum-utils device-mapper-persistent-data lvm2 // 添加docker y ...

  3. Qualcomm_Mobile_OpenCL.pdf 翻译-3

    3 在骁龙上使用OpenCL 在今天安卓操作系统和IOT(Internet of Things)市场上,骁龙是性能最强的也是最被广泛使用的芯片.骁龙的手机平台将最好的组件组合在一起放到了单个芯片上,这 ...

  4. 关于sharekey 与Open system+wep

    Open_system+wep与open_system的区别在于: 对于开放系统认证,在设置时启用WEP,此时,WEP用于在传输数据时加密,对于认证没有任何作用. 抓包open_system+wep: ...

  5. [易学易懂系列|rustlang语言|零基础|快速入门|(6)|变量绑定]

    [易学易懂系列|rustlang语言|零基础|快速入门|(6)] 有意思的基础知识 变量绑定 我们现在回过头来看看,一些基础知识. 因为理解了前面的重要概念:所有权,借用,生命周期. 我们现在看基础知 ...

  6. 什么是lease机制?

    分布式系统理论之租约机制学习 一,租约机制介绍 在分布式系统中,往往会有一个中心服务器节点.该节点负责存储.维护系统中的元数据.如果系统中的各种操作都依赖于中心服务器上的元数据,那么中心服务器很容易成 ...

  7. Vue框架使用sass

    引入: cnpm install node-sass --save-dev //安装node-sass cnpm install sass-loader@7.3.1 --save-dev cnpm i ...

  8. linux系统下导出MySQL文件

    1.配置:从centOS6.5系统,MySQL数据库 2.导出.sql文件的命令: mysqldump -uroot -h116.228.90.147 -P18006 -p aimo > /ho ...

  9. 集合综合练习<一>

    1208421001,关羽,数学,93 1208421001,关羽,英语,88 1208421002,张飞,语文,82 1208421002,张飞,数学,83 1208421002,张飞,英语,89 ...

  10. day2计算机基础作业题

    1.什么是编程? 编程就是程序员用某种编程语言的语法格式将将自己脑中想让计算机做的事情写成文件.所以编程的结果就是一堆的文件,一堆文件就是的程序. 2.计算机的组成: 1).CPU 其中CPU又分为控 ...