Pytest学习笔记2——先后置处理高级函数Fixture(完整篇)

  引言

  前面介绍了pytest传统的先后置处理方法,经过一些实例,知道了它对处理先后置的场景是有必定的局限性。因此才引入fixture装饰器函数,fixture是pytest的核心功能,也是亮点功能,它能够灵活的处理不少特殊的场景,利用pytest作接口测试,熟练掌握fixture的使用方法,pytest用起来才会驾轻就熟!session

  Pytest简介

  fixture的目的是提供一个固定基线,在该基线上测试能够可靠地和重复地执行。fixture提供了区别于传统单元测试(setup/teardown)有显著改进:框架

  1.有独立的命名,并经过声明它们从测试函数、模块、类或整个项目中的使用来激活。less

  2.按模块化的方式实现,每一个fixture均可以互相调用。ide

  3.fixture的范围从简单的单元扩展到复杂的功能测试,容许根据配置和组件选项对fixture和测试用例进行参数化,或者跨函数function、类class、模块module或整个测试会话sessio范围。模块化

  Fixture函数定义

  先看一下fixture的函数定义:函数

def fixture(
    callable_or_scope=None,
    *args,
    scope="function",
    params=None,
    autouse=False,
    ids=None,
    name=None
):
    """Decorator to mark a fixture factory function.

    This decorator can be used, with or without parameters, to define a
    fixture function.

    The name of the fixture function can later be referenced to cause its
    invocation ahead of running tests: test
    modules or classes can use the ``pytest.mark.usefixtures(fixturename)``
    marker.

    Test functions can directly use fixture names as input
    arguments in which case the fixture instance returned from the fixture
    function will be injected.

    Fixtures can provide their values to test functions using ``return`` or ``yield``
    statements. When using ``yield`` the code block after the ``yield`` statement is executed
    as teardown code regardless of the test outcome, and must yield exactly once.

    :arg scope: the scope for which this fixture is shared, one of
                ``"function"`` (default), ``"class"``, ``"module"``,
                ``"package"`` or ``"session"`` (``"package"`` is considered **experimental**
                at this time).

                This parameter may also be a callable which receives ``(fixture_name, config)``
                as parameters, and must return a ``str`` with one of the values mentioned above.

                See :ref:`dynamic scope` in the docs for more information.

    :arg params: an optional list of parameters which will cause multiple
                invocations of the fixture function and all of the tests
                using it.
                The current parameter is available in ``request.param``.

    :arg autouse: if True, the fixture func is activated for all tests that
                can see it.  If False (the default) then an explicit
                reference is needed to activate the fixture.

    :arg ids: list of string ids each corresponding to the params
                so that they are part of the test id. If no ids are provided
                they will be generated automatically from the params.

    :arg name: the name of the fixture. This defaults to the name of the
                decorated function. If a fixture is used in the same module in
                which it is defined, the function name of the fixture will be
                shadowed by the function arg that requests the fixture; one way
                to resolve this is to name the decorated function
                ``fixture_<fixturename>`` and then use
                ``@pytest.fixture(name='<fixturename>')``.
    """

  

  大体翻译了一下:单元测试

