龙空技术网

软件测试/测试开发丨Pytest和Allure报告 学习笔记

霍格沃兹软件测试 130

前言:

现在咱们对“php中双冒号”大约比较重视,姐妹们都想要分析一些“php中双冒号”的相关文章。那么小编同时在网摘上汇集了一些对于“php中双冒号””的相关文章,希望大家能喜欢,各位老铁们一起来了解一下吧!

免费领取:测试资料+性能测试+接口测试+自动化测试+测试开发+测试用例+简历模板+测试文档 - 公众号 - 测试人社区

本文为霍格沃兹测试开发学社学员学习笔记分享

原文链接:Pytest和Allure报告 - 学习笔记 - 测试人社区

Pytest 命名规则

类型

规则

文件

test_开头 或者 _test 结尾

Test 开头

方法/函数

test_开头

注意:测试类中不可以添加__init__构造函数

注意:pytest对于测试包的命名没有要求

方法:类中定义的函数

函数:类外面定义的函数

谷歌风格开源项目风格指南:

Pytest用例断言

断言的概念

断言(assert),是一种在程序中的一阶逻辑(如:一个结果为真或假的逻辑判断式),目的为了表示与验证软件开发者预期的结果。当程序执行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止执行,并给出错误信息。

断言的用法

断言写法assert <表达式>assert <表达式>,<描述>

assert <bool expression>;       assert <bool expression> : <message>;    

示例一

def test_a():    assert Truedef test_b():    a = 1    b = 1    c = 2    assert a + b == c, f"{a}+{b}=={c}, 结果为真"

示例二

def test_c():    a = 1    b = 1    c = 2    assert 'abc' in "abcd"import sysdef test_plat():    assert ('linux' in sys.platform), "该代码只能在 Linux 下执行"
Pytest测试框架结构(setup/teardown)测试装置介绍

类型

规则

setup_module/teardown_module

全局模块级

setup_class/teardown_class

类级,只在类中前后运行一次

setup_function/teardown_function

函数级,在类外

setup_method/teardown_method

方法级,类中的每个方法执行前后

setup/teardown

在类中,运行在调用方法的前后(重点)

Pyrest参数化用例参数化通过参数的方式传递数据,从而实现数据和脚本分离。并且可以实现用例的重复生成与执行。参数化测试函数使用单参数情况单参数,可以将数据放在列表中

search_list = ['appium','selenium','pytest']@pytest.mark.parametrize('name',search_list)def test_search(name):    assert name in search_list
多参数情况将数据放在列表嵌套元组中将数据放在列表嵌套列表中
# 数据放在元组中@pytest.mark.parametrize("test_input,expected",[    ("3+5",8),("2+5",7),("7+5",12)])def test_mark_more(test_input,expected):    assert eval(test_input) == expected# 数据放在列表中@pytest.mark.parametrize("test_input,expected",[    ["3+5",8],["2+5",7],["7+5",12]])def test_mark_more(test_input,expected):    assert eval(test_input) == expected
用例重命名-添加 ids 参数通过ids参数,将别名放在列表中
@pytest.mark.parametrize("test_input,expected",[    ("3+5",8),("2+5",7),("7+5",12)],ids=['add_3+5=8','add_2+5=7','add_3+5=12'])def test_mark_more(test_input,expected):    assert eval(test_input) == expected
用例重命名-添加 ids 参数(中文)
@pytest.mark.parametrize("test_input,expected",[    ("3+5",8),("2+5",7),("7+5",12)],ids=["3和5相加","2和5相加","7和5相加"])def test_mark_more(test_input,expected):    assert eval(test_input) == expected

ids不支持中文,默认是unicode编码格式,可以用如下方法转换

