【测试】Pytest框架

本文深入介绍了pytest测试框架的使用,包括基础示例、断言操作、setup和teardown函数、fixture的使用及其参数、pytest.mark标记以及配置文件的设置。此外,还探讨了常用插件的功能,如生成测试报告、控制执行顺序、失败重试和取消插件。最后,简要介绍了yaml数据序列化标准及其在Python中的处理方法。
摘要由CSDN通过智能技术生成


安装依赖包:

 pip install -U pytest -i https://pypi.tuna.tsinghua.edu.cn/simple

特点:

是一个命令行工具,编写用例简单,可读性强
非常容易上手,入门简单,文档丰富,文档中有很多实例可以参考
支持单元测试和功能测试
支持参数化
执行测试过程中可以将某些测试跳过,或者对某些预期失败的Case标记成失败
支持重复执行失败的Case
支持运行由unittest编写的测试Case
具有很多第三方插件,并且可以自定义扩展
方便的和持续集成工具集成
可以运行以test开头或test结尾的包、文件和方法
可以使用assert进行断言

1、pytest基础示例

pytest 默认能够自动识别 test 开头函数和方法,以及 Test 开头的类。,其他的不会自动识别。

测试代码示例:

import pytest


def test_one():
    print('1111')


def test_two():
    print('22222')


class TestTask(object):
    def test_1(self):
        print('test 11111111')

    def test_2(self):
        print('test 222222222222')

if __name__ == '__main__':
    pytest.main(['-s'])
pytest -s  # 运行当前目录所有test开头的文件
pytest -s xxx.py  # 运行指定的文件

# 代码
pytest.main(['-s'])
pytest.main(['-s', 'xxx.py'])

终端测试示例代码:pytest test_1.py -s

====================== test session starts =====================
platform linux -- Python 3.5.2, pytest-6.1.1, py-1.9.0, pluggy-0.13.1
rootdir: /home/python/Desktop/code/demo
plugins: celery-4.4.5
collected 4 items                                                                                                                                                                                                          

test_1.py 1111
.22222
.test 11111111
.test 222222222222
.

======================= 4 passed in 0.02s ========================
  • 命令如果不加-s不会显示代码中的标准输出(printlogging),示例代码最后一句不加-s也是一个道理。
    在这里插入图片描述

2、断言

2.1断言用法:
import pytest


def test_1():
    num = 1 + 1
    # assert 条件, '产生异常的提示信息'
    assert num == 3, '实际:num=2,预期:num==3;条件不满足,所以断言失败,打印本条信息'

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

终端测试:
在这里插入图片描述

2.2pytest.raises()抛异常的使用

预测到参数不符合要求,会抛出TypeError异常;若出现该异常,则测试用例执行通过

  • 用法:
    # with pytest.raises(预期的异常类型):
    #           实际产生的异常
    # 如果实际产生的异常和预期的异常类型一样,测试通过
import pytest


def foo(a):
	"""测试函数"""
    if a == 1:
        raise TypeError("异常a=11111111")
    elif a == 2:
        raise ValueError("异常a=22222222")
    elif a == 3:
        raise ValueError("异常a=33333333")


class TestAssert(object):

    def test_exception_typeerror(self):
        with pytest.raises(TypeError):
            # 传入a=1, 引发测试函数错误
            foo(1)

在这里插入图片描述

  • 把异常信息保存到一个变量err_info
    def test_exception_typeerror(self):
        # with pytest.raises(预期的异常类型) as 异常对象:
        #           实际产生的异常
        # 如果实际产生的异常和预期的异常类型一样,测试通过
        with pytest.raises(TypeError) as err_info:
            foo(1)  # 产生TypeError,保存到err_info

        if err_info.type == TypeError:
            print('err_info保存了异常信息')
  • 通过异常的内容捕捉异常match = ''
import pytest


def foo(a):
    """测试函数"""

    if a == 1:
        raise TypeError("异常1111111")
    elif a == 2:
        raise ValueError("异常22222222")
    else:
        raise ValueError("异常33333333")


def test_3():
    with pytest.raises(ValueError, match="异常33333333") as err_info:
        foo(2)  # 触发ValueError,同时异常内容为match

    if err_info.type == ValueError:
        print(err_info, 'err_info保存了异常信息')


if __name__ == '__main__':
    pytest.main(['-s'])
2.3 pytest.warns()抛警告的使用

注意

警告不会终止程序运行,异常会终止程序运行

import warnings
import pytest


def test_1():
    # with pytest.warns(预期的警告类型):
    #           实际产生的警告
    # 如果实际产生的警告和预期的警告类型一样,测试通过
    with pytest.warns(RuntimeWarning):
        warnings.warn("my warning", RuntimeWarning)