def fixture(
    callable_or_scope=None,
    *args,
    scope="function",
    params=None,
    autouse=False,
    ids=None,
    name=None
):
    """
	一、fixture无论有没有参数,均可以用来标记夹具功能;
	二、test模块或类均可以使用'pytest.mark.usefixture(fixturename)'装饰器来标记,标记以后就每一个测试用例运行以前会调用fixturename;
	三、测试函数能够直接使用fixture名称做为输入参数,在这种状况下,fixture实例从fixture返回函数将被注入。
	四、fixture可使用' return '或' yield '来提供它们的值来测试函数语句。当使用'yield'语句后的代码块被执行不管测试结果如何,都必须精确地产生一次。

    :arg scope: scope做用域有4个级别,默认是function,其次class,而后是module和session.

    :arg params: 一个可选的形参列表,它将致使多个参数对夹具功能和全部测试的调用使用它。

    :arg autouse:若是为真,则对全部测试激活fixture func能够看到它。若是为False(默认值),则显式须要引用来激活夹具。 

    :arg ids: 每一个参数对应的字符串id列表所以它们是测试id的一部分。若是没有提供id它们将由参数自动生成。

    :arg name:设备的名称。方法的默认名称装饰功能。若是在同一模块中使用了一个fixture哪一个定义了,夹具的函数名会是被要求夹具的功能参数所遮蔽;的一种方法要解决这个问题,能够命名修饰后的函数'fixture_<fixturename>'而后使用
@pytest.fixture (name = ' < fixturename > ')。
    """
	
	

  

  Scope参数介绍与使用

  scope参数主要控制fixture做用范围,逻辑优先级是:session > module > class > function.
  scope参数有四种选择:function(测试函数级别),class(测试类级别),module(测试模块“.py”级别),session(多个文件级别)。默认是function级别。
  这里须要注意的pytest文档中讲的模块是针对".py"文件的叫法。也就是模块就是py文件的意思。
  学习

  级别介绍:
  function级别(针对函数):每一个测试用例运行以前运行
  class级别(针对测试类):每一个类执行一次(全部测试用例运行以前运行,这个节点从引入fixture的测试用例开始算),一个类能够有多个测试方法(用例)。
  module级别(针对单模块):每一个模块(.py)执行一次,无论类中测试方法仍是类外的测试方法。
  session级别(针对多模块):是多个文件调用一次,能够跨.py文件调用,每一个.py文件就是module。测试

  Fixture做用范围:scope = 'function'

  @pytest.fixture()函数使用方式:做为参数传入(单个)

  装饰器@pytest.fixture()若是不写参数,默认就是scope="function",它的做用范围是每一个测试用例来以前运行一次,销毁代码在测试用例运行以后运行。this

  以前的文章已经介绍过了,这里再贴一下代码:

  (单个fixture函数,没有类)

# 建立fixture函数(无类)——法1,做为参数传入,做为范围:functions
@pytest.fixture()
def login():
	print("输入帐号")
	a = "account"
	return a


def test_001(login):
	print("帐号是: %s"%login)
	assert login == "account"

def test_002():
	print("单击登录")

if __name__ == '__main__':
    pytest.main()

  运行结果:

plugins: allure-pytest-2.8.6, rerunfailures-5.0
collected 2 items                                                                                                                                                                       

fixtrue_001.py 输入帐号
帐号是: account
.单击登录
.

================================================================================== 2 passed in 0.02s ===================================================================================

  (单个fixture函数,有类)

# 建立fixture函数(类中)——法2,做为参数传入,做为范围:functions

@pytest.fixture(scope="function")
def login():
	print("输入帐号")
	a = "account"
	return a


class TestLogin:
	def test_001(self,login):
		print("输入的帐号: %s"%login)
		assert login == "account"
	def test_002(self):
		print("")


if __name__ == '__main__':
	pytest.main(["-s","fixtrue_001.py"])

  运行结果:

plugins: allure-pytest-2.8.6, rerunfailures-5.0
collected 2 items                                                                                                                                                                       

fixtrue_001.py 输入帐号
输入的帐号: account
.用例2
.

================================================================================== 2 passed in 0.02s ===================================================================================

  

  @pytest.fixture()函数使用方式:做为参数传入(多个fixture使用)

   一些场景,好比登录以后有退出,这样的话须要两个fixture函数处理,示例以下:

# fixture函数(类中) 做为多个参数传入
@pytest.fixture()
def login():
	print("输入帐号")
	a = "account"
	return a

@pytest.fixture()
def logout():
	print("退出")

class TestLogin:
	def test_001(self,login):
		print("输入的帐号: %s"%login)
		assert login == "account"
	def test_002(self,logout):
		print("退出")
	def test_003(self,login,logout):
		print("步骤1:%s"%login)
		print("步骤2:%s"%logout)


