欢迎光临
我们一直在努力

Pytest测试框架全攻略

开始使用

使用规则

1、命名规则

1).py文件:文件名称以test_开头(或以_test结尾)。

例如login_test.py、test_login.py
2)测试类:类名以Test开头且不能带init方法。

例如class TestTestPaper
3)函数:方法名以test_开头
 

运行参数

pytest -q:简洁输出运行结果信息

pytest -k:TODO

-v:输出详细信息

-s:输出调试信息

-x:出现一个用例失败则停止测试

–maxfail:出现几个失败才终止,如:pytest –maxfail=2

-n:多线程运行 (插件:pytest-xdist) 如:pytest -vs -n=2

–reruns num:失败重跑 (插件:pytest-rerunfailures)如:pytest -reruns=2

–html:生成html的测试报告 (插件:pytest_html)

-k:运行名称中包含关键字的测试用例 如:

单个关键字:pytest -k "baili"

多个关键字or:pytest -k "baili or xingyao"

-m:运行指定标记的测试用例。支持 and、or 、not 等表达式

-m login or logout

装饰器mark

分为内置装饰器、自定义装饰器

下面是自定义的名为slow的mark装饰器,所有被这种标注的函数都会被执行测试

@pytest.mark.slow

def test_a():

xxx

运行:pytest -m slow

通过pytest.ini文件设置参数时,文件内容如下

运行方式

命令行运行方式

1、运行指定测试文件

pytest test_login.py

2、运行指定文件夹路径下的所有测试文件

pytest testcase/test_login.py

3、运行指定python包下的所有测试文件

pytest –pyargs pkg.testing

4、运行测试文件指定的Class、function

pytest test_mod.py::test_func
pytest test_mod.py::TestClass::test_method

从Python代码调用

前面提到的比如pytest –pyargs pkg.testing,都是直接在命令行中运行

也可以在python代码中运行。通常在项目根目录下新建run.py进行调用,不会在测试用例文件中写main方法

import pytest

if __name__ == '__main__':
pytest.main(['-vs'])

通过全局配置文件pytest.ini运行

1、一般放在项目根目录,名称必须是pytest.ini

2、当有中文时,可以将文件编码修改成ANSI

3、pytest.ini文件可以改变默认的测试用例规则

4、不管是命令行运行,还是主函数运行,都会加载这个配置文件

addopts:运行参数,详细见前文

testpaths:指定测试用例所在路径

python_files:.py文件的命名可以被pytest识别的规则,默认test_*.py。可自定义的意思

python_classes:类的命名可以被pytest识别的规则,默认Test*。可自定义的意思

python_functions:函数命名可以被pytest识别的规则,默认test_*。可自定义的意思

marker:标记

[pytest]
# 参数
addopts = -vs
testpaths = ./testcases
python_files = test_*.py
python_classes = Test*
python_functions = test_*
# 标记
makers =
smoke:冒烟用例
product_manage:商品管理

标记的使用场景:如果只运行冒烟用例

最好搭配–strict,如果代码中标记打错了,还能提示找不到xx标记

1、修改pytest.ini:addopts = -vs -m "smoke" –strict

[pytest]
# 参数
addopts = -vs -m "smoke"

2、给测试类、函数添加@pytest.mark.smoke

@pytest.mark.smoke
Test_firstpage():
xxx

def test_register():
xxx

@pytest.mark.smoke
def test_login():
xxx

核心功能

断言

就是使用python自己的断言

关键字:>、<、==、in、is

有以下几种类型:

assert 0 == 1
assert 0 < 1, "判断0小于1"
assert 'a' in 'abc'
flag = True
assert flag is True

def f():
return 3

def test_function():
assert f() == 4

跳过测试用例

无条件跳过

@pytest.mark.skip(reason='无条件跳过')
def test_one():

有条件跳过

workage = 8

@pytest.mark.skipif(workage < 10, reason='工作经验小于10年,跳过')
def test_one():

前后置操作

继承方式

class类下的用例都会应用,不能对部分函数应用

项目结构

class CommonUtil:
def setup(self):
print('setup')

def teardown(self):
print('teardown')

def setup_class(self):
print('setup_class')

def teardown_class(self):
print('teardown_class')

class TestLogin(CommonUtil):#继承CommonUtil类

