自动化测试框架Pytest No.3(Pytest:fixture)

Pytest:fixture

前文提到,测试用例加上setup和teardown可以实现在用例之前或之后加入一些操作,但这种是整个脚本全局生效的。
如果想实现场景:用例1需要先登录,用例2不需要登录,用例3需要先登录。这种无法用setup和teardown来实现,需要自定义测试用例的预置条件。

一、fixture优势

  1. 命名方式灵活,不局限于setup和teardown这几个命名
  2. conftest.py配置里可以实现数据共享,不需要import就能自动找到一些配置
  3. scope="module"可以实现多个.py 跨文件共享前置
  4. scope="session"可以实现多个.py 跨文件使用一个session来完成多个用例

二、使用装饰器标记fixture的功能

@pytest.fixture(scope=“function”,params=None,autouse=False,ids=None,name=None)

可以使用此装饰器(带或不带参数)来定义fixture功能。
fixture功能的名称可以在以后使用,引用时会在运行测试之前调用。

调用fixture的三种方式:

  1. 函数或类里面方法直接传fixture的函数参数名称
  2. test模块或类,使用装饰器@pytest.mark.usefixtures(fixturename)修饰;类里面所有测试用例都会调用该 fixture
  • 测试功能可以直接使用fixture名称作为输入参数,在这种情况下,夹具实例从fixture返回功能将被注入
  • 可以叠加多个 @pytest.mark.usefixtures() ,先执行的放底层,后执行的放上层
  • 可以传多个 fixture 参数,先执行的放前面,后执行的放后面
  • 如果 fixture 有返回值,用 @pytest.mark.usefixtures() 是无法获取到返回值的,必须用传参的方式(方式1)
  1. autouse=True:每个测试函数都会自动调用该fixture,无需传任何参数,作用范围跟着scope走(谨慎使用)

fixture参数

参数说明
scope用于控制fixture的作用范围,作用类似于Pytest的setup/teardown。
params一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它。
autouse如果为True,则为所有测试激活fixture func 可以看到它。 如果为False(默认值)则显式需要参考来激活fixture。
ids每个字符串id的列表,每个字符串对应于params 这样他们就是测试ID的一部分。 如果没有提供ID它们将从params自动生成。
namefixture的名称,默认为装饰函数的名称。 如果fixture在定义它的同一模块中使用,夹具的功能名称将被请求夹具的功能arg遮蔽; 解决这个问题的一种方法是将装饰函数命名 fixture_<fixturename>然后使用@ pytest.fixture(name ='<fixturename>')

fixture可以选择使用yield语句为测试函数提供它们的值,而不是return。 在这种情况下,yield语句之后的代码块作为拆卸代码执行,而不管测试结果如何。fixture功能必须只产生一次。

1. scope

  • 有四个级别的参数,默认取值为function(函数级别),控制范围的排序为:session > module > class > function
取值范围说明
function函数级每一个函数或方法都会调用
class类级别每个测试类只运行一次
module模块级每一个.py文件调用一次
session会话级每次会话只需要运行一次,会话内所有方法及类,模块都共享这个方法

scope = "function"
语法:

@pytest.fixture()
or
@pytest.fixture(scope=‘function’)

场景一:做为参数传入

import pytest

# fixture 函数(类中)作为多个参数传入
@pytest.fixture()
def login():
    print("打开浏览器")
    a = "account"
    return a

@pytest.fixture()
def logout():
    print("关闭浏览器")

class TestLogin:
    # 传入login fixture

    def test_001(self, login):
        print("001传入了login fixture")
        assert login == "account"

    def test_002(self, logout):
        print("002传入了logout fixture")

    def test_003(self, login, logout):
        print("003传入了两个fixture")

    def test_004(self):
        print("004未传入仍何fixture哦")

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

在这里插入图片描述

从运行结果可以看出,fixture做为参数传入时,会在执行函数之前执行该fixture函数,再将值传入测试函数做为参数使用。这个场景多用于登录。

场景二:fixture传递测试数据

import pytest