# 在项目(最末一级)下创建conftest.py 文件 ,将下面内容添加进去,运行脚本def pytest_collection_modifyitems(items):    """    测试用例收集完成时,将收集到的用例名name和用例标识nodeid的中文信息显示在控制台上    """    for i in items:        i.name=i.name.encode("utf-8").decode("unicode_escape")        i._nodeid=i.nodeid.encode("utf-8").decode("unicode_escape")
笛卡尔积

接口测试中用的较多,因为接口的值很多

两组数据a=[1,2,3]b=[a,b,c]对应有几种组合形势 ?(1,a),(1,b),(1,c)(2,a),(2,b),(2,c)(3,a),(3,b),(3,c)

@pytest.mark.parametrize("b",["a","b","c"])@pytest.mark.parametrize("a",[1,2,3])def test_param1(a,b):    print(f"笛卡积形式的参数化中 a={a} , b={b}")

执行方向:由近致远

使用 Mark 标记测试用例Mark:标记测试用例场景:只执行符合要求的某一部分用例 可以把一个web项目划分多个模块,然后指定模块名称执行。解决: 在测试用例方法上加 @pytest.mark.标签名执行: -m 执行自定义标记的相关用例pytest -s 文件名 -m=webtestpytest -s test_mark_zi_09.py -m apptestpytest -s test_mark_zi_09.py -m "not ios"(必须要是双引号)pytest -v是能输出更详细的用例信息,成功与失败不再是.和F

如何解决warnings问题

[pytest]# 在项目目录下创建一个pytest.ini放标签,这样这些标签就不会warning。而且要换行写,也不要顶头写,会被认为是key# 在这里注册好标签名后,pytest可以识别markers = str          bignum          float          int          minus          zero
pytest 设置跳过、预期失败Mark:跳过(Skip)及预期失败(xFail)这是 pytest 的内置标签,可以处理一些特殊的测试用例,不能成功的测试用例skip - 始终跳过该测试用例skipif - 遇到特定情况跳过该测试用例xfail - 遇到特定情况,产生一个“期望失败”输出Skip 使用场景调试时不想运行这个用例标记无法在某些平台上运行的测试功能在某些版本中执行,其他版本中跳过比如:当前的外部资源不可用时跳过如果测试数据是从数据库中取到的,连接数据库的功能如果返回结果未成功就跳过,因为执行也都报错解决 1:添加装饰器@pytest.mark.skip@pytest.mark.skipif解决 2:代码中添加跳过代码pytest.skip(reason)
# 形式一:跳过这个方法@pytest.mark.skip(reason="存在bug")def test_double_str():    print("代码未开发完")    assert 'aa' == 'aa'# 形式二:跳过这个方法@pytest.mark.skipif(sys.platform == "win32", reason="does not run on win32")def test_case():    print(sys.platform)    assert True        # 形式二:在代码中跳过代码def check_login():    return Falsedef test_double_str():    print("start")    if not check_login():        pytest.skip("未登录,不进行下去")    print("end")# ============================= 1 skipped in 0.02s ======
xfail 使用场景

用于标记此用例可能会失败,当脚本失败时,测试报告也不会打印错误追踪,只是会显示xfail状态。xfail的主要作用是比如在进行测试提前时,当产品某功能尚未开发完成而进行自动化脚本开发,当然此时也可以把这些脚本注释起来,但这不是pytest推荐的做法,pytest推荐使用xfail标记,如此则虽然产品功能尚未开发完成,但是自动化脚本已经可以跑起来了,只不过在测试报告中会显示xfail而已。

与 skip 类似 ,预期结果为 fail ,标记用例为 fail与skip不同,xfail标记的用例依然会被执行,如果执行成功就返回XPASS,执行失败就返回XFAIL。只是起到一个提示的作用。用法:添加装饰器@pytest.mark.xfail

