Pytest是成熟的功能齐全的Python测试工具,有助于编写更好的程序。
一、Pytest基本知识
参考官方文档翻译过来,了解一下Pytest知识点。
1、Pytest中可以按节点ID运行测试。
在命令行中指定测试方法的另一个示例:
pytest test_mod.py::TestClass::test_method
2、通过标记表达式运行测试
pytest -m slow
将运行用@Pytest.mark.slow装饰器装饰的所有测试。
3、详细的总结报告
pytest –ra
4、分析测试执行持续时间
要获取最慢的10个测试持续时间的列表,请执行以下操作:
pytest --durations=10
5、将测试报告发送到在线pastebin服务
为每个测试失败创建一个URL:
pytest --pastebin=failed
为整个测试会话日志创建一个URL
pytest --pastebin=all
6、从python代码中调用Pytest
pytest.main()
注意:
调用Pytest.main()将导致导入你的测试及其导入的任何模块。由于python导入系统的缓存机制,Pytest.main()从同一进程进行后续调用不会反映两次调用之间对这些文件的更改。因此,Pytest.main()不建议从同一进程进行多次调用(例如,以重新运行测试)。
7、断言
使用标准python assert来验证python测试中的期望和值。
8、conftest.py
对于可能的值scope有:function,class,module,package或session。
9、Pytest将建立一个字符串,它是用于在参数化fixture,例如每个fixtures测试ID。这些ID可以用于-k选择要运行的特定情况,并且当一个故障发生时,它们还将标识特定情况。使用Pytest运行--collect-only将显示生成的ID。
10、使用直接测试参数化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'
11、使用非参数化的参数覆盖参数化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'
12、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选项。
13、设置捕获方法或禁用捕获
有两种pytest执行捕获的方法:
您可以从命令行影响输出捕获机制:
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
14、内部Pytest警告
(1)类PytestWarning
基类:UserWarning。
Pytest发出的所有警告的基类。
(2)类PytestAssertRewriteWarning
基类:PytestWarning。
Pytest断言重写模块发出的警告。
(3)类PytestCacheWarning
基地:PytestWarning。
缓存插件在各种情况下发出的警告。
(4)类PytestCollectionWarning
基类:PytestWarning。
Pytest无法在模块中收集文件或符号时发出警告。
(5)类PytestConfigWarning
基地:PytestWarning。
针对配置问题发出警告。
(6)类PytestDeprecationWarning
基类:Pytest.PytestWarning,DeprecationWarning。
在将来的版本中将删除的功能的警告类。
(7)类PytestExperimentalApiWarning
基类:Pytest.PytestWarning,FutureWarning。
警告类别,用于表示Pytest中的实验。请谨慎使用,因为API可能会更改,甚至在将来的版本中会完全删除。
(8)类PytestUnhandledCoroutineWarning
基类:PytestWarning。
当Pytest遇到作为协程的测试函数时发出警告,但任何异步感知插件均未处理该警告。本机不支持协程测试功能。
(9)类PytestUnknownMarkWarning
基类:PytestWarning。
使用未知标记会发出警告。
1)doctest
就像普通的一样conftest.py,fixtures是在目录树conftest中发现的。这意味着,如果将doctest与源代码一起放入,则相关的conftest.py需要位于同一目录树中。fixtures不会在同级目录树中发现!
2)跳过和xfail
一个xfail意味着你期望测试失败的某些原因。一个常见的示例是对尚未实现的功能或尚未修复的错误进行测试。如果尽管测试通过但预期会失败(标记为pytest.mark.xfail),则为xpass,并将在测试摘要中报告。
Pytest分别统计和列出跳过和xfail测试。默认情况下,不显示有关跳过/未通过测试的详细信息,以避免混乱输出。
pytest -rxXs # show extra info on xfailed, xpassed, and skipped tests
17、如何在不同情况下跳过模块中的测试的快速指南:
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")
18、跨测试运行
该插件提供了两个命令行选项,以从上次Pytest调用重新运行失败:
最后一次运行没有测试失败,或者找不到缓存的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
19、Pytest支持unittest开箱即用地运行基于Python 的测试
到目前为止,Pytest不支持以下功能:
1)具有在给定上下文中自动使用的fixture,@pytest.fixture(autouse=True)
2)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.
"""
3)工具启动时插件发现顺序
通常最好将conftest.py文件保存在顶级测试或项目根目录中。
20、典型setup.py摘录:
setup(..., entry_points={"pytest11": ["foo = pytest_foo.plugin"]}, ...)
21、通过名字来访问另一个插件
插件想要与另一个插件的代码协作,则可以通过插件管理器获取引用
plugin = config.pluginmanager.get_plugin("name_of_plugin")
22、Pytest API
23、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
24、pip用于安装应用程序和所有依赖项以及Pytest程序包本身。这样可确保您的代码和依赖项与系统Python安装隔离。
25、Pytest.approx
26、失败的视频/屏幕截图Pytest-splinter、Pytest-bdd
27. 考虑以下文件和目录布局:
root/
|- foo/
|- __init__.py
|- conftest.py
|- bar/
|- __init__.py
|- tests/
|- __init__.py
|- test_foo.py
执行时会执行以下所有的目录文件
pytest root/
28、测试模块名称不能相同
29、从中找到rootdir的算法args:
二、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 multibyte 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
我是谁?
我是一名从事了多年软件测试的老测试员,今年年初我花了一个月整理了一份最适合2020年学习的软件测试学习干货,可以送给每一位对软件测试感兴趣的小伙伴,想要获取的可以关注我的头条号并在后台私信我:【测试】,即可免费获取。