pytest框架(三)

1、简单代码示例

pytest捕获异常

with pytest.raise(异常类型):

        cal.div(1,0)

#测试类
class Calculator:
    def add(self,a,b):
        return a+b

    def div(self,a,b):
        return a/b
from pytestdemo.calculator import Calculator
import pytest


class TestCal:
    def setup(self):
        print("开始计算")

    def teardown(self):
        print("结束计算")

    @pytest.mark.parametrize(("a", "b", "expect"), [(1, 1, 2), (5, 4, 9)])
    def test_add_int(self, a, b, expect):
        cal = Calculator()
        assert expect == cal.add(a, b)

    @pytest.mark.parametrize(("a", "b", "expect"), [(0.1, 0.1, 0.2), (0.1, 0.2, 0.3)], ids=["float1", "float2"])
    def test_add_float(self, a, b, expect):
        cal = Calculator()
        assert expect == round(cal.add(a, b),2)


    def test_div(self):
        cal = Calculator()
        # try:
        #     cal.div(1,0)
        # except ZeroDivisionError as e:
        #     print("除数为0")
        with pytest.raises(ZeroDivisionError):
            cal.div(1,0)


2、pytest框架结构

模块、函数、类、类方法共4种,第四跟第五是同一种

  • 模块级(setup_module/teardown_module)模块始末,全局优先级最高
  • 函数级(setup_function/teardown_function)只对函数用例生效,不在类中的函数
  • 类级(setup_class/teardown_class)只在类中,前后调用一次
  • 方法级(setup_method/teardown_method)在方法的前后,在类中的方法
  • 类中(setup/teardown)运行在调用方法前后

3、pytest fixture的作用

fixture是运行在测试函数前后,由pytest执行的外壳函数,可以定制代码,满足多种测试需求,功能包括:

  •         定义传入测试中的数据集
  •         配置测试前系统的初始状态
  •         为批量测试提供数据

fixture是pytest用于测试前后进行准备,清理工作代码,分离核心测试逻辑的机制

(1)类似于setup/teardown功能,但是更加灵活

(2)直接通过函数名字调用或使用装饰器@pytest.mark,usefixture('test1')

tips:使用这种方式,无法获取test1方法的返回值

(3)可使用多个fixture

(4)使用autouse自动应用,会自动在所有测试用例上执行,使用返回值,需要传fixture函数名

@pytest.fixture(autouse=True)
def login():
    print("登录")
    token="xxaascxa"
    return token

(5)作用域 session>module>class>function

session是整个项目的域

module是模板即测试文件

class即测试类

function即测试方法

@pytest.fixture(scope="module")
def login():
    print("登录")
    token="xxaascxa"
    return token

说明:这里使用了模块级别的作用域,那么这个登录操作只会在整个模块前执行一次

代码示例:

import pytest


@pytest.fixture
def login():
    print("登录")
    token="xxaascxa"
    return token

def test_search():
    print("搜索")

def test_cart():
    print("购物车")

# def test_order(login):
#     print("下单")
#     print(login)

@pytest.mark.usefixtures('login')
def test_order():
    print("下单")
    print(login)

测试结果只显示了测试前置的fixture操作,怎么显示fixture测试后置的操作结果呢?

小tips:

(1)--setup-show 回溯fixture的执行过程

例子:pytest test_demo.py --setup-show

(2)fixture中可以使用yield激活teardown操作

4、命令行运行

  • 运行当前目录下的所有测试文件  pytest
  • 运行指定的测试文件  pytest 文件名
  • 运行指定文件中的指定类或方法  pytest 文件名::测试类名::测试方法名
  • 查看执行过程中的详细信息和打印信息  pytest -vs
  • 只收集测试用例不运行  pytest --collect-only
  • 生成执行结果文件  pytest --junitxml=./result.xml

5、场景一:前端自动化应用-按需操作

场景:测试用例执行,假设有的用例需要登录,有的用例不需要,setup/teardown不满足

可以使用fixture,默认scope 范围是function

步骤:

  1. 导入pytest
  2. 在登录函数上加装饰器@pytest.fixture()
  3. 在需要登录操作的测试方法中传入 登录函数名称
  4. 不需要登录操作的测试方法就直接执行

6、场景二:前端自动化应用 conftest.py 共享文件

场景:多人共同测试开发,公共资源要让大家都能访问到

解决方案:使用conftest.py文件进行资源共享,并且在不同位置起着不同范围的共享作用

前提条件:conftest.py文件名不能更换,放在项目下是全局数据共享的地方,全局的配置和前期工作都可以写在里面,假设放在某个包下,就是这个包数据共享的地方

