Fixture
以下命令可以列出所有可用的fixture,包括内置的、插件中的、以及当前项目定义的。
pytest --fixtures
-
fixture作为函数参数
测试用例可以接受一个fixture
函数作为参数(函数命名),fixture
函数的注册通过@pytest. fixture
来标记,下面看一个简单的例子:
# test_sample.pyimport pytest @pytest.fixture def data(): return [1, 2, 3] def test_equal(data): print data[0] assert data[0] == 1
test_equal
需要data
的值,pytest
将会查找并调用@pytest. fixture
标记的data
函数,运行将会看到如下结果:
-
共享fixture函数
上面的一个例子fixture函数只运用于单个py
文件,如果想要共享数据函数怎么办呢?只需要将这些fixture
函数放入到conftest.py
文件中即可(名称不可变);在当前路径创建conftest.py
文件,输入以下代码:
# conftest.pyimport pytest @pytest.fixture def data(): return [1, 2, 3]
test_sample.py
更新代码:# test_sample.py
import pytest def test_equal(data): print data[0] assert data[0] == 1
运行代码将会得到同样的结果;如果此时在
test_sample.py
文件中也包含fixture标记过的data函数(返回数据改变),那么将会重载该数据(即会调用当前文件中的data) -
pytest.fixture
特性实现pytest
中的setup/teardown
经典的xunit风格的setUp、tearDown参考:上篇博客和官网介绍。
pytest.fixture
特性实现:
这里的scope支持 function, class, module, package or session四种,默认情况下的scope是function。- function作用于函数(类中或模块中)
- class开始于类的始末
- module开始于模块的始末
- package/session开始于测试用例的始末(整个包)
默认使用return的时候是没有tearDown的,但是可以使用yield 、addfinalizer函数来实现tearDown功能。
具体请参考:tests │-- conftest.py │-- test_sample.py │-- test_sample1.py
conftest.py
代码:import pytest pytest.fixture(scope="module") def try_module(): print "#######################module begin####################" yield [6, 7, 8] print "#######################module end####################" @pytest.fixture() # 不加(scope="function")默认scope就是function def try_function(): print "#######################function begin####################" yield "just a test about function scope" print "#######################function end####################" @pytest.fixture(scope="class") def try_class(request): print "#######################class begin####################" def end(): print "#######################class end####################" request.addfinalizer(end) return "just a test about class scope" @pytest.fixture(scope="package") # session == package def try_session(request): print "#######################session begin####################" def end(): print "#######################session end####################" request.addfinalizer(end) return "just a test about session scope"
test_sample.py
代码:class TestAdd(object): def test1(self, try_session): print "test1 something" assert 4 == 4 def test2(self, try_module): print "test2 something" assert 5 == 5 def test3(self, try_class): print "test3 something" assert 5 == 5 def test4(self, try_function): print "test4 something" assert 5 == 5 def test5(self): print "test5 something" assert 5 == 5
test_sample1.py
代码:def test_file2_1(try_module): print "test_file2_1 something" assert 2 == 2 def test_file2_2(try_function): print "test_file2_2 something" assert "a"*3 == "aaa" def test_file2_3(try_session): print "test_file2_3 something" assert "a"*3 == "aaa" def test_file2_4(): print "test_file2_4 something" assert "a"*3 == "aaa" def test_file2_5(try_function): print "test_file2_5 something" assert "a"*3 == "aaa"
命令行运行:
pytest -v -s -q
运行结果:============================= test session starts ============================= platform win32 -- Python 2.7.15, pytest-4.5.0, py-1.8.0, pluggy-0.11.0 rootdir: C:\diy\old coding\test\locust demo\pytest collected 10 items test_sample.py #######################session begin#################### test1 something .#######################module begin#################### test2 something .#######################class begin#################### test3 something .#######################function begin#################### test4 something .#######################function end#################### test5 something .#######################class end#################### #######################module end#################### test_sample1.py #######################module begin#################### test_file2_1 something .#######################function begin#################### test_file2_2 something .#######################function end#################### test_file2_3 something .test_file2_4 something .#######################function begin#################### test_file2_2 something .#######################function end#################### #######################module end#################### #######################session end#################### ========================== 10 passed in 0.16 seconds ==========================
简单应用:
import time import pytest from selenium import webdriver from selenium.webdriver.common.by import By from utils.log import logger from utils.config import get_url @pytest.fixture() def chrome_driver(scope="function"): print("setup() begin") driver = webdriver.Chrome() driver.get(get_url()) print("setup() end") yield driver #test_函数结束后,会回到这里,关闭浏览器 print("teardown() begin") driver.close() print("teardown() end") class TestBaiDu(object): locator_kw = (By.ID, 'kw') locator_su = (By.ID, 'su') locator_result = (By.XPATH, '//div[contains(@class, "result")]/h3/a') def test_search_0(self, chrome_driver): chrome_driver.find_element(*self.locator_kw).send_keys(u'selenium 测试') chrome_driver.find_element(*self.locator_su).click() time.sleep(2) links = chrome_driver.find_elements(*self.locator_result) for link in links: logger.info(link.text)
-
调用fixture的三种方式
- 通过传参的方式调用(即上边例子中的用法)
- 通过
usefixtures
decorator调用(pytest.mark.usefixtures()
标记用例) - 通过
autouse
调用
usefixtures
decorator调用例子:test_fixture │-- conftest.py │-- test_example.py
conftest.py
代码:import pytest @pytest.fixture() # 不加(scope="function")默认scope就是function def before_function(): print "#######################function begin####################" yield "just a test about function scope" print "#######################function end####################" @pytest.fixture(scope="class") def before_class(request): print "#######################class begin####################" def end(): print "#######################class end####################" request.addfinalizer(end) return "just a test about class scope"
test_example.py
代码:import pytest @pytest.mark.usefixtures("before_function") def test_1(): print('test_1()') @pytest.mark.usefixtures("before_function") def test_2(): print('test_2()') class TestExample(object): @pytest.mark.usefixtures("before_function") def test_1(self): print('test_1()') @pytest.mark.usefixtures("before_function") def test_2(self): print('test_2()') @pytest.mark.usefixtures("before_function") @pytest.mark.usefixtures("before_class") class TestExample1(object): # @pytest.mark.usefixtures("before_class") def test_1(self): print('test_1()') def test_2(self): print('test_2()')
运行
pytest -v -s test_example.py
输出结果:============================= test session starts ============================= platform win32 -- Python 2.7.15, pytest-4.5.0, py-1.8.0, pluggy-0.11.0 -- c:\python27\python.exe cachedir: .pytest_cache rootdir: C:\diy\old coding\test\locust demo\pytest collected 6 items test_fixture/test_example.py::test_1 #######################function begin#################### test_1() PASSED#######################function end#################### test_fixture/test_example.py::test_2 #######################function begin#################### test_2() PASSED#######################function end#################### test_fixture/test_example.py::TestExample::test_1 #######################function begin#################### test_1() PASSED#######################function end#################### test_fixture/test_example.py::TestExample::test_2 #######################function begin#################### test_2() PASSED#######################function end#################### test_fixture/test_example.py::TestExample1::test_1 #######################class begin#################### #######################function begin#################### test_1() PASSED#######################function end#################### test_fixture/test_example.py::TestExample1::test_2 #######################function begin#################### test_2() PASSED#######################function end#################### #######################class end#################### ========================== 6 passed in 0.19 seconds ===========================
autouse
调用例子:
当管理用例比较多的时候,这种方法比较方便高效,但是用该功能时也要小心,一定要注意fixture的作用范围。需要注意的是,当使用这种方式时,就不能使用返回值的功了。autouse
默认设置为False。当默认为False,就可以选择用上面两种方式来试用fixture。当设置为True时,所有的test都会自动调用这个fixture。autouse
遵循scope="关键字参数"
规则:当scope="session"
时,无论怎样定义只运行一次;当scope="module"
时,每个py文件只运行一次;当scope="class"
时,每个class
只运行一次(但是一个文件中包括function和class时,会在每个function(不在class中)运行一次);当scope="function"
时,每个function运行一次;
目录结构:test_fixture │-- conftest.py │-- test_autouse.py
conftest.py
代码:# coding=utf-8 import pytest @pytest.fixture(scope="module", autouse=True) def before_module(): print "#######################module begin####################" yield [6, 7, 8] print "#######################module end####################" @pytest.fixture(autouse=True) # 不加(scope="function")默认scope就是function def before_function(): print "#######################function begin####################" yield "just a test about function scope" print "#######################function end####################" @pytest.fixture(scope="class",autouse=True) def before_class(request): print "#######################class begin####################" def end(): print "#######################class end####################" request.addfinalizer(end) return "just a test about class scope" @pytest.fixture(scope="package",autouse=True) # session == package def before_package(request): print "#######################session begin####################" def end(): print "#######################session end####################" request.addfinalizer(end) return "just a test about session scope"
test_autouse.py
代码:def test_1(): print('test_1()') def test_2(): print('test_2()') class TestExample(object): def test_1(self): print('test_1()') def test_2(self): print('test_2()')
运行
pytest -v -s test_autouse.py
输出结果:============================= test session starts ============================= platform win32 -- Python 2.7.15, pytest-4.5.0, py-1.8.0, pluggy-0.11.0 -- c:\python27\python.exe cachedir: .pytest_cache rootdir: C:\diy\old coding\test\locust demo\pytest collected 4 items test_fixture/test_autouse.py::test_1 #######################session begin#################### #######################module begin#################### #######################class begin#################### #######################function begin#################### test_1() PASSED#######################function end#################### #######################class end#################### test_fixture/test_autouse.py::test_2 #######################class begin#################### #######################function begin#################### test_2() PASSED#######################function end#################### #######################class end#################### test_fixture/test_autouse.py::TestExample::test_1 #######################class begin#################### #######################function begin#################### test_1() PASSED#######################function end#################### test_fixture/test_autouse.py::TestExample::test_2 #######################function begin#################### test_2() PASSED#######################function end#################### #######################class end#################### #######################module end#################### #######################session end#################### ========================== 4 passed in 0.11 seconds ===========================
fixture中使用fixture(
test_something.py
):import pytest @pytest.fixture(scope="module") def foo(request): print('\nfoo setup - module fixture') def fin(): print('foo teardown - module fixture') request.addfinalizer(fin) @pytest.fixture() def bar(request, foo): print('bar setup - function fixture') def fin(): print('bar teardown - function fixture') request.addfinalizer(fin) @pytest.fixture() def baz(request, bar): print('baz setup - function fixture') def fin(): print('baz teardown - function fixture') request.addfinalizer(fin) def test_one(baz): print('in test_one()') def test_two(bar): # only use bar print('in test_two()')
运行
py.test -s test_something.py
,输出:============================= test session starts ============================= platform win32 -- Python 2.7.2 -- pytest-2.4.2 collected 2 items test_modular.py foo setup - module fixture bar setup - function fixture baz setup - function fixture in test_one() .baz teardown - function fixture bar teardown - function fixture bar setup - function fixture in test_two() .bar teardown - function fixture foo teardown - module fixture ========================== 2 passed in 0.02 seconds ===========================
-
参数化
利用fixture的params参数进行参数化:@pytest.fixture(params=[0, 1, 3]) def c_set(request): temp = request.param yield temp def test_c(c_set): assert c_set == 3
上面例子运行了三个测试用例,运行结果:
============================= test session starts ============================= platform win32 -- Python 2.7.15, pytest-4.5.0, py-1.8.0, pluggy-0.11.0 -- c:\python27\python.exe cachedir: .pytest_cache rootdir: C:\diy\old coding\test\locust demo\pytest collected 3 items test_parametrizing.py::test_c[0] FAILED test_parametrizing.py::test_c[1] FAILED test_parametrizing.py::test_c[3] PASSED ================================== FAILURES =================================== __________________________________ test_c[0] __________________________________ c_set = 0 def test_c(c_set): > assert c_set == 3 E assert 0 == 3 E -0 E +3 test_parametrizing.py:50: AssertionError __________________________________ test_c[1] __________________________________ c_set = 1 def test_c(c_set): > assert c_set == 3 E assert 1 == 3 E -1 E +3 test_parametrizing.py:50: AssertionError ===================== 2 failed, 1 passed in 0.18 seconds ======================
也可以利用
ids
参数对每个测试定制一个名称标志,将上边函数加一个ids
参数,可以用–collect-only参数来查看(pytest --collect-only test_parametrizing.py
),添加的ids参数可以用-k来过滤运行指定参数的方法(pytest -k "apple" test_parametrizing.py
只会运行参数params=[0, 1, 3]
中值为1的,运行一次),代码如下:@pytest.fixture(params=[0, 1, 3], ids=["apple","banana","orange"]) def c_set(request): temp = request.param yield temp def test_c(c_set): assert c_set == 3
也可以为ids传递函数作为参数:
def idfn(fixture_value): if fixture_value == 0: return "eggs" else: return None @pytest.fixture(params=[0, 1], ids=idfn) def d(request): return request.param def test_d(d): pass
使用
pytest.param()
,参数值2将被跳过:import pytest @pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)]) def e_set(request): return request.param def test_e(e_set): pass
利用
@pytest.mark.parametrize
直接对测试函数进行参数化:import pytest def add(a, b): return a + b @pytest.mark.parametrize("test_input, expected", [ ([1, 1], 2), ([2, 2], 4), ([0, 1], 1), ]) def test_add(test_input, expected): assert expected == add(test_input[0], test_input[1])
-
不同级别的重写fixture
- 文件夹级别(
conftest.py
)重写fixture - module级别的重写fixture
- 直接利用参数重写fixture
文件夹级别(
conftest.py
)重写fixturetests/ __init__.py conftest.py # content of tests/conftest.py import pytest @pytest.fixture def username(): return 'username' test_something.py # content of tests/test_something.py def test_username(username): assert username == 'username' subfolder/ __init__.py conftest.py # content of tests/subfolder/conftest.py import pytest @pytest.fixture def username(username): return 'overridden-' + username test_something.py # content of tests/subfolder/test_something.py def test_username(username): assert username == 'overridden-username'
通过上面的例子可以看到,对于特定的测试文件夹级别,可以覆盖具有相同名称的fixture。注意,重写的fixture可以轻松地访问被重写的base fixture或super fixture
module级别的重写fixturetests/ __init__.py conftest.py # content of tests/conftest.py @pytest.fixture def username(): return 'username' test_something.py # content of tests/test_something.py import pytest @pytest.fixture def username(username): return 'overridden-' + username def test_username(username): assert username == 'overridden-username' test_something_else.py # content of tests/test_something_else.py import pytest @pytest.fixture def username(username): return 'overridden-else-' + username def test_username(username): assert username == 'overridden-else-username'
在上面的示例中,在模块级别,相同名称的fixture可以被重写
直接利用参数重写fixturetests/ __init__.py conftest.py # content of tests/conftest.py import pytest @pytest.fixture def username(): return 'username' @pytest.fixture def other_username(username): return 'other-' + username test_something.py # content of tests/test_something.py import pytest @pytest.mark.parametrize('username', ['directly-overridden-username']) def test_username(username): assert username == 'directly-overridden-username' @pytest.mark.parametrize('username', ['directly-overridden-username-other']) def test_username_other(other_username): assert other_username == 'other-directly-overridden-username-other'
在上面的例子中,fixture值被test参数值覆盖。请注意,即使测试没有直接使用fixture(在函数原型中没有提到),fixture的值也可以通过这种方式被覆盖
- 文件夹级别(