文章目录
单元测试的概念
随着软件开发规模的不断变大,代码体积的膨胀,路径覆盖,以及整体测试会变得十分困难
所以错误定位,以及 代码单元可靠 十分必要。
单元测试,简单讲,就是对 一部分代码编写测试用例,以确保被测试的代码单元代码的可靠性。
下边举一个很简单的单元测试例子
待测试单元(模块)
my_math.py
很简单的模块,他只包含了,加法,乘法,阶乘
#!/usr/bin/python
# -*- coding:utf-8 -*-
'''
@File : my_math.py
@Time : 2020/12/24 16:38:24
@Author : lmk
@Version : 1.0
'''
def my_add(a, b):
return a+b
def my_multiply(a, b):
return a*b
def my_factorial(a, b):
return a**b
if __name__ == '__main__':
pass
编写一个 加法 测试单元
#!/usr/bin/python
# -*- coding:utf-8 -*-
'''
@File : test_my_math.py
@Time : 2020/12/24 16:26:49
@Author : lmk
@Version : 1.0
'''
from testtools import TestCase
import my_math
class Test_my_math(TestCase):
def setUp(self):
super(Test_my_math, self).setUp()
print("this is set_up")
def test_my_add_case_1(self):
self.assertEqual(6, my_math.my_add(1, 5))
def test_my_add_case_2(self):
self.assertEqual(7, my_math.my_add(1, 5))
if __name__ == '__main__':
pass
执行这个测试单元
python3 -m testtools.run test_my_math.py
RuntimeWarning: 'testtools.run' found in sys.modules after import of package 'testtools', but prior to execution of 'testtools.run'; this may result in unpredictable behaviour
warn(RuntimeWarning(msg))
Tests running...
this is set_up
this is set_up
======================================================================
FAIL: test_my_math.Test_my_math.test_my_add_case_2
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/lmk/codes/learn_testtools/test_my_math.py", line 25, in test_my_add_case_2
self.assertEqual(7, my_math.my_add(1, 5))
File "/home/lmk/.local/lib/python3.6/site-packages/testtools/testcase.py", line 415, in assertEqual
self.assertThat(observed, matcher, message)
File "/home/lmk/.local/lib/python3.6/site-packages/testtools/testcase.py", line 502, in assertThat
raise mismatch_error
testtools.matchers._impl.MismatchError: 7 != 6
Ran 2 tests in 0.002s
FAILED (failures=1)
Ran 2 tests in 0.002s
FAILED (failures=1)
输出结果表明 两项 测试 有一项失败了
assertEqual 断言函数,接受三个参数
def assertEqual(self, expected, observed, message=’’):
expected 是 逻辑上期待 的正确结果
observed 是 代码实际 的运行结果
message 是 额外的 错误信息
测试命令的其他可选项
python3 -m testtools.run -h
-h, --help show this help message and exit
-v, --verbose Verbose output
-q, --quiet Quiet output
--locals Show local variables in tracebacks
-f, --failfast Stop on first fail or error
-c, --catch Catch ctrl-C and display results so far
-b, --buffer Buffer stdout and stderr during tests
-l, --list List tests rather than executing them
--load-list LOAD_LIST
Specifies a file containing test ids, only tests
matching those ids are executed
-s START, --start-directory START
Directory to start discovery ('.' default)
-p PATTERN, --pattern PATTERN
Pattern to match tests ('test*.py' default)
-t TOP, --top-level-directory TOP
Top level directory of project (defaults to start
directory)
进阶1 - mock 模拟测试
- 在开发工作中,难免有一些函数,项目是 并行开发的。
- 这个func 依赖其他项目的 输出。
那么此时想要 进行 单元测试。屏蔽外部干扰。
如果不进行模拟而是进行,真正的依赖,那么很有可能导致错误的产生。
- 上游错误,导致下游所有测试失败。
- 多个错误发生了叠加,使得错误更难被追踪。
所以我们尽量采用 mock 来屏蔽外部的干扰,让程序得到预期的输入
在 Test_my_math 添加 模拟 测试函数指定返回 固定值
添加一个@patch 装饰器 来 模拟函数返回值
@patch("my_math.my_add", new=Mock(return_value=7))
def test_my_add_case_3(self):
res = my_math.my_add(1, 5)
self.assertEqual(7, res)
python3 -m testtools.run test_my_math.py
Ran 2 tests in 0.002s
OK
指定 模拟函数 多次调用的 不同的返回值
from cinder import test
from cinder.volume import ceph_api
from cinder.tests.unit.volume.volume_constant import val
import mock
class TestCephApi(test.TestCase):
def setUp(self):
super(TestCephApi, self).setUp()
self.api = ceph_api.API()
self.context = None
# return list when then input is right
@mock.patch("cinder.volume.ceph_api.API._handle_ceph_cmd", mock.Mock(side_effect = [[_, val.RIGHT_POOL_INFO_OUTBUF, _], [_, val.RIGHT_POOL_DF_OUTBUF, _]]))
def test_get_all_pools_with_right_input(self):
res = self.api.get_all_pools(self.context)
self.assertIsInstance(res, list)
# return AttributeError when input is err
@mock.patch("cinder.volume.ceph_api.API._handle_ceph_cmd", mock.Mock(side_effect = [[_, val.ERR_POOL_INFO_OUTBUF, _], [_, val.ERR_RIGHT_POOL_DF_OUTBUF, _]]))
def test_get_all_pools_with_err_input(self):
self.assertRaises(AttributeError, self.api.get_all_pools, self.context)
# return dict when then input is right
@mock.patch("cinder.volume.ceph_api.API.get_all_mons", mock.Mock(side_effect = [""]))
@mock.patch("cinder.volume.ceph_api.API.get_all_pools", mock.Mock(side_effect = [""]))
@mock.patch("cinder.volume.ceph_api.API._handle_ceph_cmd", mock.Mock(side_effect = [[_, val.RIGHT_POOL_STATUS_OUTBUF, _]]))
def test_get_all_status_with_right_input(self):
res = self.api.get_all_status(self.context)
self.assertIsInstance(res, dict)
# return keyerr when input is err
@mock.patch("cinder.volume.ceph_api.API.get_all_mons", mock.Mock(side_effect = [""]))
@mock.patch("cinder.volume.ceph_api.API.get_all_pools", mock.Mock(side_effect = [""]))
@mock.patch("cinder.volume.ceph_api.API._handle_ceph_cmd", mock.Mock(side_effect = [[_, val.ERR_POOL_STATUS_OUTBUF, _]]))
def test_get_all_status_with_err_input(self):
self.assertRaises(KeyError, self.api.get_all_status, self.context)
side_effect 拓展用法,采用函数替换,patch 目标函数
如果希望更加动态的 指定 mock 返回值。
可以考虑采用一个 简单函数 来 mock 原有函数。
mock = Mock(return_value=3)
def side_effect_func(*args, **kwargs):
return DEFAULT
mock.side_effect = side_effect_func
mock()