pytest是一個非常成熟的全功能的Python測試框架,主要有以下幾個特點:
- 簡單靈活,容易上手
- 支持參數化
- 能夠支持簡單的單元測試和復雜的功能測試,還可以用來做selenium/appnium等自動化測試、接口自動化測試(pytest+requests)
- pytest具有很多第三方插件,並且可以自定義擴展,比較好用的如pytest-selenium(集成selenium)、pytest-html(完美html測試報告生成)、pytest-rerunfailures(失敗case重復執行)、pytest-xdist(多CPU分發)等
- 測試用例的skip和xfail處理
- 可以很好的和jenkins集成
- report框架----allure 也支持了pytest
- 安裝 pip install -U pytest
2.查看版本
pytest --version
3.用例編寫規范
-
- 測試文件以test_開頭(以 _test結尾也可以)
- 測試類以Test開頭,並且不能帶init方法
- 測試函數以test_開頭
- 斷言使用基本的assert即可
運行參數
- 無參數
- 讀取路徑下符合條件的所有類、函數、方法全部執行
- -v
- 打印詳細運行日志
- -s
- 打印print輸出
- -k
- 跳過運行某個或某些用例
- pytest -k '類名'
- pytest -k '方法名
- pytest -k '類名 and not 方法名' #運行類里所有方法,不包含某個方法
- -x
- 遇到用例失敗立即停止運行
- --maxfail
- 用例失敗數達到某個設定的值停止運行
- pytest --maxfail=[num]
- -m
- 運行所有@pytest.mark.[標記名] 標記的用例
框架結構
與unittest類似,執行前后會執行setup,teardown來增加用例的前置和后置條件。pytest框架使用setup,teardown更為靈活,按照用例運行級別可以分為以下幾類
- setup_module/teardown_module 模塊級別,在模塊始末調用
- setup_function/teardown_function 函數級別,在函數始末調用(在類外部)
- setup_class/teardown_class 類級別,每個類里面執行前后分別執行
- setup_method/teardown_method 方法級別,在方法始末調用(在類中)
- setup/teardown 方法級別,在方法始末調用(在類中)
調用順序:setup_module > setup_class >setup_method > setup > teardown > teardown_method > teardown_class > teardown_module
for example:
#!/usr/bin/env python # encoding: utf-8
''' @Auther:chenshifeng @version: v1.0 @file: test_calc.py @time: 2020/9/14 9:39 PM '''
# 測試文件
import sys, os import pytest sys.path.append(os.pardir) from pythoncode.calc import Calculator # 模塊級別,在模塊始末調用
def setup_module(): print('模塊級別setup') def teardown_module(): print('模塊級別teardown') # 函數級別,在函數始末調用(在類外部)
def setup_function(): print('函數級別setup') def teardown_function(): print('函數級別teardown') def test_case1(): print('testcase1') class TestCalc: # setup_class,teardown_class 類級別每個類里面執行前后分別執行
def setup_class(self): self.cal = Calculator() print('類級別setup') def teardown_class(self): print('類級別teardown') # 方法級別,每個方法里面的測試用例前后分別執行setup、teardown
def setup(self): # self.cal = Calculator()
print('setup') def teardown(self): print('teardown') # 方法級別,每個方法里面的測試用例前后分別執行setup、teardown
def setup_method(self): # self.cal = Calculator()
print('方法級別setup') def teardown_method(self): print('方法級別teardown') @pytest.mark.add def test_add1(self): # cal = Calculator()
assert 3 == self.cal.add(1, 2) @pytest.mark.div def test_div(self): # cal = Calculator()
assert 1 == self.cal.div(1, 1)
運行結果如下
Testing started at 11:05 下午 ... /usr/local/bin/python3.6 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_calc.py Launching pytest with arguments /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_calc.py in /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode ============================= test session starts ============================== platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6 cachedir: .pytest_cache rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini collecting ... collected 3 items test_calc.py::test_case1 模塊級別setup 函數級別setup PASSED [ 33%]testcase1 函數級別teardown test_calc.py::TestCalc::test_add1 類級別setup 方法級別setup setup PASSED [ 66%]teardown 方法級別teardown test_calc.py::TestCalc::test_div 方法級別setup setup PASSED [100%]teardown 方法級別teardown 類級別teardown 模塊級別teardown ============================== 3 passed in 0.02s =============================== Process finished with exit code 0
pytest參數化
Pytest是使用@pytest.mark.parametrize裝飾器來實現數據驅動測試的
for example:
import pytest @pytest.mark.parametrize('a,b,result', [ (1, 1, 2), (2, 3, 5), (100, 200, 300) ]) def test_add(a, b, result): cal = Calculator() assert cal.add(a, b) == result
結果:
Testing started at 11:22 ... "D:\Program Files\Python\python.exe" "D:\Program Files\JetBrains\PyCharm Community Edition 2020.2.1\plugins\python-ce\helpers\pycharm\_jb_pytest_runner.py" --target test_calc.py::test_add Launching pytest with arguments test_calc.py::test_add in D:\chenshifeng\mycode\Python\test_pytest\testing ============================= test session starts ============================= platform win32 -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- D:\Program Files\Python\python.exe cachedir: .pytest_cache rootdir: D:\chenshifeng\mycode\Python, configfile: pytest.ini collecting ... collected 3 items test_calc.py::test_add[1-1-2] PASSED [ 33%] test_calc.py::test_add[2-3-5] PASSED [ 66%] test_calc.py::test_add[100-200-300] PASSED [100%] ============================== 3 passed in 0.03s ============================== Process finished with exit code 0
修改結果顯示名稱
通過上面的運行結果,我們可以看到,為了區分參數化的運行結果,在結果中都會顯示數據組合而成的名稱。
數據短小還好說,如果數據比較長而復雜的話,那么就會很難看。
@pytest.mark.parametrize() 還提供了第三個 ids 參數來自定義顯示結果。
import pytest # 參數化 @pytest.mark.parametrize('a,b,result', [ (1, 1, 2), (2, 3, 5), (100, 200, 300) ], ids=['int0', 'int1', 'int2']) # 修改結果顯示名稱 def test_add(a, b, result): cal = Calculator() assert cal.add(a, b) == result
結果:
============================= test session starts ============================= platform win32 -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- D:\Program Files\Python\python.exe cachedir: .pytest_cache rootdir: D:\chenshifeng\mycode\Python, configfile: pytest.ini collecting ... collected 3 items test_calc.py::test_add[int0] PASSED [ 33%] test_calc.py::test_add[int1] PASSED [ 66%] test_calc.py::test_add[int2] PASSED [100%] ============================== 3 passed in 0.03s ============================== Process finished with exit code 0
pytest fixtures
fixture用途
pytest fixture 與setup,teardown功能一樣,但比之更加靈活,完全可以代替setup,teardown
1.做測試前后的初始化設置,如測試數據准備,鏈接數據庫,打開瀏覽器等這些操作都可以使用fixture來實現
2.測試用例的前置條件可以使用fixture實現
3.支持經典的xunit fixture ,像unittest使用的setup和teardown
4.fixture可以實現unittest不能實現的功能,比如unittest中的測試用例和測試用例之間是無法傳遞參數和數據的,但是fixture卻可以解決這個問題
fixture定義
fixture通過@pytest.fixture()裝飾器裝飾一個函數,那么這個函數就是一個fixture
#!/usr/bin/python # -*- coding: UTF-8 -*-
""" @author:chenshifeng @file:test_login.py @time:2020/09/15 """
import pytest @pytest.fixture() def login(): print('登陸方法') yield #激活fixture teardown方法
print('teardown') # 測試用例之前,先執行login方法
def test_case1(login): print('case1') def test_case2(): print('case2') def test_case3(): print('case3')
運行結果如下:
test_login.py::test_case2 test_login.py::test_case3 ============================== 3 passed in 0.02s =============================== Process finished with exit code 0 登陸方法 PASSED [ 33%]case1 teardown PASSED [ 66%]case2 PASSED [100%]case3
fixture作用范圍(scope)
fixture里面有個scope參數可以控制fixture的作用范圍:session > module > class > function
- - function 每一個函數或方法都會調用,默認為function
- - class 每一個類調用一次,一個類可以有多個方法
- - module,每一個.py文件調用一次,該文件內又有多個function和class
- - session 是多個文件調用一次,可以跨.py文件調用,每個.py文件就是module
cope='function'
#!/usr/bin/python # -*- coding: UTF-8 -*- """ @author:chenshifeng @file:test_login.py @time:2020/09/15 """ import pytest @pytest.fixture() #默認為scope='function' def login(): print('登陸方法') yield ['username','passwd'] #激活fixture teardown方法 print('teardown') # 測試用例之前,先執行login方法 def test_case1(login): print(f'case1 login={login}') def test_case2(login): print('case2') def test_case3(login): print('case3')
運行結果如下:
Testing started at 12:02 上午 ... /usr/local/bin/python3.6 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py Launching pytest with arguments /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py in /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode ============================= test session starts ============================== platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6 cachedir: .pytest_cache rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini collecting ... collected 3 items test_login.py::test_case1 test_login.py::test_case2 test_login.py::test_case3 ============================== 3 passed in 0.02s =============================== Process finished with exit code 0 登陸方法 PASSED [ 33%]case1 login=['username', 'passwd'] teardown 登陸方法 PASSED [ 66%]case2 teardown 登陸方法 PASSED [100%]case3 teardown
scope='module'
#!/usr/bin/python # -*- coding: UTF-8 -*- """ @author:chenshifeng @file:test_login.py @time:2020/09/15 """ import pytest @pytest.fixture(scope='module') #默認為scope='function' def login(): print('登陸方法') yield ['username','passwd'] #激活fixture teardown方法 print('teardown') # 測試用例之前,先執行login方法 def test_case1(login): print(f'case1 login={login}') def test_case2(login): print('case2') def test_case3(login): print('case3')
結果:
Testing started at 12:08 上午 ... /usr/local/bin/python3.6 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py Launching pytest with arguments /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py in /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode ============================= test session starts ============================== platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6 cachedir: .pytest_cache rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini collecting ... collected 3 items test_login.py::test_case1 test_login.py::test_case2 test_login.py::test_case3 ============================== 3 passed in 0.02s =============================== Process finished with exit code 0 登陸方法 PASSED [ 33%]case1 login=['username', 'passwd'] PASSED [ 66%]case2 PASSED [100%]case3 teardown
fixture自動調用(autouse=True)
autouse設置為True,自動調用fixture功能,無需額外繼承
#!/usr/bin/python # -*- coding: UTF-8 -*- """ @author:chenshifeng @file:test_search.py @time:2020/09/16 """ import pytest @pytest.fixture(autouse=True) # 默認為scope='function' def login(): print('登陸方法') yield ['username', 'passwd'] # 激活fixture teardown方法 print('teardown') def test_search1(): #無需繼承login print('搜索用例1') def test_search2(): print('搜索用例2')
結果:
============================= test session starts ============================== platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6 cachedir: .pytest_cache rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini collecting ... collected 2 items test_search.py::test_search1 登陸方法 PASSED [ 50%]搜索用例1 teardown test_search.py::test_search2 登陸方法 PASSED [100%]搜索用例2 teardown ============================== 2 passed in 0.01s =============================== Process finished with exit code 0
fixture參數化(params)
@pytest.fixture有一個params參數,接受一個列表,列表中每個數據都可以作為用例的輸入。也就說有多少數據,就會形成多少用例。可以通過'''request.param'''來獲取該次調用的參數
#!/usr/bin/python # -*- coding: UTF-8 -*- """ @author:chenshifeng @file:test_login.py @time:2020/09/15 """ import pytest @pytest.fixture(params=['user1', 'user2', 'user3']) def login(request): print('登陸方法') print('傳入的參數為:'+request.param) # 獲取params參數 yield ['username', 'passwd'] # 激活fixture teardown方法 print('teardown') # 測試用例之前,先執行login方法 def test_case1(login): print(f'case1 login={login}') def test_case2(login): print('case2') def test_case3(login): print('case3')
結果:
============================= test session starts ============================== platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6 cachedir: .pytest_cache rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini collecting ... collected 9 items test_login.py::test_case1[user1] test_login.py::test_case1[user2] test_login.py::test_case1[user3] test_login.py::test_case2[user1] test_login.py::test_case2[user2] test_login.py::test_case2[user3] test_login.py::test_case3[user1] 登陸方法 傳入的參數為:user1 PASSED [ 11%]case1 login=['username', 'passwd'] teardown 登陸方法 傳入的參數為:user2 PASSED [ 22%]case1 login=['username', 'passwd'] teardown 登陸方法 傳入的參數為:user3 PASSED [ 33%]case1 login=['username', 'passwd'] teardown 登陸方法 傳入的參數為:user1 PASSED [ 44%]case2 teardown 登陸方法 傳入的參數為:user2 PASSED [ 55%]case2 teardown 登陸方法 傳入的參數為:user3 PASSED [ 66%]case2 teardown 登陸方法 傳入的參數為:user1 PASSED [ 77%]case3 teardown test_login.py::test_case3[user2] 登陸方法 傳入的參數為:user2 PASSED [ 88%]case3 teardown test_login.py::test_case3[user3] 登陸方法 傳入的參數為:user3 PASSED [100%]case3 teardown ============================== 9 passed in 0.06s =============================== Process finished with exit code 0
參數化與fixture結合(indirect=True)
#!/usr/bin/python # -*- coding: UTF-8 -*- """ @author:chenshifeng @file:test_cart.py @time:2020/09/16 """ import pytest @pytest.fixture(params=['user1', 'user2', 'user3']) def login(request): print('登陸方法') print('傳入的參數為:' + str(request.param)) # 獲取params參數 yield ['username', 'passwd'] # 激活fixture teardown方法 print('teardown') # 參數化結合fixture使用 # 情況一:傳入值和數據 # 情況二:傳入一個fixture方法,將數據傳入到fixture方法中,fixture使用request參數來接受這組數據,在方法體中使用request.param來接受這個數據 @pytest.mark.parametrize('login', [ ('username1', 'passwd1'), ('username2', 'passwd2') ], indirect=True) def test_cart3(login): print('購物車用例3')
結果:
============================= test session starts ============================== platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6 cachedir: .pytest_cache rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini collecting ... collected 2 items test_cart.py::test_cart3[login0] test_cart.py::test_cart3[login1] ============================== 2 passed in 0.02s =============================== Process finished with exit code 0 登陸方法 傳入的參數為:('username1', 'passwd1') PASSED [ 50%]購物車用例3 teardown 登陸方法 傳入的參數為:('username2', 'passwd2') PASSED [100%]購物車用例3 teardown
conftest.py
1.conftest.py文件名字是固定的,不可以做任何修改
2.文件和用例文件在同一個目錄下,那么conftest.py作用於整個目錄
3.conftest.py文件不能被其他文件導入
4.所有同目錄測試文件運行前都會執行conftest.py文件
#!/usr/bin/python # -*- coding: UTF-8 -*- """ @author:chenshifeng @file:conftest.py @time:2020/09/15 """ import pytest @pytest.fixture(params=['user1', 'user2', 'user3']) def login(request): print('登陸方法') print('傳入的參數為:'+str(request.param)) # 獲取params參數 yield ['username', 'passwd'] # 激活fixture teardown方法 print('teardown')
#!/usr/bin/python # -*- coding: UTF-8 -*- """ @author:chenshifeng @file:test_login.py @time:2020/09/15 """ # 測試用例之前,先執行login方法 import pytest def test_case1(login): print(f'case1 login={login}') @pytest.mark.usefixtures('login') def test_case2(): print('case2') # print(f'case1 login={login}') #該方法無法獲取返回值 def test_case3(login): print('case3')
運行test_login.py文件,結果如下
============================= test session starts ============================== platform darwin -- Python 3.9.0, pytest-6.1.1, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.9 cachedir: .pytest_cache rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini plugins: allure-pytest-2.8.18 collecting ... collected 9 items test_login.py::test_case1[user1] test_login.py::test_case1[user2] test_login.py::test_case1[user3] test_login.py::test_case2[user1] test_login.py::test_case2[user2] test_login.py::test_case2[user3] test_login.py::test_case3[user1] 登陸方法 傳入的參數為:user1 PASSED [ 11%]case1 login=['username', 'passwd'] teardown 登陸方法 傳入的參數為:user2 PASSED [ 22%]case1 login=['username', 'passwd'] teardown 登陸方法 傳入的參數為:user3 PASSED [ 33%]case1 login=['username', 'passwd'] teardown 登陸方法 傳入的參數為:user1 PASSED [ 44%]case2 teardown 登陸方法 傳入的參數為:user2 PASSED [ 55%]case2 teardown 登陸方法 傳入的參數為:user3 PASSED [ 66%]case2 teardown 登陸方法 傳入的參數為:user1 PASSED [ 77%]case3 teardown test_login.py::test_case3[user2] 登陸方法 傳入的參數為:user2 PASSED [ 88%]case3 teardown test_login.py::test_case3[user3] 登陸方法 傳入的參數為:user3 PASSED [100%]case3 teardown ============================== 9 passed in 0.04s =============================== Process finished with exit code 0
end