@pytest.mark.xfaildef test_case():    print("test_xfail 方法执行")    assert 2 == 2# XPASS                                   [100%]test_xfail @pytest.mark.xfaildef test_case():    print("test_xfail 方法执行")    assert 1 == 2    # XFAIL                                            [100%]test_xfail 方法执行xfail = pytest.mark.xfail@xfail(reason="bug 110")def test_case():    print("test_xfail 方法执行")    assert 1 == 2# XFAIL (bug 110)                                  [100%]test_xfail 方法执行
pytest 运行用例运行多条用例方式

如果要进入某个文件所在的目录终端,可以右键文件->选择open in terminal

执行包下所有的用例:pytest/py.test [包名]执行单独一个 pytest 模块:pytest 文件名.py运行某个模块里面某个类:pytest 文件名.py::类名运行某个模块里面某个类里面的方法:pytest 文件名.py::类名::方法名加-v可以具体展示,如pytest -v 文件名.py::类名::方法名 (-v在前在后都行)test_skip.py::TestDemo::test_demo1 PASSED [100%]运行结果分析常用的:fail/error/pass(error可能是代码写错了)特殊的结果:warning/deselect(后面会讲)python 执行 pytest

前面已经介绍了几种执行用例的方法,一个是点击代码方法或类的左侧绿色箭头,一个是右键测试用例,一个是终端pytest解释器执行,我们也可以用python解释器执行

Python 代码执行 pytest方法一:使用 main 函数方法二:使用 python -m pytest 调用 pytest(jenkins 持续集成用到),相当于在原来pytest 用例前加了python -m。方便指定python版本,比如有的用例使用python老版本写的Python 代码执行 pytest - main 函数

if __name__ == '__main__':    # 1、运行当前目录下所有符合规则的用例,包括子目录(test_*.py 和 *_test.py)    pytest.main()    # 2、运行test_mark1.py::test_dkej模块中的某一条用例    pytest.main(['test_mark1.py::test_dkej','-vs'])    # 3、运行某个 标签    pytest.main(['test_mark1.py','-vs','-m','dkej'])运行方式  `python test_*.py `
pytest 异常处理异常处理方法 try …except
try:    可能产生异常的代码块except [ (Error1, Error2, ... ) [as e] ]:    处理异常的代码块1except [ (Error3, Error4, ... ) [as e] ]:    处理异常的代码块2except  [Exception]:    处理其它异常
异常处理方法 pytest.raise()可以捕获特定的异常获取捕获的异常的细节(异常类型,异常信息)发生异常,后面的代码将不会被执行
def test_raise():    with pytest.raises(ValueError, match='must be 0 or None'):        raise ValueError("value must be 0 or None")def test_raise1():    with pytest.raises(ValueError) as exc_info:        raise ValueError("value must be 42")    assert exc_info.type is ValueError    assert exc_info.value.args[0] == "value must be 42"

这样可以选择一个异常

 def test_raise():    with pytest.raises((ZeroDivisionError,ValueError)):        raise ZeroDivisionError("value must be 0 or None")
Pytest 结合数据驱动 YAML数据驱动什么是数据驱动?数据驱动就是数据的改变从而驱动自动化测试的执行,最终引起测试结果的改变。简单来说,就是参数化的应用。数据量小的测试用例可以使用代码的参数化来实现数据驱动,数据量大的情况下建议大家使用一种结构化的文件(例如 yaml,json 等)来对数据进行存储,然后在测试用例中读取这些数据。应用:App、Web、接口自动化测试测试步骤的数据驱动测试数据的数据驱动配置的数据驱动yaml 文件介绍对象:键值对的集合,用冒号 “:” 表示数组:一组按次序排列的值,前加 “-”纯量:单个的、不可再分的值字符串布尔值整数浮点数Null时间日期
# 编程语言languages:  - PHP  - Java  - Pythonbook:  Python入门: # 书籍名称    price: 55.5    author: Lily    available: True    repertory: 20    date: 2018-02-17  Java入门:    price: 60    author: Lily    available: False    repertory: Null    date: 2018-05-11

相当于:

