pytest
- pytest认知
- pytest第一个简单例子
- pytest 使用方法
-
- 断言
- Fixture
- 参数化
- 运行测试
-
- 查看帮助
- 运行名称中包含某字符串的测试用例
- 减少测试的运行冗长
- 如果出现一条测试用例失败,则退出测试
- 运行测试目录
- 指定特定类或方法执行
- 通过main()方法运行测试
- 运行所有用mark修饰的测试用例
- 生成测试报告
- pytest扩展
-
- pytest-html
- pytest-rerunfailures
- pytest-parallel扩展
pytest认知
pytest是Python的一个第三方单元测试框架,提供了更加丰富的扩展,更加简单、灵活,弥补了unittest在做web自动化测试的一些不足。
pytest支持pip安装
pip install -U pytest
查看pytest版本
pytest --version
对于pytest学习,可以参考:
- 官方文档
- pytest中文文档
- pytest – 中文文档
pytest第一个简单例子
- 首先通过pytest编写一个简单的测试例子,test_sample.py
def inc(x):
return x + 1
def test_answer():
assert inc(3) == 5
- 切换到测试用例目录下,执行【pytest】命令
(py3_heima) D:\zhenghou\python_learning>cd test_pytest
(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: D:\zhenghou\python_learning\test_pytest
collected 1 item
test_sample.py F [100%]
==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_answer ___________________________________________________________________________________
def test_answer():
> assert inc(3) == 5
E assert 4 == 5
E + where 4 = inc(3)
test_sample.py:13: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_sample.py::test_answer - assert 4 == 5
================================================================================ 1 failed in 0.46s ================================================================================
注
:
- pytest更加简单,不需要想unittest一样必须创建测试类
- 使用assert断言
- pytest的测试文件和测试函数必须以【test】开头
此外,pytest也可以使用像unittest一样,通过main()方法执行测试用例
import pytest
def inc(x):
return x + 1
def test_answer():
assert inc(3) == 5
if __name__ == "__main__":
pytest.main()
在一个类中执行多组测试
class TestClass:
def test_one(self):
x = "this"
assert "h" in x
def test_two(self):
x = "hello"
assert hasattr(x, "check")
进入test_class所在目录,执行【test_class.py】
(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -q test_class.py
.F [100%]
==================================================================================== FAILURES =====================================================================================
_______________________________________________________________________________ TestClass.test_two ________________________________________________________________________________
self = <test_pytest.test_class.TestClass object at 0x000002D1ACA22780>
def test_two(self):
x = "hello"
> assert hasattr(x, "check")
E AssertionError: assert False
E + where False = hasattr('hello', 'check')
test_class.py:16: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.38s
pytest 使用方法
断言
pytest单元测试框架没有提供专门的断言方法,而是直接使用Python的asser进行断言。
# 计算a + b
def add(a, b):
return a + b
# 判断是否为素数
def is_prime(n):
if n <= 1:
return False
else:
for i in range(2, n):
if n % i == 0:
return False
return True
# 测试相等
def test_add_1():
assert add(3, 4) == 7
# 测试不相等
def test_add_2():
assert add(5, 8) != 12
# 测试小于等于
def test_add_3():
assert add(5, 8) <= 10
# 测试大于等于
def test_add_4():
assert add(4, 5) >= 3
# 测试包含于
def test_in():
assert "h" in "hello"
# 测试不包含
def test_not_in():
assert "he" not in "hello"
# 测试是否为True
def test_true_1():
assert is_prime(1)
# 测试是否为True
def test_true_2():
assert is_prime(1) is True
# 测试是否为True
def test_true_3():
assert is_prime(1) is not True
# 测试是否为False
def test_false():
assert is_prime(1) is False
Fixture
Fixture通常用来对测试方法、测试函数、测试类和整个测试文件进行初始化或还原测试环境
# 功能函数:计算两个数相加
def add(a, b):
return a + b
# ==============fixture=======================
def setup_module(module):
print("setup_module=========================>")
def teardown_module(module):
print("teardown_module==========================>")
def setup_function(function):
print("setup_function=====================>")
def teardown_function(function):
print("teardown_function===========================>")
def setup():
print("setup=================================>")
def teardown():
print("teardown=================================>")
# 测试用例
def test_add_2_1():
assert add(2, 1) == 3
def test_add_gg_aa():
assert add("gg", "aa") == "ag"
- setup_module/teardown_module: 在当前文件中,在所有测试用例执行之前与之后执行
- setup_function/teardown_function: 在每个测试函数之前与之后执行
- setup/teardown: 在每个测试函数之前与之后执行。
测试类
# 功能函数:计算两个数相加
def add(a, b):
return a + b
class TestAdd:
@classmethod
def setup_class(cls):
print("setup_class======================>")
@classmethod
def teardown_class(cls):
print("teardown_class====================>")
def setup_method(self, method):
print("setup_method==================>")
def teardown_method(self, method):
print("teardown_method==========================>")
def setup(self):
print("setup=====================>")
def teardown(self):
print("teardown=============================>")
# 测试用例
def test_number_3_4(self):
print("test add number 3 and 4")
assert add(3, 4) == 7
def test_string_gg_aa(self):
print("test add string gg and aa")
assert add("gg", "aa") == "ga"
- setup_class/teardown_class: 在当前测试类的开始与结束时执行
- setup_method/teardown_method: 在每个测试方法开始与结束执行
- setup/teardown: 在每个测试方法开始与结束时执行,同样可以用于测试函数
参数化
当一组测试用例有固定的测试数据是,可以通过参数化的方式简化测试用例书写。
import pytest
import math
@pytest.mark.parametrize(
"base, exponent, expected",
[(2, 2, 4),
(2, 3, 8),
(1, 9, 1),
(0, 9, 0)],
ids=["case1", "case2", "case3", "case4"]
)
def test_pow(base, exponent, expected):
assert math.pow(base, exponent) == expected
-
: 用于定义参数的名称"base, exponent, expected"
-
: 默认为None,用于定义测试用例的名称ids
执行结果
(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -v test_parametrize.py
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- d:\env_director\envs\py3_heima\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\zhenghou\python_learning\test_pytest
collected 4 items
test_parametrize.py::test_pow[case1] PASSED [ 25%]
test_parametrize.py::test_pow[case2] PASSED [ 50%]
test_parametrize.py::test_pow[case3] PASSED [ 75%]
test_parametrize.py::test_pow[case4] PASSED [100%]
================================================================================ 4 passed in 0.16s ================================================================================
运行测试
您可以从命令行通过python解释器调用测试:
python -m pytest [...]
查看帮助
运行名称中包含某字符串的测试用例
-k EXPRESSION only run tests which match the given substring expression. An expression is a python evaluatable
expression where all names are substring-matched against test names and their parent classes.
Example: -k 'test_method or test_other' matches all test functions and classes whose name
contains 'test_method' or 'test_other', while -k 'not test_method' matches those that don't
contain 'test_method' in their names. -k 'not test_method and not test_other' will eliminate the
matches. Additionally keywords are matched to classes and functions containing extra names in
their 'extra_keyword_matches' set, as well as functions which have names assigned directly to
them. The matching is case-insensitive.
使用
-k
指定名称中包含某字符串的测试用例。
pytest -k add test_assert.py
执行【test_assert.py】
(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -k add -v test_assert.py
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- d:\env_director\envs\py3_heima\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\zhenghou\python_learning\test_pytest
collected 10 items / 6 deselected / 4 selected
test_assert.py::test_add_1 PASSED [ 25%]
test_assert.py::test_add_2 PASSED [ 50%]
test_assert.py::test_add_3 FAILED [ 75%]
test_assert.py::test_add_4 PASSED [100%]
==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________
def test_add_3():
> assert add(5, 8) <= 10
E assert 13 <= 10
E + where 13 = add(5, 8)
test_assert.py:33: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_assert.py::test_add_3 - assert 13 <= 10
==================================================================== 1 failed, 3 passed, 6 deselected in 0.24s ====================================================================
减少测试的运行冗长
(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -q test_assert.py
..F..FFF.. [100%]
==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________
def test_add_3():
> assert add(5, 8) <= 10
E assert 13 <= 10
E + where 13 = add(5, 8)
test_assert.py:33: AssertionError
___________________________________________________________________________________ test_not_in ___________________________________________________________________________________
def test_not_in():
> assert "he" not in "hello"
E AssertionError: assert 'he' not in 'hello'
E 'he' is contained here:
E hello
E ? ++
test_assert.py:45: AssertionError
___________________________________________________________________________________ test_true_1 ___________________________________________________________________________________
def test_true_1():
> assert is_prime(1)
E assert False
E + where False = is_prime(1)
test_assert.py:49: AssertionError
___________________________________________________________________________________ test_true_2 ___________________________________________________________________________________
def test_true_2():
> assert is_prime(1) is True
E assert False is True
E + where False = is_prime(1)
test_assert.py:53: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_assert.py::test_add_3 - assert 13 <= 10
FAILED test_assert.py::test_not_in - AssertionError: assert 'he' not in 'hello'
FAILED test_assert.py::test_true_1 - assert False
FAILED test_assert.py::test_true_2 - assert False is True
4 failed, 6 passed in 0.27s
如果出现一条测试用例失败,则退出测试
使用
-x
执行测试用例
(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -x test_assert.py
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: D:\zhenghou\python_learning\test_pytest
collected 10 items
test_assert.py ..F
==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________
def test_add_3():
> assert add(5, 8) <= 10
E assert 13 <= 10
E + where 13 = add(5, 8)
test_assert.py:33: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_assert.py::test_add_3 - assert 13 <= 10
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
=========================================================================== 1 failed, 2 passed in 0.21s ===========================================================================
注
:
pytest --maxfail=2
--maxfail=num exit after first num failures or errors.
运行测试目录
测试目录既可以指定相对路径,也可以使用绝对路径
(py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: D:\zhenghou\python_learning
collected 19 items
test_pytest\test_assert.py ..F..FFF.. [ 52%]
test_pytest\test_class.py .F [ 63%]
test_pytest\test_fixture.py .F [ 73%]
test_pytest\test_parametrize.py .... [ 94%]
test_pytest\test_sample.py F [100%]
==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________
def test_add_3():
> assert add(5, 8) <= 10
E assert 13 <= 10
E + where 13 = add(5, 8)
test_pytest\test_assert.py:33: AssertionError
___________________________________________________________________________________ test_not_in ___________________________________________________________________________________
def test_not_in():
> assert "he" not in "hello"
E AssertionError: assert 'he' not in 'hello'
E 'he' is contained here:
E hello
E ? ++
test_pytest\test_assert.py:45: AssertionError
___________________________________________________________________________________ test_true_1 ___________________________________________________________________________________
def test_true_1():
> assert is_prime(1)
E assert False
E + where False = is_prime(1)
test_pytest\test_assert.py:49: AssertionError
___________________________________________________________________________________ test_true_2 ___________________________________________________________________________________
def test_true_2():
> assert is_prime(1) is True
E assert False is True
E + where False = is_prime(1)
test_pytest\test_assert.py:53: AssertionError
_______________________________________________________________________________ TestClass.test_two ________________________________________________________________________________
self = <test_pytest.test_class.TestClass object at 0x00000243DBAF00B8>
def test_two(self):
x = "hello"
> assert hasattr(x, "check")
E AssertionError: assert False
E + where False = hasattr('hello', 'check')
test_pytest\test_class.py:16: AssertionError
____________________________________________________________________________ TestAdd.test_string_gg_aa ____________________________________________________________________________
self = <test_pytest.test_fixture.TestAdd object at 0x00000243DBAF0470>
def test_string_gg_aa(self):
print("test add string gg and aa")
> assert add("gg", "aa") == "ga"
E AssertionError: assert 'ggaa' == 'ga'
E - ga
E + ggaa
test_pytest\test_fixture.py:68: AssertionError
------------------------------------------------------------------------------ Captured stdout setup ------------------------------------------------------------------------------
setup_method==================>
setup=====================>
------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------
test add string gg and aa
---------------------------------------------------------------------------- Captured stdout teardown -----------------------------------------------------------------------------
teardown=============================>
teardown_method==========================>
teardown_class====================>
___________________________________________________________________________________ test_answer ___________________________________________________________________________________
def test_answer():
> assert inc(3) == 5
E assert 4 == 5
E + where 4 = inc(3)
test_pytest\test_sample.py:14: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_pytest/test_assert.py::test_add_3 - assert 13 <= 10
FAILED test_pytest/test_assert.py::test_not_in - AssertionError: assert 'he' not in 'hello'
FAILED test_pytest/test_assert.py::test_true_1 - assert False
FAILED test_pytest/test_assert.py::test_true_2 - assert False is True
FAILED test_pytest/test_class.py::TestClass::test_two - AssertionError: assert False
FAILED test_pytest/test_fixture.py::TestAdd::test_string_gg_aa - AssertionError: assert 'ggaa' == 'ga'
FAILED test_pytest/test_sample.py::test_answer - assert 4 == 5
========================================================================== 7 failed, 12 passed in 0.37s ===========================================================================
指定特定类或方法执行
每个收集的测试都被分配一个唯一的 nodeid 它由模块文件名和诸如类名、函数名和参数化参数等说明符组成,用 :: 字符。
在模块内运行特定测试:
pytest test_mod.py::test_func
在命令行中指定测试方法的另一个示例:
pytest test_mod.py::TestClass::test_method
例如,指定运行test_fixture.py文件中TestAdd类下的test_number_3_4方法
py3_heima) D:\zhenghou\python_learning\test_pytest>pytest test_fixture.py::TestAdd::test_number_3_4
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: D:\zhenghou\python_learning\test_pytest
collected 1 item
test_fixture.py . [100%]
================================================================================ 1 passed in 0.02s ================================================================================
通过main()方法运行测试
import pytest
if __name__ == "__main__":
pytest.main("-s", "./test_dir")
运行所有用mark修饰的测试用例
Run tests by marker expressions
pytest -m slow
将运行所有用 @pytest.mark.slow 装饰符。
生成测试报告
- 生成JUnit XML文件
(py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest --junit-xml=./report/log.xml
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL90zdiFDZyglb1cVWwBnMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL1UjN2EDMxEjMwIjNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
- 生成在线测试报告
执行上述代码会生成一个session-log链接,使用浏览器打开,会得到一张HTML格式的测试报告
pytest扩展
pytest-html
pytest-html可以生成HTML格式的测试报告,还支持测试用例失败截图,对于web自动化测试来说非常有用。
安装
pip install pytest-html
运行测试用例
(py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest --html=./report/result.html
执行结果
pytest-rerunfailures
pytest-rerunfailures可以在测试用例失败时进行重试
安装
pip install pytest-rerunfailures
安装完成,通过【–reruns】参数设置测试用例运行失败后的重试次数
(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -v test_sample.py --reruns 3
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- d:\env_director\envs\py3_heima\scripts\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.6.4', 'Platform': 'Windows-10-10.0.16299-SP0', 'Packages': {'pytest': '5.4.3', 'py': '1.8.2', 'pluggy': '0.13.1'}, 'Plugins': {'html': '2.1.1', 'metadata':
'1.9.0', 'rerunfailures': '9.0'}, 'JAVA_HOME': 'F:\\java8'}
rootdir: D:\zhenghou\python_learning\test_pytest
plugins: html-2.1.1, metadata-1.9.0, rerunfailures-9.0
collected 1 item
test_sample.py::test_answer RERUN [100%]
test_sample.py::test_answer RERUN [100%]
test_sample.py::test_answer RERUN [100%]
test_sample.py::test_answer FAILED [100%]
==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_answer ___________________________________________________________________________________
def test_answer():
> assert inc(3) == 5
E assert 4 == 5
E +4
E -5
test_sample.py:14: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_sample.py::test_answer - assert 4 == 5
=========================================================================== 1 failed, 3 rerun in 0.09s ============================================================================
pytest-parallel扩展
pytest-parallel扩展可以实现测试用例的并行运行
安装
pip install pytest-parallel
创建测试用例
import time
def test_01():
time.sleep(3)
def test_02():
time.sleep(5)
def test_03():
time.sleep(6)
不使用线程执行测试用例
(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -q test_parallel.py
... [100%]
3 passed in 14.18s
使用【–test-per-worker】指定线程数,【auto】为自动分配
pytest -q test_parallel.py --tests-per-worker auto
其他用法
pytest --workers 2 # run 2 workers with 1 test per worker at a time
pytest --workers auto # run 4 workers(4核) with 1 test per worker
pytest --tests-per-worker 4 # run 1 worker with 4 tests at a time
pytest --tests-per-worker auto # runs 1 worker with up to 50 test2 at a time
pytest --workers 2 --tests-per-worker auto
注
:
而pytest-parallel支持python3.6及以上版本,如果是想做多进程并发的需要在linux平台或mac上做,windows上不起作用即(workers永远=1),如果是做多线程的Linux/Mac/Windows平台都支持,进程数为workers设置的值
参考:python-pytest使用(4)-多线程多进程运行