From: https://www.jianshu.com/p/54b0f4016300
一. fixture介紹
fixture是pytest的一個閃光點,pytest要精通怎么能不學習fixture呢?跟著我一起深入學習fixture吧。其實unittest和nose都支持fixture,但是pytest做得更炫。
fixture是pytest特有的功能,它用pytest.fixture標識,定義在函數前面。在你編寫測試函數的時候,你可以將此函數名稱做為傳入參數,pytest將會以依賴注入方式,將該函數的返回值作為測試函數的傳入參數。
fixture有明確的名字,在其他函數,模塊,類或整個工程調用它時會被激活。
fixture是基于模塊來執行的,每個fixture的名字就可以觸發一個fixture的函數,它自身也可以調用其他的fixture。
我們可以把fixture看做是資源,在你的測試用例執行之前需要去配置這些資源,執行完后需要去釋放資源。比如module類型的fixture,適合于那些許多測試用例都只需要執行一次的操作。
fixture還提供了參數化功能,根據配置和不同組件來選擇不同的參數。
fixture主要的目的是為了提供一種可靠和可重復性的手段去運行那些最基本的測試內容。比如在測試網站的功能時,每個測試用例都要登錄和退出,利用fixture就可以只做一次,否則每個測試用例都要做這兩步也是冗余。
下面會反復提高Python的Module概念,Python中的一個Module對應的就是一個.py文件。其中定義的所有函數或者是變量都屬于這個Module。這個Module 對于所有函數而言就相當于一個全局的命名空間,而每個函數又都有自己局部的命名空間。
二. Fixture基礎實例入門
把一個函數定義為Fixture很簡單,只能在函數聲明之前加上“@pytest.fixture”。其他函數要來調用這個Fixture,只用把它當做一個輸入的參數即可。
test_fixture_basic.py
import pytest@pytest.fixture()
def before(): print '\nbefore each test' def test_1(before): print 'test_1()' def test_2(before): print 'test_2()' assert 0
下面是運行結果,test_1和test_2運行之前都調用了before,也就是before執行了兩次。默認情況下,fixture是每個測試用例如果調用了該fixture就會執行一次的。
C:\Users\yatyang\PycharmProjects\pytest_example>pytest -v -s test_fixture_basic.py
============================= test session starts =============================
platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- C:\Python27\python.exe cachedir: .cache metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA_HOME': 'C:\\Program Files (x86)\\Java\\jd k1.7.0_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}} rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile: plugins: metadata-1.3.0, html-1.14.2 collected 2 items test_fixture_basic.py::test_1 before each test test_1() PASSED test_fixture_basic.py::test_2 before each test test_2() FAILED ================================== FAILURES =================================== ___________________________________ test_2 ____________________________________ before = None def test_2(before): print 'test_2()' > assert 0 E assert 0 test_fixture_basic.py:12: AssertionError ===================== 1 failed, 1 passed in 0.23 seconds ======================
如果你的程序出現了下面的錯誤,就是開始忘記添加‘import pytest',所以不要忘記羅。
=================================== ERRORS ====================================
_________________ ERROR collecting test_fixture_decorator.py __________________
test_fixture_decorator.py:2: in <module> @pytest.fixture() E NameError: name 'pytest' is not defined !!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!! =========================== 1 error in 0.36 seconds ===========================
三. 調用fixture的三種方式
1. 在測試用例中直接調用它,例如第二部分的基礎實例。
2. 用fixture decorator調用fixture
可以用以下三種不同的方式來寫,我只變化了函數名字和類名字,內容沒有變。第一種是每個函數前聲明,第二種是封裝在類里,類里的每個成員函數聲明,第三種是封裝在類里在前聲明。在可以看到3中不同方式的運行結果都是一樣。
test_fixture_decorator.py
import pytest@pytest.fixture()
def before(): print('\nbefore each test') @pytest.mark.usefixtures("before") def test_1(): print('test_1()') @pytest.mark.usefixtures("before") def test_2(): print('test_2()') class Test1: @pytest.mark.usefixtures("before") def test_3(self): print('test_1()') @pytest.mark.usefixtures("before") def test_4(self): print('test_2()') @pytest.mark.usefixtures("before") class Test2: def test_5(self): print('test_1()') def test_6(self): print('test_2()')
運行結果如下,和上面的基礎實例的運行效果一樣。
C:\Users\yatyang\PycharmProjects\pytest_example>pytest -v -s test_fixture_decorator.py
============================= test session starts =============================
platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- C:\Python27\python.exe cachedir: .cache metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA_HOME': 'C:\\Program Files (x86)\\Java\\jd k1.7.0_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}} rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile: plugins: metadata-1.3.0, html-1.14.2 collected 6 items test_fixture_decorator.py::test_1 before each test test_1() PASSED test_fixture_decorator.py::test_2 before each test test_2() PASSED test_fixture_decorator.py::Test1::test_3 before each test test_1() PASSED test_fixture_decorator.py::Test1::test_4 before each test test_2() PASSED test_fixture_decorator.py::Test2::test_5 before each test test_1() PASSED test_fixture_decorator.py::Test2::test_6 before each test test_2() PASSED ========================== 6 passed in 0.10 seconds ===========================
3. 用autos調用fixture
fixture decorator一個optional的參數是autouse, 默認設置為False。
當默認為False,就可以選擇用上面兩種方式來試用fixture。
當設置為True時,在一個session內的所有的test都會自動調用這個fixture。
權限大,責任也大,所以用該功能時也要謹慎小心。
import time
import pytest@pytest.fixture(scope="module", autouse=True)
def mod_header(request): print('\n-----------------') print('module : %s' % request.module.__name__) print('-----------------') @pytest.fixture(scope="function", autouse=True) def func_header(request): print('\n-----------------') print('function : %s' % request.function.__name__) print('time : %s' % time.asctime()) print('-----------------') def test_one(): print('in test_one()') def test_two(): print('in test_two()')
從下面的運行結果,可以看到mod_header在該module內運行了一次,而func_header對于每個test都運行了一次,總共兩次。該方式如果用得好,還是可以使代碼更為簡潔。
但是對于不熟悉自己組的測試框架的人來說,在pytest里面去新寫測試用例,需要去了解是否已有一些fixture是module或者class級別的需要注意。
C:\Users\yatyang\PycharmProjects\pytest_example>pytest -v -s test_fixture_auto.py
============================= test session starts =============================
platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- C:\Python27\python.exe cachedir: .cache metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA_HOME': 'C:\\Program Files (x86)\\Java\\jd k1.7.0_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}} rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile: plugins: metadata-1.3.0, html-1.14.2 collected 2 items test_fixture_auto.py::test_one ----------------- module : test_fixture_auto ----------------- ----------------- function : test_one time : Sat Mar 18 06:56:54 2017 ----------------- in test_one() PASSED test_fixture_auto.py::test_two ----------------- function : test_two time : Sat Mar 18 06:56:54 2017 ----------------- in test_two() PASSED ========================== 2 passed in 0.03 seconds ===========================
四. fixture scope
function:每個test都運行,默認是function的scope
class:每個class的所有test只運行一次
module:每個module的所有test只運行一次
session:每個session只運行一次
比如你的所有test都需要連接同一個數據庫,那可以設置為module,只需要連接一次數據庫,對于module內的所有test,這樣可以極大的提高運行效率。
五. fixture 返回值
在上面的例子中,fixture返回值都是默認None,我們可以選擇讓fixture返回我們需要的東西。如果你的fixture需要配置一些數據,讀個文件,或者連接一個數據庫,那么你可以讓fixture返回這些數據或資源。
如何帶參數
fixture還可以帶參數,可以把參數賦值給params,默認是None。對于param里面的每個值,fixture都會去調用執行一次,就像執行for循環一樣把params里的值遍歷一次。
test_fixture_param.py
import pytest@pytest.fixture(params=[1, 2, 3])
def test_data(request): return request.param def test_not_2(test_data): print('test_data: %s' % test_data) assert test_data != 2
可以看到test_not_2里面把用test_data里面定義的3個參數運行里三次。
C:\Users\yatyang\PycharmProjects\pytest_example>pytest -v -s test_fixture_param.py
============================= test session starts =============================
platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- C:\Python27\python.exe cachedir: .cache metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA_HOME': 'C:\\Program Files (x86)\\Java\\jd k1.7.0_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}} rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile: plugins: metadata-1.3.0, html-1.14.2 collected 3 items test_fixture_param.py::test_not_2[1] test_data: 1 PASSED test_fixture_param.py::test_not_2[2] test_data: 2 FAILED test_fixture_param.py::test_not_2[3] test_data: 3 PASSED ================================== FAILURES =================================== ________________________________ test_not_2[2] ________________________________ test_data = 2 def test_not_2(test_data): print('test_data: %s' % test_data) > assert test_data != 2 E assert 2 != 2 test_fixture_param.py:9: AssertionError ===================== 1 failed, 2 passed in 0.24 seconds ======================