执行:假设系统执行时,需要参数login,先从本文件查找是否有此名称的fixture方法,找不到会去conftest.py查找

步骤:需要先将登录模块带@pytest.fixture写入conftest.py

conftest.py用法

  • 文件名称不能更换
  • conftest.py与执行的测试用例要在同一个package下,并且具有__init__.py文件
  • 不需要import导入,pytest会自动查找

查找的规律是就近原则,先在同级目录下查找conftest.py,没有就回去父目录查找

  • 所有同目录测试文件执行前,都会先运行conftest.py文件(就近原则)
  • 全局配置,前期准备工作都可以写入conftest.py

代码示例

yaml文件写法

add_int:
  datas:
    - [1,1,2]
    - [100,100,200]

add_float:
  datas:
    - [0.1,0.1,0.2]
    - [0.2,0.3,0.5]
  ids:
    - 'float1'
    - 'float2'
from pytestdemo.calculator import Calculator
import pytest


@pytest.fixture()
def cal():
    print("开始计算")
    cal = Calculator()
    yield cal
    print("结束计算")
class Calculator:
    def add(self,a,b):
        return a+b

    def div(self,a,b):
        return a/b
import pytest
import yaml


def get_datas():
    datas = yaml.safe_load((open('data.yaml')))
    return datas


class TestCal:

    @pytest.mark.parametrize(("a", "b", "expect"), get_datas()['add_int']['datas'])
    def test_add_int(self, cal, a, b, expect):
        assert expect == cal.add(a, b)

    @pytest.mark.parametrize("a, b, expect", get_datas()['add_float']['datas'],
                             ids=get_datas()['add_float']['ids'])
    def test_add_float(self, cal, a, b, expect):
        assert expect == round(cal.add(a, b), 2)

    def test_div(self, cal):
        # try:
        #     cal.div(1,0)
        # except ZeroDivisionError as e:
        #     print("除数为0")
        with pytest.raises(ZeroDivisionError):
            cal.div(1, 0)

7、pytest常用插件

pip install pytest-ordering 控制用例的执行顺序

pip install pytest-dependency 控制用例的依赖关系

pip install pytest-xdist 分布式并发执行用例

pip install pytest-rerunfailures 失败重跑

pip install pytest-assume 多重校验

pip install pytest-random-order 随机执行用例

pip install pytest-html 测试报告

8、pytest-ordering举例

GitHub源码的顺序映射



import pytest

@pytest.mark.run(order=2)
def test_foo():
    assert True

@pytest.mark.last
def test_last():
    assert True

@pytest.mark.first
def test_first():
    assert True

@pytest.mark.run(order=3)
def test_bar():
    assert True

9、pytest-dependency

控制用例之间的依赖关系

结果:

a失败

b成功

c失败

d成功

e失败

import pytest


@pytest.mark.dependency()
@pytest.mark.xfail(reason="deliberate fail")
def test_a():
    assert False


@pytest.mark.dependency()
def test_b():
    pass


@pytest.mark.dependency(depends=["test_a"])
# c依赖于a
def test_c():
    pass


@pytest.mark.dependency(depends=["test_b"])
# d依赖于b
def test_d():
    pass


@pytest.mark.dependency(depends=["test_b", "test_c"])
# e依赖于b c
def test_e():
    pass

10、pytest插件开发

插件加载方式

外部插件:pip install 安装的第三方插件

本地插件:pytest自动模块发现机制,在conftest.py文件中定义的fixture

内置插件:_pytest目录加载

什么是hook

钩子,理解为挂钩,在某一时刻将自定义改写的hook函数挂载到程序中

D:\a_study_env\env2\venv\Lib\site-packages\_pytest\hookspec.py

pytest hook的执行顺序(帖子获取)

 定制pytest插件必备之pytest hook的执行顺序 - pytest - 测试人社区

11、pytest扩展插件-实现测试用例中文

conftest.py

# 含有中文的测试用例名称,改写编码格式
def pytest_collection_modifyitems(session, config, items):
    for item in items:
        item.name = item.name.encode('utf-8').decode('unicode-escape')
        item._nodeid = item.nodeid.encode('utf-8').decode('unicode-escape')

test_extend1.py

import pytest

@pytest.mark.parametrize('name',['哈利','赫敏'])
def test_extend1(name):
    print(name)

12、pytest扩展插件-执行路径含有login的测试用例

conftest.py

# 含有中文的测试用例名称,改写编码格式
import pytest


