对于单元测试,我们的总的原则是:
单元测试应该写,因为这样才能保证程序的质量和养成良好的习惯,但是又不能将单元测试搞得太复杂,花太多的精力在这上面,那就本末倒置了。
pytest的简单使用
单元测试工具选用pytest(这个工具和go test有点类似),简单的使用:
代码语言:javascript复制# 文件: example.py
def func(i: int) -> int:
return i * 2
# 文件: example_test.py
from .example import func
def test_func():
assert func(10) == 20
assert func(20) == 30
代码语言:javascript复制
在相同的目录下执行命令pytest,该命令会自动找到*_test.py的文件(注意:当前目录需要文件__init__.py)执行测试用例。显然这个单元测试是不通过的,报错信息如下:
代码语言:javascript复制 def test_func():
assert func(10) == 20
> assert func(20) == 30
E assert 40 == 30
E where 40 = func(20)
doctest_example_test.py:7: AssertionError
代码语言:javascript复制
pytest可以递归自动发现测试文件,在使用过程中,也支持执行指定用例:
- 指定测试文件路径 pytest /path/to/test/file.py
- 指定测试类 pytest /path/to/test/file.py:TestCase
- 指定测试方法 pytest another.test::TestClass::test_method
- 指定测试函数 pytest /path/to/test/file.py:test_function
关于单元测试的几个规范
关于单元测试,我们定义几个使用规范:
- 我们写的函数或者类等,要时刻保持可测试的状态(或者说叫可观测的状态)。
- 测试用例文件名要对应,例如文件名为filename.py,则对应的测试用例文件名为filename_test.py。
- 测试函数名要对应,例如业务函数名为func_name,则对应的测试函数名为test_func_name。
- 测试类名要对应,例如原类名为ClassName,则对应测试类名为TestClassName。
- 建议测试用例文件和功能文件放在相同目录下,方便查找,通常不需要一个单独的测试目录。
使用参数化测试来优化测试用例
代码语言:javascript复制在实际使用中,我们应该优先考虑使用参数化测试:
# 前面那个测试用例其实应该优化成这样:
import pytest
# 这里数据的部分完全可以定义成一个变量
# 这样就不必重复写很多个assert语句了
@pytest.mark.parametrize("params, expected", [([10], 20), ([20], 30)])
def test_func2(params, expected):
assert func(*params) == expected
同样,执行之后,测试也是不通过的:
代码语言:javascript复制 @pytest.mark.parametrize("params, expected", [([10], 20), ([20], 30)])
def test_func2(params, expected):
> assert func(*params) == expected
E assert 40 == 30
E where 40 = func(*[20])
doctest_example_test.py:18: AssertionError
代码语言:javascript复制
如果把这个parametrize函数进行封装的话,应该可以做到更加简单,例如单元测试只要描述输入输出即可:
代码语言:javascript复制# 只要专注于编写测试用例即可
test_data = [
(func, [10], {}, 20),
(func, [20], {}, 30)
]
@pytest.mark.parametrize("action, args, kwargs, expected", test_data)
def test_all_func(action, args, kwargs, expected):
assert action(*args, **kwargs) == expected
这样,只要专注写好测试用例即可。
20210626