目录

单元测试的原理

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

单元测试的实现

这里使用一个 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. display:inline-block元素之间空隙的产生原因和解决办法

    在CSS布局中,如果我们想要将一些元素在同一行显示,其中的一种方法就是把要同行显示的元素设置display属性为inline-block.但是你会发现这些同行显示的inline-block元素之间会出 ...

  2. 初探 -1 JavaScript

    JavaScript 教程 JavaScript 是 Web 的编程语言. 所有现代的 HTML 页面都使用 JavaScript. JavaScript 非常容易学. 本教程将教你学习从初级到高级J ...

  3. php实用小技巧【持续更新】

    这是本人开始做项目的时候遇到过的问题还有解决方法 1.eval函数 能把字符串转换成可执行的php代码,如果字符串不是可执行的php代码的话,需要在前面加上@,屏蔽notice 2.array_mer ...

  4. 一、Signalr WebApi客服

    一.搭建环境 (redis服务) 链接测试 二.项目搭建 参考 1.搭建项目(直接项目-不包含MVC以及API) 项目结构 但是需要访问(所以还需要添加控制器Api的模式)选择Api 添加类库一个专门 ...

  5. java poi 操作

    Java POI 操作Excel(读取/写入) https://www.cnblogs.com/dzpykj/p/8417738.html Java操作Excel之Poi基本操作 https://my ...

  6. linux 最大文件打开数

    配置文件 vim /etc/security/limits.conf   # /etc/security/limits.conf##This file sets the resource limits ...

  7. node.js 实现 AES CTR 加解密

    node.js 实现 AES CTR 加解密 node aesctr 前言 由于最近我们在做一款安全的文件分享 App, 所有文件均需要使用 aes ctr 来进行加密,aes key 还有一整套完整 ...

  8. 4. ClustrixDB CLX命令详解

    Clustrix提供了一个名为clx的实用程序来管理其分布式ClustrixDB数据库. 命令在 /opt/clustrix/bin/ 下面 sudo su - clxm 用户即可使用 clx hel ...

  9. linux-shell脚本基础-2

    1,用户组 添加用户 useradd -u UID -o -g 指定 GID或组名 -c 注释信息 -d 家目录 -s shell -G 附加组 -r 系统用户 -m 家目录,系统用户 -M 不创建家 ...

  10. Mysql基本原理和概念

    一.引言 随着互联网应用的广泛普及,海量数据的存储和访问成为了系统设计的瓶颈问题.对于一个大型的互联网应用,每天几十亿的PV无疑对数据库造成了相当高的负载.对于系统的稳定性和扩展性造成了极大的问题.通 ...