3-pytest-固件

什么是固件

固件(Fixture)是一些函数,pytest 会在执行测试函数之前或之后加载运行它们。

可以利用固件做很多事情,最常见的就是数据库的初始连接和最后关闭操作。

举个例子:
使用 pytest.fixture() 定义固件,只返回北京邮编

# test_postcode.py

import pytest

@pytest.fixture()
def postcode():
	return '010'

def test_postcode(postcode):
	assert postcode == '010'

if __name__ == '__main__':
	pytest.main('-s test_postcode.py'.split())

运行结果:

============================= test session starts =============================
platform win32 -- Python 3.9.2, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\Administrator\Desktop\test\20210407\test_postcode
collected 1 item

test_postcode.py .

============================== 1 passed in 0.02s ==============================

***Repl Closed***

固件可以直接定义在各测试脚本中,就像上面的例子。更多时候,我们希望一个固件可以在更大程度上复用,这就需要对固件进行集中管理。Pytest 使用文件 conftest.py 集中管理固件。

注意:
不要自己显式调用 conftest.py,pytest 会自动调用,可以把 conftest 当做插件来理解。

在复杂的项目中,可以在不同的目录层级定义 conftest.py,其作用域为其所在的目录和子目录。

预处理和后处理

当大量重复预处理(数据库建立)和后处理(测试完成进行清理)这类操作,最佳方法是使用固件来自动化所有预处理和后处理。

Pytest 使用 yield 关键词将固件分为两部分,yield 之前的代码属于预处理,会在测试前执行;yield 之后的代码属于后处理,将在测试完成后执行。

以下测试模拟数据库查询,使用固件来模拟数据库的连接和关闭:

# test_db.py

import pytest

@pytest.fixture()
def db():
	print('Connection successful')

	yield

	print('Connection closed')


def search_user(user_id):
	d = {
		'001': 'xiaoming'
	}
	return d[user_id]

def test_search(db):
	assert search_user('001') == 'xiaoming'

if __name__ == '__main__':
	pytest.main('-s test_db.py'.split())

这里使用 -s 选项,阻止消息被吞

运行结果:

============================= test session starts =============================
platform win32 -- Python 3.9.2, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\Administrator\Desktop\test\20210407\test_db
collected 1 item

test_db.py Connection successful
.Connection closed


============================== 1 passed in 0.01s ==============================

***Repl Closed***

从结果可以看到,测试成功的 . 标识前后有数据库的连接和关闭操作。

如果想要更细的跟踪固件操作,可以使用 --setup-show 选项。
在这里插入图片描述

在这里插入图片描述

作用域

固件的作用是为了抽离重复的工作和方便复用,为了更精细化控制固件,比如只想对数据库访问测试脚本使用自动连接关闭的固件,pytest 使用作用域来进行指定固件的使用范围。

定义固件时,通过 scope 参数声明作用域,可选项有:

  • function:函数级,每个测试函数都会执行一次固件
  • class:类级别,每个测试类执行一次,所有方法都可以使用
  • module:模块级,每个模块执行一次,模块内函数和方法都可使用
  • session:会话级,一次测试只执行一次,所有被找到的函数和方法都可用

默认的作用域为 function

# test_scope.py

import pytest

@pytest.fixture(scope='function')
def func_scope():
	pass

@pytest.fixture(scope='module')
def mod_scope():
	pass


@pytest.fixture(scope='session')
def sess_scope():
	pass

@pytest.fixture(scope='class')
def class_scope():
	pass

# 最简单使用固件方式是作为测试函数参数
def test_multi_scope(sess_scope,mod_scope,func_scope):
	pass

if __name__ == '__main__':
	pytest.main('--setup-show test_scope.py'.split())

可清楚看到各固件的作用域和执行顺序
在这里插入图片描述

对于类使用作用域,需要使用 pytest.mark.usefixtures (对函数和方法也适用):

# test_scope.py

import pytest

@pytest.fixture(scope='class')
def class_scope():
    pass

@pytest.mark.usefixtures('class_scope')
class TestClassScope:
    def test_1(self):
        pass

    def test_2(self):
        pass


if __name__ == '__main__':
	pytest.main('--setup-show test_scope.py::TestClassScope'.split())

从结果可以看到,所有测试函数都在固件作用范围内
在这里插入图片描述

自动执行

从学习入门到现在,所有的固件的使用都是手动指定,或者作为参数,或者使用 usefixtures

如果想要固件自动执行,可以在定义时指定 autouse 参数

