Pytest測試框架基礎#
Pytest測試框架介紹#
Pytest是Python一款三方測試框架,用于編寫和運行單元測試、集成測試和功能測試。Pytest測試框架具有簡單、靈活、易于擴展等特點,被廣泛應用于Python項目的測試工作中。
Pytest主要特點:
- 簡單易用:Pytest測試框架的API簡單易用,可以快速編寫測試用例。
- 靈活多樣:Pytest測試框架支持多種測試方式,包括函數式測試、類式測試、參數化測試、fixture測試等。
- 插件機制:Pytest測試框架支持插件機制,可以通過插件擴展測試框架的功能。
- 斷言機制:Pytest測試框架支持多種斷言方式,包括assert語句、assert關鍵字、assert表達式等。
- 報告機制:Pytest測試框架支持生成多種測試報告,包括控制臺報告、HTML報告、JUnit報告等。
B站講的最好的Pytest自動化測試教程,自動化必備之Pytest測試框架訓練營,三小時速成,學會直接上手實操!
安裝方法:
$ pip install pytest
Pytest基本用法#
編寫及運行測試用例#
- 新建test開頭的測試腳本,如test_calc.py,編寫測試函數 或 測試類
def test_add(): # 測試函數需以test_開頭“”“測試加法”“”s = 1 + 2assert s == 3, f'斷言失敗, {s} != 3' # 斷言
或
class TestAdd: # 測試類def test_add_01(self): # 測試方法“”“測試加法01”“”s = 1 + 2assert s == 3, f'斷言失敗, {s} != 3' # 斷言
- 運行測試用例
$ pytest test_calc.py
或
if __name__ == '__main__':import pytestpytest.main([__file__]) # pytest測試當前文件
測試準備及清理#
Pytest中可以使用不同范圍的setup/teardown方法進行測試準備及清理。
def setup_module():print('測試模塊準備')def teardown_module():print('測試模塊清理')def setup_function():print('測試函數準備')def teardown_function():print('測試函數清理')def test_add(): # 測試函數"""測試加法"""s = 1 + 2assert s == 3, f'斷言失敗, {s} != 3' # 斷言
或
class TestAdd: # 測試類def setup_class(self):print('測試類準備')def teardown_class(self):print('測試類清理')def setup_method(self):print('測試方法準備')def teardown_method(self):print('測試方法清理')def test_add_01(self): # 測試方法"""測試加法01"""s = 1 + 2assert s == 3, f'斷言失敗, {s} != 3' # 斷言
參數化(數據驅動)#
Pytest中可以@pytest.mark.paramitrize()
裝飾器將每條數據變成一條用例。
@pytest.mark.parametrize('a', [1,2,3]) # 參數變量名,數據列表
def test_data(a): # 需要添加和上面同名的參數"""測試參數化數據"""print('a =', a)
支持多個參數變量,也支持為每個數據添加自定義說明(id)。
data = [(1,2,3), (0,0,0), (-1,2,1)]
ids = ['test1+2', 'test0+0', 'test-1+2’]@pytest.mark.parametrize(‘a,b,excepted’, data, ids=ids) # 多個參數變量名寫到同一字符串里
def test_add(a,b,excepted): # 需要添加和上面同名的參數“”“測試加法"""s = a + bassert s == excepted, f'斷言失敗, {s} != {excepted}' # 斷言
跳過及期望失敗#
Pytest中可以@pytest.mark.skip()
或@pytest.mark.skipif()
裝飾器來跳過或根據條件跳過用例。
無條件跳過
@pytest.mark.skip('待實現')
def test_sub():pass
或根據條件跳過
from platform import platform@pytest.mark.skipif(platform == 'Windows', reason='不支持Windows')
def test_linux_cmd():pass
也可以使用@pytest.mark.xfail()
來期望用例失敗。
@pytest.mark.xfail(reason='期望失敗,1+1!=3')
def test_add():assert 1 + 1 == 3
Pytest測試框架的命令行參數#
用例挑選相關命令行參數
- pytest <目錄或文件路徑1> <目錄或文件路徑2> :運行指定目錄或文件中所有的測試用例,支持指定多個路徑。
- pytest <文件路徑::測試函數名>:運行指定測試函數
- pytest <文件路徑::測試類名>:運行指定測試類中所有用例
- pytest <文件路徑::測試類::測試方法名>:運行指定測試類中指定測試方法
- pytest –k=<正則表達式>:指定測試類/測試函數名稱匹配規則,篩選指定用例。
- pytest –m=<標簽>:指定標簽篩選用例,支持and、or、not,例如 –m 'api not web'
Pytest內置makers標記
在命令行使用pytest –markers可以查看所有可使用(包含用戶已注冊)標記。
@pytest.mark.filterwarnings(warning):過濾指定警告
@pytest.mark.skip(reason=None):無條件跳過用例個
@pytest.mark.skipif(condition, ..., *, reason=...):根據條件跳過用例
@pytest.mark.xfail(condition, ..., *, reason=..., run=True, raises=None, strict=xfail_strict):根據條件期望用例失敗
@pytest.mark.parametrize(argnames, argvalues):參數化數據驅動
@pytest.mark.usefixtures(fixturename1, fixturename2, ...):標記引用(依賴)某些Fixture函數
報告(命令行顯示)相關命令行參數
- --verbosity=<數值>:指定顯示詳細級別,0-1,默認0。
- -v / --verbose:提高顯示詳細級別
- -q / --quiet:安靜模式
- -s:不捕獲用例輸出(用例print信息直接輸出到命令行)
- --capture=<捕獲方法>:選擇捕獲用例輸出到方法,支持fd、sys、no、tee-sys
- --no-header:不顯示頭部信息(測試開始環境信息)
- --no-summary:不顯示運行總結
- -r <字符>:顯示額外測試狀態的詳情信息,支持f-失敗用例,E-異常用例,s-跳過用例,x-預期失敗用例,X-非預期成功用例,p-通過用例,P-通過用例帶輸出,a-所有成功用例,A-全部用例
- --duration=<數字>:顯示運行最慢的前幾條用例
- --duration-min: 設置運行時間最小閾值(大于該值視為運行較慢)
緩存(重復運行)相關命令行參數
- --lf / --last-failed:運行上次失敗的用例
- --ff / --failed-first:先運行上次失敗的用例,再運行其他用例
- --nf / --last-failed:先運行新的測試用例文件,在運行其他用例
- --cache-clear:清理之前測試的緩存結果
- ... ...
用例收集相關命令行參數
- --log-level=LEVEL:設置日志等級
- --log-format=LOG_FORMAT:設置日志格式
- --log-date-format=LOG_DATE_FORMAT:設置日志日期格式
- --log-cli-level=LOG_CLI_LEVEL:設置命令行日志登記
- --log-cli-format=LOG_CLI_FORMAT:設置命令行日志格式
- --log-cli-date-format=LOG_CLI_DATE_FORMAT:設置命令行日志日期格式
- --log-file=LOG_FILE:設置日志文件路徑
- --log-file-level=LOG_FILE_LEVEL:設置日志文件等級
- --log-file-format=LOG_FILE_FORMAT:設置日志文件日志格式
- --log-file-date-format=LOG_FILE_DATE_FORMAT:設置日志文件日期格式
- --log-auto-indent=LOG_AUTO_INDENT:設置多行文本日志縮進,支持true|on, false|off 或整數值。
- --log-disable=LOGGER_DISABLE:根據名稱禁用某個logger,支持指定多次。
Pytest測試框架的配置項#
pytest.ini文件[pytest]中常用配置項如下
配置項 | 說明 |
---|---|
addopts | 默認額外的參數; |
cache_dir | 緩存目錄,用于緩存上次執行失敗的用例; |
markers | 注冊的自定義標記及說明; |
norecursedirs | 不遍歷的目錄; |
testpaths | 測試目錄; |
python_files | 測試腳本匹配規則,默認test_開頭的py文件視為用例,如果有的測試腳本不是以test_開頭,可以配置為pythonfiles = *; |
python_class | 測試類匹配規則,默認Test開頭的類視為測試類; |
python_functions | 測試函數及測試方法匹配規則,默認test_開頭的函數及方法視為測試用例; |
console_output_style | 命令行輸出樣式,支持classic、progress、counts三種樣式; |
filterwarnings | 過濾的警告; |
xfail_strict | 啟用時,標記為xfail的用例通過時狀態為Fail,否則為XPassed。 |
日志相關配置如下
配置項 | 說明 |
---|---|
log_print | 用例失敗時是否顯示相關日志; |
log_cli | 配置為ture時開啟命令行日志; |
log_file | 配置日志文件路徑,每次覆蓋,不支持追加模式;v |
log_cli_level/log_file_level | 配置輸出到命令行及文件的日志等級; |
log_cli_format/log_file_format | 配置輸出到命令行及文件的日志格式; |
log_cli_date_format/log_file_date_format | 配置日志的日期格式。 |
Pytest測試框架進階#
Pytest測試框架的擴展(鉤子)機制#
鉤子函數(Hooks)是一種特殊的函數,可以在執行過程中“順帶”執行一些自定義的操作。
Pytest測試框架提供了許多鉤子函數,可以在測試過程的不同階段執行自定義的操作。
- 初始化
- 添加鉤子pytest_addhooks
- 添加參數pytest_addoption
- 注冊插件pytest_plugin_registered
- 啟動
- 啟動命令行主流程pytest_cmdline_main
- 生成配置 pytest_configure
- 啟動運行會話 pytest_sessionstart
- 收集用例
- 判斷是否忽略pytest_ignore_collect
- 創建待收集文件對象pytest_collect_file
- 創建待收集模塊對象pytest_pycollect_makemodule
- 開始收集當前模塊用例pytest_collectstart
- 創建當前模塊收集報告對象pytest_make_collect_report
- 創建待收集用例對象pytest_pycollect_makeitem
- 生成用例pytest_generate_tests
- 收集用例完畢pytest_itemcollected
- 生成收集報告pytest_collectreport
- 調整收集的用例pytest_collection_modifyitems
- 用例收集完畢pytest_report_collectionfinish
- 執行測試
- 記錄警告信息pytest_warning_recorded
- 啟動執行循環pytest_runtestloop
- 開始執行當前用例pytest_runtest_protocol
- 開始記錄日志pytest_runtest_logstart
- 開始運行測試準備pytest_runtest_setup
- 執行某個測試準備方法pytest_fixture_setup
- 執行測試用例函數pytest_pyfunc_call
- 開始運行測試清理pytest_runtest_teardown
- 執行某個測試清理方法pytest_fixture_post_finalizer
- 停止記錄日志pytest_runtest_logfinish
- 結束
- 測試會話結束 pytest_sessionfinish
- 生成命令行測試總結 pytest_terminal_summary
- 恢復配置 pytest_unconfigure
初始化時的鉤子函數#
- pytest_addhooks(pluginmanager):添加新的鉤子函數
- pytest_addoption(parser, pluginmanager):添加命令行參數及ini配置項
- pytest_configure(config):初始化配置
- pytest_unconfigure(config):恢復配置,退出測試進程前執行
- pytest_sessionstart(session):測試會話開始
- pytest_sessionfinish(session, exitstatus):測試會話結束
- pytest_plugin_registered(plugin, manager):注冊了新插件
示例-添加自定義參數及配置項
def pytest_addoption(parser): # 初始化是的鉤子放假,用來增加命令行參數# 添加命令行參數--send_email,執行時帶上該參數則視為tureparser.addoption("--send_email", action="store_true", help="send email after test")# 添加郵件主題、收件人、正文配置項parser.addini('email_subject', help='test report email subject')parser.addini('email_receivers', help='test report email receivers')parser.addini('email_body', help='test report email body')
示例-修改配置-組裝絕對路徑
from datetime import datetimedef pytest_configure(config): # 初始化是的配置方法log_file = config.getini('log_file') # 如果配置文件配置了log_fileif log_file:# 使用當前時間格式化后log_file名稱,替換原有的配置# 如果配置的是不帶時間格式的如log_file=last_run.log,格式化后不變,# 如果配置log_file=%Y%m%d%H%M%S.log,格式后成當前時間config.option.log_file = datetime.now().strftime(log_file)
啟動時的鉤子函數#
- pytest_load_initial_conftests(early_config, parser, args):調用初始化conftest.py文件(僅作為安裝插件時)
- pytest_cmdline_parse(pluginmanager, args):初始化config配置,解析命令行參數
- pytest_cmdline_main(config):調用執行主命令行操作,啟動測試主循環
收集用例時的鉤子函數#
- pytest_collection(session):開始收集用例
- pytest_ignore_collect(collection_path, path, config):判斷是否忽略該目錄
- pytest_collect_file(file_path, path, parent):創建待收集文件對象
- pytest_pycollect_makemodule(module_path,?path,?parent):創建待收集模塊
- pytest_pycollect_makeitem(collector,?name,?obj):創建待收集用例對象
- pytest_generate_tests(metafunc):生成用例
- pytest_make_parametrize_id(config,?val,?argname):生成參數化用例id
- pytest_collection_modifyitems(session,?config,?items):調整收集結果
- pytest_collection_finish(session):收集用例結束
示例-調整用例搜集結果-收集用例
import pytestclass TestCollection:def __init__(self):self.collected = []def pytest_collection_modifyitems(self, items):for item in items:self.collected.append(item.nodeid)def get_testcases(testpath):coll = TestCollection()pytest.main([testpath, '--collect-only', '-q'], plugins=[coll]) # 指定插件return coll.collectedget_testcases('./testcases')
執行測試時的鉤子函數#
- pytest_runtestloop(session):開始執行用例
- pytest_runtest_protocol(item,?nextitem):開始執行某條用例
- pytest_runtest_logstart(nodeid,?location):開始記錄日志
- pytest_runtest_logfinish(nodeid,?location):停止記錄日志
- pytest_runtest_makereport(item,?call):創建報告對象
- pytest_runtest_setup(item):開始運行測試準備方法
- pytest_runtest_call(item):開始調用測試方法
- pytest_pyfunc_call(pyfuncitem):調用測試函數
- pytest_runtest_teardown(item,?nextitem):開始執行測試清理方法
報告相關鉤子函數#
- pytest_collectstart(collector):開始收集某個模塊的用例
- pytest_make_collect_report(collector):為當前模塊創建收集報告對象
- pytest_itemcollected(item):收集到一條用例
- pytest_collectreport(report):收集當前模塊結束
- pytest_deselected(items):排除部分用例
- pytest_report_header(config,?start_path,?startdir):命令行輸出收集信息
- pytest_report_collectionfinish(config,?start_path,?startdir,?items):收集完畢
- pytest_report_teststatus(report,?config):報告setup/用例/teardown執行狀態
- pytest_report_to_serializable(config,?report):序列化報告內容
- pytest_report_from_serializable(config,?data):加載序列化報告內容
- pytest_terminal_summary(terminalreporter,?exitstatus,?config):生成命令行運行總結
- pytest_fixture_setup(fixturedef,?request):執行某個測試準備方法
- pytest_fixture_post_finalizer(fixturedef,?request):執行某個測試清理方法
- pytest_warning_recorded(warning_message,?when,?nodeid,?location):記錄到警告信息
- pytest_runtest_logreport(report):輸出setup/用例/teardown日志
- pytest_assertrepr_compare(config,?op,?left,?right):處理斷言
- pytest_assertion_pass(item,?lineno,?orig,?expl):處理用例斷言通過
示例-命令行總結-運行后額外操作
def pytest_terminal_summary(config): # 生成報告時的命令行最終總結方法send_email = config.getoption("--send-email")email_receivers = config.getini('email_receivers').split(',')if send_email is True and email_receivers:log_file = config.getoption('log_file')email_subject = config.getini('email_subject') or 'TestReport'email_body = config.getini('email_body') or 'Hi'if email_receivers:print('發送郵件 ...')
調試/交互相關的鉤子函數#
- pytest_internalerror(excrepr,?excinfo):運行時出現內部錯誤
- pytest_keyboard_interrupt(excinfo):運行時用戶Ctrl+C中斷測試
- pytest_exception_interact(node,?call,?report):引發可交互的異常
- pytest_enter_pdb(config,?pdb):進入pdb調試器
- pytest_leave_pdb(config,?pdb):退出pdb調試器
Pytest測試框架的Fixture機制#
在Pytest測試框架中,Fixture機制是一種用于管理測試用例依賴關系的機制。Fixture機制可以在測試用例執行之前和之后執行一些操作,例如創建測試數據、打開數據庫連接、清理測試數據等。
在Pytest中使用@pytest.fixture裝飾器裝飾一個函數,該函數可以yield或return返回數據、對象或者一個函數。在測試用例中,使用@pytest.mark.usefixtures裝飾器來或將Fixture函數名作為參數或使用Fixture。
import pytest@pytest.fixture()
def user():return 'admin', '123456'@pytest.fixture
def login(user): # Fixture函數可以引用(依賴)其他Fixture函數# 使用的user參數實際為調用user()函數后的返回值username, password = userprint(f'{username} 登錄')token = 'abcdef'yield token # yield上為測試準備,yield下為測試清理print('退出登錄’)def test_add_project(login): # 引用(依賴)login方法# 使用login參數實際是調用login()函數的返回值即'abcdef'print('token =', login)# 也可以標記使用指定Fixture函數,執行其測試準備即清理方法
@pytest.mark.userfixtures('login')
def test_add_project02():pass # 這種情況下拿不到Fixture函數的返回結果
Fixture生效范圍#
Fixture函數可以通過scope指定作用范圍,Pytest中的Fixture函數支持以下5種范圍:
- Session會話級:scope=‘session’,運行一次Pytest算一次會話。運行期間只setup/teardown一次
Package包級:scope=‘pacakge’,對每個包Python包setup/teardown一次 - Module模塊級:scope=‘module’,對每個Python腳本setup/teardown一次
- Class級:scope=‘class’,對每個測試類setup/teardown一次
- Function級:scope=‘function’,默認,每個測試函數或測試方法setup/teardown一次。
from selenium import webdriver# 整個運行過程中僅啟動關閉一次瀏覽器
@pytest.fixture(scope='session')
def driver():dr = webdriver.Chrome()yield drdr.quit()
Fixture共享及同名覆蓋#
Fixture函數一般作為公用的輔助方法或全局變量來使用,因此需要在不同用例中都能使用。Pytest框架中使用固定名稱的conftest.py文件,來集中管理Fixture函數 。
conftest.py文件同級即下級目錄中的所有用例可以無需導入,直接使用conftest.py中的所有Fixture函數。
conftest.py文件在不同的目錄中可以有多個(生效范圍不同),同時用例文件中也可以編寫Fixture函數,當Fixture函數有重名時,采用“就近”原則,離當前用例最近的Fixture函數生效。
@pytest.fixture(scope='session')
def base_url():return 'http://localhost:3000'def test_base_url(base_url):print('base_url =', base_url)if __name__ == '__main__':# pip install pytest-base-urlimport pytestpytest.main([__file__, '-sq','--base-url=http://www.xxx.com'])
conftest.py文件的作用
用來編寫Fixture函數
編寫鉤子Hooks函數
導入其所在目錄或包的路徑(可以確保項目根目錄被導入)
conftest.py所在導入規則:
如果conftest.py所在目錄沒有__init__.py文件,則Pytest自動導入conftest.py所在目錄。
如果有則向上找到最上層的包(最上層一個包含__init__.py的目錄),并導入包的所在目錄,以確保conftest.py可以使用
Fixture返回函數對象#
由于Fixture函數不接收普通參數,無法根據用例需要進行特定的測試準備,此時我們一般需要封裝一個實用函數并導入使用,也可以把函數定義到Fixture函數的內部,并返回該函數對象,這樣用例無需導入便可直接使用功能函數。示例如下:
常規導入模塊方式
auth.py
def login(username, password):# ...token = 'abcdef'return token
test_project_api.py
from .auth import logindef test_add_project():token = login('admin', 'abc123')# ...
封裝成Fixture方式
conftest.py
@pytest.fixture
def login():def _login(username, password):# ...token = ‘abcdef’return tokenreturn _login
test_project_api.py
def test_add_project(login): # 無需導入直接使用# 使用的login參數實際是login()函數的調用結果,# 即內部的_login函數token = login('admin', 'abc123')# ...
Pytest測試框架的參數化機制#
Pytest提供了三種參數化方式:
使用@pytest.mark.paramitrize()標記進行數據驅動測試
users = [('admin', '123456'),('kevin','abc123'),('lily', 'abcdef')
]@pytest.mark.parametrize('user,pwd', users)
def test_login(user, pwd):print('登錄', user, pwd)
使用Fixture函數的params參數,進行參數化測試準備
users = [('admin', '123456'),('kevin','abc123'), ('lily', 'abcdef')
]@pytest.fixture(params=users)
def login(request): # request是系統內置Fixtureuser, pwd = request.paramprint('登錄', user, pwd)def test_add_project(login): """測試不同用戶添加項目""" # ...
使用鉤子函數pytest_generate_tests(),動態成多條用例
*?conftest.py*
def pytest_addoption(parser): # 添加命令行參數parser.addoption("--filepath", action="append",default=[], help="run file list")def pytest_generate_tests(metafunc):if "filepath" in metafunc.fixturenames:filepaths = metafunc.config.getoption("filepath")metafunc.parametrize("filepath",filepaths)
test_a.py
def test_a(filepath):print('test_a', filepath)
Pytest測試框架的收集機制#
在Pytest測試框架中,收集機制是一種用于自動收集測試用例的機制。Pytest測試框架會自動搜索指定目錄下的測試文件,并收集其中的測試用例。
以下是Pytest測試框架的收集機制的一些常用用法:
默認收集規則
Pytest測試框架的默認收集規則是搜索以test_開頭或以_test結尾的文件,并收集其中的測試用例。例如,test_sum.py、sum_test.py、test_sum.py::test_sum等文件和測試用例都會被收集。
根據正則匹配收集用例
可以使用pytest命令的-k選項來指定收集指定目錄下的測試文件。例如,可以使用以下命令來收集tests目錄下的所有測試文件:
$ pytest -k tests
在運行上面的命令后,Pytest測試框架會自動搜索tests目錄下的測試文件,并收集其中的測試用例。
自定義收集規則
可以使用pytest_collection_modifyitems
鉤子函數來自定義收集規則。
例如,可以使用以下代碼來自定義收集規則:
def pytest_collection_modifyitems(items):for item in items:if "slow" in item.keywords:item.add_marker(pytest.mark.slow)
在上面的代碼中,使用pytest_collection_modifyitems
鉤子函數來修改測試用例列表。如果測試用例的關鍵字中包含slow,則為該測試用例添加@pytest.mark.slow
標記。
Pytest測試框架高級應用#
Pytest插件開發#
- 新建一個項目,項目中新建一個包例如pytest_data_file,包含空白的__init__.py文件和一個plugin.py文件。
- 在plugin.py中使用添加自動參數及Fixture函數。
- 編輯項目中的setup.py文件,在setup函數中指定入口entry_points為pytest11,例如:
entry_points={'pytest11': ['pytest-data-file = pytest_data_file.plugin',]
}
以下是一個自定義標記插件開發的示例,為用例添加owner歸宿人標記,并可以根據—owner篩選用例。
示例插件安裝腳本setup.py代碼
示例插件核心代碼
B站講的最好的Pytest自動化測試教程,自動化必備之Pytest測試框架訓練營,三小時速成,學會直接上手實操!