languages:['PHP', 'Java', 'Python'] # languages是key值
yaml 文件使用查看 yaml 文件pycharmtxt 记事本读取 yaml 文件安装:pip install pyyaml方法:yaml.safe_load(f)(yaml->python)方法:yaml.safe_dump(f) (python->yaml)
import yamlfile_path = './my.yaml'with open(file_path, 'r', encoding='utf-8') as f:    data = yaml.safe_load(f)
工程目录结构(看项目文件datadriver_yaml)data 目录:存放 yaml 数据文件func 目录:存放被测函数文件testcase 目录:存放测试用例文件
# 工程目录结构.├── data│   └── data.yaml├── func│   ├── __init__.py│   └── operation.py└── testcase    ├── __init__.py    └── test_add.py
测试准备被测对象:operation.py测试用例:test_add.py测试数据:data.yaml
# operation.py 文件内容def my_add(x, y):    result = x + y    return result# test_add.py 文件内容class TestWithYAML:  @pytest.mark.parametrize('x,y,expected', [[1, 1, 2]])  def test_add(self, x, y, expected):    assert my_add(int(x), int(y)) == int(expected)# data.yaml 文件内容-  - 1  - 1  - 2-  - 3  - 6  - 9-  - 100  - 200  - 300
Pytest 数据驱动结合 yaml 文件
# 读取yaml文件def get_yaml():    """    获取json数据    :return: 返回数据的结构:[[1, 1, 2], [3, 6, 9], [100, 200, 300]]    """    with open('../datas/data.yaml', 'r') as f:        data = yaml.safe_load(f)        return data
Pytest 结合数据驱动 Excel读取 Excel 文件第三方库xlrdxlwingspandasopenpyxl官方文档: openpyxl 库的安装安装:pip install openpyxl导入:import openpyxlopenpyxl 库的操作读取工作簿读取工作表读取单元格
import openpyxl# 获取工作簿book = openpyxl.load_workbook('../data/params.xlsx')# 读取工作表sheet = book.active# 读取单个单元格cell_a1 = sheet['A1']cell_a3 = sheet.cell(column=1, row=3)  # A3# 读取多个连续单元格cells = sheet["A1":"C3"]# 获取单元格的值cell_a1.value
工程目录结构data 目录:存放 excel 数据文件func 目录:存放被测函数文件testcase 目录:存放测试用例文件
# 工程目录结构.├── data│   └── params.excel├── func│   ├── __init__.py│   └── operation.py└── testcase    ├── __init__.py    └── test_add.py
测试准备被测对象:operation.py测试用例:test_add.py
# operation.py 文件内容def my_add(x, y):    result = x + y    return result# test_add.py 文件内容class TestWithEXCEL:    @pytest.mark.parametrize('x,y,expected', get_excel())    def test_add(self, x, y, expected):        assert my_add(int(x), int(y)) == int(expected)
测试准备测试数据:params.xlsx

注意:.xlsx文件要在外面创建,不要在编辑器里创建

Pytest 数据驱动结合 Excel 文件

