Pytest测试框架[持续更新]

1,163 阅读8分钟

apytest是python的一种单元测试框架,与python自带的unittest测试框架类似,但更简洁并高效。特点:

1 安装pytest

pip3 install pytest

2 执行pytest

import pytest
pytest.main([paramas])

paramas可带参数介绍(多参数用 , 分隔)

  • -s 显示执行的输出内容
  • -v 显示执行的用例信息
  • -m 选择执行的标记用例

3 pytest 用例识别机制

  • 在运行的项目目录下自动查找 test_开头的或者_test结尾的文件
  • 兼容unittest的用例编写方法
  • 用例文件中以test_开头的函数会被当成测试用例
  • 用例文件中以Test开头的类,里面test_开头的方法,会被当成用例
  • 不能在测试类中写init方法
  • 可以通过pytest.ini文件去自定义用例识别的机制

3.1 pytest中用例执行的顺序:

  • 用例文件名以ascii码进行排序
  • 用例文件中的用例:按照用例编写的先后顺序来执行的

3.2 pytest.ini 自定义用例识别:

pytest.ini 可以用来修改pytest识别测试用例的机制 在项目根目录下创建pytest.ini文件[文件名不可自定义]

[pytest]

python_files =
    test_*.py
    _test*.py
    example_*.py

python_functions =
    *_test
    test_*

python_classes =
    Test*
    *Test
  • python_files 可被识别的python文件名
  • python_functions 可被识别的函数名
  • python_classes 可被识别的类名

4 pytest 前置后置

4.1 setup/teardown

级别写法
用例级setup/teardown
setup_class/teardown_class
模块setup/teardown
def setup():
    print("-----模块的前置setup-----")


def teardown():
    """用例基本的前置"""
    print("----------模块的后置-----------------")


class TestDome2(object):

    def setup(self):
        """用例基本的前置"""
        print("----------用例级别的前置-----------------")

    def teardown(self):
        """用例基本的前置"""
        print("----------用例级别的后置-----------------")

    def setup_class(self):
        """用例基本的前置"""
        print("----------测试类级别的前置-----------------")

    def teardown_class(self):
        """用例基本的前置"""
        print("----------测试类级别的后置-----------------")

    def test_demo2_method01(self):
        print("测试用例:test_demo2_method01")
        assert 100 == 100

    def test_demo2_method03(self):
        print("测试用例:test_demo2_method03")
        assert 10 in [10, 22, 33]

4.2 测试夹具 pytest.fixture(*params)

pytest.fixture 测试夹具添加前置后置

4.2.1 pytest.fixture(autouse=) 测试夹具的执行方式

  • True 测试夹具自动执行
@pytest.fixture(autouse=True)
def case_setup():
    print("---测试用例执行的前置---")
    yield  # 分割前后置的 yeild之前是前置方法,yeild之后是后置
    print("---测试用例执行的后置---")

class TestDemo:

    def test_01(self):
        print('--case3----99 == 99--------')
        assert 99 == 99

    def test_02(self, case_setup):
        print('--case4----100 == 100--------')
        assert 100 == 100
  • False 指定具体的测试用例(泛指)填写需要执行的测试夹具
@pytest.fixture()
def case_setup():
    print("---测试用例执行的前置---")
    token = 'safiudhfoahsof8q294181'
    id = 999
    yield token, id  # 分割前后置的 yeild之前是前置方法,yeild之后是后置
    print("---测试用例执行的后置---")

class TestDemo:

    @pytest.mark.main
    def test_01(self, case_setup):  # 执行测试夹具
        print('--case1----100 == 100--------')
        assert 100 == 100

    def test_02(self, case_setup):  # 执行测试夹具
        print('--case2----99 == 99--------')
        assert 100 == 100

    def test_03(self):  # 不执行测试夹具
        print('--case3----99 == 99--------')
        assert 99 == 99

    def test_04(self, case_setup):  # 执行测试夹具
        print('--case4----100 == 100--------')
        assert 100 == 100

4.2.2 pytest.fixture(scope=) 测试夹具的执行级别

  • function 用例级[默认]
  • class 类级[同一个类中 类级别的前后置只会执行一次 用例指定时需写在类中第一条用例参数中]
  • module 模块级
  • package 包级
  • session 会话级
@pytest.fixture(scope='module', autouse=True)
def case_setup03():
    print("---&&&&&用例模块执行的前置***---")
    yield  # 分割前后置的 yeild之前是前置方法,yeild之后是后置
    print("---&&&&&用例模块执行的后置****---")


@pytest.fixture()
def case_setup():
    print("---测试用例执行的前置---")
    token = 'safiudhfoahsof8q294181'
    id = 999
    yield token, id  # 分割前后置的 yeild之前是前置方法,yeild之后是后置
    print("---测试用例执行的后置---")