def pytest_collection_modifyitems(session, config, items):
    for item in items:
        #测试用例的名字
        item.name = item.name.encode('utf-8').decode('unicode-escape')
        #测试用例的路径
        item._nodeid = item.nodeid.encode('utf-8').decode('unicode-escape')
        #如果路径存在login  那么就加上login的标签
        if 'login' in item.nodeid:
            item.add_marker(pytest.mark.login)
    items.reverse()

test_extend1.py

import pytest

@pytest.mark.parametrize('name',['哈利','赫敏'])
def test_extend1(name):
    print(name)

def test_login():
    print('login')
    pass

def test_login_fail():
    print('login')
    assert False

def test_search():
    print('search')

执行命令,只执行带有login的测试用例

pytest -m login -vs

13、pytest扩展插件-添加命令行参数

conftest.py

# 含有中文的测试用例名称,改写编码格式
import pytest
import yaml

def pytest_collection_modifyitems(session, config, items):
    for item in items:
        # 测试用例的名字
        item.name = item.name.encode('utf-8').decode('unicode-escape')
        # 测试用例的路径
        item._nodeid = item.nodeid.encode('utf-8').decode('unicode-escape')
        # 如果路径存在login  那么就加上login的标签
        if 'login' in item.nodeid:
            item.add_marker(pytest.mark.login)
    items.reverse()


# 添加命令行参数
def pytest_addoption(parser):
    # group将下面所有的option都展示在这个group下
    mygroup = parser.getgroup('testaaa')
    # 注册一个命令行选项
    mygroup.addoption("--env",
                      default='test',  # 参数默认值
                      dest='env',  # 存储的变量
                      help='set your run env'  # 帮助提示 参数的描述信息
                      )


@pytest.fixture(scope='session')
def cmdoption(request):
    env = request.config.getoption('--env', default='test')
    if env == 'test':
        print("测试环境")
        dataspath='./test/datas/datas.yaml'
    elif env == 'dev':
        print("开发环境")
        dataspath='./dev/datas/datas.yaml'
    datas=yaml.safe_load(open(dataspath))
    return env,datas

test_extend1.py

import pytest

@pytest.mark.parametrize('name',['哈利','赫敏'])
def test_extend1(name):
    print(name)

def test_login():
    print('login')
    pass

def test_login_fail():
    print('login')
    assert False

def test_search():
    print('search')


def test_env(cmdoption):
    print(cmdoption)
    env,datas=cmdoption
    ip=datas[0]['ip']
    port=datas[0]['port']
    url=ip+":"+port
    print(url)

14、发布插件

Packaging Python Projects — Python Packaging User Guide

pypi官网打包步骤

(1)准备好必须文件:setup.py   包

setup.py示例

from setuptools import setup

setup(
    name='pytest_encode',
    url='http://github.com/xxx/pytest-encode',
    version='1.0',
    author='moon',
    author_email='784994407@qq.com',
    description='set your encoding and logger',
    long_description='Show Chinese for your mark.parametrize(). Define logger variable ',
    classifiers=[
        'Framework :: Pytest',
        'Programming Language :: Python',
        'Topic :: Software Development :: Testing',
        'Programming Language :: Python ::3.8',

    ],
    license='proprietary',
    packages=['pytest_encode'],
    keywords=['pytest', 'py.test', 'pytest_encode', ],
    # 需要安装的依赖
    install_requires=['pytest'],
    # 入口函数
    entry_points={
        'pytest11': ['pytest-encode = pytest_encode', ]
    },
    zip_safe=False

)

参考pytest-ordering插件目录

(2)安装打包工具:wheel、setuptools(自带)

(3)在setup.py的文件下执行打包命令:python setup.py sdist bdist_wheel

 

 (4)打包完成后的目录示例

dist目录:上面是源码包,下面是pip命令安装

pip安装:pip install   文件绝对路径

 tips:当依赖多时,可使用命令导出

pip freeze >requirements.txt

15、常用的日志模块

import logging

logging.basicConfig(
    level=logging.INFO,
    # 日志格式
    # 时间、代码所在文件名、代码行号、日志级别名字、日志信息
    format='%(asctime)s %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
    # 打印日志的时间
    datefmt='%a, %d %b %Y %H:%M:%S',
    # 日志文件存放的目录及文件名
    filename='report.log',
    filemode='w'

)
logger = logging.getLogger(__name__)

使用方式

import logging

import pytest

@pytest.mark.parametrize('name',['哈利','赫敏'])
def test_extend1(name):
    logging.info("测试")
    print(name)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值