pytest測試框架系列 - Pytest Fixture和conftest.py還能這樣使用?

前言

Fixture是pytest的非常核心功能之一,在不改變被裝飾函數的前提下對函數進行功能增強,經常用于測試用例前置和后置工作。與setup/teardown類似,但更強大靈活。

fixture的優勢

  • fixture命名方式靈活,不局限于 setup 和teardown 那幾個命名規則
  • conftest.py 配置里可以實現數據共享,能夠自動搜索需要的fixture
  • fixture 配置不同的參數可以輕松實現跨文件、session會話共享

fixture工作原理

  • 在普通函數上使用@Pytest.fixture()裝飾器,聲明函數為一個fixture函數
  • 如果測試用例函數的參數列表中存在fixture的函數名或者使用@pytest.mark.usefixtures(fixture_name)
  • 在測試執行階段,pytest執行用例之前執行fixture函數功能
  • 如果fixture裝飾的函數無返回值,相當于用例前置操作,否則相當于傳參
  • 如果fixture設置了后置操作,則用例執行完成后進行執行

fixture搜索順序

  • 第一步:優先搜索測試所在的模塊
  • 第二步:搜索模塊同一文件路徑下的conftest.py
  • 第三步:找不到再搜索上一層的conftest.py,直到項目根目錄

fixture 函數說明

def fixture(
    fixture_function: Optional[_FixtureFunction] = None,
    *,
    scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = "function",
    params: Optional[Iterable[object]] = None,
    autouse: bool = False,
    ids: Optional[
        Union[
            Iterable[Union[None, str, float, int, bool]],
            Callable[[Any], Optional[object]],
        ]
    ] = None,
    name: Optional[str] = None,
) -> Union[FixtureFunctionMarker, _FixtureFunction]:
    """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.

    :param scope:
        The scope for which this fixture is shared; one of ``"function"``
        (default), ``"class"``, ``"module"``, ``"package"`` or ``"session"``.

        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.

    :param 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``.

    :param autouse:
        If True, the fixture func is activated for all tests that can see it.
        If False (the default), an explicit reference is needed to activate
        the fixture.

    :param 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.

    :param 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>')``.
    """
    """
    翻譯:
     可以使用此裝飾器(帶或不帶參數)來定義fixture功能。 
     
     fixture功能的名稱可以在后面使用引用它會在運行測試之前調用它:test模塊或類可以使用
     pytest.mark.usefixtures(fixturename標記)。 
     
     測試功能可以直接使用fixture名稱作為輸入參數,在這種情況下,夾具實例從fixture返回功能將被注入。
     
     fixture可以使用``return`` or``yield`` 語句返回測試函數所需要的參數值,在``yield``后面的代碼塊
     不管測試的執行結果怎么樣都會執行
     
    :arg scope: scope 有四個級別參數 "function" (默認), "class", "module" or "session".

    :arg params: 一個可選的參數列表,它將導致多個參數調用fixture功能和所有測試使用它

    :arg autouse:  如果為True,自動執行fixture函數,所有函數都可以使用。 如果為False(默認值)
    則手動使用fixture才能調用

    :arg ids: 每個字符串id的列表,每個字符串對應于params 這樣他們就是測試ID的一部分。 
    如果沒有提供ID它們將從params自動生成

    :arg name:   fixture的名稱。 默認為裝飾函數的名稱。 如果設置了name的值,在使用fixture時需要
    使用設置的name,否則無法識別
    """

fixture參數詳解

使用語法:

@pytest.fixture(scope = "function",params=None,autouse=False,ids=None,name=None)
def login():
    print("我是login函數")

scope 參數

  • 說明:可以認為是fixture的作用域,默認:function,還有class、module、package、session四個
  • 區別:
取值 范圍 說明
function 函數級 每一個函數或方法都會調用
class 類級 每個測試類只運行一次
module 模塊級 每一個.py文件調用一次
session 會話級 每次會話只需要運行一次,一般用于打開瀏覽器、啟動APP、登錄等操作

關注點

  • fixture可以給函數和類使用
  • fixture通過測試用例參數方式使用,這樣fixture的返回值可以通過fixture名稱作為參數傳遞給測試用例,也可以使用@pytest.mark.usefixtures("login")方式,但無法給測試用例傳遞fixture的返回值
  • fixture函數內可以調用其他fixture函數,必須要通過函數參數方式
  • 非測試用例的函數無法使用fixture
    作用范圍示例:

scope = "function"

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 14:39
# @Author  : king
# @File    :test_fixture.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【測試之路筆記】
import pytest

@pytest.fixture(scope="function")
def login():
    print("我是login函數。。")
    return "king"

# fixture內調用其他fixture
@pytest.fixture(scope="function")
def login_01(login):
    print("我是調用了 login的fixture函數")

# 通過參數方式使用fixture
def test_01(login):
    print("我是 test_01 測試用例\n")

# 通過@pytest.mark.usefixtures("login")調用fixture
@pytest.mark.usefixtures("login")
def test_02():
    print("我是 test_02 測試用例\n")

# 通過參數方式使用調用其他fixture的fixture
def test_03(login_01):
    print("我是 test_03 測試用例\n")

# 給類使用fixture
@pytest.mark.usefixtures("login")
class TestFixture:
    def test_one(self):
        print("我是類里面的 test_one 測試用例")

    def test_two(self):
        print("我是類里面的 test_two 測試用例")

# 非test開頭函數調用fixture
def reg_01(login):
    print("我是reg函數\n")

# 非test開頭函數使用@pytest.mark.usefixtures("login")調用fixture
@pytest.mark.usefixtures("login")
def reg_02():
    print("我是reg函數\n")

if __name__ == '__main__':
    pytest.main(["-s", "test_fixture.py"])


執行結果為:


在這里插入圖片描述

scope = "class"

示例:

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 15:35
# @Author  : king
# @File    :test_fixture_class.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【測試之路筆記】
import pytest

@pytest.fixture(scope="class")
def login():
    print("我是login函數。。")
    return "king"

# 通過參數方式使用fixture
def test_01(login):
    print("我是 test_01 測試用例\n")

# 通過參數方式使用fixture
def test_02(login):
    print("我是 test_02 測試用例\n")

@pytest.mark.usefixtures("login")
class TestFixture:
    def test_one(self):
        print("我是類里面的 test_one 測試用例")

    def test_two(self):
        print("我是類里面的 test_two 測試用例")

if __name__ == '__main__':
    pytest.main(["-s", "test_fixture_class.py"])

執行結果:


在這里插入圖片描述

scope = "module"

示例:

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 16:52
# @Author  : king
# @File    :test_fixture_module.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【測試之路筆記】
import pytest

@pytest.fixture(scope="module")
def login():
    print("我是login函數。。")
    return "king"

# 通過參數方式使用fixture
def test_01(login):
    print("我是 test_01 測試用例\n")

# 通過參數方式使用fixture
def test_02(login):
    print("我是 test_02 測試用例\n")

@pytest.mark.usefixtures("login")
class TestFixture:
    def test_one(self):
        print("我是類里面的 test_one 測試用例")

    def test_two(self):
        print("我是類里面的 test_two 測試用例")

if __name__ == '__main__':
    pytest.main(["-s", "test_fixture_module.py"])

執行結果為:


在這里插入圖片描述

scope = "session"

  • 注意:session代表會話級,就是從啟動測試到結束測試,看作為一次session會話,scope = "session"使用到后面conftest.py詳細講解

params 參數

  • fixture的可選形參列表,支持列表傳入
  • params 參數包含幾個,調用時就會執行幾次
  • 可與參數ids一起使用,作為每個參數的標識,詳見ids
  • 需要使用時,參數調用寫法固定為:Request.param
    示例:
# _*_coding:utf-8 _*_
# @Time  :2021/7/3 17:13
# @Author  : king
# @File    :test_fixture_params.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【測試之路筆記】
import pytest

@pytest.fixture(params=[1, 2, 3, 4, 5])
def login(request):
    print("我是login函數。。")
    return request.param

# 通過參數方式使用fixture
def test_01(login):
    print("我是 test_01 測試用例-params- {}\n".format(login))

if __name__ == '__main__':
    pytest.main(["-s", "test_fixture_params.py"])

執行結果:


在這里插入圖片描述

autouse 參數

  • 默認False
  • 如果設置為True,則每個測試函數都會自動調用該fixture,無需傳入fixture函數名,作用范圍跟著scope走(注意使用)
    示例:
