测试学习之–pytest的fixture使用

1. fixtures

fixture 的功能是: 能够实现前置动作或者重复性动作.

1. 简介

Setup 和Teardown 实现在测试用例之前或者之后添加一些操作, 但这种是整个脚本全局生效的,如果我想实现以下场景:   用例1需要先登录,用例2不需要登录,用例3需要先登录。很显然这就无法用setup和teardown来实现了.

fixture是pytest特有的功能,它用pytest.fixture标识,以装饰器形式定义在函数上面,在编写测试函数的时候,可以将此函数名称做为传入参数,pytest将会以依赖注入方式,将该函数的返回值作为测试函数的传入参数.

fixture有明确的名字,在其他函数,模块,类或整个工程调用它时会被激活。

fixture是基于模块来执行的,每个fixture的名字就可以触发一个fixture的函数,它自身也可以调用其他的fixture.

我们可以把fixture看做是资源,在你的测试用例执行之前需要去配置这些资源,执行完后需要去释放资源。比如module类型的fixture,适合于那些许多测试用例都只需要执行一次的操作。

fixture还提供了参数化功能,根据配置和不同组件来选择不同的参数。

fixture修饰器来标记固定的工厂函数,在其他函数,模块,类或整个工程调用它时会被激活并优先执行,通常会被用于完成预置处理和重复操作

例如,

  • 在测试网站的功能时,每个测试用例都要登录和退出,利用fixture就可以只做一次,否则每个测试用例都要做这两步也是冗余。
  • 完成setup和teardown操作,处理数据库或文件的打开、关闭操作
  • 准备测试数据.将数据提前写入数据库或通过params返回给测试用例

2. 使用方法

1
2
3
4
5
6
7
8
9
10
11
pytest.fixture(scope='function', params=None, autouse=False, ids=None, name=None)

常用参数解释:
- scope: 被标记方法的作用域, 可以传入以下四个值;
             "function": 默认值,表示每个测试方法都要执行一次
        "class": 作用于整个类, 表示每个类的所有测试方法只运行一次
        "module": 作用于整个模块, 每个module的所有测试方法只运行一次.
        "session": 作用于整个session, 每次session只运行一次. ??(此方法慎用!!)

- params: list类型,默认None, 接收参数值,对于param里面的每个值,fixture都会去遍历执行一次.
- autouse: 是否自动运行,默认为false, 为true时此session中的所有测试函数都会调用fixture

注意: scope 参数的作用域,不是说只能作用于函数,类,而是作用的范围。如,function, 表示每个测试方法都要执行一次, 但仅仅作用于单独的函数;class内的无效, 但可以使用标记函数pytest.mark.usefixtures()实现

3. 基本使用

测试用例可以通过在其参数中使用fixtures名称来接收fixture对象。 每个fixture参数名称所对应的函数,可以通过使用@pytest.fixture注册成为一个fixture函数,来为测试用例提供一个fixture对象

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import pytest

# 将这个函数注册成为一个fixture函数, 默认的是 函数级别的
@pytest.fixture()
def user_login():
    print('登陆成功')

def test_center(user_login):
    print('用户中心')

# 在类中使用 fixture 工具 ,当 fixture 为函数级别的时候 类中的每一个测试函数执行前都会执行
@pytest.mark.usefixtures('user_login')
class TestUser():

    def test_payment(self):

        print('支付成功')

    def test_user(self):
        print('获取用户信息')

输出的信息如下

1
2
3
4
5
6
7
8
9
collected 3 items                                                                                                          
test_fix_gj.py 登陆成功
用户中心
.登陆成功
支付成功
.登陆成功
获取用户信息
.
============================== 3 passed in 0.04s =========================================

4. 共享 fixture 函数

如果在测试中多个测试文件中用例用到同一个的fixture函数,则可以将其移动到conftest.py文件中,所需的fixture对象会自动被Pytest发现,而不需要再每次导入。

1
2
3
4
5
6
7
8
9
10
# conftest.py 此名字不能改变,否则无效

import pytest
import requests

# 默认是function级别的
@pytest.fixture()
def baidu_login():
    """可以把函数作为参数传递"""
    print("\n公用的登陆方法")

新建test_fix.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pytest