def test_a(self):
print('test_a')

import pytest

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

[pytest]
addopts = -vs –html=./day02/reports/report.html

运行结果

fixture方式

相比继承方式更加灵活,可以实现全部、部分测试用例前后置

scope参数:作用域。默认None,枚举值有function、class、session/package、module

autouse参数:自动执行。默认False,

params参数:

ids参数:

name参数:

scope参数
前置操作

给前后置函数connect_app,加上@pytest.fixture(scope='function'),function表示作用域为函数级别。到此只完成了前后置函数的定义

import pytest

@pytest.fixture(scope='function')
def connect_app():
print('connect_app')

class TestLogin():
def test_a(self):
print('test_a')

def test_b(self):
print('test_b')

由于并没有函数调用,所以没有函数会执行前后置函数。输出如下

如何让当前.py文件下的所有函数都执行前后置操作?

@pytest.fixture(scope='function', autouse=True),autouse默认是False

import pytest

@pytest.fixture(scope='function', autouse=True)
def connect_app():
print('connect_app')

class TestLogin():
def test_a(self):
print('test_a')

def test_b(self):
print('test_b')

输出如下

如何只让test_b函数执行前后置函数?

def test_b(self, connect_app)

import pytest

@pytest.fixture(scope='function')
def connect_app():
print('connect_app')

class TestLogin():
def test_a(self):
print('test_a')

def test_b(self, connect_app): #在函数的入参中加上前后置函数名称
print('test_b')

输出如下

后置操作

yield关键字,在connect_app函数加上后置操作

import pytest

@pytest.fixture(scope='function')
def connect_app():
print('connect_app')
yield
print('disconnect app')

class TestLogin():
def test_a(self):
print('test_a')

def test_b(self, connect_app): #在函数的入参中加上前后置函数名称
print('test_b')

输出如下

支持多个前后置函数

给test_b函数添加2个前后置操作,def test_b(self, connect_app, exe_sql)

import pytest

@pytest.fixture(scope='function')
def connect_app():
print('connect_app')
yield
print('disconnect app')

@pytest.fixture(scope='function')
def exe_sql():
print('创建数据库连接')
yield
print('关闭数据库连接')

class TestLogin():
def test_a(self):
print('test_a')

def test_b(self, connect_app, exe_sql): #在函数的入参中加上前后置函数名称
print('test_b')

输出如下

前后置操作可以传递返回值

yield还支持返回值传递

yield '连接成功':返回'连接成功'

print(exe_sql):打印前置操作返回值

import pytest

@pytest.fixture(scope='function')
def exe_sql():
print('创建数据库连接')
yield '连接成功'
print('关闭数据库连接')

class TestLogin():
def test_a(self):
print('test_a')

def test_b(self, exe_sql): #在函数的入参中加上前后置函数名称
print('test_b')
print(exe_sql)

类如何调用

@pytest.fixture(scope='class')

import pytest

@pytest.fixture(scope='class')
def exe_sql():
print('创建数据库连接')
yield '连接成功'
print('关闭数据库连接')

@pytest.mark.usefixtures("exe_sql")
class TestLogin():
def test_a(self):
print('test_a')

def test_b(self): #在函数的入参中加上前后置函数名称
print('test_b')
print(exe_sql)

输出如下

params参数

list中有n组数据test_b函数就会执行n次

request、request.param:固定写法,不能重命名

import pytest

# 这里假装读取ymal文件
def read_yaml():
return [{"name": "amy"}, {"age": "12"}]

@pytest.fixture(scope='function', params=read_yaml())
def exe_sql(request):
print('创建数据库连接')
yield request.param
print('关闭数据库连接')

def test_b(self, exe_sql):
print('test_b')
print(str(exe_sql)) # list需要转换一下

输出结果

ids参数

不能单独使用,必须和params一起使用,作用是给参数起别名

@pytest.fixture(scope='function', params=read_yaml(), ids=['name', 'age'])

name参数

给fixture起别名

注意:使用了别名,fixture名称就不能用了,只能用别名

@pytest.fixture(scope='function', params=read_yaml(), ids=['name', 'age'], name='db')

def test_b(self, db):
print('test_b')
print(str(db))

fixture结合conftest.py文件使用

