Pytest基础

发布于:2024-04-27 ⋅ 阅读:(21) ⋅ 点赞:(0)

1.用例的设计原则

用Pytest写用例时候,一定要按照下面的规则去写,否则不符合规则的测试用例是不会执行的
1、文件名以 test_.py 文件和test.py
2、以 test
开头的函数
3、以 Test 开头的类,不能包含__init__方法
4、以 test_ 开头的类里面的方法
5、所有的包 pakege 必须要有__init__.py 文件

2.pycharm执行用例配置

在这里插入图片描述

3.常用pytest执行用例命令

1.查看pytest命令行参数,可以用pytest -h 或pytest --help
2.执行当前文件夹下面的所有文件

pytest 文件名/

3.执行具体的某一测试文件

pytest 脚本名称.py

4.-k 匹配用例名称
执行测试用例名称包含qq的所有用例:

pytest -k qq test_1.py

根据用例名称排除某些用例

pytest -k "not qq" test_1.py

同时匹配不同的用例名称

pytest -k "wechat or webo" test_1.py

5.按节点运行
每个收集的测试都分配了一个唯一的nodeid,它由模块文件名和 :: 组成 来自参数化的类名,函数名和参数,由:: characters分隔
运行.py模块里面的某个函数:

pytest test_mod.py::test_func

运行.py模块里面,测试类里面的某个方法

pytest test_mod.py::TestClass::test_method

6.-x 遇到错误时停止测试

pytest -x test_class.py

7.–maxfail=num 当用例错误个数达到指定数量时,停止测试

pytest --maxfail=1

8.-q 简单打印,只打印测试用例的执行结果

pytest -q test_1.py

9.-s 详细打印,-v 更加详细的打印,通过的.用pass表示

pytest -s test_1.py
pytest -v test_1.py

4.用例执行状态

用例执行完成后,每条用例都有自己的状态,常见的状态有:
passed:测试通过
failed:断言失败
error:用例本身写的质量不行,本身代码报错(譬如:fixture不存在,fixture里面有报错)
xfail:预期失败,加了 @pytest.mark.xfail()

5.常用断言

pytest 里面断言实际上就是 python 里面的 assert 断言方法,常用的有以下几种:
assert xx :判断 xx 为真
assert not xx :判断 xx 不为真
assert a in b :判断 b 包含 a
assert a == b :判断 a 等于 b
assert a != b :判断 a 不等于 b

6.setup和teardown

按照范围的不同,一共有以下十种:

  • 模块级别:setup_module、teardown_module
    setup_module:在每个模块执行前执行
    teardown_module:在每个模块执行后执行
    有几个模块就有几对
  • 函数级别:setup_function、teardown_function,不在类中的方法
    setup_function:在每个函数执行前执行
    teardown_function:在每个函数执行后执行
    有几个函数就有几对
  • 类级别:setup_class、teardown_class
    setup_class:在每个类执行前执行
    teardown_class:在每个类执行后执行
    有几个类就有几对
  • 方法级别:setup_method、teardown_method
    setup_method:在类里面的每个方法执行前执行
    teardown_method:在类里面每个方法执行后执行
  • 方法细化级别:setup、teardown
    跟上面的方法级别的含义一致,两种写法都可以

注意:函数级别和方法级别的区别在于:函数级别的前置后置是针对不在类中的方法,而不是测试用例

7.跳过测试用例

7.1 @pytest.mark.skip

跳过执行测试用例,有可选参数reason:跳过的原因,会在执行结果中打印

@pytest.mark.skip
def test_wechat():
print('此测试用例不会被执行')

7.1 @pytest.mark.skipif(condition, reason=“”)

作用:希望有条件地跳过某些测试用例
注意:condition需要返回True才会跳过

@pytest.mark.skipif(sys.platform == 'win32', reason="does not run on windows")
def test_function(self):
print("不能在window上运行")

8.参数化

8.1语法

@pytest.mark.parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
1.argnames
含义:参数名字
格式:字符串"arg1,arg2,arg3"【需要用逗号分隔】
2.argvalues
含义:参数值列表
格式:必须是列表,如:[ val1,val2,val3 ]
如果只有一个参数,里面则是值的列表
@pytest.mark.parametrize(“username”, [“yy”, “yy2”,“yy3”])
如果有多个参数例,则需要用元组来存放值,一个元组对应一组参数的值,如:
@pytest.mark.parametrize(“name,pwd”, [(“yy1”, “123”), (“yy2”, “123”), (“yy3”, “123”)]),
也可以用字典保存
@pytest.mark.parametrize(“name,pwd”,[{‘name’:‘yy1’,‘pwd’:‘123’},
{‘name’:‘yy2’,‘pwd’:‘123’},{‘name’:‘yy3’,‘pwd’:‘123’}])
3.ids
含义:用例的ID
格式:传一个列表,[“”,“”,“”]
作用:可以标识每一个测试用例,自定义测试数据结果的显示,为了增加可读性
强调:ids的长度需要与测试数据列表的长度一致
场景:只有测试数据和期望结果不一样,但操作步骤是一样的测试用例可以用上参数化;
示例:

