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参数名称所对应的函数,可以通过使用
示例代码如下:
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函数,则可以将其移动到
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 ======== |