【原文链接】
一、通过request获取模块及文件中的属性
通过request可以获取到测试文件及模块的属性值,这样就可以动态的对测试文件做一些操作或者控制,本质上就是pytest中的反射
如下在test_demo.py的代码,其中定义了一个变量name
name="张无忌"
def test_func3():
assert 10==9
如下为conftest.py代码,这里定义了一个module级的fixture,在这个fixture中可以获取到用例中的name属性,在实际应用中,比如就可以通过获取到的属性值进一步做一些判断或者处理等等,这里仅仅打印出来展示一下
import pytest
@pytest.fixture(autouse=True,scope="module")
def session_fixture(request):
name=getattr(request.module,"name",None)
print(name)
执行结果如下:
$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.py 张无忌
F
=============================================================================== FAILURES ===============================================================================
______________________________________________________________________________ test_func3 ______________________________________________________________________________
def test_func3():
> assert 10==9
E assert 10 == 9
test_demo.py:6: AssertionError
======================================================================= short test summary info ========================================================================
FAILED test_demo.py::test_func3 - assert 10 == 9
========================================================================== 1 failed in 0.10s ===========================================================================
二、通过marker向fixture传值
可以通过request.node获取到marker对象,进而获取到其参数,从而可以向fixture传递此参数,具体示例如下
test_demo.py代码如下:
import pytest
@pytest.fixture()
def fixt(request):
marker = request.node.get_closest_marker("fixt_data")
if marker is None:
data = None
else:
data = marker.args[0]
return data
@pytest.mark.fixt_data(42)
def test_fixt(fixt):
assert fixt == 42
执行结果为:
$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.py . [100%]
=========================================================================== warnings summary ===========================================================================
test_demo.py:14
D:\src\blog\tests\test_demo.py:14: PytestUnknownMarkWarning: Unknown pytest.mark.fixt_data - is this a typo? You can register custom marks to avoid this warning - for
details, see https://docs.pytest.org/en/stable/mark.html
@pytest.mark.fixt_data(42)
-- Docs: https://docs.pytest.org/en/stable/warnings.html
===================================================================== 1 passed, 1 warning in 0.02s =====================================================================
三、将工厂函数作为fixture
可以将工厂函数作为fixture,这样可以在测试用例中很方便的构造一些对象,如下
test_demo.py代码如下:
import pytest
@pytest.fixture
def make_customer_record():
def _make_customer_record(name):
return {"name": name, "orders": []}
return _make_customer_record
def test_customer_records(make_customer_record):
customer_1 = make_customer_record("Lisa")
customer_2 = make_customer_record("Mike")
customer_3 = make_customer_record("Meredith")
print("\n")
print(customer_1)
print(customer_2)
print(customer_3)
执行结果如下:
$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.py
{'name': 'Lisa', 'orders': []}
{'name': 'Mike', 'orders': []}
{'name': 'Meredith', 'orders': []}
.
========================================================================== 1 passed in 0.03s ===========================================================================
四、支持参数化的fixture
定义fixture中使用params,然后再fixture中通过request.param抛出,则可以实现参数化的过程,即params中有几个参数就可以执行几个测试用例,亦即数据驱动,如下params中有三个参数,虽然这里只有一个测试函数,但从执行结果可以看出仍然显示是执行了三个测试用例
test_demo.py代码如下
import pytest
@pytest.fixture(scope="function",params=[1,2,3])
def f1(request):
print("in f1 fixture ...")
yield request.param
def test_func(f1):
print(f1)
assert f1>0
执行结果如下
$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items
test_demo.py in f1 fixture ...
1
.in f1 fixture ...
2
.in f1 fixture ...
3
.
========================================================================== 3 passed in 0.04s ===========================================================================
五、参数化的fixture指定用例id
如下,通过pytest --collect-only 查看id
test_demo.py的代码如下:
import pytest
@pytest.fixture(scope="function",params=[1,2,3])
def f1(request):
print("in f1 fixture ...")
yield request.param
def test_func(f1):
print(f1)
assert f1 > 0
执行结果如下
$ pytest --collect-only
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items
<Module test_demo.py>
<Function test_func[1]>
<Function test_func[2]>
<Function test_func[3]>
====================================================================== 3 tests collected in 0.02s ======================================================================
修改test_demo.py代码如下,通过ids设置用例id
import pytest
@pytest.fixture(scope="function",params=[1,2,3],ids=["test_01", "test_02","test_03"])
def f1(request):
print("in f1 fixture ...")
yield request.param
def test_func(f1):
print(f1)
assert f1 > 0
然后执行,结果如下,可以发现,此时用例id已经发生了变化
$ pytest --collect-only
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items
<Module test_demo.py>
<Function test_func[test_01]>
<Function test_func[test_02]>
<Function test_func[test_03]>
====================================================================== 3 tests collected in 0.03s ======================================================================
六、参数化的fixture指定某个参数使用skip标记
在使用参数的fixture的时候,对一些被测数值也可以像普通的测试函数一样使用skip标记跳过,如下
test_demo.py代码如下:
import pytest
@pytest.fixture(scope="function",params=[1,2,pytest.param(3,marks=pytest.mark.skip)])
def f1(request):
print("in f1 fixture ...")
yield request.param
def test_func(f1):
print(f1)
assert f1 > 0
执行结果如下:
$ pytest -v
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python39\python.exe
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('G:\\src\\blog\\tests\\.hypothesis\\examples')
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items
test_demo.py::test_func[1] PASSED [ 33%]
test_demo.py::test_func[2] PASSED [ 66%]
test_demo.py::test_func[3] SKIPPED (unconditional skip) [100%]
===================================================================== 2 passed, 1 skipped in 0.19s =====================================================================
七、调用参数化的fixture可以实现两组数据的全排列组合测试数据
当在一个测试函数中调用两个参数化的fixture,可以实现两组参数的全排列组合,比如一个参数列表为[1,2],另一个fixture的参数列表为[10,20],当一个测试函数同时调用此两个fixture时,则会产生[(1,10),(1,20),(2,10),(2,20)]这样四组被测数据
test_demo.py代码如下
import pytest
@pytest.fixture(scope="function",params=[1,2])
def f1(request):
print("in f1 fixture setup...")
yield request.param
print("in f1 fixture teardown...")
@pytest.fixture(scope="function",params=[10,20])
def f2(request):
print("in f2 fixture setup...")
yield request.param
print("in f2 fixture teardown...")
def test_func(f1,f2):
assert f2<f1
执行结果如下:
$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 4 items
test_demo.py in f1 fixture setup...
in f2 fixture setup...
Fin f2 fixture teardown...
in f1 fixture teardown...
in f1 fixture setup...
in f2 fixture setup...
Fin f2 fixture teardown...
in f1 fixture teardown...
in f1 fixture setup...
in f2 fixture setup...
Fin f2 fixture teardown...
in f1 fixture teardown...
in f1 fixture setup...
in f2 fixture setup...
Fin f2 fixture teardown...
in f1 fixture teardown...
=============================================================================== FAILURES ===============================================================================
___________________________________________________________________________ test_func[1-10] ____________________________________________________________________________
f1 = 1, f2 = 10
def test_func(f1,f2):
> assert f2<f1
E assert 10 < 1
test_demo.py:16: AssertionError
___________________________________________________________________________ test_func[1-20] ____________________________________________________________________________
f1 = 1, f2 = 20
def test_func(f1,f2):
> assert f2<f1
E assert 20 < 1
test_demo.py:16: AssertionError
___________________________________________________________________________ test_func[2-10] ____________________________________________________________________________
f1 = 2, f2 = 10
def test_func(f1,f2):
> assert f2<f1
E assert 10 < 2
test_demo.py:16: AssertionError
___________________________________________________________________________ test_func[2-20] ____________________________________________________________________________
f1 = 2, f2 = 20
def test_func(f1,f2):
> assert f2<f1
E assert 20 < 2
test_demo.py:16: AssertionError
======================================================================= short test summary info ========================================================================
FAILED test_demo.py::test_func[1-10] - assert 10 < 1
FAILED test_demo.py::test_func[1-20] - assert 20 < 1
FAILED test_demo.py::test_func[2-10] - assert 10 < 2
FAILED test_demo.py::test_func[2-20] - assert 20 < 2
========================================================================== 4 failed in 0.20s ===========================================================================
八、通过usefixtures为一个测试类调用fixture
假如想为一个测试类中的每个测试函数都去调用一个fixture,则此时可以在类上通过使用@pytest.mark.usefixtures()来指定
test_demo.py代码如下:
import pytest
@pytest.fixture(scope="function")
def f1():
print("in f1 fixture setup...")
yield
print("in f1 fixture teardown...")
@pytest.mark.usefixtures("f1")
class TestDemo(object):
def test_01(self):
print("in test_01...")
assert 1==1
def test_02(self):
print("in test_02...")
assert 1==1
def test_03(self):
print("in test_03...")
assert 1==1
执行结果如下:
$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items
test_demo.py in f1 fixture setup...
in test_01...
.in f1 fixture teardown...
in f1 fixture setup...
in test_02...
.in f1 fixture teardown...
in f1 fixture setup...
in test_03...
.in f1 fixture teardown...
========================================================================== 3 passed in 0.04s ===========================================================================
九、fixture覆盖重写
fixture的覆盖重写简单点来说就是遵循就近原则,即在测试函数中总是调用离测试函数最近的fixture,具体点说就是:
(1)内层的conftest.py中的fixture会覆盖外层conftest.py中的fixture
(2)测试模块中的fixture会覆盖conftest.py中的同名fixture
(3)参数化中的参数会覆盖测试模块中的同名fixture
目录结构如下:
tests
|----conftest.py
|----demo
|----__init__.py
|----conftest.py
|----test_demo.py
其中tests/conftest.py内容如下:
import pytest
@pytest.fixture()
def conf_fixture():
print("in outer conftest.py fixture...")
tests/demo/conftest.py内容如下:
import pytest
@pytest.fixture()
def conf_fixture():
print("in inner conftest.py fixture...")
@pytest.fixture()
def module_fixture():
print("in conftest.py module fixture...")
test_demo.py内容如下:
import pytest
@pytest.fixture()
def module_fixture():
print("in test_demo.py module fixture...")
@pytest.fixture()
def f1():
return "hello f1 function"
def test_01(conf_fixture):
print("in test_01...")
def test_02(module_fixture):
print("in test_02")
@pytest.mark.parametrize("f1",["hello zhangwuji"])
def test_03(f1):
print("in test_03...")
print(f1)
执行结果如下,可以看出,test_01函数调用了内层的conftest.py中的fixture,test_02函数调用了test_demo.py中的fixture,test_03函数则直接调用了参数化的参数,即均遵循就近原则
$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items
demo\test_demo.py in inner conftest.py fixture...
in test_01...
.in test_demo.py module fixture...
in test_02
.in test_03...
hello zhangwuji
.
========================================================================== 3 passed in 0.07s ===========================================================================