def test_2():
    # 警告断言,可以同时断言多个(异常不可以),保存在一个变量中,
    with pytest.warns((RuntimeWarning, UserWarning)) as warn_info:
        warnings.warn("my warning aaaaa", RuntimeWarning)
        warnings.warn("value must be 0 or None", UserWarning)
        print('11111111111111')

    # warn_info对象的成员,它是列表
    print(warn_info.__dict__)
    print(len(warn_info))
    print(warn_info[0].__dict__)  # 第0个对象的成员信息
    print(warn_info[0].category)
    print(warn_info[0].message)


def test_3():
    # 断言: 警告为RuntimeWarning,通过内容包含warning
    with pytest.warns(RuntimeWarning, match='warning'):
        warnings.warn("my warning", RuntimeWarning)

if __name__ == '__main__':
    # 默认不打印,需要加上-s参数, []中元素是字符串
    pytest.main(['-s'])

3、setup和teardown函数

  • 函数级别:

    • 作用域为:普通函数
    • setup_function, teardown_function,普通测试用例函数前后执行一次
  • 类级别

    • 作用域为类:类、实例方法
    • setup_class 和 teardown_class , 类的前后执行1次
    • setup_method, teardown_method,测试用例实例方法前后各执行1次
  • 模块级别

    • 作用域:运行的文件
    • setup_module(), teardown_module() 定义在类外
    • 文件的前后,执行1次

4、fixture基本使用

4.1基础

fixture是pytest特有的功能,它以装饰器形式定义在函数上面, 在编写测试函数的时候,可以将被fixture装饰的函数的名字做为测试函数的参数,运行测试脚本时,执行测试函数时就会自动传入被fixture装饰的函数的返回值。

import pytest
import requests


# 1. @pytest.fixture装饰一个函数,非测试用例
@pytest.fixture()
def get_web():
    print('get_web111111111')
    return 'https://www.baidu.com'

# 2. pytest.fixture装饰函数的函数名,作为测试用例的参数
def test_1(get_web):
    # 3. 测试用例函数里面使用到pytest.fixture装饰函数的函数名,使用的是这个函数的返回值
    print('aaaa = ', get_web)
    resp = requests.get(get_web)
    print(resp.status_code)


def test_2(get_web):
    print('test_2 = ', get_web)


# 执行流程
# 1. 执行测试用例,测试用例有get_web参数
# 2. 先执行get_web函数后,再执行测试用例里面的代码

if __name__ == '__main__':
    # 默认不打印,需要加上-s参数, []中元素是字符串
    pytest.main(['-s'])

4.2conftest.py文件

共享fixture函数

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

  • conftest.py文件名固定

  • 在conftest.py文件中实现共用的fixture函数

在这里插入图片描述

拓展:pytest.mark.usefixtures
在这里插入图片描述

5、fixture参数

pytest.fixture(scope='function', params=None, autouse=False, ids=None, name=None)

scope: 被标记方法的作用域, 可以传入以下四个值;
“function”: 默认值,每个测试用例都要执行一次 fixture 函数
“class”: 作用于整个类, 表示每个类只运行一次 fixture 函数
“module”: 作用于整个模块, 每个 module 的只执行一次 fixture 函数
“session”: 作用于整个 session , 一次 session 只运行一次 fixture

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

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

测试代码示例:

import pytest

# scope='class':
# 如果测试用例加上fixture修饰函数作为形参,
# 对于类中测试用例,只会在第一次的时候执行

@pytest.fixture(scope='class')
def func():
    print('fixture修饰的func')

# ============================
# 普通函数测试用例
def test_1(func):
    print('test_1111')

# 类实例方法测试用例
class TestDemo(object):
    def test_2(self, func):
        print('test222222222222222')

    def test_3(self, func):
        print('test33333333333333')