def test_get_carts():
    """购物车不需要登陆"""
    print("测试查询购物车")

@pytest.mark.usefixtures('baidu_login')
class Test_fixtures:
    """需要登陆的信息"""

    def test_get_user_info(self):
        print("获取用户信息")

    def test_order_info(self):
        print("查询订单信息")

运行结果如下, 发现可以灵活的使用fixtures函数,

1
2
3
4
5
6
7
8
9
10
11
12
ollected 3 items                                                                                                                        

test_fix.py 测试查询购物车
.
公用的登陆方法
获取用户信息
.
公用的登陆方法
查询订单信息
.

============================================================ 3 passed in 0.01s ======================

5. fixture的参数

scope 设置不同级别

  • function 默认, 可以不设置. 每个调用fixture的都会执行
  • Class 在测试类中只执行一次, 若和method一起使用, 互不影响
  • Module 作用于整个模块, 模块中所有的测试方法只运行一次
  • Session 作用于一个session, 每个session只运行一次

params

  • list类型,默认None, 接收参数值,对于param里面的每个值,fixture都会去遍历执行一次.

autouse

  • 是否自动运行,默认为false, 为true时此session中的所有测试函数都会调用fixture

1. scope 参数

fixture 的 默认的是 function 级别,该级别,当使用的这个fixture的时候,无论是普通的测试函数还是测试类中的方法,都会在每个函数/方法 前执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@pytest.fixture()
def login():
    print('\nscope参数====登录成功')


def test_center(login):
    print('\n 用户中心')

@pytest.mark.usefixtures('login')
class TestScope():
    def test_pay(self):
        print('\n支付')

    def test_area(self):
        print('\n 地址')

输出结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
test_scope.py
scope参数====登录成功

 用户中心
.
scope参数====登录成功

支付
.
scope参数====登录成功

 地址
.

class 级别 普通函数使用的时候会在每个函数执行前执行一次 若是类使用,无论类中有几个方法,在这个类中只执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@pytest.fixture(scope='class')
def login():
    print('\nscope参数====登录成功')


def test_hello(login):
    print('\n hello')

def test_center(login):
    print('\n 用户中心')

@pytest.mark.usefixtures('login')
class TestScope():
    def test_pay(self):
        print('\n支付')

    def test_area(self):
        print('\n 地址')

执行结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
test_scope.py
scope参数====登录成功

 hello
.
scope参数====登录成功

 用户中心
.
scope参数====登录成功

支付
.
 地址

module 级别 在一个模块中(无论几个函数使用它)只执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@pytest.fixture(scope='module')
def login():
    print('\nscope参数====登录成功')


def test_hello(login):
    print('\n hello')

def test_center(login):
    print('\n 用户中心')

@pytest.mark.usefixtures('login')
class TestScope():
    def test_pay(self):
        print('\n支付')

    def test_area(self):
        print('\n 地址')

输出结果为

1
2
3
4
5
6
7
8
9
10
11
test_scope.py
scope参数====登录成功

 hello
.
 用户中心
.
支付
.
 地址
.

session 级别 再一次会话中只执行一次(一次会话指的是一次运行程序)

conftest.py

1
2
3
4
5
import pytest

@pytest.fixture(scope='session')
def login():
    print('\nscope参数====登录成功')

test_scope.py

1
2
3
4
5
6
7
@pytest.mark.usefixtures('login')
class TestScope():
    def test_pay(self):
        print('\n支付')

    def test_area(self):
        print('\n 地址')

test_scope2.py

1
2
3
4
5
def test_hello(login):
    print('\n hello')

def test_center(login):
    print('\n 用户中心')

当使用 pytest -s 命令行在当前目录下运行的时候 test_scope.py,test_scope2.py文件都会运行, 运行一次程序。这是一次会话,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
test_scope.py
scope参数====登录成功

支付
.
 地址
.
test_scope2.py
 hello
.
 用户中心
.

2. param 参数

Pytest.fixture(params=None) 接收list类型的参数, 对于param里面的每个值,fixture都会去遍历执行一次.

即在函数使用 fixture的时候, 列表中有几个值,函数就会执行几次

conftest.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
test_user_data = [{"user": "admin1", "passwd": "111111"},
                  {"user": "admin12", "passwd": ""}]

