目录

单元测试的原理

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

单元测试的实现

这里使用一个 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. JVM内存分配和垃圾回收以及性能调优

    JVM内存分配策略 一:堆中优先分配Eden 大多数情况下,对象都在新生代的Eden区中分配内存.而新生代会频繁进行垃圾回收. 二:大对象直接进入老年代 需要大量连续空间的对象,如:长字符串.数组等, ...

  2. 安装superset踩过的坑

    问题一: # fabmanager create-admin --app superset setuptools_scm.version.SetuptoolsOutdatedWarning: your ...

  3. 如何修改Git已提交的日志

    情况一:最后一次提交且未push 执行以下命令: git commit --amend git会打开$EDITOR编辑器,它会加载这次提交的日志,这样我们就可以在上面编辑,编辑后保存即完成此次的修改. ...

  4. 下载并安装eclipse

    一. 下载eclipse 1. 进入eclipse官网——www.eclipse.org 2. 点击“DOWNLOAD”,进入下载界面 3. 点击“Download Packages”,选择所需ecl ...

  5. Web Api 接口返回值不困惑:返回值类型详解

    前言:已经有一个月没写点什么了,感觉心里空落落的.今天再来篇干货,想要学习Webapi的园友们速速动起来,跟着博主一起来学习吧.之前分享过一篇 WebApi 接口参数:传参详解,这篇博文内容本身很基础 ...

  6. java 对象与类

    类与类之间的关系 一.继承关系      继承指的是一个类(称为子类.子接口)继承另外的一个类(称为父类.父接口)的功能,并可以增加它自己的新功能的能力.在Java中继承关系通过关键字extends明 ...

  7. MyBatis:Parameter Maps collection does not contain value for 的问题解决

    Result Maps collection does not contain value for   frontpreviewprofitManage.cdata 出现上述错误 主要是因为你的sel ...

  8. pt-align的用法简要记录

    pt-align的用法简要记录 1.pt-align 功能:将其它工具的输出按列对齐用法:pt-align [FILES]如果没有指定文件,则默认读取标准输入的内容. 2.例如: [root@dbte ...

  9. Python抽象类(abc模块)

    1.抽象类概念 抽象类是一个特殊的类,只能被继承,不能实例化 2.为什么要有抽象类 其实在未接触抽象类概念时,我们可以构造香蕉.苹果.梨之类的类,然后让它们继承水果这个基类,水果的基类包含一个eat函 ...

  10. React Native中集成友盟社会化分享-----童叟无欺

    1.下载所需的jar,下载地址https://developer.umeng.com/sdk/reactnative?spm=a211g2.211692.0.0.28967d238GW6mC 2.将以 ...