# 读取Excel文件import openpyxlimport pytestdef get_excel():    # 获取工作簿    book = openpyxl.load_workbook('../data/params.xlsx')    # 获取活动行(非空白的)    sheet = book.active    # 提取数据,格式:[[1, 2, 3], [3, 6, 9], [100, 200, 300]]    values = []    for row in sheet:        line = []        for cell in row:            line.append(cell.value)        values.append(line)    return values
Pytest 结合数据驱动 csvcsv 文件介绍csv:逗号分隔值是 Comma-Separated Values 的缩写以纯文本形式存储数字和文本文件由任意数目的记录组成每行记录由多个字段组成
Linux从入门到高级,linux,¥5000web自动化测试进阶,python,¥3000app自动化测试进阶,python,¥6000Docker容器化技术,linux,¥5000测试平台开发与实战,python,¥8000
csv 文件使用读取数据内置函数:open()内置模块:csv方法:csv.reader(iterable)参数:iterable ,文件或列表对象返回:迭代器,每次迭代会返回一行数据。
# 读取csv文件内容def get_csv():    with open('demo.csv', 'r') as file:        raw = csv.reader(file)        for line in raw:            print(line)
工程目录结构data 目录:存放 csv 数据文件func 目录:存放被测函数文件testcase 目录:存放测试用例文件
# 工程目录结构.├── data│   └── params.csv├── func│   ├── __init__.py│   └── operation.py└── testcase    ├── __init__.py    └── test_add.py
测试准备被测对象:operation.py测试用例:test_add.py测试数据:params.csv
# operation.py 文件内容def my_add(x, y):    result = x + y    return result# test_add.py 文件内容class TestWithCSV:    @pytest.mark.parametrize('x,y,expected', [[1, 1, 2]])    def test_add(self, x, y, expected):        assert my_add(int(x), int(y)) == int(expected)# params.csv 文件内容1,1,23,6,9100,200,300
Pytest 数据驱动结合 csv 文件
# 读取 data目录下的 params.csv 文件import csvdef get_csv():    """    获取csv数据    :return: 返回数据的结构:[[1, 1, 2], [3, 6, 9], [100, 200, 300]]    """    with open('../data/params.csv', 'r') as file:        raw = csv.reader(file)        data = []        for line in raw:            data.append(line)    return data
Pytest 结合数据驱动 jsonjson 文件介绍json 是 JS 对象全称是 JavaScript Object Notation是一种轻量级的数据交换格式json 结构对象 {"key": value}数组 [value1, value2 ...]
{  "name:": "hogwarts ",  "detail": {    "course": "python",    "city": "北京"  },  "remark": [1000, 666, 888]}
json 文件使用查看 json 文件pycharmtxt 记事本读取 json 文件内置函数 open()内置库 json方法:json.loads()方法:json.dumps()
# 读取json文件内容def get_json():    with open('demo.json', 'r') as f:        data = json.loads(f.read())        print(data)
测试准备被测对象:operation.py测试用例:test_add.py测试数据:params.json
# operation.py 文件内容def my_add(x, y):    result = x + y    return result# test_add.py 文件内容class TestWithJSON:    @pytest.mark.parametrize('x,y,expected', [[1, 1, 2]])    def test_add(self, x, y, expected):        assert my_add(int(x), int(y)) == int(expected)# params.json 文件内容{  "case1": [1, 1, 2],  "case2": [3, 6, 9],  "case3": [100, 200, 300]}
Pytest 数据驱动结合 json 文件
# 读取json文件def get_json():    """    获取json数据    :return: 返回数据的结构:[[1, 1, 2], [3, 6, 9], [100, 200, 300]]    """    with open('../data/params.json', 'r') as f:        data = json.loads(f.read())        return list(data.values())
pytest测试用例生命周期管理Fixture 用法Fixture 特点及优势1、命令灵活:对于 setup,teardown,可以不起这两个名字2、数据共享:在 conftest.py 配置⾥写⽅法可以实现数据共享,不需要 import 导⼊。可以跨⽂件共享3、scope 的层次及神奇的 yield 组合相当于各种 setup 和 teardown4、实现参数化Fixture 在自动化中的应用- 基本用法场景:测试⽤例执⾏时,有的⽤例需要登陆才能执⾏,有些⽤例不需要登陆。setup 和 teardown ⽆法满⾜。fixture 可以。默认 scope(范围)function步骤:1.导⼊ pytest2.在登陆的函数上⾯加@pytest.fixture()3.在要使⽤的测试⽅法中传⼊(登陆函数名称),就先登陆4.不传⼊的就不登陆直接执⾏测试⽅法。
# test_fixture.pyimport pytest# 定义登录的fixture@pytest.fixture()def login():    print("完成登录操作")def test_search():    print("搜索")def test_cart(login): #不需要把login放在函数里面,只要传参就可以    print("购物车")def test_order(login):    print("下单")
Fixture 在自动化中的应用 - 作用域