# 使用 param 参数 则 被装饰的函数 需要传递一个参数 即 request  , 对于param里面的每个值,fixture都会去遍历执行一次
@pytest.fixture(params=test_user_data)
def param_user(request):
    # request 参数的类型为 <SubRequest 'param_user' for <Function test_username[param_user0]>>
    print(request)
    # 使用 request.param可以获取 这次遍历的 params 列表中的值 {'user': 'admin1', 'passwd': '111111'} 和 {'user': 'admin12', 'passwd': ''}
    print(request.param)
    user = request.param.get('user')
    passwd = request.param.get('passwd')
    print('用户名:%s 密码是:%s'%(user,passwd))
    if passwd:
        return True
    else:
        return False

test_param.py文件

1
2
3
4
import pytest

def test_username(param_user):
    print('测试结果为:',param_user)

输出的结果为:

1
2
3
4
5
6
7
8
test_param.py <SubRequest 'param_user' for <Function test_username[param_user0]>>
{'user': 'admin1', 'passwd': '111111'}
用户名:admin1 密码是:111111
测试结果为: True
.<SubRequest 'param_user' for <Function test_username[param_user1]>>
{'user': 'admin12', 'passwd': ''}
用户名:admin12 密码是:
测试结果为: False

案例2

conftest.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 测试账号数据
test_user = ["admin1", "admin2"]
test_password = ["11111", "22222"]

@pytest.fixture(params=test_user)
def input_user(request):
    user = request.param
    print('登录账号:',user)
    return user

@pytest.fixture(params=test_password)
def input_password(request):
    password = request.param
    print('登录密码:',password)
    return password

test_param.py文件

1
2
3
# 当 同一个 函数使用多个 fixture的时候, 会对这几个fixture进行组合测试每一种情况
def test_login(input_user,input_password):
    print("测试数据user-> %s, password-> %s" % (input_user,input_password))

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
collected 4 items

test_param.py 登录账号: admin1
登录密码: 11111
测试数据user-> admin1, password-> 11111
.登录账号: admin1
登录密码: 22222
测试数据user-> admin1, password-> 22222
.登录账号: admin2
登录密码: 11111
测试数据user-> admin2, password-> 11111
.登录账号: admin2
登录密码: 22222
测试数据user-> admin2, password-> 22222

3. autouse 参数

autouse 默认为false,不会自动执行, 设置为true时此session中的所有测试函数都会调用fixture

conftest.py文件

1
2
3
@pytest.fixture(autouse=True)
def before():
    print('\nbefore each test')

test_autouse.py

1
2
3
4
5
6
def test_one():
    print('one----')

class Test1():
    def test_2(self):
        print('two----')

运行结果

1
2
3
4
5
6
7
8
collected 2 items                                            

test_param.py
before each test
one----
.
before each test
two----

6. 实现setup和teardown

我们可以使用conftest.py 对测试用例实现setup, 那我们也能实现teardown的类似功能.

我们可以使用yield 来实现

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# conftest.py


import pytest

info = {"username": "root", "password": "mysql"}

@pytest.fixture(scope='module')
def login_ini():
    # 建立数据库链接
    db = pymysql.connect(host="127.0.0.1", user=info['username'], password=info['password'], database='school')
    cursor = db.cursor()

    # 使用 execute()  方法执行 SQL 查询
    cursor.execute("select s_name from student")
    # 获取所有的数据
    data = cursor.fetchall()
    print(data)

    # 获取断开数据库连接
    # 使用 yield 实现 teardown的类似功能.即测试函数执行结束后执行yield后面的内容
    # yield login_ini
    # 可以直接写个 yield 不用写其他的
    yield
    cursor.close()
    db.close()
    print("断开连接了")

test_fix_setup.py

1
2
3
4
5
def test_login(login_ini):
    print('查询数据库成功')

def test_2(login_ini):
    print('测试*****')

运行结果

1
2
3
4
5
6
7
8
collected 1 item                                            

test_fix_setup.py (('赵雷',), ('钱电',), ('孙风',), ('李云',), ('周梅',), ('吴兰',), ('郑竹',), ('王菊',))
查询数据库成功
.测试*****
.断开连接

============================================================ 2 passed in 0.04s ========