# _*_coding:utf-8 _*_
# @Time  :2021/7/3 17:25
# @Author  : king
# @File    :test_fixture_autouse.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【測試之路筆記】
import pytest

@pytest.fixture(autouse=True)
def login():
    print("我是login函數。。")

# 通過參數方式使用fixture
def test_01():
    print("我是 test_01 測試用例\n")

if __name__ == '__main__':
    pytest.main(["-s", "test_fixture_autouse.py"])

執行結果:


在這里插入圖片描述

ids 參數

  • 用例標題,需要與params配合使用,一對一關系

未配置ids時:

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 17:29
# @Author  : king
# @File    :test_fixture_ids.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【測試之路筆記】
import pytest

@pytest.fixture(params=[1, 2, 3])
def login(request):
    return request.param

# 通過參數方式使用fixture
def test_01(login):
    print("我是 test_01 測試用例\n")

if __name__ == '__main__':
    pytest.main(["-s", "test_fixture_ids.py"])

執行結果:
[圖片上傳失敗...(image-7d1ae1-1625479261110)]

配置了ids的示例:

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 17:29
# @Author  : king
# @File    :test_fixture_ids.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【測試之路筆記】
import pytest

@pytest.fixture(params=[1, 2, 3], ids=["case01", "case02", "case03"])
def login(request):
    return request.param

# 通過參數方式使用fixture
def test_01(login):
    print("我是 test_01 測試用例\n")

if __name__ == '__main__':
    pytest.main(["-s", "test_fixture_ids.py"])

執行結果:


在這里插入圖片描述

問題:

當我們多個測試用例文件(test_*.py)的所有用例都需要用登錄、打開瀏覽器、啟動APP等功能來作為前置操作,那就不能把登錄、打開瀏覽器、啟動APP功能寫到某個用例文件,如何解決呢?

解決方案:引入conftest.py文件,為了解決上述問題,單獨管理一些全局的fixture

conftest.py使用

說明:

  • pytest會默認讀取conftest.py里面的所有fixture
  • conftest.py 文件名稱是固定的,不能隨意改動
  • conftest.py只對同一個package下的所有測試用例生效,并且有init.py文件
  • 不同目錄可以有自己的conftest.py,一個項目中可以有多個conftest.py
  • pytest會自動查找項目中的conftest.py文件,逐層往上查找

示例:

  • 項目目錄
case
│  conftest.py
│  __init__.py
│
├─case01
│      conftest.py
│      test_01.py
│      __init__.py
│
└─case02
        conftest.py
        test_02.py
        __init__.py

case/conftest.py

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 18:04
# @Author  : king
# @File    :conftest.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【測試之路筆記】
import pytest

@pytest.fixture(scope="session")
def login():
    print("我是case目錄的login")

@pytest.fixture(scope="session")
def open_browser():
    print("我是case目錄的 open_browser")

case01/conftest.py

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 18:06
# @Author  : king
# @File    :conftest.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【測試之路筆記】
import pytest

@pytest.fixture(scope="session")
def login():
    print("我是case01目錄的login")

case01/conftest.py

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 18:04
# @Author  : king
# @File    :test_01.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【測試之路筆記】

def test_01(login):
    print("我是case01 里面test_01 測試用例")

def test_02(open_browser):
    print("我是case01 里面test_02 測試用例")

case02/conftest.py

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 18:07
# @Author  : king
# @File    :conftest.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【測試之路筆記】
import pytest

@pytest.fixture(scope="session")
def _login():
    print("我是case02 目錄的login")

case02/test_02.py

# _*_coding:utf-8 _*_
# @Time  :2021/7/3 18:04
# @Author  : king
# @File    :test_02.py
# @Software  :PyCharm
# @blog     :https://blog.csdn.net/u010454117
# @WeChat Official Account: 【測試之路筆記】

def test_02(login):
    print("我是case02的 test_02 測試用例")

在case目錄下,執行 pytest -s

在這里插入圖片描述

注意點

  • 外層的fixture不能調用內層的fixture
  • 不同包下面的fixture只能當前包使用,不能被其他包使用,例如case01下面的fixture不能被case02的測試函數使用

以上為內容純屬個人理解,如有不足,歡迎各位大神指正,轉載請注明出處!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容