取值

范围

说明

function

函数级

每一个函数或方法都会调用

class

类级别

每个测试类只运行一次

module

模块级

每一个.py 文件调用一次

package

包级

每一个 python 包只调用一次(暂不支持)

session

会话级

每次会话只需要运行一次,会话内所有方法及类,模块都共享这个方法

注:整个项目就是一个会话

import pytest# 定义登录的fixture@pytest.fixture(scope="class")def login():    print("完成登录操作")def test_search(login):    print("搜索")def test_cart(login):    print("购物车")def test_order(login):    print("下单")class TestDemo:    def test_case1(self,login):        print("case1")    def test_case2(self,login):        print("case2")
Fixture 在自动化中的应用 - yield 关键字场景:

你已经可以将测试⽅法【前要执⾏的或依赖的】解决了,

测试⽅法后销毁清除数据的要如何进⾏呢?

解决:

通过在 fixture 函数中加⼊ yield 关键字,yield 是调⽤第⼀次返回结果,

第⼆次执⾏它下⾯的语句返回。

步骤:

在@pytest.fixture(scope=module)。

在登陆的⽅法中加 yield,之后加销毁清除的步骤

import pytest# 定义登录的fixture@pytest.fixture(scope="class")def login():    # setup 操作    print("完成登录操作")    token = "abcd"    username = "hogwarts"    yield token, username  # 相当于return    # teardown 操作    print("完成登出操作")def test_search(login):    token, username = login    print(f"token:{token},name:{username}")    print("搜索")def test_cart(login):    print("购物车")def test_order(login):    print("下单")class TestDemo:    def test_case1(self,login):        print("case1")    def test_case2(self,login):        print("case2")
Fixture 在自动化中的应用 - 数据共享场景:

你与其他测试⼯程师合作⼀起开发时,公共的模块要在不同⽂件中,要在⼤家都访问到的地⽅。

解决:

使⽤ conftest.py 这个⽂件进⾏数据共享,并且他可以放在不同位置起着不同的范围共享作⽤。

前提:conftest ⽂件名是不能换的放在项⽬下是全局的数据共享的地⽅执⾏:系统执⾏到参数 login 时先从本模块中查找是否有这个名字的变量什么的,之后在 conftest.py 中找是否有。步骤:将登陆模块带@pytest.fixture 写在 conftest.pyFixture 在自动化中的应用 - 自动应用

场景:

不想原测试⽅法有任何改动,或全部都⾃动实现⾃动应⽤,

没特例,也都不需要返回值时可以选择⾃动应⽤

解决:

使⽤ fixture 中参数 autouse=True 实现

步骤:

在⽅法上⾯加 @pytest.fixture(autouse=True)

比如要实现fixture时session级别的,就要每个用例都添加fixture方法。可以通过自动应用来避免。

问题:那yield返回参数的怎么办?

Fixture 在自动化中的应用 -参数化

场景:

测试离不开数据,为了数据灵活,⼀般数据都是通过参数传的

解决:

fixture 通过固定参数 request 传递

步骤:

在 fixture 中增加@pytest.fixture(params=[1, 2, 3, ‘linda’])

在⽅法参数写 request,方法体里面使用 request.param 接收参数

Fixture 的用法总结模拟 setup,teardown(一个用例可以引用多个 fixture)yield 的用法作用域( session,module, 类级别,方法级别 )自动执行 (autouse 参数)conftest.py 用法,一般会把 fixture 写在 conftest.py 文件中(这个文件名字是固定的,不能改)实现参数化

# test_fixture_param.pyimport pytest@pytest.fixture(params=[["selenium",123], ["appium",123]])def login(request):    print(f"用户名:{request.param}")    return request.paramdef test_demo1(login):    print(f"demo1 case:数据为{login}")

标签: #php中双冒号