最近工作中用到了pytest,总结一些用法:
1. 安装:
pip install pytest
2. 运行:
pytest 默认只能识别以test_
开头的文件和测试用例,如果pytest后面不带文件名,则默认执行当前目录下所有以test_
开头的文件。
- 执行某个文件里所有以 test 开头的用例:
pytest test_demo.py
# test_demo.py
def test_01():
assert 1+1 == 2
def test_02():
assert 1+3 == 4
执行结果:
$ pytest test_demo.py
========================================= test session starts ============================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /Users/libo/python3workspace/learn, inifile:
plugins: rerunfailures-7.0, picked-0.4.1, parallel-0.0.9, forked-1.0.2, cov-2.7.1, allure-pytest-2.6.1
collected 2 items
test_demo.py .. [100%]
======================================== 2 passed in 0.04 seconds =========================================
如果只想执行test_01,可以使用pytest test_demo.py::test_01
- 如果用例是用类组织起来的:
# test_class.py
class TestClass(object):
def test_one(self):
x = "this"
assert 'h' in x
def test_two(self):
x = "hello"
assert hasattr(x, 'check')
执行结果:
$ pytest test_class.py
=========================================== test session starts ============================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /Users/libo/python3workspace/learn, inifile:
plugins: rerunfailures-7.0, picked-0.4.1, parallel-0.0.9, forked-1.0.2, cov-2.7.1, allure-pytest-2.6.1
collected 2 items
test_class.py .F [100%]
================================================= FAILURES =================================================
____________________________________________ TestClass.test_two ____________________________________________
self = <test_class.TestClass object at 0x102f46780>
def test_two(self):
y = "world"
> assert 'h' in y
E AssertionError: assert 'h' in 'world'
test_class.py:9: AssertionError
========================================== 1 failed, 1 passed in 0.08 seconds ====================================
如果一个文件里有多个类,你只想执行其中一个类里的用例:
pytest test_class.py::TestClass
- 执行某个文件夹下所有的以
test_
开头的文件:pytest testcase/
# testcase/test_demo01.py
def test_one():
x = 'this'
assert 'h' in x
# testcase/test_demo02.py
def test_two():
y = 'world'
assert 'h' in y
执行结果:
$ pytest testcase/
========================================== test session starts ============================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /Users/libo/python3workspace/learn, inifile:
plugins: rerunfailures-7.0, picked-0.4.1, parallel-0.0.9, forked-1.0.2, cov-2.7.1, allure-pytest-2.6.1
collected 2 items
testcase/test_demo01.py . [ 50%]
testcase/test_demo02.py F [100%]
============================================== FAILURES ===========================================
_________________________________________________ test_two _________________________________________________
def test_two():
y = 'world'
> assert 'h' in y
E AssertionError: assert 'h' in 'world'
testcase/test_demo02.py:3: AssertionError
==================================== 1 failed, 1 passed in 0.09 seconds ================================
- 执行带某个标记的用例,比如用例中带了
@pytest.mark.smoke
标记的用例:
# test_mark.py
@pytest.mark.smoke
def test_01():
assert 1+1 == 2
def test_02():
assert 1+3 == 4
$ pytest -m smoke test_mark.py
=========================================== test session starts ============================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /Users/libo/python3workspace/learn, inifile:
plugins: rerunfailures-7.0, picked-0.4.1, parallel-0.0.9, forked-1.0.2, cov-2.7.1, allure-pytest-2.6.1
collected 2 items / 1 deselected / 1 selected
test_mark.py . [100%]
================================== 1 passed, 1 deselected in 0.03 seconds ==================================
可以看到只执行带@pytest.mark.smoke
的用例。
- 按用例函数的名称来执行:
pytest -k 01 test_mark.py
$ pytest -k 01 test_mark.py
=========================================== test session starts ============================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /Users/libo/python3workspace/learn, inifile:
plugins: rerunfailures-7.0, picked-0.4.1, parallel-0.0.9, forked-1.0.2, cov-2.7.1, allure-pytest-2.6.1
collected 2 items / 1 deselected / 1 selected
test_mark.py . [100%]
================================== 1 passed, 1 deselected in 0.01 seconds ==================================
按照用例名字来执行,只执行名字里含有01的测试用例。
3. pytest fixture
@pytest_fixture
的作用是提供一个固定的参数给测试重复的使用,相当于unittest里的setup
和teardown
.
- pytest fixture 做函数的参数
# test_fixture.py
@pytest.fixture(scope="module")
def num():
n = random.randint(1, 5)
return n
def test_01(num):
print(num)
assert 0
def test_02(num):
print(num)
assert 0
- pytest fixture的scope有class, session 和 module.
- 此时执行可以多次执行看看两个函数里num的数值是否是一样:
$ pytest test_fixture.py
=========================================== test session starts ============================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /Users/libo/python3workspace/learn, inifile:
plugins: rerunfailures-7.0, picked-0.4.1, parallel-0.0.9, forked-1.0.2, cov-2.7.1, allure-pytest-2.6.1
collected 2 items
test_fixture.py FF [100%]
================================================= FAILURES =================================================
_________________________________________________ test_01 __________________________________________________
num = 3
def test_01(num):
print(num)
> assert 0
E assert 0
test_fixture.py:12: AssertionError
------------------------------------------- Captured stdout call -------------------------------------------
3
_________________________________________________ test_02 __________________________________________________
num = 3
def test_02(num):
print(num)
> assert 0
E assert 0
test_fixture.py:16: AssertionError
------------------------------------------- Captured stdout call -------------------------------------------
3
========================================= 2 failed in 0.06 seconds =========================================
- 多次执行可以看见两个函数里num的值是总是相同的,如果只是普通的函数调用,两个函数里的值肯定不可能总是相等的。因此pytest fixture可以用来共享数据。
- 此外pytest fixture 还可以实现类似
teardown
的操作,方法是首先把return
换成yield
,然后把销毁的相关操作放在yield后面。
# conftest.py
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp_connection():
smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
yield smtp_connection # provide the fixture value
print("teardown smtp")
smtp_connection.close()
# test_module.py
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
assert 0 # for demo purposes
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
assert 0 # for demo purpose
$ pytest -s -q --tb=no test_module.py
FFteardown smtp
2 failed in 6.81 seconds
从上面的运行可以看出来,在执行完两个测试用例之后才调用yield
后面的print
语句。如果我们把scope改成scope="function"
那么fixture的setup和teardown就会在每一个测试用例开始和结束时执行。
- 像上面的close操作也可以使用
with
语句来实现:
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp_connection():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
yield smtp_connection # provide the fixture value
- 此外pytest fixture 还有一些其他的功能,比如带参数的fixture等等,具体可以看这里。
4. pytest.mark.parametrize
pytest 内置的pytest.mark.parametrize
装饰器支持将测试函数的参数的参数化。
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.mark.parametrize
传递参数,而不需要在测试函数里面通过for循环来实现。
@pytest.mark.parametrize
还可以添加起来使用:
import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
assert x + y == 2
上面的例子里会一共有4中情况:x=0/y=2,x=0/y=3,x=1/y=2,x=1/y=3一共四种情况。
更多用法可以参考官方文档.
5. pytest.ini
pytest的主配置文件,可以改变pytest的默认行为。我们可以把经常使用的一些选项放到pytest.ini
中,或者在里面添加一些markers。
# pytest.ini 文件
[pytest]
addopts =
--verbose
--reruns 5 # 需要安装 pytest-rerunfailures 插件
--clean-alluredir # 需要 allure-pytest 插件
--alluredir=/tmp/allure-results/
markers =
smoke: 冒烟测试用例
使用 pytest --help 可以看到常用的pytest.ini
选项:
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:
markers (linelist) markers for test functions
empty_parameter_set_mark (string) default marker for empty parametersets
norecursedirs (args) directory patterns to avoid for recursion
testpaths (args) directories to search for tests when no files or directories are given in the command line.
console_output_style (string) console output: classic or with additional progress information (classic|progress).
usefixtures (args) list of default fixtures to be used with this project
python_files (args) glob-style file patterns for Python test module discovery
python_classes (args) prefixes or glob names for Python test class discovery
python_functions (args) prefixes or glob names for Python test function and method discovery
xfail_strict (bool) default for the strict parameter of xfail markers when not given explicitly (default: False)
doctest_optionflags (args) option flags for doctests
doctest_encoding (string) encoding used for doctest files
cache_dir (string) cache directory path.
filterwarnings (linelist) Each line specifies a pattern for warnings.filterwarnings. Processed after -W and --pythonwarnings.
log_print (bool) default value for --no-print-logs
log_level (string) default value for --log-level
log_format (string) default value for --log-format
log_date_format (string) default value for --log-date-format
log_cli (bool) enable log display during test run (also known as "live logging").
log_cli_level (string) default value for --log-cli-level
log_cli_format (string) default value for --log-cli-format
log_cli_date_format (string) default value for --log-cli-date-format
log_file (string) default value for --log-file
log_file_level (string) default value for --log-file-level
log_file_format (string) default value for --log-file-format
log_file_date_format (string) default value for --log-file-date-format
addopts (args) extra command line options
minversion (string) minimally required pytest version
workers (string) Set the max num of workers (aka processes) to start (int or "auto" - one per core)
tests_per_worker (string) Set the max num of concurrent tests for each worker (int or "auto" - split evenly)
-
addopts
:更改命令行的默认行为,可以把一些总是用到的选项添加到这里,这样就不用每次执行的时候都输入该命令。 -
markers
:自定义的标记,可以使用pytest --markers
看到。 -
pytest.ini
文件放到工程的目录即可。
网友评论