Pytest學習筆記3-fixture

前言

個人認為,fixture是pytest最精髓的地方,也是學習pytest必會的知識點。

fixture用途

  • 用於執行測試前後的初始化操作,比如打開瀏覽器、準備測試數據、清除之前的測試數據等等
  • 用於測試用例的前置條件,比如UI自動化的登錄操作,讀取config參數等
  • 用於測試用例之間的參數和數據傳遞

fixture優勢

firture相對於unittest中的setup和teardown來說應該有以下幾點優勢

  • 命名方式更加的靈活,不局限於setup和teardown
  • conftest.py 配置里可以實現數據共享,不需要import就能自動找到一些配置
  • scope=”module” 可以實現多個.py跨文件共享前置, 每一個.py文件調用一次
  • scope=”session” 以實現多個.py跨文件使用一個session來完成多個用例

fixture語法

fixture(callable_or_scope=None, *args, scope="function", params=None, autouse=False, ids=None, name=None)
  • scope:fixture的作用域,默認為function
  • autouse:默認為False,表示需要用例手動調用該fixture;當為True時,表示所有作用域內的測試用例都會自動調用該fixture
  • name:裝飾器的名稱,同一模組的fixture相互調用建議使用不同的name

fixture定義

fixture通過@pytest.fixture()裝飾器裝飾一個函數,這個函數就是一個fixture,實例如下:

# test_fixture.py

import pytest


@pytest.fixture()
def fixtureDemo():
    return "一個fixture"


def test_fixture(fixtureDemo):
    print("測試用例執行時調用了{}".format(fixtureDemo))


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

執行結果如下:

image-20210622175407924

fixture調用

調用fixture有三種方式

  • fixture名字作為用例函數的參數
  • 使用@pytest.mark.usefixtures(‘fixture名稱’)裝飾器
  • 使用autouse參數

fixture名字作為用例函數的參數

將fixture名稱作為參數傳入測試用例,如果fixture有返回值,那麼測試用例將會接收返回值

舉個🌰:

import pytest


@pytest.fixture()
def fixtureDemo():
    return '打開瀏覽器'


def test_fixture(fixtureDemo):
    print('執行測試用例的時候,先{}'.format(fixtureDemo))


class TestFixture(object):
    def test_fixture_class(self, fixtureDemo):
        print('在類中執行測試用例的時候,先 "{}"'.format(fixtureDemo))

執行結果如下:

image-20210622180857595

使用@pytest.mark.usefixtures(‘fixture名稱’)裝飾器

每個函數或者類前使用@pytest.mark.usefixtures(‘fixture名稱’)裝飾器裝飾

舉個🌰:

import pytest


@pytest.fixture()
def fixtureDemo():
    print('執行fixture')


@pytest.mark.usefixtures('fixtureDemo')
def test_fixture():
    print('執行測試用例')


@pytest.mark.usefixtures('fixtureDemo')
class TestFixture(object):
    def test_fixture_class(self):
        print('在類中執行測試用例')

執行結果如下:

image-20210622220259171

使用autouse參數

指定fixture的參數autouse=True,這樣同一作用域的每個測試用例會自動調用fixture,autouse為False時,需要手動調用fixture

舉個🌰:

import pytest


@pytest.fixture(autouse=True)
def fixtureDemo():
    print('執行fixture')



def test_fixture():
    print('執行測試用例')


class TestFixture(object):
    def test_fixture_class(self):
        print('在類中執行測試用例')

執行結果如下:

image-20210622220821598

三種方式的區別

以上便是fixture的三種調用方式,那麼這三種方式有何不同呢,觀察🌰,可以看出:

如果在測試用例中需要使用到fixture中返回的參數,就只能使用第一種調用方式了,因為fixture中返回的數據是默認在fixture名字中存儲的。

fixture作用範圍

fixture參數中的scope參數可以控制fixture的作用範圍,scope參數可以是session, module,class,function, 默認為function

  • session 會話級別:是多個文件調用一次,可以跨.py文件調用,每個.py文件就是module(通常配合conftest.py文件使用);
  • module 模組級別:模組里所有的用例執行前執行一次module級別的fixture;
  • class 類級別 :每個類執行前都會執行一次class級別的fixture;
  • function 函數級別:每個測試用例執行前都會執行一次function級別的fixture(前面的🌰)

說明:當 fixture 有返回值時,pytest 會把返回值快取起來,如果 fixture 在指定的作用域內被多次調用,只有第一次調用會真正的被執行,後續調用會使用被快取起來的返回值,而不是再執行一遍;

下面我們通過實例來對fixture的作用範圍進行了解

function級別

每個測試用例之前運行一次

舉個🌰:

import pytest


@pytest.fixture()
def fixtureDemo():
    return "fixture"


def test_01(fixtureDemo):
    print("測試用例1執行時調用了{}".format(fixtureDemo))

def test_02(fixtureDemo):
    print("測試用例2執行時調用了{}".format(fixtureDemo))

運行結果如下:

image-20210623000220362

class級別

如果一個class裡面有多個用例,都調用了此fixture,那麼fixture只在此class里所有用例開始前執行一次