#未参数化
def test_1():
assert 3 + 5 == 9
def test_2():
assert 2 + 4 == 6
def test_3():
assert 6 * 9 == 42
#参数化
@pytest.mark.parametrize("test_input,expected", [("3+5", 8),("2+4", 6), ("6*9",42)])
def test_eval(test_input, expected):
print(f"测试数据{test_input},期望结果{expected}")
assert eval(test_input) == expected

8.2 参数化之数据格式

1.[(),(),()…] 列表嵌套元组

@pytest.mark.parametrize("test_input,expected", [("3+5", 8),("2+4", 6), ("6*9",42)])
def test_eval(test_input, expected):
print(f"测试数据{test_input},期望结果{expected}")
assert eval(test_input) == expected

在这里插入图片描述

2.[{},{},{}…] 列表嵌套字典

data = [
{'test_input':"3+5",'expected':8},
{'test_input':"6+5",'expected':11},
{'test_input':"3*5",'expected':15},
]
@pytest.mark.parametrize('dic',data)
def test_eval(dic):
assert eval(dic['test_input']) == dic['expected']

在这里插入图片描述

只有一条用例,但是利用参数化输入三组不同的测试数据和期望结果,最终执行的测试用例数=3,可以节省很多代码

8.3参数化之“笛卡尔积”

# 笛卡尔积,组合数据
data_1 = [1, 2, 3]
data_2 = ['x', 'y']
@pytest.mark.parametrize('a', data_1)
@pytest.mark.parametrize('b', data_2)
def test_parametrize_1(a, b):
print(f'笛卡尔积 测试数据为 : {a}{b}')

运行结果如下:
在这里插入图片描述
备注
一个函数或一个类可以装饰多个 @pytest.mark.parametrize,最终生成的用例数是nm,比如上面的代码就是:参数a的数据有3个,参数b的数据有2个,所以最终的用例数有32=6条。当参数化装饰器有很多个的时候,用例数都等于nnnn

8.4参数化之标记数据

# 标记参数化
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
pytest.param("6*9", 42, marks=pytest.mark.xfail),# 预期失败
pytest.param("6*6", 42, marks=pytest.mark.skip) # 跳过
])
def test_mark(test_input, expected):
assert eval(test_input) == expected

运行结果如下:
在这里插入图片描述

8.5参数化之增加可读性

ids传递的数据:

import pytest
data = [
{'test_input':"3+5",'expected':8},
{'test_input':"6+5",'expected':11},
{'test_input':"3*5",'expected':15},
]
ids = [f"test_input的值是{dic['test_input']},expected的值是{dic['expected']}"for
dic in data]
@pytest.mark.parametrize('dic',data,ids=ids)
def test_eval(dic):
assert eval(dic['test_input']) == dic['expected']

9.conftest.py

conftest.py配置fixture注意事项
pytest会默认读取conftest.py里面的所有fixture。conftest.py 文件名称是固定的,不能改动
不同目录可以有自己的conftest.py,一个项目中可以有多个conftest.py。测试用例文件中不需要手动import conftest.py,pytest会自动查找

10. fixture

10.1 fixture的优势

  • 命名方式灵活,不局限于 setup 和teardown 这几个命名
  • conftest.py 配置里可以实现数据共享,不需要 import 就能自动找到fixture
  • scope=“module” 可以实现多个.py 跨文件共享前置
  • scope=“session” 以实现多个.py 跨文件使用一个 session 来完成多个用例
  • 如果fixture使用了yield返回值,则不能在用例中显示调用fixture

10.2定义

语法:

@pytest.fixture(scope="function", params=None, autouse=False, ids=None,
name=None)
def fixture_name():
print("fixture初始化的参数列表")

参数含义:
scope:可以理解成fixture的作用域,默认:function,还有class、module、package、session
四个【常用】
function(默认):每个测试函数都会得到一个新的 fixture 实例。这是最常见的用法,适用于大多数情况。
class:每个测试类会得到一个新的 fixture 实例,该实例在类中的所有测试方法之间共享。这在你需要在类的多个测试方法之间共享状态时非常有用。
module:整个测试模块只创建一个 fixture 实例,并在该模块的所有测试函数之间共享。这适用于模块级别的设置和清理。
session:整个测试会话只创建一个 fixture 实例,并在所有测试模块和函数之间共享。这通常用于设置和清理那些非常昂贵或耗时的资源,如数据库连接。
package:在一个包中,该fixture只会被执行一次
autouse:默认:False,需要用例手动调用该fixture;如果是True,所有作用域内的测试用例都
会自动调用该fixture
name:默认:装饰器的名称,同一模块的fixture相互调用建议写个不同的name

10.3调用

调用方法:
只需要在引用fixture函数的测试用例里面传入被@pytest.fixture这个装饰器装饰的函数的名字即可,就
会调用fixture函数中定义的功能,用yield关键字去划分是setup还是teardown,yield前面实现的功能是
setup初始化功能,yield后面实现的功能是teardown清场功能。