一、前言
fixture的teardown操作並不是獨立的函數,用yield關鍵字呼喚teardown操作。上一次實現了在每個用例之前執行初始化操作,那么用例執行完之后,如需要清除數據(或還原)操作,可以使用yield來實現。fixture通過scope參數控制setup級別,既然有setup作為用例之前的操作,用例執行完之后那肯定也有teardown操作。
這里用到fixture的teardown操作並不是獨立的函數,用yield關鍵字呼喚teardown操作。fixture的teardown操作並不是獨立的函數,可以用yield關鍵字呼喚teardown操作。
我們之前學習的都是測試用例的前置固件,也就是相當於“setup”。那自然就引申出一個問題“teardown”?這就是我們今天學習的yield和addfinalizer。
(1)yield
yield是一個關鍵字,它不是單獨存在的,要寫在fixture標記的固件中。
我們在聲明的固件myfixture中加入yield關鍵字,在它下面寫測試用例執行后想要運行的代碼;其他有關於固件的使用沒有任何差別。需要說明的一點是我們在pytest主函數中增加一個參數“-setup-show”,它會顯示出固件的執行情況。
fixture里面的teardown用yield來喚醒teardown的執行。
如果測試用例中的代碼出現異常或者斷言失敗,並不會影響他的固件中yield后的代碼執行;但是如果固件中的yield之前的代碼也是相當於setup部分的代碼,出現錯誤或斷言失敗,那么yield后的代碼將不會再執行,當然測試用例中的代碼也不會執行。
我們也可以通過request.addfinalizer()的方式實現“teardown”。
我們在固件中傳入request參數;又在固件中定義了一個內置函數;最后將定義的內置函數添加到request的addfinalize中。
二、scope="function"
當pytest.fixture(scope="function")時,pytest的yield類似unittest的teardown。每個方法(函數)都會執行一次。
1.新建test_function1.py文件
import pytest @pytest.fixture(scope="function") def login(): print("登錄成功") yield print("用例執行完成,收尾") def test1(login): print("操作1") print("---------------------------------------------------") def test2(login): print("操作2") print("---------------------------------------------------") def test3(login): print("操作3") print("---------------------------------------------------") if __name__=="__main__": pytest.main(["-s","test_function1.py"])
運行結果:
從結果看錯,雖然test1,test2,test3三個地方都調用了login函數,並且它會在每一個用例前執行一次。
2.如果test1不調用,test2(調用login),test3不調用,運行順序會是怎樣的?
import pytest @pytest.fixture(scope="function") def login(): print("登錄成功") yield print("用例執行完成,收尾") def test1(): print("操作1") print("---------------------------------------") def test2(login): print("操作2") print("---------------------------------------") def test3(): print("操作3") print("---------------------------------------") if __name__=="__main__": pytest.main(["-s","test_function2.py"])
運行結果:
從結果看出,function級別的fixture在當前.py模塊里,只會在用例(test_s2)第一次調用前執行一次。
三、scope=“module”
(1)fixture參數scope=“module”,module作用是整個.py文件都會生效(整個文件只會執行一次),用例調用時,參數寫上函數名稱就行。
import pytest @pytest.fixture(scope="module") def login(): print("登錄成功") yield print("用例執行完成,收尾") def test1(login): print("操作1") print("-------------------------------------------") def test2(login): print("操作2") print("-------------------------------------------") def test3(login): print("操作3") print("-------------------------------------------") if __name__=="__main__": pytest.main(["-s","test_function3.py"])
運行結果:
從結果看出,雖然test1,test2,test3三個地方都調用了login函數,但是它只會在第一個用例前執行一次。
(2)如果test1,不調用,test2(調用login),test3不調用,運行順序會是怎樣的?
import pytest @pytest.fixture(scope="module") def login(): print("登錄成功") yield print("用例執行完成,收尾") def test1(): print("操作1") print("-------------------------------------------") def test2(login): print("操作2") print("-------------------------------------------") def test3(): print("操作3") print("-------------------------------------------") if __name__=="__main__": pytest.main(["-s","test_function4.py"])
運行結果:
從結果看出,module級別的fixture在當前.py模塊里,只會在用例(test_s2)第一次調用前執行一次。
四、yield執行teardown
有個問題前面的代碼中有一個yield關鍵字是用來干什么的?其實是用來喚醒teardown。
1.fixture里面的teardown用yield來喚醒teardown的執行
import pytest @pytest.fixture(scope="module") def login(): print("登錄成功") yield print("執行teardown!") print("用例執行完成,收尾") def test1(login): print("操作1") print("--------------------------------------") def test2(login): print("操作2") print("--------------------------------------") def test3(login): print("操作3") print("--------------------------------------") if __name__=="__main__": pytest.main(["-s","test_function5.py"])
運行結果:
五、yield遇到異常
1.如果其中一個用例出現異常,不影響yield后面的teardown執行,運行結果互不影響,並且在用例全部執行完之后,會喚醒teardown的內容。
import pytest @pytest.fixture(scope="module") def login(): print("登錄成功") yield print("執行teardown!") print("用例執行完成,收尾") def test1(login): print("操作1") print("----------------------------------") # 如果第一個用例異常了,不影響其他用例執行 raise NameError # 模擬異常 def test2(login): print("操作2") print("----------------------------------") def test3(login): print("操作3") print("----------------------------------") if __name__=="__main__": pytest.main(["-s","test_function6.py"])
運行結果:
2.如果在setup就異常了,那么是不會去執行yield后面的teardown內容了
3.yield也可以配合with語句使用,以下是官方案例:
import smtplib import pytest @pytest.fixture(scope="module") def smtp(): with smtplib.SMTP("smtp.gmail.com") as smtp: yield smtp # provide the fixture value
六、addfinalizer終結函數
1.除了yield可以實現teardown,在request-context對象中注冊addfinalizer方法也可以實現終結函數。
import smtplib import pytest @pytest.fixture(scope="module") def smtp_connection(request): smtp_connection = smtplib.SMTP("smtp.gmail.com",587,timeout=5) def fin(): print("teardown smpt_connection") smtp_connection.close() request.addfinalizer(fin) return smtp_connection # provide the fixture value
2.yield和addfinalizer方法都是在測試完成后呼叫相應的代碼。但是addfinalizer不同的是:
(1)它可以注冊多個終結函數;
(2)這些終結方法總是會被執行,無論在之前的setup code有沒有拋出錯誤。這個方法對於正確關閉所有的fixture創建的資源非常便利,即使其一在創建或獲取時失敗。
參考文章:https://mp.weixin.qq.com/s/QcmjhOhWvdcMFL0sOpaiDA