zl程序教程

您现在的位置是:首页 >  其它

当前栏目

Openstack_单元测试

单元测试 Openstack
2023-09-27 14:28:47 时间
单元测试中的单元可以是一个模块文件, 测试的内容就是模块自身的代码(非导入型代码)是否正确执行.

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


单元测试文件存储路径: /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() 用于返回当我们正确执行数据库调用时, 所被返回的伪数据.


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


这样的话, 就针对 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


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)

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 单元测试, 我们最重要的是要理解单元测试的原理及其存在的意义.
原理: 确保被测试的单元模块中的导入型数据都被替换成伪数据, 以此来保证单元的独立性. 并在此独立的条件下确保单元正确的逻辑和正确的异常处理.

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


单元测试集成测试自动化工具 GAIO公司的覆盖率专家winAMS获得机能安全标百准ISO26262/IEC61508工具认证,是日本工业制造度领域普遍使用的针对C/C++的单元/集成测试工具. winAMS将通过交叉编译生成的原始代码作为评价代码,具有使用芯片仿真器进行仿真功能的测试工具. 不仅可以对C/C++语言编写的程序进行逻辑水平的测试,还可以对嵌入式软件特有的依存于芯片的问题点进行确认.
Spock单元测试框架初探 软件工程发生在代码被非原作者阅读之时 Spock vs JUnit 单元测试框架,JUnit读者已了解,因此直接开门见山,基于JUnit和Spock做一个对比,明显Spock在工程化更有优势。