pytest 3.1
版本新增特性
1. 告警信息的默認捕獲行為
pytest
可以自動捕獲測試中產生的告警信息,並在執行結束后進行展示;
下面這個例子,我們在測試中人為的產生一條告警:
# src/chapter-8/test_show_warning.py
import warnings
def api_v1():
warnings.warn(UserWarning('請使用新版本的API。'))
return 1
def test_one():
assert api_v1() == 1
我們也可以通過-W arg
命令行選項來自定義告警的捕獲行為:
arg
參數的格式為:action:message:category:module:lineno
;
action
只能在"error", "ignore", "always(all)", "default", "module", "once"
中取值,默認取值為default
;category
必須是Warning
的子類,默認取值為Warning
類,表示所有的告警;module
必須為字符串,表示特定模塊產生的告警信息;
下面是一些常見的使用場景:
-
忽略某一種類型的告警信息;例如,忽略
UserWarning
類型的告警(-W ignore::UserWarning
):λ pipenv run pytest -W ignore::UserWarning src/chapter-8/test_show_warnings.py ============================ test session starts ============================= platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0 rootdir: D:\Personal Files\Projects\pytest-chinese-doc collected 1 item src\chapter-8\test_show_warnings.py . [100%] ============================= 1 passed in 0.02s ==============================
-
將某一種類型的告警轉換為異常來處理;例如,將
UserWarning
告警轉換為異常處理(-W error::UserWarning
):λ pipenv run pytest -W error::UserWarning src/chapter-8/test_show_warnings.py ============================ test session starts ============================= platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0 rootdir: D:\Personal Files\Projects\pytest-chinese-doc collected 1 item src\chapter-8\test_show_warnings.py F [100%] ================================== FAILURES ================================== __________________________________ test_one __________________________________ def test_one(): > assert api_v1() == 1 src\chapter-8\test_show_warnings.py:31: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ def api_v1(): > warnings.warn(UserWarning('請使用新版本的API。')) E UserWarning: 請使用新版本的API。 src\chapter-8\test_show_warnings.py:26: UserWarning ============================= 1 failed in 0.05s ==============================
-
只展示某一個模塊中產生的告警;例如,只展示
test_show_warnings
模塊產生的告警,忽略其它所有的告警(-W ignore -W default:::test_show_warnings
):λ pipenv run pytest -W ignore -W default:::test_show_warnings src/chapter-8/ ============================ test session starts ============================= platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0 rootdir: D:\Personal Files\Projects\pytest-chinese-doc collected 1 item src\chapter-8\test_show_warnings.py . [100%] ============================== warnings summary ============================== src/chapter-8/test_show_warnings.py::test_one D:\Personal Files\Projects\pytest-chinese-doc\src\chapter-8\test_show_warnings.py:26: UserWarning: 請使用新版本的API。 warnings.warn(UserWarning('請使用新版本的API。')) -- Docs: https://docs.pytest.org/en/latest/warnings.html ======================= 1 passed, 1 warnings in 0.03s ========================
這里我們演示了多個
-W
選項的組合操作,優先級是從左到右依次遞增的;這里如果將它們調換一下順序(即-W default:::test_show_warnings -W ignore
),因為-W ignore
最后生效,覆蓋掉之前的操作,最終的結果就是我們一個告警信息都沒有捕獲到; -
我們也可以通過在
pytest.ini
文件中配置filterwarnings
項,來實現同樣的效果;例如,上述的例子在pytest.ini
的配置為:# src/chapter-8/pytest.ini [pytest] filterwarnings = ignore default:::test_show_warnings
不帶
-W
選項執行:λ pipenv run pytest src/chapter-8/ ============================ test session starts ============================= platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0 rootdir: D:\Personal Files\Projects\pytest-chinese-doc\src\chapter-8, inifile: pytest.ini collected 1 item src\chapter-8\test_show_warnings.py . [100%] ============================== warnings summary ============================== test_show_warnings.py::test_one D:\Personal Files\Projects\pytest-chinese-doc\src\chapter-8\test_show_warnings.py:26: UserWarning: 請使用新版本的API。 warnings.warn(UserWarning('請使用新版本的API。')) -- Docs: https://docs.pytest.org/en/latest/warnings.html ======================= 1 passed, 1 warnings in 0.04s ========================
-W
其實是python
本身自帶的命令行選項,你可以通過訪問官方文檔以了解更多:https://docs.python.org/3.7/library/warnings.html#warning-filter
2. @pytest.mark.filterwarnings
上述操作我們是在命令行上實現的,如果想要在用例、類甚至是模塊級別上自定義告警的捕獲行為,上面的方法就不是很便利了;這里,我們可以通過為測試項添加告警過濾器來實現這種需求;
還記得在上一章中pytest.ini
中的配置嗎?我們禁止了除test_show_warnings
模塊外,其它所有告警的捕獲行為;現在,我們在這個模塊中新加一個用例test_two
,禁止捕獲由它所觸發的用戶告警:
# src/chapter-8/test_show_warning.py
@pytest.mark.filterwarnings('ignore::UserWarning')
def test_two():
assert api_v1() == 1
執行這個用例:
λ pipenv run pytest -k "test_two" src/chapter-8/
============================ test session starts =============================
platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
rootdir: D:\Personal Files\Projects\pytest-chinese-doc\src\chapter-8, inifile: pytest.ini
collected 2 items / 1 deselected / 1 selected
src\chapter-8\test_show_warnings.py . [100%]
====================== 1 passed, 1 deselected in 0.03s =======================
我們沒有捕獲任何告警信息,這說明通過@pytest.mark.filterwarnings
添加的過濾器優先級要高於命令行或pytest.ini
添加的過濾器;你也可以通過執行test_one
用例來對比它們之間的不同;
我們可以通過將@pytest.mark.filterwarnings
應用於測試類來為這個類中所有的用例添加告警過濾器;
也可以通過設置pytestmark
變量為整個測試模塊中所有的用例添加告警過濾器;例如,將模塊中所有的告警轉換為異常處理:
pytestmark = pytest.mark.filterwarnings("error")
3. 去使能告警信息的展示
我們可以通過--disable-warnings
命令行選項來禁止告警信息的展示;例如,我們在測試輸出中不展示test_one
用例所產生到的告警信息:
λ pipenv run pytest -k "test_one" --disable-warnings src/chapter-8/
============================ test session starts =============================
platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
rootdir: D:\Personal Files\Projects\pytest-chinese-doc\src\chapter-8, inifile: pytest.ini
collected 2 items / 1 deselected / 1 selected
src\chapter-8\test_show_warnings.py . [100%]
================ 1 passed, 1 deselected, 1 warnings in 0.03s =================
4. 去使能告警的捕獲行為
上一章我們只是不展示捕獲到的告警信息,這里我們可以通過-p no:warnings
命令行選項徹底禁止告警的捕獲行為:
λ pipenv run pytest -k "test_one" -p no:warnings src/chapter-8/
============================ test session starts =============================
platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
rootdir: D:\Personal Files\Projects\pytest-chinese-doc\src\chapter-8, inifile: pytest.ini
collected 2 items / 1 deselected / 1 selected
src\chapter-8\test_show_warnings.py . [100%]
====================== 1 passed, 1 deselected in 0.03s =======================
如果你足夠細心的話,你可以看到它們的區別:
================ 1 passed, 1 deselected, 1 warnings in 0.03s =================
和
====================== 1 passed, 1 deselected in 0.03s =======================
5. DeprecationWarning
和PendingDeprecationWarning
告警
遵循PEP-0565的建議,pytest
會默認捕獲DeprecationWarning
和PendingDeprecationWarning
類型的告警;
有時候,你並不需要這種行為,可以通過在pytest.ini
添加配置;例如,忽略告警信息匹配".*U.*mode is deprecated"
的DeprecationWarning
告警:
[pytest]
filterwarnings =
ignore:.*U.*mode is deprecated:DeprecationWarning
注意:
如果已經在
python
解釋器中配置了告警選項,那么pytest
不會再添加任何默認的告警過濾器;這一點,可以在pytest
的源碼中得到證實:# _pytest/warnings.py if not sys.warnoptions: # if user is not explicitly configuring warning filters, show deprecation warnings by default (#2908) warnings.filterwarnings("always", category=DeprecationWarning) warnings.filterwarnings("always", category=PendingDeprecationWarning)
pytest issue #2908
:https://github.com/pytest-dev/pytest/issues/2908
5.1. pytest.deprecated_call
方法
我們可以通過deprecated_call
方法確保一段代碼觸發了DeprecationWarning
或PendingDeprecationWarning
告警:
# src/chapter-8/test_deprecation.py
import warnings
import pytest
def api_call_v1():
warnings.warn('v1版本已廢棄,請使用v2版本的api;', DeprecationWarning)
return 200
def test_deprecation():
assert pytest.deprecated_call(api_call_v1) == 200
同時,deprecated_call
也支持上下文管理器的寫法,所以上面的例子也可以寫成:
def test_deprecation():
with pytest.deprecated_call():
assert api_call_v1() == 200
6. 編寫觸發期望告警的斷言
我們可以使用pytest.warns()
作為上下文管理器,來編寫一個觸發期望告警的斷言,它和pytest.raises()
的用法很接近;
在正式開始之前,我們來看一下上一節中deprecated_call
方法的源碼:
# _pytest/warnings.py
def deprecated_call(func=None, *args, **kwargs):
__tracebackhide__ = True
if func is not None:
args = (func,) + args
return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs)
可以看到,deprecated_call
也不過是pytest.warns()
的封裝,區別在於其指定了具體期望的告警類型;
現在,我們來具體看一下pytest.warns()
的用法(以上一節的例子說明):
-
我們可以為其傳遞一個關鍵字參數
match
,判斷捕獲到的告警信息是否匹配既定的正則表達式:def test_deprecation(): with pytest.warns((DeprecationWarning, PendingDeprecationWarning), match=r'v1版本已廢棄'): assert api_call_v1() == 200
-
我們也可以直接傳遞可調用對象,表達式返回執行這個可調用對象的結果:
def test_deprecation(): assert pytest.warns((DeprecationWarning, PendingDeprecationWarning), api_call_v1, match=r'和 pytest.raises() 方法一樣,這時 pytest 不再判斷告警信息是否正確') == 200
注意:和
pytest.raises()
一樣,此時match
參數不再生效; -
pytest.warns()
可以返回一個列表,包含所有捕獲到的告警對象(warnings.WarningMessage
):import re def test_deprecation(): with pytest.warns((DeprecationWarning, PendingDeprecationWarning)) as records: assert api_call_v1() == 200 assert len(records) == 1 assert re.search(r'v1版本已廢棄', records[0].message.args[0])
實際上,其返回的並不是一個列表,只是實現了
__getitem__()
和__len__()
方法的普通類,其內部本身有一個_list
的私有屬性用於存儲所有的數據;
學習這一章節最好的辦法就是結合
pytest.warns()
的源碼一起看,上面所有的用法和特性都可以體現在里面:# _pytest/recwarn.py def warns( expected_warning: Union["Type[Warning]", Tuple["Type[Warning]", ...]], *args: Any, match: Optional[Union[str, "Pattern"]] = None, **kwargs: Any ) -> Union["WarningsChecker", Any]: __tracebackhide__ = True if not args: if kwargs: msg = "Unexpected keyword arguments passed to pytest.warns: " msg += ", ".join(sorted(kwargs)) msg += "\nUse context-manager form instead?" raise TypeError(msg) return WarningsChecker(expected_warning, match_expr=match) else: func = args[0] if not callable(func): raise TypeError( "{!r} object (type: {}) must be callable".format(func, type(func)) ) with WarningsChecker(expected_warning): return func(*args[1:], **kwargs)
6.1. 自定義失敗時的提示消息
當我們使用一段代碼,期望其觸發告警時,我們可以通過一下方法,自定義失敗時的提示消息,增加其可讀性:
def test_deprecation():
with pytest.warns(Warning) as records:
rsp = api_call_v1()
if not records:
pytest.fail('期望 api_call_v1 觸發一個告警,實際上沒有;')
assert rsp == 200
如果api_call_v1
沒有觸發任何告警,pytest
就會顯示pytest.fail
中自定義的提示消息;
7. recwarn fixture
上一章的最后,我們通過接收pytest.warns()
的返回值來記錄捕獲到的所有告警;在這一章,我們可以通過recwarn
來實現同樣的功能;
recwarn
是一個用例級別的fixture
,它可以記錄用例產生的所有的告警;
同樣,重寫之前的例子來說明:
import re
def test_deprecation(recwarn):
api_call_v1()
assert len(recwarn) == 1
w = recwarn.pop() # 不指定告警類型的話,默認彈出最先捕獲的告警
assert issubclass(w.category, (DeprecationWarning, PendingDeprecationWarning))
assert re.search(r'v1版本已廢棄', w.message.args[0])
recwarn
和之前pytest.warns()
返回值一樣,都是一個WarningsRecorder
的實例;
8. pytest
自定義的告警類型
pytest
本身封裝了一些告警的類型,並作為公共接口以供用戶使用;
下面列舉了一些常見的內部告警:
告警 | 父類 | 描述 |
---|---|---|
PytestWarning |
UserWarning |
所有告警的父類; |
PytestCollectionWarning |
PytestWarning |
不能夠收集某個模塊中的用例; |
PytestConfigWarning |
PytestWarning |
配置錯誤; |
PytestUnknownMarkWarning |
PytestWarning |
使用了未知的標記; |
更多的內部告警可以查看:https://docs.pytest.org/en/5.1.3/warnings.html#pytest.PytestWarning