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)-多線程多程序運作