@pytest.fixture(scope='class')
def case_setup02():
    # 测试类级别的前后置:同一个测试类只会执行一次
    print("---***测试类执行的前置***---")
    yield  # 分割前后置的 yeild之前是前置方法,yeild之后是后置
    print("---***测试类执行的后置****---")


class TestDemo:

    @pytest.mark.main
    def test_01(self, case_setup, case_setup02): # 执行用例级别 和 类级别前后置
        res = case_setup
        print("前置中返回的数据:", res)
        print('--case1----100 == 100--------')
        assert 100 == 100

    def test_02(self, case_setup):
        print('--case2----99 == 99--------')
        assert 100 == 100

    def test_03(self):
        print('--case3----99 == 99--------')
        assert 99 == 99

    def test_04(self, case_setup):
        print('--case4----100 == 100--------')
        assert 100 == 100


class TestDemo2:

    def test_201(self):
        print('--case1----100 == 100--------')
        assert 100 == 100

    def test_202(self):
        print('--case2----99 == 99--------')
        assert 100 == 100

    def test_203(self):
        print('--case3----99 == 99--------')
        assert 99 == 99

    def test_204(self):
        print('--case4----100 == 100--------')
        assert 100 == 100

4.2.3 @pytest.mark.usefixtures(*params)

params:为添加的夹具名称 名称要求是字符串 同样可以为添加前后置 但是无法传递前后置内部参数

import pytest


@pytest.fixture()
def case_setup():
    print("---测试用例执行的前置---")
    token = 'safiudhfoahsof8q294181'
    id = 999
    yield token, id  # 分割前后置的 yeild之前是前置方法,yeild之后是后置
    print("---测试用例执行的后置---")


@pytest.fixture(scope='class')
def case_setup02():
    # 测试类级别的前后置:同一个测试类只会执行一次
    print("---***测试类执行的前置***---")
    yield  # 分割前后置的 yeild之前是前置方法,yeild之后是后置
    print("---***测试类执行的后置****---")


@pytest.mark.usefixtures("case_setup", 'case_setup02') # 添加类级别和用例级别前后置
class TestDemo2:
    pytestmark = [pytest.mark.main, ]

    def test_201(sel):
        print('--case1----100 == 100--------')
        assert 100 == 100

    def test_202(self):
        print('--case2----99 == 99--------')
        assert 100 == 100

    def test_203(self):
        print('--case3----99 == 99--------')
        assert 99 == 99

    def test_204(self):
        print('--case4----100 == 100--------')
        assert 100 == 100

4.3 前后置传递参数(yield)

  • 测试夹具内的 yield 后 跟需要返回的参数 然后在测试用例中接收
@pytest.fixture()
def case_setup():
    print("---测试用例执行的前置---")
    token = 'safiudhfoahsof8q294181'
    id = 999
    yield token, id  # 分割前后置的 yeild之前是前置方法,yeild之后是后置
    print("---测试用例执行的后置---")

class TestDemo:
    def test_01(self, case_setup):
        res = case_setup  # 接收 case_setup 中返回的参数
        print("前置中返回的数据:", res)
        print('--case1----100 == 100--------')
        assert 100 == 100
  • 向测试夹具内的传递参数 在测试夹具内调用内部函数,向函数传递参数
@pytest.fixture()
def login_setup():
    '''登录测试用例前后置'''
    def _login_setup(loginpage):
        loginpage.fresh()
    yield _login_setup  # 分割前后置的 yeild之前是前置方法,yeild之后是后置
    
    
class Test_login():
    '''
    description: 网页登录用例类
    param : 
    return {type} 
    '''
    test_data = yaml.load(open(testpath.TestData, encoding="utf-8"), Loader=yaml.FullLoader)["test_data"]

    @pytest.mark.parametrize('kwargs', test_data)
    def test_login(self, kwargs, login_setup_class, login_setup):
        self.loginpage = login_setup_class
        login_setup(self.loginpage)
        self.loginpage.login(kwargs['phone'], kwargs['pwd'])
        res_ele = self.loginpage.find_ele(pat=kwargs['xpath'],pat_params=kwargs['expected'])
        assert res_ele

4.4 测试夹具统一管理文件(conftest)

conftest.py 文件内统一存放测试用例的前后置 pytest可以自动去查找而无需进行导包[文件名不可修改]

5 标记

标记可以在执行测试过程中有选择的执行部分用例.标记分自带标记和自定义标记两种

5.1 自定义标记

  • 注册标记 在pytest.ini文件中写入标记名称(marks = "标记" 必须用双影号)
