赞
踩
build:构建脚本,使用makefile管理安装,测试,发布等
docs:使用文档
src:核心代码,也可以和项目名相同
test:测试代码,与src目录结构一一对应,发布时不需要带该目录
Test_App
pyetst.ini配置文件内容
readme.md:项目介绍
requirements.txt:记录测试,编译和文档生成所有依赖的包信息,同maven的pom.xml文件,项目引入新包时
更新:pip freeze > requirements.txt
,其他开发者安装依赖pip install -r requirements.txt
setUpClass->setUp->Test Methond->tearDown
pip install -U pytest # -U 是upgrade, 表示已安装就升级为最新版本
pip install pytest-cov # pytest代码覆盖率插件
pip install pytest-mock
pytst--cov --cov-report=html
pytest的配置文件通常放在测试目录下,名称为pytest.ini,命令运行时会使用该配置文件中的配置
[pytest]
# 配置pytest命令行运行参数 ,空格分隔,可添加多个命令行参数 -所有参数均为插件包的参数
addopts = -s ...
# 配置测试搜索的路径,当前目录下的scrip文件夹可自定义
testpaths = ./scripts
# 配置测试搜索的文件名,当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件 -可自定义
python_files = test_*.py
# 配置测试搜索的测试类名,当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件中,以Test_开头的类
python_classes = Test_*
# 配置测试搜索的测试函数名,当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件中,以Test_开头的类内,以test_开头的⽅法 -可自定义
python_functions = test_*
object
import os
def rm(filename):
os.remove(filename)
def test_rm(mocker):
filename = 'test.file'
mocker.patch('os.remove') # 输入被替换的函数字符串名,需要输入完整路径
rm(filename)
os.remove.assert_called_once_with(filename)
method
class ForTest: field = 'origin' def method(): pass def test_for_test(mocker): # 仅仅需要mock一个object里的method,而无需mock整个object test = ForTest() mock_method = mocker.patch.object(test, 'method') test.method() assert mock_method.called assert 'origin' == test.field mocker.patch.object(test, 'field', 'mocked') assert 'mocked' == test.field def test_patch_object_listdir(mocker):# 对一个给定module的function mock_listdir = mocker.patch.object(os, 'listdir') os.listdir() assert mock_listdir.called
object.path的参数 | 含义 |
---|---|
return_value | return_value 修改了os.path.isfile 的返回值,控制程序执行流,而无需在文件系统中生成文件 |
side_effect | side_effect 可以令某些函数抛出指定的异常 |
wraps | wraps 可以既把某些函数包装成MagicMock,又不改变它的执行效果(这一点类似spy ),当然,也完全可以替换成另一个函数。 |
import os import pytest def name_length(filename): if not os.path.isfile(filename): raise ValueError('{} is not a file!'.format(filename)) print(filename) return len(filename) def test_name_length0(mocker): isfile = mocker.patch('os.path.isfile', return_value=True) assert 4 == name_length('test') isfile.assert_called_once() isfile.return_value = False with pytest.raises(ValueError): name_length('test') assert 2 == isfile.call_count def test_name_length1(mocker): mocker.patch('os.path.isfile', side_effect=TypeError) with pytest.raises(TypeError): name_length('test') def test_name_length2(mocker): mocker.patch('os.path.isfile', return_value=True) mock_print = mocker.patch('builtins.print', wraps=print) mock_len = mocker.patch(__name__ + '.len', wraps=len) assert 4 == name_length('test') assert mock_print.called assert mock_len.called
def test_spy_listdir(mocker):
mock_listdir = mocker.spy(os, 'listdir')
os.listdir()
assert mock_listdir.called
注意:
函数级别:运行测试方法的始末,即运行一次测试函数运行一次setup和teardown
类级别:在一个测试内只运行一次setup_class和teardown_class,不关心测试类 内有多少个测试函数
import pytest class Test_ABC: def setup_class(self): print("-----> setup_class") def teardown_class(self): print("----> teardown_class") def setup(self): print("-----> setup_method") def teardown(self): print("----> teardown_method") def test_a(self): print("----> test_a") assert 1 def test_b(self): print("----> test_b") if __name__ == "__main__": pytest.main("-s test.py")
用于完成预置处理和重复操作
pytest.fixture(scope='function', params=None, autouse=False, ids=None, name=None)
import pytest @pytest.fixture() def before(): print("-----> 在每个函数前执行\n") def test_1(before): print('-----> test_11\n') assert 1 def test_2(before): print("----> test_22\n") assert 1 if __name__ == "__main__": pytest.main("-s test.py")
import pytest @pytest.fixture() def before(): print('\nbefore each test') @pytest.mark.usefixtures("before") def test_1(): print('--->test_11') @pytest.mark.usefixtures("before") def test_2(before): print("----> test_22") @pytest.mark.usefixtures("before") class Test3: def test_5(self): print('test_31') def test_6(self): print('test_32') if __name__ == "__main__": pytest.main("-s test.py")
scope: 被标记方法的作用域;
“function”: 默认值,表示每个测试方法都要执行一次
“class”: 作用于整个类, 表示每个类的所有测试方法只运行一次
“module”: 作用于整个模块, 每个module的所有测试方法只运行一次.
“session”: 作用于整个session, 每次session只运行一次. (此方法慎用!!)
autouse: 是否自动运行,默认为false, 为true时此session中的所有测试函数都会调用fixture
import pytest #作用于整个模块,表示这个模块的只运行一次fixture,例如所有的test都需要连接同一个数据库,可以使用此方法,对于此模块的所有test都有效 @pytest.fixture(scope="module", autouse=True) def before(): print('\nbefore each test') @pytest.mark.usefixtures("before") def test_1(): print('--->test_1') class Test2: def test_5(self): print('test_5') def test_6(self): print('test_6') if __name__ == "__main__": pytest.main("-s test_1.py")
import pytest
@pytest.fixture(params=[1, 2, 3])
def need_data(request): # 传入参数request 系统封装参数
return request.param # 取列表中单个值,默认的取值方式
class Test_ABC:
def test_a(self,need_data):
print("test_a 的值是 %s" % need_data)
assert need_data != 3 # 断言need_data不等于3
if __name__ == '__main__':
pytest.main("-s test_abc.py")
根据特定条件,不执行标识的测试函数
@pytest.mark.skipif(condition, reason='xxx')
import pytest class Test_ABC: def setup_class(self): print("\nsetup") def teardown_class(self): print("\nteardown") def test_a(self): print("test_a") @pytest.mark.skipif(condition=2>1, reason="跳过") def test_b(self): print("test_b") assert 0
标记某测试函数会失败
@pytest.mark.xfail(condition, reason="xxx")
import pytest class Test_ABC: def setup_class(self): print("\nsetup") def teardown_class(self): print("\nteardown") def test_a(self): print("test_a") @pytest.mark.xfail(condition=2>1, reason="预期失败") def test_b(self): print("test_b") assert 0
标记某测试函数会失败
@pytest.mark.xfail(condition, reason="xxx")
import pytest class Test_ABC: def setup_class(self): print("setup") def teardown_class(self): print("teardown") def test_a(self): print("test_a") @pytest.mark.parametrize("a", [3,6]) def test_b(self, a): print("test data:a=%d" % a) assert a%3 == 0
统计代码覆盖率
pip install pytest-cover
pytest-cov --cov-report=html
注意:
pytest-cov是基于coverage插件使用的
以函数修饰符的方式标记被测函数,通过参数控制函数执行顺序
pip install pytest-ordering
import pytest class Test_ABD: def setup_class(self): print("-----> setup_class") def teardown_class(self): print("----> teardown_class") @pytest.mark.run(order=2) def test_a(self): print("----> test_a") assert 1 @pytest.mark.run(order=1) def test_b(self): print("----> test_b") assert 0 if __name__ == "__main__": pytest.main("-s test_abc.py")
使用命令行方式,控制失败函数的重试次数
pip install pytest-rerunfailures
pytest addopts = -s --reruns 2 --html=./report.html # -s是输出程序运行信息 -n为重试的次数
编写和运行异步测试用例,轻松处理异步代码中的事件循环,协程和任务
pip install pytest-asyncio
import asyncio import pytest async def add_numbers(a, b): await asyncio.sleep(1) # 模拟异步操作 return a + b @pytest.mark.asyncio async def test_add_numbers(): result = await add_numbers(2, 3) assert result == 5 async def calculate_by_tool(value) -> int: value += 999 process = await asyncio.create_subprocess_shell(f'echo {value}', stdout=asyncio.subprocess.PIPE) out, err = await process.communicate() return int(out and out.decode() or '0') @pytest.mark.asyncio async def test_should_return_right_value_when_calculate_by_tool(): value = await calculate_by_tool(1000) assert value == 1999
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。