@pytest.fixture()
def fixturefun():
    return (1,2,3,4)

def test_one(fixturefun):
    assert fixturefun[0] == 2

def test_two(fixturefun):
    assert fixturefun[1] == 2

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

在这里插入图片描述

场景三:fixture的相互调用

import  pytest
# fixture作为参数,互相调用传入
@pytest.fixture()
def account():
    a = "account"
    print("第一层fixture")
    return a

# fixture的相互调用一定是要在测试类里调用这层fixture才会生效,普通函数单独调用是不生效的

@pytest.fixture()
def login(account):
    print("第二层fixture")

class TestLogin:
    def test_1(self, login):
        print("直接使用第二层fixture,返回值为{}".format(login))

    def test_2(self, account):
        print("只调用account fixture,返回值为{}".format(account))

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

在这里插入图片描述
fixture相互调用时,会先找到所有的fixture,再一层层执行。
当有多层调用时,直接被调用的fixture(这里指login),不会将上一层的值自动返回,这里跟函数的调用一样。

注:

  1. 即使fixture之间支持相互调用,但普通函数直接使用fixture是不支持的,一定是在测试函数内调用才会逐级调用生效
  2. 有多层fixture调用时,最先执行的是最后一层fixture,而不是先执行传入测试函数的fixture
  3. 上层fixture的值不会自动return,类似函数相互调用的逻辑

scope = "class"
当测试类内的每一个测试方法都调用了fixture,fixture只在该class下所有测试用例执行前执行一次
测试类下面只有一些测试方法使用了fixture函数名,这样的话,fixture只在该class下第一个使用fixture函数的测试用例位置开始算,后面所有的测试用例执行前只执行一次。而该位置之前的测试用例就不管。

语法:

@pytest.fixture(scope=‘class’)

场景一

import pytest
# fixture 作用域scope = 'class'

@pytest.fixture(scope='class')
def login():
    print("scope为class")

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(["-s", "test_fixture3.py"])

在这里插入图片描述
场景二:第一次调用的函数后面开始执行

import pytest

@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(["-s", "test_fixture3.py"])

在这里插入图片描述
scope = "module"
与class相同,只从.py文件开始引用fixture的位置生效
场景、只对开始引用的测试方法以下的类和方法生效

import pytest
# fixture scope = 'module'
@pytest.fixture(scope='module')
def login():
    print("fixture的范围为module")

def test_01():
    print("用例01")

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

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

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

在这里插入图片描述

scope = "session"
用法将在conftest.py文章内详细介绍

  • session的作用范围是针对.py级别的,module是对当前.py生效,seesion是对多个.py文件生效
  • session只作用于一个.py文件时,作用相当于module
  • session多数与contest.py文件一起使用,作为全局fixture

2. params

  • fixture的可选形参列表,支持列表传入
  • 默认None,每个param的值,fixture都会去调用执行一次,类似for循环
  • 可以与参数ids一起使用,作为每个参数的标识,详见ids
  • 被fixture装饰的函数要调用是采用:Request.param

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

import pytest

@pytest.fixture(params = [1,2,{'a':1,'b':2},(4,5,5)])
def demo(request):
    return request.param

def test_f(demo):
    print("值{}".format(demo))

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

在这里插入图片描述
3. ids

  • 用例标识ID
  • 与params配合使用,一对一关系

场景:配置ids之后

import pytest

@pytest.fixture(params = [1,2,{'a':1,'b':2},(4,5,5)],ids=['one','two','three','four'])
def demo(request):
    return request.param

def test_f(demo):
    print("值{}".format(demo))

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

4.autouse

  • 默认False
  • 若为True,则每个测试函数都会自动调用该fixture,无需传入fixture函数名

场景:当autouse=True时,即使测试类和测试方法没有传入函数名,用例执行时也都调用了fixture

import pytest

@pytest.fixture(autouse="true")
def login():
    print("当autouse为true时")

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

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