1、conftest.py文件名称固定,不能变。测试用例调用fixture时不需要导包。

2、conftest.py文件中fixture可以被多个测试用例调用;一个测试用例可以调用多个fixture。

3、conftest.py文件中的fixture放在项目根目录时,全局可用。

4、每个测试用例所在文件夹都可以有自己的conftest.py文件。

5、conftest.py文件中的fixture,autouse参数一般都会把它设置为True。

下面的例子基于autouse=True

项目结构

全局conftest.py

import pytest

def read_yaml():
return [{"name": "amy"}, {"age": "12"}]

@pytest.fixture(scope='function', autouse=True, params=read_yaml(), ids=['name', 'age'], name='db')
def exe_sql(request):
print('创建数据库连接')
yield request.param
print('关闭数据库连接')

testcase文件夹下的conftest.py

import pytest

@pytest.fixture(scope='function', autouse=True, name='connect')
def connect_app():
print('connect_app')
yield
print('disconnect app')

测试用例有2个,如下

class TestLogin:

def test_a(self):
print('test_a')

def test_b(self):
print('test_b')
print(str(db))

import pytest

def test_unbind_card():
print('unbind_card')

输出如下

day02/testcase/test_login.py::TestLogin::test_a[name] 创建数据库连接
connect_app
test_a
PASSEDdisconnect app
关闭数据库连接

day02/testcase/test_login.py::TestLogin::test_a[age] 创建数据库连接
connect_app
test_a
PASSEDdisconnect app
关闭数据库连接

day02/testcase/test_login.py::TestLogin::test_b[name] 创建数据库连接
connect_app
test_b
{'name': 'amy'}
PASSEDdisconnect app
关闭数据库连接

day02/testcase/test_login.py::TestLogin::test_b[age] 创建数据库连接
connect_app
test_b
{'age': '12'}
PASSEDdisconnect app
关闭数据库连接

day02/testcase/test_unbind_card.py::test_unbind_card[name] 创建数据库连接
connect_app
unbind_card
PASSEDdisconnect app
关闭数据库连接

day02/testcase/test_unbind_card.py::test_unbind_card[age] 创建数据库连接
connect_app
unbind_card
PASSEDdisconnect app
关闭数据库连接

多线程,scope='session'的fixture也只会执行一次

@pytest.fixture(scope='session', autouse=True, name='connect')

setup、setup_class、fixture、conftest.py优先级

全局conftest.py优先级高于局部conftest.py

1、会话:fixture的scope=session的优先级最高。

2、类:fixture的scope=class优先级高于setup_class。

3、函数:fixture的scope=function优先级高于setup。

总排行榜

NO1:[全局]conftest.py的fixture的scope=session

NO2:[局部]conftest.py的fixture的scope=session

NO3:[全局]conftest.py的fixture的scope=class

NO4:[局部]conftest.py的fixture的scope=class

NO5:setup_class

NO6:[全局]conftest.py的fixture的scope=function

NO7:[局部]conftest.py的fixture的scope=function

NO8:setup

总结:pytest的执行过程

1、查找[全局]conftest.py文件。

2、查找pytest.ini文件,找出测试用例位置。

3、根据测试用例位置,查找[局部]conftest.py文件。

4、查找[局部]conftest.py文件中的setup_class、teardown_class、setup、teardown

4、根据pytest.ini文件中(如有,没有则使用pytest默认的)命名规则,找出测试用例并执行

数据驱动

@pytest.mark.parametrize(args_name,args_value)

虽然前后置操作中也提到了params参数,但常用parametrize

1个参数,使用场景:

1)元祖

2)list

@pytest.mark.parametrize('name', {"夏利", "王柏"})
def test_c(self, name):
print('test_c:' + name)

@pytest.mark.parametrize('age', ["12", "18"])
def test_d(self, age):
print('test_d:' + age)

2个参数,需要解包,使用场景:

1)list中包了元组

2)list中包了list

@pytest.mark.parametrize('name,age', [{"小李", "12"}])
def test_a(self, name, age):
print('test_a:' + name + ' ' + age)

@pytest.mark.parametrize('name,age', [["小李", "12"], ["小王", "18"]])
def test_b(self, name, age):
print('test_b:' + name + ' ' + age)

结果可以看出list中的元祖使用不是从前到后的顺序

