pytest 的前置與后置處理
Pytest貼心的提供了類似setup、teardown的方法,并且還超過四個(gè),一共有十種
-
模塊級別:setup_module、teardown_module
-
函數(shù)級別:setup_function、teardown_function,不在類中的方法
-
類級別:setup_class、teardown_class
-
方法級別:setup_method、teardown_method
-
方法細(xì)化級別:setup、teardown
setup和teardown的詳細(xì)使用
代碼
#!/usr/bin/env Python/ target=_blank class=infotextkey>Python
# -*- coding: utf-8 -*-
import pytest
def setup_module():
print("=====整個(gè).py模塊開始前只執(zhí)行一次:登錄=====")
def teardown_module():
print("=====整個(gè).py模塊結(jié)束后只執(zhí)行一次:數(shù)據(jù)清理=====")
def setup_function():
print("===每個(gè)函數(shù)級別用例開始前都執(zhí)行setup_function===")
def teardown_function():
print("===每個(gè)函數(shù)級別用例結(jié)束后都執(zhí)行teardown_function====")
def test_one():
print("1")
def test_two():
print("2")
class TestCase():
def setup_class(self):
print("====整個(gè)測試類開始前只執(zhí)行一次setup_class====")
def teardown_class(self):
print("====整個(gè)測試類結(jié)束后只執(zhí)行一次teardown_class====")
def setup_method(self):
print("==測試類里面每個(gè)用例執(zhí)行前都會執(zhí)行setup_method==")
def teardown_method(self):
print("==測試類里面每個(gè)用例結(jié)束后都會執(zhí)行teardown_method==")
def setup(self):
print("=測試類里面每個(gè)用例執(zhí)行前都會執(zhí)行setup=")
def teardown(self):
print("=測試類里面每個(gè)用例結(jié)束后都會執(zhí)行teardown=")
def test_three(self):
print("3")
def test_four(self):
print("4")
if __name__ == '__mAIn__':
pytest.main(["-q", "-s", "-ra", "testc.py"])
運(yùn)行結(jié)果
C:Userschenyongzhi11.virtualenvsapi-test-project-FfYYNBU1Scriptspython.exe D:/apk_api/api-test-project/debug/debug_pytest.py
=====整個(gè).py模塊開始前只執(zhí)行一次:登錄=====
===每個(gè)函數(shù)級別用例開始前都執(zhí)行setup_function===
1
.===每個(gè)函數(shù)級別用例結(jié)束后都執(zhí)行teardown_function====
===每個(gè)函數(shù)級別用例開始前都執(zhí)行setup_function===
2
.===每個(gè)函數(shù)級別用例結(jié)束后都執(zhí)行teardown_function====
====整個(gè)測試類開始前只執(zhí)行一次setup_class====
==測試類里面每個(gè)用例執(zhí)行前都會執(zhí)行setup_method==
3
.==測試類里面每個(gè)用例結(jié)束后都會執(zhí)行teardown_method==
==測試類里面每個(gè)用例執(zhí)行前都會執(zhí)行setup_method==
4
.==測試類里面每個(gè)用例結(jié)束后都會執(zhí)行teardown_method==
====整個(gè)測試類結(jié)束后只執(zhí)行一次teardown_class====
=====整個(gè).py模塊結(jié)束后只執(zhí)行一次:數(shù)據(jù)清理=====
4 passed in 0.33s
進(jìn)程已結(jié)束,退出代碼0
-
模塊級別:模塊級別的初始化、清除分別再整個(gè)模塊的測試用例執(zhí)行錢后執(zhí)行,并且只執(zhí)行一次
-
類級別:類級別的初始化、清除分別再整個(gè)類的測試用例執(zhí)行前后執(zhí)行,并且只執(zhí)行一次
-
方法級別:方法級別的初始化、清除分別在類的每個(gè)測試方法執(zhí)行前后執(zhí)行,并且每個(gè)用例執(zhí)行一次
注:上述都是針對整個(gè)腳本全局生效
fixture的詳細(xì)使用
fixture的優(yōu)勢
-
名命靈活,不局限與setupteardown 的名命
-
coNFTest.py 配置可以實(shí)現(xiàn)數(shù)據(jù)共享,不需要import 就能自動找到fixture
-
scope="module" 實(shí)現(xiàn)多py文件共享前置
-
scope="session" 實(shí)現(xiàn)多個(gè)py文件使用一個(gè)session完成多用例
fixture參數(shù)列表
@pytest.fixture(scope="function", params=None, autouse=False, ids=None, name=None)
def test():
print("fixture初始化的參數(shù)列表")
參數(shù)列表
-
scope:可以理解成fixture的作用域,默認(rèn):function,還有class、module、package、session四個(gè)【常用】
-
autouse:默認(rèn):False,需要用例手動調(diào)用該fixture;如果是True,所有作用域內(nèi)的測試用例都會自動調(diào)用該fixture
-
name:默認(rèn):裝飾器的名稱,同一模塊的fixture相互調(diào)用建議寫個(gè)不同的name
注意
session的作用域:是整個(gè)測試會話,即開始執(zhí)行pytest到結(jié)束測試
測試用例調(diào)用fixture的方式
-
將fixture名稱作為測試用例函數(shù)的輸入?yún)?shù)
-
測試用例加上裝飾器:@pytest.mark.usefixtures(fixture_name)
-
fixture設(shè)置autouse=True
1. 將fixture名稱作為測試用例函數(shù)的輸入?yún)?shù)
import pytest
# 調(diào)用方式一
@pytest.fixture
def login():
print("輸入賬號,密碼先登錄")
def test_s1(login):
print("用例 1:登錄之后其它動作 111")
def test_s2(): # 不傳 login
print("用例 2:不需要登錄,操作 222")
if __name__ == '__main__':
pytest.main(["-s", 'debug_pytest.py'])
結(jié)果
============================= test session starts =============================
platform win32 -- Python 3.9.5, pytest-7.3.1, pluggy-1.0.0
rootdir: D:apk_apiapi-test-projectdebug
plugins: Faker-18.4.0
collected 2 items
debug_pytest.py 輸入賬號,密碼先登錄
用例 1:登錄之后其它動作 111
.用例 2:不需要登錄,操作 222
.
============================== 2 passed in 0.14s ==============================
進(jìn)程已結(jié)束,退出代碼0
方式2:測試用例加上裝飾器:@pytest.mark.usefixtures(fixture_name)
import pytest
# 調(diào)用方式一
@pytest.fixture
def login():
print("輸入賬號,密碼先登錄")
# 調(diào)用方式二
@pytest.fixture
def login2():
print("please輸入賬號,密碼先登錄")
@pytest.mark.usefixtures("login2", "login")
def test_s11():
print("用例 11:登錄之后其它動作 111")
輸出:
============================= test session starts =============================
collecting ... collected 1 item
teat_learn03.py::test_s11 please輸入賬號,密碼先登錄
輸入賬號,密碼先登錄
PASSED [100%]用例 11:登錄之后其它動作 111
============================== 1 passed in 0.01s ==============================
Process finished with exit code 0
注意:
-
在類聲明上面加 @pytest.mark.usefixtures() ,代表這個(gè)類里面所有測試用例都會調(diào)用該fixture
-
可以疊加多個(gè) @pytest.mark.usefixtures() ,先執(zhí)行的放底層,后執(zhí)行的放上層
-
可以傳多個(gè)fixture參數(shù),先執(zhí)行的放前面,后執(zhí)行的放后面
-
如果fixture有返回值,用 @pytest.mark.usefixtures() 是無法獲取到返回值的,必須用傳參的方式(方式一)
-
方式3:fixture設(shè)置autouse=True
-
import pytest
# 調(diào)用方式三
@pytest.fixture(autouse=True)
def login3():
print("====所有作用域內(nèi)的測試用例都會自動調(diào)用該fixture===")
def test_s1(login3):
print("用例 1:登錄之后其它動作 111")
def test_s2(): # 不傳 login
print("用例 2:不需要登錄,操作 222")
# 不是test開頭,加了裝飾器也不會執(zhí)行fixture
@pytest.mark.usefixtures("login3")
def loginss():
print(123)
-
====所有作用域內(nèi)的測試用例都會自動調(diào)用該fixture===
PASSED [ 50%]用例 1:登錄之后其它動作 111
teat_learn03.py::test_s2 ====所有作用域內(nèi)的測試用例都會自動調(diào)用該fixture===
PASSED [100%]用例 2:不需要登錄,操作 222
============================== 2 passed in 0.02s ==============================
-
較高 scope 范圍的fixture(session)在較低 scope 范圍的fixture( function 、 class )之前實(shí)例化【session > package > module > class > function】
-
具有相同作用域的fixture遵循測試函數(shù)中聲明的順序,并遵循fixture之間的依賴關(guān)系【在fixture_A里面依賴的fixture_B優(yōu)先實(shí)例化,然后到fixture_A實(shí)例化】
-
自動使用(autouse=True)的fixture將在顯式使用(傳參或裝飾器)的fixture之前實(shí)例化
import pytest
order = []
@pytest.fixture(scope="session")
def s1():
order.Append("s1")
@pytest.fixture(scope="module")
def m1():
order.append("m1")
@pytest.fixture
def f1(f3, a1):
# 先實(shí)例化f3, 再實(shí)例化a1, 最后實(shí)例化f1
order.append("f1")
assert f3 == 123
@pytest.fixture
def f3():
order.append("f3")
a = 123
yield a
@pytest.fixture
def a1():
order.append("a1")
@pytest.fixture
def f2():
order.append("f2")
def test_order(f1, m1, f2, s1):
# m1、s1在f1后,但因?yàn)閟cope范圍大,所以會優(yōu)先實(shí)例化
assert order == ["s1", "m1", "f3", "a1", "f1", "f2"]
執(zhí)行結(jié)果:斷言成功
關(guān)于fixture的注意點(diǎn)
添加了 @pytest.fixture ,如果fixture還想依賴其他fixture,需要用函數(shù)傳參的方式,不能用 @pytest.mark.usefixtures() 的方式,否則會不生效
@pytest.fixture(scope="session")
def open():
print("===打開瀏覽器===")
@pytest.fixture
# @pytest.mark.usefixtures("open") 不可?。。?!不生效!!!
def login(open):
# 方法級別前置操作setup
print(f"輸入賬號,密碼先登錄{open}")
fixture的后置teardown
用fixture實(shí)現(xiàn)teardown并不是一個(gè)獨(dú)立的函數(shù),而是用 yield 關(guān)鍵字來開啟teardown操作(yield 之前是前置,之后是后置)
import pytest
@pytest.fixture(scope="session")
def open():
# 會話前置操作setup
print("===打開瀏覽器===")
test = "測試變量是否返回"
yield test
# 會話后置操作teardown
print("==關(guān)閉瀏覽器==")
@pytest.fixture
def login(open):
# 方法級別前置操作setup
print(f"輸入賬號,密碼先登錄---{open}")
name = "==我是賬號=="
pwd = "==我是密碼=="
age = "==我是年齡=="
# 返回變量
yield name, pwd, age
# 方法級別后置操作teardown
print("登錄成功")
def test_s2(login):
print("==用例2==")
print(login)
輸出:
============================= test session starts =============================
collecting ... collected 1 item
test_05.py::test_s2 ===打開瀏覽器===
輸入賬號,密碼先登錄---測試變量是否返回
PASSED [100%]==用例2==
('==我是賬號==', '==我是密碼==', '==我是年齡==')
登錄成功
==關(guān)閉瀏覽器==
============================== 1 passed in 0.01s ==============================
yield注意事項(xiàng)
-
如果yield前面的代碼,即setup部分已經(jīng)拋出異常了,則不會執(zhí)行yield后面的teardown內(nèi)容
-
如果測試用例拋出異常,yield后面的teardown內(nèi)容還是會正常執(zhí)行
addfinalizer
在用法上,addfinalizer跟yield是不同的,需要你去注冊作為終結(jié)器使用的函數(shù)。
import pytest
@pytest.fixture()
def demo_fixture(request):
print("n這個(gè)fixture在每個(gè)case前執(zhí)行一次")
def demo_finalizer():
print("n在每個(gè)case完成后執(zhí)行的teardown")
# 注冊demo_finalizer為終結(jié)函數(shù)
request.addfinalizer(demo_finalizer)
def test_01(demo_fixture):
print("n===執(zhí)行了case: test_01===")
def test_02(demo_fixture):
print("n===執(zhí)行了case: test_02===")
def test_03(demo_fixture):
print("n===執(zhí)行了case: test_03===")
輸出:
============================= test session starts =============================
collecting ... collected 3 items
test_06.py::test_01
這個(gè)fixture在每個(gè)case前執(zhí)行一次
PASSED [ 33%]
===執(zhí)行了case: test_01===
在每個(gè)case完成后執(zhí)行的teardown
test_06.py::test_02
這個(gè)fixture在每個(gè)case前執(zhí)行一次
PASSED [ 66%]
===執(zhí)行了case: test_02===
在每個(gè)case完成后執(zhí)行的teardown
test_06.py::test_03
這個(gè)fixture在每個(gè)case前執(zhí)行一次
PASSED [100%]
===執(zhí)行了case: test_03===
在每個(gè)case完成后執(zhí)行的teardown
============================== 3 passed in 0.04s ==============================
Process finished with exit code 0
yield與addfinalizer的區(qū)別
1. addfinalizer可以注冊多個(gè)終結(jié)函數(shù)。
import pytest
import pytest
@pytest.fixture()
def demo_fixture(request):
print("n這個(gè)fixture在每個(gè)case前執(zhí)行一次")
def demo_finalizer():
print("n在每個(gè)case完成后執(zhí)行的teardown")
def demo_finalizer2():
print("n在每個(gè)case完成后執(zhí)行的teardown2")
def demo_finalizer3():
print("n在每個(gè)case完成后執(zhí)行的teardown3")
# 注冊demo_finalizer為終結(jié)函數(shù)
request.addfinalizer(demo_finalizer)
request.addfinalizer(demo_finalizer2)
request.addfinalizer(demo_finalizer3)
def test_01(demo_fixture):
print("n===執(zhí)行了case: test_01===")
def test_02(demo_fixture):
print("n===執(zhí)行了case: test_02===")
def test_03(demo_fixture):
print("n===執(zhí)行了case: test_03===")
輸出:
============================= test session starts =============================
collecting ... collected 3 items
test_06.py::test_01
這個(gè)fixture在每個(gè)case前執(zhí)行一次
PASSED [ 33%]
===執(zhí)行了case: test_01===
在每個(gè)case完成后執(zhí)行的teardown3
在每個(gè)case完成后執(zhí)行的teardown2
在每個(gè)case完成后執(zhí)行的teardown
test_06.py::test_02
這個(gè)fixture在每個(gè)case前執(zhí)行一次
PASSED [ 66%]
===執(zhí)行了case: test_02===
在每個(gè)case完成后執(zhí)行的teardown3
在每個(gè)case完成后執(zhí)行的teardown2
在每個(gè)case完成后執(zhí)行的teardown
test_06.py::test_03
這個(gè)fixture在每個(gè)case前執(zhí)行一次
PASSED [100%]
===執(zhí)行了case: test_03===
在每個(gè)case完成后執(zhí)行的teardown3
在每個(gè)case完成后執(zhí)行的teardown2
在每個(gè)case完成后執(zhí)行的teardown
============================== 3 passed in 0.04s ==============================
Process finished with exit code 0
可以看到,注冊的3個(gè)函數(shù)都被執(zhí)行了,但是要注意的是執(zhí)行順序,與注冊的順序相反。
2. 當(dāng)setUp的代碼執(zhí)行錯(cuò)誤,addfinalizer依舊會執(zhí)行
conftest.py的詳細(xì)講解
可以理解成一個(gè)專門存放fixture的配置文件,如果多個(gè)測試用例文件(test_*.py)的所有用例都需要用登錄功能來作為前置操作,那就不能把登錄功能寫到某個(gè)用例文件中去了,conftest.py的出現(xiàn),就是為了解決上述問題,單獨(dú)管理一些全局的fixture。
conftest.py配置fixture注意事項(xiàng)
-
pytest會默認(rèn)讀取conftest.py里面的所有fixture
-
conftest.py 文件名稱是固定的,不能改動
-
conftest.py只對同一個(gè)package下的所有測試用例生效
-
不同目錄可以有自己的conftest.py,一個(gè)項(xiàng)目中可以有多個(gè)conftest.py
-
測試用例文件中不需要手動import conftest.py,pytest會自動查找
conftest.py使用舉例
conftest.py文件(scope=“session”)
import pytest
@pytest.fixture(scope="session")
def login():
print("輸入賬號密碼")
yield
print("清理數(shù)據(jù)完成")
case文件:
import pytest
class TestLogin1():
def test_1(self, login):
print("用例1")
def test_2(self):
print("用例2")
def test_3(self, login):
print("用例3")
if __name__ == '__main__':
pytest.main()
輸出:
輸入賬號密碼
PASSED [ 33%]用例1
PASSED [ 66%]用例2
PASSED [100%]用例3
清理數(shù)據(jù)完成
可以看出,conftest.py內(nèi)的fixture方法的作用范圍是session,調(diào)用時(shí),整個(gè).py文件只會調(diào)用一次
conftest.py注意事項(xiàng)
-
conftest.py的作用域與Python變量作用域相同
-
內(nèi)層包內(nèi)conftest.py不允許被其它包的測試類或方法使用,相當(dāng)于本地變量
-
外層conftest.py可被內(nèi)層測試類和方法使用,相當(dāng)于全局變量