[pytest]
markers =
    mains
    musen
    demo5
  • 给用例打标记(@pytest.mark.main)
import pytest

@pytest.mark.next
@pytest.mark.usefixtures("case_setup", 'case_setup02')
class TestDemo3:
    @pytest.mark.main
    def test_301(sel):
        print('--case1----100 == 100--------')
        assert 100 == 100

    def test_302(self):
        print('--case2----99 == 99--------')
        assert 100 == 100

    def test_303(self):
        print('--case3----99 == 99--------')
        assert 99 == 99

    def test_304(self):
        print('--case4----100 == 100--------')
        assert 100 == 100

或者

import pytest

@pytest.mark.usefixtures("case_setup", 'case_setup02')
class TestDemo3:
    pytestmark = [pytest.mark.标记名]  # 类标记
    def test_301(sel):
        print('--case1----100 == 100--------')
        assert 100 == 100

    def test_302(self):
        print('--case2----99 == 99--------')
        assert 100 == 100

    def test_303(self):
        print('--case3----99 == 99--------')
        assert 99 == 99

    def test_304(self):
        print('--case4----100 == 100--------')
        assert 100 == 100

  • 执行标记用例
pytest.main(["-m","标记名"]  # 符合单一标记
pytest.main(["-m","标记名 and 标记名"]  # 符合多个标记名用例
pytest.main(["-m","标记名 or 标记名"]  # 符合任一标记名用例
  • 执行指定路径用例
pytest.main(["-m","文件路径::模块::类::用例名"]  # 符合任一标记名用例

6 参数化标记

运用原理类似ddt @pytest.mark.parametrize('接受的变量名', 数据变量名)

import pytest

cases = [
    100,
    101,
    102,
    103,
]

@pytest.mark.demo5
class TestDemo4:

    @pytest.mark.parametrize('item', cases)
    def test_login(self, item):
        assert 100 == item

7 重运行失败用例

  • 安装插件
pip3 install pytest-rerunfailures
  • 执行命令
pytest --reruns 3 --reruns-delay 5  # 3表示运行次数  5表示运行间隔时间(s)

命令行执行pytest 需要在用例文件夹下添加__init__.py文件

8 测试报告

8.1 pytest-html

安装:pip3 install pytest-html
终端输入:pytest --html=report.html

8.2 allure

8.2.1 安装allure

下载离线版allure后 将bin目录地址添加到系统环境变量中 后cmd中确认

8.2.2 安装allure-pytest

pip3 install allure-pytest

8.2.3 allure报告生成地址

--alluredir=result/report[相对于启动文件的路径地址]"

8.2.4 启动allure

  • 通过命令启动
allure serve result/report[报告路径地址]
  • 通过代码启动
import os
os.system('allure serve result/report[报告路径地址]')

8.2.5 jenkins集成allure

  • 安装allure插件
  • 配置allure插件
  • jenkins发送allure报告
<h1><center><font>以下是Jenkins自动发送的邮件,请勿回复!</font><center></h1>
<h3><center><font color="red">allure报告在线查看or下载allure-report.zip用firefox离线查看,测试用例见附件</font><center></h3>
<br>
<hr>
<br>
项目描述:${JOB_DESCRIPTION}<br>
<br>
<hr>
项目名称:$PROJECT_NAME<br>
 构建编号:$BUILD_NUMBER<br>
 构建状态:$BUILD_STATUS<br>
 触发原因:${CAUSE}<br>
 构建地址:<A HREF="${BUILD_URL}">${BUILD_URL}</A><br>
 构建日志地址:<A HREF="${BUILD_URL}console">${BUILD_URL}console</A><br>
 系统allure测试报告:<A HREF="${PROJECT_URL}${BUILD_NUMBER}/allure">${PROJECT_URL}${BUILD_NUMBER}/allure</a><br>
 <hr>
 ${JELLY_SCRIPT}

然后点击应用点击保存就可以了

9 服务器运行web自动化

9.1 服务器安装项目所运行的python环境

  • 安装python版本
  • 安装项目运行的依赖包 导出项目第三方库 pip freeze > 第三方库文件的路径
    导入项目第三方库 pip install -r 第三方库文件的路径

9.2 服务器安装浏览器和驱动

  • 浏览器的启动参数(以chrome为例)
https://www.cnblogs.com/softSupermaket/p/13474203.html[转载]

9.3 以无头模式启动浏览器(以chrome为例)

from selenium import webdriver

options = webdriver.ChromeOptions()
# 无头模式
options.add_argument('--headless')
# 禁用GPU
options.add_argument('--disable-gpu')
# 非沙箱环境
options.add_argument('--no-sandbox')
driver = webdriver.Chrome(options=options)