if __name__ == '__main__':
    pytest.main()

  

  运行结果:

plugins: allure-pytest-2.8.6, rerunfailures-5.0
collected 3 items                                                                                                                                                                       

fixtrue_001.py 输入帐号
输入的帐号: account
.退出
退出
.输入帐号
退出
步骤1:account
步骤2:None
.

================================================================================== 3 passed in 0.03s ===================================================================================

  

  @pytest.fixture()函数使用方式:做为参数传入(互相调用)

  fixture固件能够被测试方法调用,也能够被固件本身调用。

  举个例子:

# fixtrue做为参数,互相调用传入
@pytest.fixture()
def account():
	a = "account"
	print("输入帐号:%s"%a)

@pytest.fixture()
def login(account):
	print("单击登录")

class TestLogin:
	def test_1(self,login):
		print("操做结果:%s"%login)
	def test_2(self,account):
		print("帐号: %s"%account)
	def test_3(self):
		print("测试用例3")

if __name__ == '__main__':
    pytest.main()

  

  运行结果:

plugins: allure-pytest-2.8.6, rerunfailures-5.0
collected 3 items                                                                                                                                                                       

fixtrue_001.py 输入帐号:account
单击登录
操做结果:None
.输入帐号:account
帐号: None
.测试用例3
.

================================================================================== 3 passed in 0.02s ===================================================================================

  Fixture做用范围:scope = 'class'

  fixture是class级别的时候,分为两种状况:

  第一种,测试类下面全部测试方法(用例),都使用了fixture函数名,这样的话,fixture只在该class下全部测试用例执行前执行一次。

  示例演示:

# fixture做用域 scope = 'class'
@pytest.fixture(scope='class')
def login():
	a = '123'
	print("输入帐号密码登录")



class TestLogin:
	def test_1(self,login):
		print("用例1")
	def test_2(self,login):
		print("用例2")
	def test_3(self,login):
		print("用例3")
if __name__ == '__main__':
    pytest.main()

  

  运行结果:

collected 3 items                                                                                                                                                                       

fixtrue_001.py 输入帐号密码登录
用例1
.用例2
.用例3
.

================================================================================== 3 passed in 0.02s ===================================================================================

  

  第二种,测试类下面只有一些测试方法使用了fixture函数名,这样的话,fixture只在该class下第一个使用fixture函数的测试用例位置开始算,后面全部的测试用例执行前只执行一次。而该位置以前的测试用例就无论。

# fixture做用域 scope = 'class'
@pytest.fixture(scope='class')
def login():
	a = '123'
	print("输入帐号密码登录")



class TestLogin:
	def test_1(self):
		print("用例1")
	def test_2(self,login):
		print("用例2")
	def test_3(self,login):
		print("用例3")
	def test_4(self):
		print("用例4")
if __name__ == '__main__':
    pytest.main()

  

  运行结果:

collected 4 items                                                                                                                                                                       

fixtrue_001.py 用例1
.输入帐号密码登录
用例2
.用例3
.用例4
.

================================================================================== 4 passed in 0.03s ===================================================================================

 

  Fixture做用范围:scope = 'module'

  fixture为module时,对当前模块(.py)文件下全部测试用例开始前执行一次,示例以下:

# fixture scope = 'module'
@pytest.fixture(scope='module')
def login():
	print("登录")

def test_01(login):
	print("用例01")
def test_02(login):
	print("用例02")

class TestLogin():
	def test_1(self,login):
		print("用例1")
	def test_2(self,login):
		print("用例2")
	def test_3(self,login):
		print("用例3")

if __name__ == '__main__':
    pytest.main()

  

  运行结果:

collected 5 items                                                                                                                                                                       

fixtrue_001.py 登录
用例01
.用例02
.用例1
.用例2
.用例3
.

