摘要
pytest是成熟的功能齐全的Python测试工具,有助于编写更好的程序。
pytest基本知识
参考官方文档翻译过来,了解一下pytest知识点
pytest中可以按节点ID运行测试。
在命令行中指定测试方法的另一个示例:
pytest test_mod.py::TestClass::test_method
通过标记表达式运行测试
pytest -m slow
将运行用@pytest.mark.slow装饰器装饰的所有测试。
详细的总结报告
pytest –ra
分析测试执行持续时间
要获取最慢的10个测试持续时间的列表,请执行以下操作:
pytest --durations=10
将测试报告发送到在线pastebin服务
为每个测试失败创建一个URL:
pytest --pastebin=failed
为整个测试会话日志创建一个URL
pytest --pastebin=all
从python代码中调用pytest
pytest.main()
注意:
调用pytest.main()将导致导入你的测试及其导入的任何模块。由于python导入系统的缓存机制,pytest.main()从同一进程进行后续调用不会反映两次调用之间对这些文件的更改。因此,pytest.main()不建议从同一进程进行多次调用(例如,以重新运行测试)。
断言
使用标准python assert来验证python测试中的期望和值。
conftest.py
对于可能的值scope有:function,class,module,package或session。
- pytest将建立一个字符串,它是用于在参数化fixture,例如每个fixtures测试ID。这些ID可以用于-k选择要运行的特定情况,并且当一个故障发生时,它们还将标识特定情况。使用pytest运行--collect-only将显示生成的ID。
- 使用直接测试参数化fixture
给定测试文件的结构为:
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@pytest.fixture
def username():
return 'username'
@pytest.fixture
def other_username(username):
return 'other-' + username
test_something.py
# content of tests/test_something.py
import pytest
@pytest.mark.parametrize('username', ['directly-overridden-username'])
def test_username(username):
assert username == 'directly-overridden-username'
@pytest.mark.parametrize('username', ['directly-overridden-username-other'])
def test_username_other(other_username):
assert other_username == 'other-directly-overridden-username-other'
使用非参数化的参数覆盖参数化fixture
给定测试文件的结构为:
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@pytest.fixture(params=['one', 'two', 'three'])
def parametrized_username(request):
return request.param
@pytest.fixture
def non_parametrized_username(request):
return 'username'
test_something.py
# content of tests/test_something.py
import pytest
@pytest.fixture
def parametrized_username():
return 'overridden-username'
@pytest.fixture(params=['one', 'two', 'three'])
def non_parametrized_username(request):
return request.param
def test_username(parametrized_username):
assert parametrized_username == 'overridden-username'
def test_parametrized_username(non_parametrized_username):
assert non_parametrized_username in ['one', 'two', 'three']
test_something_else.py
# content of tests/test_something_else.py
def test_username(parametrized_username):
assert parametrized_username in ['one', 'two', 'three']
def test_username(non_parametrized_username):
assert non_parametrized_username == 'username'
monkeypatch
简单的api获取例子
# contents of app.py, a simple API retrieval example
import requests
def get_json(url):
"""Takes a URL, and returns the JSON."""
r = requests.get(url)
return r.json()
此外,如果该模拟程序旨在应用于所有测试,则fixture可以将其移动到conftest.py文件中并使用with autouse=True选项。
设置捕获方法或禁用捕获
有两种pytest执行捕获的方法:
- 文件描述符(FD)级别捕获(默认):将捕获对操作系统文件描述符1和2的所有写操作。
- sys级别捕获:仅写入Python文件,sys.stdout 并且sys.stderr将被捕获。不捕获对文件描述符的写入。
您可以从命令行影响输出捕获机制:
pytest -s # disable all capturing
pytest --capture=sys # replace sys.stdout/stderr with in-mem files
pytest --capture=fd # also point filedescriptors 1 and 2 to temp file
内部pytest警告
类pytestWarning
基类:UserWarning。
pytest发出的所有警告的基类。
类pytestAssertRewriteWarning
基类:pytestWarning。
pytest断言重写模块发出的警告。
类pytestCacheWarning
基地:pytestWarning。
缓存插件在各种情况下发出的警告。
类pytestCollectionWarning
基类:pytestWarning。
pytest无法在模块中收集文件或符号时发出警告。
类pytestConfigWarning
基地:pytestWarning。
针对配置问题发出警告。
类pytestDeprecationWarning
基类:pytest.pytestWarning,DeprecationWarning。
在将来的版本中将删除的功能的警告类。
类pytestExperimentalApiWarning
基类:pytest.pytestWarning,FutureWarning。
警告类别,用于表示pytest中的实验。请谨慎使用,因为API可能会更改,甚至在将来的版本中会完全删除。
类pytestUnhandledCoroutineWarning
基类:pytestWarning。
当pytest遇到作为协程的测试函数时发出警告,但任何异步感知插件均未处理该警告。本机不支持协程测试功能。
类pytestUnknownMarkWarning
基类:pytestWarning。
使用未知标记会发出警告。
doctest
就像普通的一样conftest.py,fixtures是在目录树conftest中发现的。这意味着,如果将doctest与源代码一起放入,则相关的conftest.py需要位于同一目录树中。fixtures不会在同级目录树中发现!
跳过和xfail
一个xfail意味着你期望测试失败的某些原因。一个常见的示例是对尚未实现的功能或尚未修复的错误进行测试。如果尽管测试通过但预期会失败(标记为pytest.mark.xfail),则为xpass,并将在测试摘要中报告。
pytest分别统计和列出跳过和xfail测试。默认情况下,不显示有关跳过/未通过测试的详细信息,以避免混乱输出。
pytest -rxXs # show extra info on xfailed, xpassed, and skipped tests
如何在不同情况下跳过模块中的测试的快速指南:
1)无条件跳过模块中的所有测试:
pytestmark = pytest.mark.skip("all tests still WIP")
2)根据某些条件跳过模块中的所有测试:
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="tests for linux only")
3)如果缺少某些导入,请跳过模块中的所有测试:
pexpect = pytest.importorskip("pexpect")
跨测试运行
该插件提供了两个命令行选项,以从上次pytest调用重新运行失败:
- --lf,--last-failed-仅重新运行失败。
- --ff,--failed-first-先运行故障,然后测试的其余部分
最后一次运行没有测试失败,或者找不到缓存的lastfailed数据,
pytest则可以使用以下--last-failed-no-failures选项之一将该选项配置为运行所有测试或不运行测试:
pytest --last-failed --last-failed-no-failures all # run all tests (default behavior)
pytest --last-failed --last-failed-no-failures none # run no tests and exit
pytest支持unittest开箱即用地运行基于Python 的测试
到目前为止,pytest不支持以下功能:
- load_tests协议 ;
- 子测试
- 具有在给定上下文中自动使用的fixture,@pytest.fixture(autouse=True)
- xunit样式: 在每个模块/类/功能的基础上实现fixture
Module setup/teardown
def setup_module(module):
""" setup any state specific to the execution of the given module."""
def teardown_module(module):
""" teardown any state that was previously setup with a setup_module
method.
"""
Class setup/teardown
@classmethod
def setup_class(cls):
""" setup any state specific to the execution of the given class (which
usually contains tests).
"""
@classmethod
def teardown_class(cls):
""" teardown any state that was previously setup with a call to
setup_class.
"""
方法和功能的setup/teardown
def setup_method(self,method):
""" setup any state tied to the execution of the given method in a
class. setup_method is invoked for every test method of a class.
"""
def teardown_method(self,method):
""" teardown any state that was previously setup with a setup_method
call.
"""
def setup_function(function):
""" setup any state tied to the execution of the given function.
Invoked for every test function in the module.
"""
def teardown_function(function):
""" teardown any state that was previously setup with a setup_function call.
"""
工具启动时插件发现顺序
通常最好将conftest.py文件保存在顶级测试或项目根目录中。
典型setup.py摘录:
setup(..., entry_points={"pytest11": ["foo = pytest_foo.plugin"]}, ...)
通过名字来访问另一个插件
插件想要与另一个插件的代码协作,则可以通过插件管理器获取引用
plugin = config.pluginmanager.get_plugin("name_of_plugin")
pytest API
caplog()
访问和控制日志捕获。
捕获的日志可通过以下属性/方法获得:
* caplog.messages -> list of format-interpolated log messages
* caplog.text -> string containing formatted log output
* caplog.records -> list of logging.LogRecord instances
* caplog.record_tuples -> list of (logger_name, level, message) tuples
* caplog.clear() -> clear captured records and formatted log output string
pip用于安装应用程序和所有依赖项以及pytest程序包本身。这样可确保您的代码和依赖项与系统Python安装隔离。
pytest.approx
失败的视频/屏幕截图pytest-splinter、pytest-bdd
考虑以下文件和目录布局:
root/
|- foo/
|- __init__.py
|- conftest.py
|- bar/
|- __init__.py
|- tests/
|- __init__.py
|- test_foo.py
执行时会执行以下所有的目录文件
pytest root/
测试模块名称不能相同
从中找到rootdir的算法args:
- 确定指定的公共祖先目录,这些目录args被识别为文件系统中存在的路径。如果找不到此类路径,则将公共祖先目录设置为当前工作目录。
- 寻找pytest.ini,tox.ini并setup.cfg在父目录和文件向上。如果匹配,它将成为ini文件,并且其目录将成为rootdir。
- 如果未找到ini文件,请setup.py从公共祖先目录向上查找以确定rootdir。
- 如果没有setup.py被发现,寻找pytest.ini,tox.ini并 setup.cfg在每个指定args向上。如果匹配,它将成为ini文件,并且其目录将成为rootdir。
- 如果找不到ini文件,则使用已经确定的公共祖先作为根目录。这允许在不属于包且没有任何特定ini文件配置的结构中使用pytest。
pytest实际运用
命令行中执行
pip install pytest
pip show pytest
想要在Pycharm环境中测试用例以pytest形式运行,可以这样设置。Settings->Tools->Python Integreated Tools,选择默认的test runner为“py.test”。
pytest生成html报告,命令行中安装pytest-html插件。
pip install pytest-html
cmd中执行 >pytest test_***.py --html=./testreport.html
在报告存放路径下,可以用Chrome浏览器打开本地html链接,如file:///E:/ATS/Test_doctor/testreport.html。
pytest测试报告形式如下所示
pytest失败重跑机制。在UI自动化测试中,由于受到网络不稳定、appium server等影响,不是每次正确的测试用例都能运行通过,于是使用pytest的失败重跑提高自动化测试的稳定性。安装插件pytest-rerunsfailures,在命令行执行
pip install pytest-rerunsfailures
>pytest test_patlist.py --reruns 3 --html=./report.html
实现对测试用例重跑3次,3次不通过才算失败,反之则运行成功。
实践得到,pytest不需要创建类,直接定义函数即可。
pytest 和unittest 最大的区别是不要求我们必须创建测试类, 自己创建的测试类也不需要继承于unittest.TestCase类。但是pytest 要求我们创建的测试文件,测试类、方法、测试函数必须以“test”开头,pytest默认按照这个规则查找测试用例并执行它们。”
pytest避开的坑
1)PytestUnknownMarkWarning
解决方案:
若是单个标签
在conftest.py添加如下代码,直接拷贝过去,把标签名改成你自己的就行了
def pytest_configure(config):
config.addinivalue_line(
"markers", "login_success" # login_success 是标签名
)
若是多个标签
在conftest.py添加如下代码,直接拷贝过去,把标签名改成你自己的就行了
def pytest_configure(config):
marker_list =
["testmark1","testmark2","testmark3"] # 标签名集合
for markers in marker_list:
config.addinivalue_line(
"markers", markers
)
这样添加之后,再次运行,不再出现warning。
2)UnicodeDecodeError: 'gbk' codec can't decode byte 0xae in position 42: illegal m
ultibyte sequence
import pytest
@pytest.mark.parametrize("test_input,expected",[("3+5",8),("2+4",6),("6*9",42)])
def test_eval(test_input,expected):
assert eval(test_input) == expected
解决办法:这个跟编码有关,代码目录中有个pytest.ini文件,内容是这样:
[pytest]
doctest_encoding = UTF-8 #默认编码是UTF-8
删除pytest.ini文件之后,再次运行就可以了。
3) pytest执行用例时collected 0 items
pytest执行的文件需要以test开头才能被查找到。
4)断言assert
断言元素是否存在,例如
element = appdriver.find_element_by_id('icon_id')
assert element