在这里插入图片描述
5.Name

  • fixture的重命名

  • 使用 fixture 的测试函数会将 fixture 的函数名作为参数传递,但是 pytest
    也允许将fixture重命名

  • 如果使用了name,那只能将name传递,函数名不再生效

    调用方法:@pytest.mark.usefixtures(‘fixture1’,‘fixture2’)

场景:重命名后,用函数名作为fixture名,执行用例时会找不到

import pytest

@pytest.fixture(name="new_fixture")
def test_name():
    pass
# 使用name参数后,传入重命名函数,执行成功
def test_1(new_fixture):
    print("使用name参数后,传入重命名函数,执行成功")
# 使用name参数后,传入函数名称,执行失败
def test_2(test_name):
    print("使用name参数后,传入函数名称,执行失败")

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

在这里插入图片描述

三、conftest.py

fixture参数传入:scope=“function”

场景一、在同一个.py文件中,多个用例调用一个登陆功能:用例1需要先登录,用例2不需要登录,用例3需要先登录

import pytest

# 不带参数时默认 scope="function"
@pytest.fixture()

def login():
    print("输入账号,密码先登录")

def test_a(login):
    print("用例1:登陆之后,动作111")

# 不传login
def test_b():
    print("用例2:不需要登陆,操作222")

def test_c(login):
    print("用例3:登陆之后,动作333")

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

在这里插入图片描述

conftest.py

conftest.py是什么?
conftest.py是fixture函数的一个集合,可以理解为公共的前置函数,提取出来放在一个文件里,然后供其它模块调用。

conftest.py使用场景
如果我们有很多个前置函数,写在各个py文件中是不很乱?再或者说,我们很多个py文件想要使用同一个前置函数的话,就不能把前置函数写在用例里,此时应该要有一个配置文件,单独管理一些预置的操作场景,pytest默认读取conftest.py里面的配置。

conftest.py使用原则

  1. conftest.py配置脚本名称是固定的,不能改名称
  2. conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件
  3. 不同于普通被调用的模块不需要import导入conftest.py,python会自动查找

场景二、使用conftest.py配置前置函数

# __init__.py
# conftest.py
import pytest

@pytest.fixture()
def login():
    print("输入账号,密码先登录")
# test_fixt1.py
import pytest

def test_a(login):
    print("用例1:登陆之后,动作111")

# 不传login
def test_b():
    print("用例2:不需要登陆,操作222")

def test_c(login):
    print("用例3:登陆之后,动作333")

if __name__ ==  '__main__':
    pytest.main(["-s", "test_fixt1.py"])
# test_fixt2.py
import pytest

def test_d(login):
    print("用例4:登陆之后,动作444")

# 不传login
def test_e():
    print("用例2:不需要登陆,操作555")

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

在这里插入图片描述
在这里插入图片描述

上述场景中,单独运行test_fix1.py和test_fix2.py都能调用到login()方法,这样就能实现一些公共的操作可以单独拿出来放在conftest.py配置文件中。

conftest.py的作用域

conftest.py的作用范围跟随着scope的范围

py文件的目录结构
在这里插入图片描述

# 内层用例文件
import pytest
class TestLogin():
    # 调用了内层fixture
    def test_1(self,login2):
        print("用例1")

    # 调用了外层fixture
    def test_2(self, login1):
        print("用例2")

if __name__ == '__main__':
    pytest.main(["-s", "test_b.py"])
# 内层conftest.py文件
import pytest
@pytest.fixture(scope="session")
def login2():
    print("内层fixture前置")
# 外层用例文件
import pytest
class TestLogin():
    # 调用了内层fixture
    def test_1(self, login2):
        print("用例1")

    # 调用了外层fixture
    def test_2(self, login1):
        print("用例2")

if __name__ == '__main__':
    pytest.main(["-s", "test_a.py"])
# 外层conftest.py文件
import pytest
@pytest.fixture()
def login1():
    print("外层fixture")

在这里插入图片描述

内层用例执行时,内、外层的fixture文件都能被找到并执行。

在这里插入图片描述
外层用例执行时,外层调用内层的fixture文件执失败。

conftest.py
fixture参数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值