================================================================================== 5 passed in 0.03s ===================================================================================

  

  Fixture做用范围:scope = 'session'

  设置方式和module级别的设置方式同样,须要注意的是session级别通常都是多个.py文件共用,因此要前置函数的声明通常在conftest.py中。

  其做用在多个测试模块(.py文件)中只执行一次,而且是在传入函数名的测试用例中的第一个执行的测试用例以前执行。

  若是在同一个模块下(.py文件里),session与module特性一致,示例以下:

import pytest
@pytest.fixture(scope="session")
def login():
    print("\n输入用户名密码登录! configtest")
    yield
    print("退出登录")


def test_cart(login):
    print('用例1,登录后执行添加购物车功能操做')

def test_search():
    print('用例2,不登录查询功能操做')

def test_pay(login):
    print('用例3,登录后执行支付功能操做')

if __name__ == '__main__':
    pytest.main()

  

  运行结果:

plugins: allure-pytest-2.8.6, rerunfailures-5.0
collected 3 items                                                                                                                                                                       

fixtrue_003.py
输入用户名密码登录! configtest
用例1,登录后执行添加购物车功能操做
.用例2,不登录查询功能操做
.用例3,登录后执行支付功能操做
.退出登录


================================================================================== 3 passed in 0.02s ===================================================================================

  

  @pytest.fixture()函数使用方式:做为conftest.py文件传入

  若是在不一样模块下(.py文件里),session是给多个.py文件使用,而且写到conftest.py文件里,conftest.py文件名称是固定的,pytest会自动识别该文件。

  放到工程的根目录下,就能够全局调用了,若是放到某个package包下,那就只在该package内有效,示例以下:

  在文件夹fixture_exp下新建conftest.py文件:

# fixture 固定装饰器,做用域:scope = 'session'
import pytest
@pytest.fixture(scope='session')
def login():
    print("输入帐号密码")
    yield
    print("清理数据完成")

  

  新建两个测试文件:

# fixture scope = 'session',fixtrue_001.py
class TestLogin1():
	def test_1(self,login):
		print("用例1")
	def test_2(self):
		print("用例2")
	def test_3(self,login):
		print("用例3")


if __name__ == '__main__':
    pytest.main()

  

# fixture scope = 'session',fixtrue_002.py
import pytest

class TestLogin2():
	def test_4(self):
		print("用例4")
	def test_5(self):
		print("用例5")
	def test_6(self):
		print("用例6")


if __name__ == '__main__':
    pytest.main()

  同时运行两个测试文件,能够在控制台中输入:

pytest -s fixtrue_001.py fixtrue_002.py

  

  运行结果:

plugins: allure-pytest-2.8.6, rerunfailures-5.0
collected 6 items                                                                                                                                                                       

fixtrue_001.py 输入帐号密码
用例1
.用例2
.用例3
.
fixtrue_002.py 用例4
.用例5
.用例6
.清理数据完成


================================================================================== 6 passed in 0.04s ===================================================================================

  上面的例子,若是test_1测试用例没有传fixture函数名login的话,fixture装置将在执行test_3测试用例开始前执行一次,我去掉以后,再运行结果以下:

  做为conftest.py文件传入(扩展)

  上面讲的fixture做用域是session,通常结合conftest.py来使用,也就是做为conftest.py文件传入。

  使用背景:若是咱们有不少个前置函数,写在各个py文件中是不很乱?再或者说,咱们不少个py文件想要使用同一个前置函数该怎么办?这也就是conftest.py的做用。

  使用conftest.py的规则:

  conftest.py这个文件名是固定的,不能够更改。
  conftest.py与运行用例在同一个包下,而且该包中有__init__.py文件
  使用的时候不须要导入conftest.py,会自动寻找。
  来看个小栗子:咱们新建了一个conftest.py文件,将前置函数的声明写在里面;在同一包下又新建了一个测试模块,在测试方法中传入了conftest.py中声明的前置函数名。