下面是两个自动计时固件,一个用于统计每个函数运行时间(function 作用域),一个用于计算测试总耗时(session 作用域):

# test_autouse.py

import pytest
import time

DATE_FORMAT = '%Y-%m-%d %H:%M:%S'

@pytest.fixture(scope='session', autouse=True)
def timer_session_scope():
	start = time.time()
	print('\nstart: {}'.format(time.strftime(DATE_FORMAT, time.localtime(start))))

	yield

	finished = time.time()
	print('finished: {}'.format(time.strftime(DATE_FORMAT,time.localtime(finished))))
	print('Total time cost: {:.3f}'.format(finished-start))


@pytest.fixture(autouse=True)
def timer_function_scope():
	start = time.time()

	yield

	print(' Time cost: {:.3f}s'.format(time.time()-start))


# 无显式使用固件

def test_1():
    time.sleep(1)


def test_2():
    time.sleep(2)

if __name__ == '__main__':
	pytest.main('-s test_autouse.py'.split())

执行结果为:
固件自动执行并完成计时任务
在这里插入图片描述

重命名

固件的名称默认为定义时的函数名,如果不想使用默认,可以通过 name 选项指定名称

# test_rename.py

@pytest.fixture(name='age')
def calculate_average_age():
    return 28


def test_age(age):
    assert age == 28

参数化

因为固件也是函数,所以也能进行参数化。

假设现在有一批 API 需要测试对不同数据库的支持情况(对所有数据库进行相同操作),最简单的方法就是针对每个数据库编写一个测试用例,但这包含大量重复代码,如数据库的连接、关闭,查询等。


进一步,可以使用固件抽离出数据库的通用操作,每个 API 都能复用这些数据库固件,同时可维护性也得到提升。
更进一步,可以继续将这些固件合并为一个,而通过参数控制连接到不同的数据库。这就需要使用固件参数化来实现。

固件参数化需要使用 pytest 内置的固件 request,并通过 request.param 获取参数。

# test_fixture_param.py

import pytest

@pytest.fixture(params=[
	('redis', '6379'),
	('elasticsearch','9200')
	])
def param(request):
	return request.param


@pytest.fixture(autouse=True)
def db(param):
	print('\nSucceed to connect %s:%s' % param)

	yield

	print('\nSucceed to close %s:%s' % param)

def test_api():
	assert 1 == 1


if __name__ == '__main__':
	pytest.main('-s test_fixture_param.py'.split())

在这里插入图片描述
注意:

与函数参数化使用 @pytest.mark.parametrize 不同,固件在定义时使用 params 参数进行参数化。

固件参数化依赖于内置固件 request 及其属性 param

内置固件

tmpdir & tmpdir_factory

用于临时文件和目录管理,默认会在测试结束时删除。

tmpdir

注意:
tmpdir 只有 function 作用域,只能在函数内使用。

使用 tmpdir.mkdir() 创建临时目录,tmpdir.join() 创建临时文件(或者使用创建的目录)。

# test_tmpdir.py

import pytest

def test_tmpdir(tmpdir):
	a_dir = tmpdir.mkdir('mytmpdir')
	a_file = a_dir.join('tmpfile.txt')

	a_file.write('hello, pytest!')

	assert a_file.read() == 'hello, pytest!'

if __name__ == '__main__':
	pytest.main('test_tmpdir.py'.split())

在这里插入图片描述

tmpdir_factory

tmpdir_factory 可以在所有作用域使用,包括 function, class, module, session

举例:

# test_tmpdir.py

import pytest

@pytest.fixture(scope='module')
def my_tmpdir_factory(tmpdir_factory):
	a_dir = tmpdir_factory.mktemp('mytmpdir')
	a_file = a_dir.join('tmpfile.txt')

	a_file.write('hello, pytest!')

	return a_file

pytestconfig

使用 pytestconfig,可以很方便的读取命令行参数和配置文件。

举例:

capsys

capsys 用于捕获 stdoutstderr 的内容,并临时关闭系统输出。

monkeypatch

monkeypath 用于运行时动态修改类或模块。

recwarn

recwarn 用于捕获程序中 warnings 产生的警告。

下面代码里的 warning 没有定义。还没找到解决办法。

# test_recwarn.py

def warn():
    warnings.warn('Deprecated function', DeprecationWarning)


def test_warn(recwarn):
    warn()
    assert len(recwarn) == 1
    w = recwarn.pop()
    assert w.category == DeprecationWarning
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值