if __name__ == '__main__':
    # 默认不打印,需要加上-s参数, []中元素是字符串
    pytest.main(['-s')

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

6、pytest.mark标记

pytest.mark下提供了标记装饰器,除了之前我们使用的pytest.mark.usefixtures()装饰器以外,还有一些常用的标记装饰器

装饰器作用
pytest.mark.xfail()将测试函数标记为预期失败。
pytest.mark.skip()无条件地跳过测试函数
pytest.mark.skipif()有条件地跳过测试函数
pytest.mark.parametrize()参数化Fixture方法和测试函数。
pytest.mark.usefixtures()使用类、模块或项目中的Fixture方法。

示例:

  • 就算标志,测试用例,还是可以执行,只是结果标志为X
import pytest

@pytest.mark.xfail(reason='功能不完善,标志为失败')
def test_1():
    print('1111111111111')
    num = 1 + 1
    assert num == 2, 'num不为2,所以测试不通过'

import pytest

# @pytest.mark.skip(reason='之前注册,无需再注册') # 无条件跳过
#@pytest.mark.skipif(condition = True , reason='之前注册,无需再注册') # 条件跳过, 和上面一样
@pytest.mark.skipif(condition = False , reason='需要注册,无需跳过') # skipif,为False,这句话没有意义
def test_1():
    print('注册功能')
import pytest

@pytest.mark.parametrize(['usrname', 'pwd'], [('mike', '123'), ('yoyo', 'abc')])
def test_login(usrname, pwd):
    print('usrname = %s, pwd = %s' % (usrname, pwd))

7、pytest配置文件

通常在编写测试代码的目录下新建配置文件pytest.ini

[pytest]
;指定运行的测试文件
addopts = -s test.py
;需要测试文件的路径
testpaths = ./
;识别test开头的py文件
python_files = test*.py
;识别Test开头的类
python_classes = Test*
;识别test开头的函数
python_functions = test*
;在ini文件中注释语句是以分号开始的, 所有的注释语句不管多长都是独占一行直到结束的

8、常用插件

#1、生成测试报告
pip install pytest-html -i https://pypi.tuna.tsinghua.edu.cn/simple
#2、控制函数执行顺序
pip install pytest-ordering -i https://pypi.tuna.tsinghua.edu.cn/simple
#3、失败重试
pip install pytest-rerunfailures -i https://pypi.tuna.tsinghua.edu.cn/simple

8.1、生成测试报告

修改配置文件(指定运行的文件,指定生成的测试报告的路径),然后在终端使用命令pytest运行测试文件

[pytest]
addopts = -s test.py --html=./report.html

测试代码:

def test1():
    print('test1')
    assert True

def test2():
    print('test2')
    assert False

生成的测试报告示意:
在这里插入图片描述

8.2、控制函数执行顺序

注意
unittest的执行顺序的按照ASCII的排序执行的。
pytest使用装饰器:@pytest.mark.run(order=num),数字靠前的先运行。

import pytest

@pytest.mark.run(order=3)
def test_1():
    print('111111111111')

@pytest.mark.run(order=1) # 数字靠前的先运行
def test_2():
    print('222222222222')

@pytest.mark.run(order=2)
def test_3():
    print('3333333333333')

结果:test2被装饰的数字是1,所以先执行
在这里插入图片描述

8.3、失败重试

安装依赖pip install pytest-rerunfailures

  • 测试用例不通过的,才会重试
  • 比如:终端执行pytest -s test.py --reruns 3(重复执行三次)
  • 在这里插入图片描述
8.4按名称取消插件

在终端执行命令测试代码的时候,或者在配置文件pytest.ini中添加-p no:插件名的方式可以取消插件

  • 比如执行的时候,取消执行顺序的插件pytest -s test_21_report.py -p no:ordering

9、yaml

9.1、理论

Yaml 是一种所有编程语言可用的友好的数据序列化标准,类似于 json。其语法和其他高阶语言类似, 并且可以简单表达列表、字典、标量等资料形式。

语法规则

大小写敏感
使用缩进表示层级关系
缩进时不允许使用tab键,只允许使用空格
缩进的空格数目不重要,只要相同层级的元素左侧对齐即可

支持的数据结构

字典: 键值对的集合
数组: 一组按照次序排列的值, 又称序列(sequence)、列表
纯量: 单个的、不可再分的值, 包括 字符串、布尔值、整数、浮点数、null、日期

yaml转换工具:

9.2、使用python处理yaml文件

PyYaml库的安装pip3 install -U PyYAML

1、读取yaml文件数据yaml.load()

  • 准备一个yaml文件
  • 编写转换格式的python代码
import yaml

# 1. 只读方式打开文件,文件对象
# 2. 读取内容 = yaml.load(文件对象, Loader=yaml.FullLoader)

with open('./info.yaml''r') as f:
    ret = yaml.load(f, Loader=yaml.FullLoader)
    print(ret)

在这里插入图片描述
2、python写入生成yaml文件yaml.dump()

import yaml

# 准备一个字典数据
data={'Search_Data': {
          'search_test_002': {'expect': {'value': '你好'}, 'value': '你好'},
          'search_test_001': {'expect': [4, 5, 6], 'value': 456}
                        }
}

# 1. 只写方式,指定utf-8编码打开文件,有一个文件对象
# 2. yaml.dump(写入文件的数据, 上一步的文件对象, allow_unicode=True)

with open('xxx.yaml', 'w', encoding='utf-8') as f:
    yaml.dump(data, f, allow_unicode=True)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值