舉個🌰:

import pytest


@pytest.fixture(scope="class")
def fixtureDemo():
    print('執行fixture')


@pytest.mark.usefixtures('fixtureDemo')
class TestFixture(object):
    def test_01(self):
        print('在類中執行測試用例1')

    def test_02(self):
        print('在類中執行測試用例2')

運行結果如下:

image-20210623001002710

module級別

在當前.py腳本裡面所有用例開始前只執行一次

舉個🌰:

import pytest


@pytest.fixture(scope="module")
def fixtureDemo():
    print('執行fixture')


@pytest.mark.usefixtures('fixtureDemo')
def test_01():
    print("執行測試用例1")


@pytest.mark.usefixtures('fixtureDemo')
class TestFixture(object):
    def test_02(self):
        print('在類中執行測試用例2')

    def test_03(self):
        print('在類中執行測試用例3')

運行結果如下:

image-20210623003645546

session級別

session級別是可以跨模組調用的,如果多個模組下的用例只需調用一次fixture,可以設置scope=”session”,並且寫到conftest.py文件里。

conftest.py作用域:放到項目的根目錄下就可以全局調用了,如果放到某個package下,那就在該package內有效。

conftest.py的fixture調用方式,無需導入,直接使用

舉個🌰:

先新建一個conftest.py文件

import pytest


@pytest.fixture()
def fixturedemo():
    print("執行fixture")

test_01.py

def test_01(fixturedemo):
    print('執行測試用例1')

test_02.py

def test_02(fixturedemo):
    print('執行測試用例2')

在當前目錄中使用命令 pytest -v -s 執行,結果如下:

image-20210623143540941

可以看到,在執行不同模組中的測試用例前,都調用了fixture

案例說明

看完有點懵?我們再用一個🌰來看看

在3個測試方法中同時調用4個級別的fixture來看看效果

先新建一個conftest.py文件

import pytest


# 作用域 function
@pytest.fixture(scope='function')
def fix_func():
    print('方法級:fix_func')


# 作用域 class
@pytest.fixture(scope='class')
def fix_class():
    print('類級:fix_class')


# 作用域 module
@pytest.fixture(scope='module')
def fix_module():
    print('模組級:fix_module')


# 作用域 session
@pytest.fixture(scope='session')
def fix_session():
    print('會話級:fix_session')

test_demo.py

import pytest


class TestClass_1:
    """測試類1"""

    @pytest.mark.usefixtures('fix_func')
    @pytest.mark.usefixtures('fix_class')
    @pytest.mark.usefixtures('fix_module')
    @pytest.mark.usefixtures('fix_session')
    def test_func_1(self):
        """測試方法1"""
        print("測試方法1")

    @pytest.mark.usefixtures('fix_func')
    @pytest.mark.usefixtures('fix_class')
    @pytest.mark.usefixtures('fix_module')
    @pytest.mark.usefixtures('fix_session')
    def test_func_2(self):
        """測試方法2"""
        print("測試方法2")


class TestClass_2:
    """測試類2"""

    @pytest.mark.usefixtures('fix_func')
    @pytest.mark.usefixtures('fix_class')
    @pytest.mark.usefixtures('fix_module')
    @pytest.mark.usefixtures('fix_session')
    def test_func_3(self):
        """測試方法3"""
        print("測試方法3")

執行結果如下:

image-20210623153946395

觀察運行結果,我們可以發現:

  • test_func_1 的調用全部被執行了;

  • test_func_2 的調用只有 function 級的被執行,因為 test_func_2 和 test_func_1 同屬於同一個 session、module、class,所以這三個都不執行,只有 function 執行;

  • test_func_3 的調用執行了類級和方法級,因為 test_func_3 屬於 另外一個類,所以 class 級會被再次調用

fixture關鍵字yield

前面講的是在用例前加前置條件,相當於setup,那麼類似teardown的功能如何使用fixture實現呢,我們可以使用yield關鍵字

yield關鍵字的作用其實和函數中的return關鍵字差不多,可以返回數據給調用者,唯一的不同是當函數執行遇到yield時,會停止執行,然後執行調用處的函數,調用的函數執行完後會繼續執行yield關鍵後面的程式碼

yield實現teardown

舉個🌰:

import pytest


@pytest.fixture(scope="module")
def fixtureDemo():
    print('執行fixture')
    yield
    print('執行teardown操作')


def test_01(fixtureDemo):
    print('執行測試用例1')


def test_02(fixtureDemo):
    print('執行測試用例2')

運行結果如下:

image-20210623151242016

yield遇到異常

如果多個用例運行時,有一個用例出現異常,不會影響yield後面的程式碼執行 , 運行結果互不影響,當全部用例執行完之後,yield後面的程式碼會正常執行

舉個🌰:

import pytest


@pytest.fixture(scope="module")
def fixtureDemo():
    print('執行fixture')
    yield
    print('執行teardown操作')


def test_01(fixtureDemo):
    print('執行測試用例1')
    raise NameError # 模擬異常


def test_02(fixtureDemo):
    print('執行測試用例2')

運行結果如下:

image-20210623151912711