# fixture 固定装饰器,做用域:scope = 'session'
import pytest
@pytest.fixture()
def login():
    print("输入帐号密码")
    yield
    print("清理数据完成")

  

import pytest
# fixture scope = 'session',fixtrue_001.py
class TestLogin1():
	def test_1(self,login):
		print("用例1")
	def test_2(self):
		print("用例2")
	def test_3(self,login):
		print("用例3")


if __name__ == '__main__':
    pytest.main()

  

  运行结果:

fixtrue_001.py 输入帐号密码
用例1
.清理数据完成
用例2
.输入帐号密码
用例3
.清理数据完成


================================================================================== 3 passed in 0.02s ===================================================================================

  

  上面的栗子能够换一种写法,但须要利用另一个装饰器。

  咱们在conftest.py中声明完前置函数后,在测试模块中除了使用传入函数名的方式,还可使用@pytest.mark.userfixtures()装饰器。

  举个小栗子:声明前置函数的过程和上面同样;咱们在每一个测试方法上都加了@pytest.mark.userfixtures()装饰器,传入了前置函数名做为参数;运行结果和上图同样便再也不展现。

import pytest
# fixture scope = 'session',fixtrue_001.py
class TestLogin1():
	@pytest.mark.usefixtures('login')
	def test_1(self):
		print("用例1")
	@pytest.mark.usefixtures('login')
	def test_2(self):
		print("用例2")
	def test_3(self):
		print("用例3")


if __name__ == '__main__':
    pytest.main()

  

fixtrue_001.py 输入帐号密码
用例1
.清理数据完成
输入帐号密码
用例2
.清理数据完成
用例3
.

================================================================================== 3 passed in 0.02s ===================================================================================

  

  若是有100个测试方法,这样就要写100个装饰器,是否是不方便?

  这个时候若是你想要模块中的每一个测试用例都调用该固件,你也可使用pytestmark标记:以下代码(注意pytestmark变量名不可更改),示例以下:

import pytest
# fixture scope = 'session',fixtrue_001.py
pytestmark = pytest.mark.usefixtures('login')
class TestLogin1():
	def test_1(self):
		print("用例1")
	def test_2(self):
		print("用例2")
	def test_3(self):
		print("用例3")


if __name__ == '__main__':
    pytest.main()

  

  运行结果:

fixtrue_001.py 输入帐号密码
用例1
.清理数据完成
输入帐号密码
用例2
.清理数据完成
输入帐号密码
用例3
.清理数据完成


================================================================================== 3 passed in 0.02s ===================================================================================

  

  注意:能够在测试函数前使用 @pytest.mark.usefixtures("fixture1","fixture2")标记测试函数或者测试类。与在测试方法中添加 fixture 参数差很少,可是使用 usefixtures 不能使用 fixture 的返回值。

  补充说明一下conftest.py文件的做用域是当前包内(包括子包);若是函数调用固件优先从当前测试类中寻找,而后是模块(.py文件)中,接着是当前包中寻找(conftest.py中),若是没有再找父包直至根目录;若是咱们要声明全局的conftest.py文件,咱们能够将其放在根目录下。

  conftest.py做用范围:测试类 > .py文件 > package

 

  Autouse参数介绍与使用

  调用fixture四种方法

  1.函数或类里面方法直接传fixture的函数参数名称

  2.使用装饰器@pytest.mark.usefixtures()修饰

  3.使用pytestmark = pytest.mark.usefixtures('login')

  4.autouse=True自动使用

  前面三种已经讲过,如今就是讲第四种。

  咱们在作自动化测试的时候,用例是很是多,若是每条用例都要去传入前置函数名或装饰器,很不方便。

  这时咱们可使用@pytest.fixture()中的参数autouse(自动使用),将其设为true(默认为false),这样每一个测试函数都会自动调用该前置函数了。

  举个小栗子:

import pytest

@pytest.fixture(autouse="true")
def login():
    print("输入帐号密码")