pytest使用yaml格式测试用例实现读、写、清除的封装

安装插件pyyaml

pip install pyyaml

1、什么是yaml?

1)yaml是一种数据格式,扩展名可以是yaml、yml

2)支持#注释

3)通过缩进表示层级

4)区分大小写

2、用途

1)配置文件(yaml、ini)

2)编写自动化测试用例

3、数据组成

1)、map对象:键:(空格)值

name: 小李

2)、数组(list),使用“-”表示列表

users:
– name1: 毛毛
– name2:
– name3: 小丽
– age: 18
– name4: 灰灰

读取yaml文件

项目结构

import os
import yaml

def get_path():
# return os.getcwd().split("common")[0]
return os.path.dirname(__file__).split("common")[0]

def read_yaml(yamlpath):
with open(get_path()+yamlpath, mode='r', encoding='utf-8') as f:
value = yaml.load(stream=f, Loader=yaml.FullLoader)
return value

if __name__ == '__main__':
print(read_yaml('testcase/userInfo.yaml'))

运行yaml_util.py,输出结果

{'users': [{'name1': '毛毛'}, {'name2': [{'name3': '小丽'}, {'age': 18}]}, {'name4': '灰灰'}]}

划分层次后:

{'users': [

{'name1': '毛毛'},

{'name2': [

{'name3': '小丽'},

{'age': 18}

]},

{'name4': '灰灰'}

]}

想要取到集合中的集合,需要注意用到索引。

@pytest.mark.parametrize('caseinfo', read_yaml('testcase/userInfo.yaml'))
def test_a(self, caseinfo):
print('test_a')
name1 = caseinfo['users'][0]['name1']
print(str(name1))

输出结果

day02/testcase/test_login.py::TestLogin::test_a[caseinfo0] test_a

毛毛

一般不要这么用,数据这么组装就可以了,见下图


users:
name1: 毛毛
name2:
name3: 小丽
age: 18
name4: 灰灰

@pytest.mark.parametrize('caseinfo', read_yaml('testcase/userInfo.yaml'))
def test_a(self, caseinfo):
print('test_a')
name1 = caseinfo['users']['name1']
print(str(name1))

多组数据进行迭代,可以这么写


users:
name1: 毛毛
name2:
name3: 小丽
age: 18
name4: 灰灰

users:
name1: 天天
name2:
name3: 莱德
age: 8
name4: 阿奇

@pytest.mark.parametrize('caseinfo', read_yaml('testcase/userInfo.yaml'))
def test_a(self, caseinfo):
print('test_a')
name1 = caseinfo['users']['name1']
print(str(name1))

小功能汇总

显示最慢的10个测试步骤

pytest ‐‐durations=10

默认情况下,如果测试时间很短(<0.01s),这里不会显示执行时常,如果需要显示,在命令行中追加 **-vv**参数

实战经验

指定运行部分测试用例

这种方式还会根据运行测试用例yaml文件的排列顺序进行测试,非常好用。下面的案例会以此执行test_A、test_B,但不会执行test_B。这样可以保留所有用例,在需要时将值设置成‘1’便可加入运行

run.testcase.yaml文件,内容如下:

# 设置是否运行测试用例 1: 运行 0:不运行
test_A: '1'
test_B: '1'
test_C: '0'

在run.py中,写一个方法,用于修改pytest.ini文件

效果:

修改前:

修改后:

内置fixture之request参数

@pytest.fixture(autouse=True)
def print_request(request):
print("\\n=======================request start=================================")
print(request.module)
print(request.function)
print(request.cls)
print(request.fspath) # 可以获得当前testcase用例名称
print(request.fixturenames)
print(request.fixturename)
print(request.scope)
print("\\n=======================request end=================================")

输出

=======================request start=================================

1 <module'web.cases.module2.test_1'from'D:\\\\web\\\\cases\\\\module2\\\\test_1.py'>

2 <function test_answer_1 at0x0000012D1C9FD9D8>

3 None

4 D:\\web\\cases\\module2\\test_1.py

5 ['_verify_url', 'base_url', '__pytest_repeat_step_number', 'show_request', 'request']

6 show_request

7 function

=======================request end=================================

赞(0)
未经允许不得转载:171主机测评 » Pytest测试框架全攻略
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址