pytest 精通 fixture
首先放一句“狠話”。
如果你不會fixture,那麼你最好別說自己會pytest。
(只是為了烘托主題哈,手上的磚頭可以放下了,手動滑稽)
fixture是什麼
看看原始碼
def fixture(
callable_or_scope=None,
*args,
scope="function",
params=None,
autouse=False,
ids=None,
name=None
):
"""Decorator to mark a fixture factory function.
This decorator can be used, with or without parameters, to define a
fixture function.
The name of the fixture function can later be referenced to cause its
invocation ahead of running tests: test
modules or classes can use the ``pytest.mark.usefixtures(fixturename)``
marker.
Test functions can directly use fixture names as input
arguments in which case the fixture instance returned from the fixture
function will be injected.
Fixtures can provide their values to test functions using ``return`` or ``yield``
statements. When using ``yield`` the code block after the ``yield`` statement is executed
as teardown code regardless of the test outcome, and must yield exactly once.
:arg scope: the scope for which this fixture is shared, one of
``"function"`` (default), ``"class"``, ``"module"``,
``"package"`` or ``"session"`` (``"package"`` is considered **experimental**
at this time).
This parameter may also be a callable which receives ``(fixture_name, config)``
as parameters, and must return a ``str`` with one of the values mentioned above.
See :ref:`dynamic scope` in the docs for more information.
:arg params: an optional list of parameters which will cause multiple
invocations of the fixture function and all of the tests
using it.
The current parameter is available in ``request.param``.
:arg autouse: if True, the fixture func is activated for all tests that
can see it. If False (the default) then an explicit
reference is needed to activate the fixture.
:arg ids: list of string ids each corresponding to the params
so that they are part of the test id. If no ids are provided
they will be generated automatically from the params.
:arg name: the name of the fixture. This defaults to the name of the
decorated function. If a fixture is used in the same module in
which it is defined, the function name of the fixture will be
shadowed by the function arg that requests the fixture; one way
to resolve this is to name the decorated function
``fixture_<fixturename>`` and then use
``@pytest.fixture(name='<fixturename>')``.
"""
if params is not None:
params = list(params)
fixture_function, arguments = _parse_fixture_args(
callable_or_scope,
*args,
scope=scope,
params=params,
autouse=autouse,
ids=ids,
name=name,
)
scope = arguments.get("scope")
params = arguments.get("params")
autouse = arguments.get("autouse")
ids = arguments.get("ids")
name = arguments.get("name")
if fixture_function and params is None and autouse is False:
# direct decoration
return FixtureFunctionMarker(scope, params, autouse, name=name)(
fixture_function
)
return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
總結一下
【定義】
- fixture是一個函式,在函式上新增註解
@pytest.fixture
來定義 - 定義在conftest.py中,無需import就可以呼叫
- 定義在其他檔案中,import後也可以呼叫
- 定義在相同檔案中,直接呼叫
【使用】
- 第一種使用方式是
@pytest.mark.usefixtures(fixturename)
(如果修飾TestClass能對類中所有方法生效) - 第二種使用方式是作為函式引數
- 第三種使用方式是autouse(不需要顯示呼叫,自動執行)
conftest.py
我們常常會把fixture定義到conftest.py檔案中。
這是pytest固定的檔名,不能自定義。
必須放在package下,也就是目錄中有__init__.py。
conftest.py中的fixture可以用在當前目錄及其子目錄,不需要import,pytest會自動找。
可以建立多個conftest.py檔案,同名fixture查詢時會優先用最近的。
依賴注入
fixture實現了依賴注入。依賴注入是控制反轉(IoC, Inversion of Control)的一種技術形式。
簡單理解一下什麼是依賴注入和控制反轉
實在是妙啊!我們可以在不修改當前函式程式碼邏輯的情況下,通過fixture來額外新增一些處理。
入門示例
# content of ./test_smtpsimple.py
import smtplib
import pytest
@pytest.fixture
def smtp_connection():
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert 0 # for demo purposes
執行後程式處理邏輯
- pytest找到test_開頭的函式,發現需要名字為smtp_connection的fixture,就去找
- 找到之後,呼叫smtp_connection(),return了SMTP的例項
- 呼叫test_ehlo() ,入參
smtp_connection
等於fixture return的值
如果想看檔案定義了哪些fixture,可以使用命令,_字首的需要跟上-v
pytest --fixtures test_simplefactory.py
fixture scope & order
既然到處都可以定義fixture,那多了豈不就亂了?
pytest規定了fxture的執行範圍和執行順序。
fixture的範圍通過引數scope來指定
@pytest.fixture(scope="module")
預設是function,可以選擇function, class, module, package 或 session。
fixture都是在test第一次呼叫時建立,根據scope的不同有不同的執行和銷燬方式
- function 每個函式執行一次,函式結束時銷燬
- class 每個類執行一次,類結束時銷燬
- module 每個模組執行一次,模組結束時銷燬
- package 每個包執行一次,包結束時銷燬
- session 每個會話執行一次,會話結束時銷燬
fixture的順序優先按scope從大到小,session > package > module > class > function。
如果scope相同,就按test呼叫先後順序,以及fixture之間的依賴關係。
autouse的fixture會優先於相同scope的其他fixture。
示例
import pytest
# fixtures documentation order example
order = []
@pytest.fixture(scope="session")
def s1():
order.append("s1")
@pytest.fixture(scope="module")
def m1():
order.append("m1")
@pytest.fixture
def f1(f3):
order.append("f1")
@pytest.fixture
def f3():
order.append("f3")
@pytest.fixture(autouse=True)
def a1():
order.append("a1")
@pytest.fixture
def f2():
order.append("f2")
def test_order(f1, m1, f2, s1):
assert order == ["s1", "m1", "a1", "f3", "f1", "f2"]
雖然test_order()是按f1, m1, f2, s1呼叫的,但是結果卻不是按這個順序
- s1 scope為session
- m1 scope為module
- a1 autouse,預設function,後於session、module,先於function其他fixture
- f3 被f1依賴
- f1 test_order()引數列表第1個
- f2 test_order()引數列表第3個
fixture巢狀
fixture裝飾的是函式,那函式也有入參咯。
fixture裝飾的函式入參,只能是其他fixture。
示例,f1依賴f3,如果不定義f3的話,執行會報錯fixture 'f3' not found
@pytest.fixture
def f1(f3):
order.append("f1")
@pytest.fixture
def f3():
order.append("f3")
def test_order(f1):
pass
從test傳值給fixture
藉助request,可以把test中的值傳遞給fixture。
示例1,smtp_connection可以使用module中的smtpserver
# content of conftest.py
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp_connection(request):
server = getattr(request.module, "smtpserver", "smtp.gmail.com")
smtp_connection = smtplib.SMTP(server, 587, timeout=5)
yield smtp_connection
print("finalizing {} ({})".format(smtp_connection, server))
smtp_connection.close()
# content of test_anothersmtp.py
smtpserver = "mail.python.org" # will be read by smtp fixture
def test_showhelo(smtp_connection):
assert 0, smtp_connection.helo()
示例2,結合request+mark,把fixt_data從test_fixt傳值給了fixt
import pytest
@pytest.fixture
def fixt(request):
marker = request.node.get_closest_marker("fixt_data")
if marker is None:
# Handle missing marker in some way...
data = None
else:
data = marker.args[0]
# Do something with the data
return data
@pytest.mark.fixt_data(42)
def test_fixt(fixt):
assert fixt == 42
fixture setup / teardown
其他測試框架unittest/testng,都定義了setup和teardown函式/方法,用來測試前初始化和測試後清理。
pytest也有,不過是相容unittest等弄的,不推薦!
from loguru import logger
def setup():
logger.info("setup")
def teardown():
logger.info("teardown")
def test():
pass
建議使用fixture。
setup,fixture可以定義autouse來實現初始化。
@pytest.fixture(autouse=True)
autouse的fixture不需要呼叫,會自己執行,和test放到相同scope,就能實現setup的效果。
autouse使用說明
- autouse遵循scope的規則,scope="session"整個會話只會執行1次,其他同理
- autouse定義在module中,module中的所有function都會用它(如果scope="module",只執行1次,如果scope="function",會執行多次)
- autouse定義在conftest.py,conftest覆蓋的test都會用它
- autouse定義在plugin中,安裝plugin的test都會用它
- 在使用autouse時需要同時注意scope和定義位置
示例,transact預設scope是function,會在每個test函式執行前自動執行
# content of test_db_transact.py
import pytest
class DB:
def __init__(self):
self.intransaction = []
def begin(self, name):
self.intransaction.append(name)
def rollback(self):
self.intransaction.pop()
@pytest.fixture(scope="module")
def db():
return DB()
class TestClass:
@pytest.fixture(autouse=True)
def transact(self, request, db):
db.begin(request.function.__name__)
yield
db.rollback()
def test_method1(self, db):
assert db.intransaction == ["test_method1"]
def test_method2(self, db):
assert db.intransaction == ["test_method2"]
這個例子不用autouse,用conftest.py也能實現
# content of conftest.py
@pytest.fixture
def transact(request, db):
db.begin()
yield
db.rollback()
@pytest.mark.usefixtures("transact")
class TestClass:
def test_method1(self):
...
teardown,可以在fixture中使用yield關鍵字來實現清理。
示例,scope為module,在module結束時,會執行yield後面的print()和smtp_connection.close()
# content of conftest.py
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp_connection():
smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
yield smtp_connection # provide the fixture value
print("teardown smtp")
smtp_connection.close()
可以使用with關鍵字進一步簡化,with會自動清理上下文,執行smtp_connection.close()
# content of test_yield2.py
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp_connection():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
yield smtp_connection # provide the fixture value
fixture引數化
後續會專門講“pytest引數化”,這裡就先跳過,請各位見諒啦。
因為我覺得想用pytest做引數化,一定是先到引數化的文章裡面找,而不是到fixture。
把這部分放到引數化,更便於以後檢索。
簡要回顧
本文開頭通過原始碼介紹了fixture是什麼,並簡單總結定義和用法。然後對依賴注入進行了解釋,以更好理解fixture技術的原理。入門示例給出了官網的例子,以此展開講了範圍、順序、巢狀、傳值,以及初始化和清理的知識。
如果遇到問題,歡迎溝通討論。
更多實踐內容,請關注後續篇章《tep最佳實踐》。
參考資料
https://en.wikipedia.org/wiki/Dependency_injection
相關文章
- pytest封神之路第三步 精通fixture
- pytest fixture 使用
- Pytest測試框架(三):pytest fixture 用法框架
- pytest(6)-Fixture(韌體)
- python @pytest.fixture示例及用法Python
- Pytest學習(四) - fixture的使用
- pytest進階之fixture函式函式
- pytest 的 fixture 作用域問題
- 7.pytest 強大的 fixture (中)
- 8.pytest 強大的 fixture (下)
- 6.pytest 強大的 fixture (上)
- 『德不孤』Pytest框架 — 12、Pytest中Fixture裝飾器(二)框架
- 『德不孤』Pytest框架 — 11、Pytest中Fixture裝飾器(一)框架
- 『德不孤』Pytest框架 — 13、Pytest中Fixture裝飾器(三)框架
- 【pytest系列】- fixture測試夾具詳解
- 【pytest】fixture 與 setup, teardown 的優先順序
- pytest介面測試之fixture傳引數request
- pytest:通過scope控制fixture的作用範圍
- 關於 pytest 外掛 pytest-rerunfailures 和 fixture 的相容性問題AI
- 自動化測試 —— Pytest fixture及conftest詳解
- pytest 中,pytest.ini 檔案為什麼會對 fixture 產生影響
- 【pytest】使用parametrize將引數化變數傳遞到fixture變數
- pytest(12)-Allure常用特性allure.attach、allure.step、fixture、environment、categoriesGo
- 這次,我掌握了 pytest 中 fixture 的使用及 pytest 執行測試的載入順序
- 【pytest官方文件】解讀fixtures - 10. fixture有效性、跨檔案共享fixtures
- 【pytest官方文件】解讀fixtures - 11. fixture的執行順序,3要素詳解(長文預警)
- Pytest系列(30)- 使用 pytest-xdist 分散式外掛,如何保證 scope=session 的 fixture 在多程式執行情況下仍然能只執行一次分散式Session
- 請教一個 pytest 中 fixture 相關問題:將 fixture 中寫入 allure 附件的程式碼提出來定義成自定義方法後,就無法按預期寫入 allure 附件了
- Pytest 指令碼打包成 exe 後,無法呼叫 testcase,無法獲取到 fixture,希望大佬幫忙給以指點,多謝!指令碼
- pytes中fixture的scope: 決定可以在什麼範圍內共享fixture
- 七、fixture 的用例管理
- pytest配置檔案pytest.ini
- pytest(2)-pytest-html測試報告HTML測試報告
- 『德不孤』Pytest框架 — 8、Pytest斷言框架
- pytest-rerunfailures/pytest-repeat重跑外掛AI
- 從pytest原始碼的角度分析pytest工作原理原始碼
- 『德不孤』Pytest框架 — 15、Pytest引數化框架
- 『德不孤』Pytest框架 — 9、Pytest測試報告框架測試報告