class TestLogin:
    def test_1(self):
        print("用例1")
    def test_2(self):
        print("用例2")

if __name__ == '__main__':
    pytest.main()

  

  运行结果:

============================== 2 passed in 0.05s ==============================

Process finished with exit code 0
输入帐号密码
PASSED                              [ 50%]用例1
输入帐号密码
PASSED                              [100%]用例2

  

  注意:

  对于那些不依赖于任何系统状态或者外部数据,又须要屡次运行的代码,能够在 fixture 中添加 autouse=True选项,例如 @pytest.fixture(autouse=True, scope="session")。

  可是,若是能够的话,尽可能应当选择参数传递或者 usefixtures 的方法而不是 autouse。autouse 会让测试函数逻辑看上去没有那么清晰,更像是一个特例。

 

  Params参数介绍与使用

  前面介绍Fixture定义的时候讲了params,:arg params: 一个可选的形参列表,它将致使多个参数对夹具功能和全部测试的调用使用它。

  1.fixture能够带参数,params支持列表;

  2.默认是None;

  3.对于param里面的每一个值,fixture都会去调用执行一次,就像执行for循环同样把params里的值遍历一次。

  

  举个例子:

import pytest
seq = [1,2,3]

@pytest.fixture(params=seq)
def test_data(request):
    print("参数")
    return request.param


class TestData:
    def test_1(self,test_data):
        print("用例",test_data)

if __name__ == '__main__':
    pytest.main()

  

运行结果:

 

  原理:

  在 pytest 中有一个内建的 fixture 叫作 request,表明 fixture 的调用状态。request 有一个字段 param,可使用相似 @pytest.fixture(param=tasks_list)的方式,在 fixture 中使用 request.param的方式做为返回值供测试函数调用。其中 tasks_list 包含多少元素,该 fixture 就会被调用几回,分别做用在每一个用到的测试函数上。

  Ids参数介绍与使用

  ids一般能够与params一块儿使用,因为没有指定 id,因此在输出时 pytest 会以 fixture 名加上数字做为标识,fixture 也能够指定 id,例如@pytest.fixture(param=tasks_list,ids=task_ids)  ids能够是列表,也能够是函数供 pytest 生成 task 标识。

  数字、字符串、布尔值和None将在测试ID中使用其一般的字符串表示形式,对于其余对象,pytest会根据参数名称建立一个字符串,能够经过使用ids关键字参数来自定义用于测试ID的字符串。

 举个例子:
import pytest
seq = [1,2,3]

@pytest.fixture(params=seq,ids=["a","b","c"])
def test_data(request):
    print("参数")
    # print(request)
    return request.param


class TestData:
    def test_1(self,test_data):
        print("用例",test_data)

if __name__ == '__main__':
    pytest.main()

  

  运行结果:

 

  Name参数介绍与使用

  一般来讲使用 fixture 的测试函数会将 fixture 的函数名做为参数传递,可是 pytest 也容许将 fixture 重命名。只须要使用 @pytest.fixture(name="new")便可,在测试函数中使用该 fixture 时只须要传入 new 便可。
 
import pytest


@pytest.fixture(name="new_fixture")
def test_name():
    pass

def test_1(new_fixture):
    print("测试用例1")

  

  运行结果:

collecting ... collected 1 item

fixture_test03.py::test_1 PASSED                                         [100%]测试用例1


============================== 1 passed in 0.03s ==============================

  总结:默认使用fixture函数名称做为参数,也就是test_name做为参数传入,若是使用name,就须要将name的值做为新的参数名称传递给测试函数使用。

  总结

  以上就是pytest框架中fixture函数的介绍与使用,每种参数都介绍了一遍,原理和方法了解好,以便在实践中驾轻就熟。若是对你有帮助或喜欢自动化测试开发的朋友,能够加入右下方QQ交流群学习与探索,更多干货与你分

享。

相关文章
相关标签/搜索