pytest简介

一、pytest常用插件

  • pytest-html 生成测试报告
  • pytest-xdist 多线程运行
  • pytest-ordering 执行顺序
  • pytest-rerunfailures 失败用例重跑
  • allure-pytest 用于在运行测试时收集 Allure 格式的测试结果(如用例状态、步骤、附件、日志等)
  • pytest-base-url 管理用例的基础路径
  • pytest 框架本身

二、pytest默认的测试用例规则

  • 包名和模块名以及用例名(函数、方法)必须符合以test_开头或者 _test结尾
  • 测试用例类必须以Test开头,而且测试类不能存在init魔法函数
  • 以上规则属于pytest的默认形式,需要修改可以修改pytest.ini修改

三、pytest的运行方式

3.1主函数方式执行

pytest.main()

参数详解:

  • -s:表示输出调试信息,包括print打印的信息

  • -v表示详细的信息,一般以"-vs"使用

  • -n支持多线程运行,比如 -n 2

  • --reruns num :表示失败用例重跑,比如pytest.main(['-vs','./testcase','-n=2'])

  • -x表示只要有一个用例报错,那么测试停止

  • --maxfail=2出现2个用例失败就停止

  • -k=value:被指定执行的用例

  • --html ./report/report.html 指定默认报告生成的路径。与allure生成的报告不相同

  • 指定运行模块pytest.main(['-vs', './testcase/test_01.py')

  • 指定运行目录pytest.main(['-vs', './testcase/')

  • 通过nodeid指定用例运行,可以指定函数或者方法 pytest.main(['-vs', './testcase/test_01.py::test_03'])

  • 一般会安装allure-pytest插件,其提供了两个命令参数:--alluredir=:将allure格式的测试数据输出到指定目录,用于后续allure报告生成; --clean-alluredir:在生成新的测试数据之前将清理指定的目录的数据。pytest.main(['-s', '-v', './testcase/', '--alluredir', './report/result/', '--clean-alluredir'])

  • 也可以带上-log-format和--log-date-format控制 pytest 内置日志系统(pytest logging)的输出格式的,不过一般是在pytest.ini中定义

    python 复制代码
    pytest.main(['-s', '-v', './api_case/test01.py', '--alluredir', './report/result/', '--clean-alluredir','--log-format','%(asctime)s.%(msecs)03d [%(
    filename)s:%(lineno)s] [%(levelname)5s] %(message)s','--log-date-format','%Y-%m-%d %H:%M:%S'])

3.2、命令行方式执行

参数与pytest.main()一致。

python 复制代码
py.test.exe -vs .\testcase\test_01.py::Test_pytest::test_03 #windows下也可以直接用py.test.exe

pytest -vs  --alluredir=/var/lib/jenkins/workspace/test/result --clean-alluredir testcase/

3.3、读取pytest.py配置文件运行

pytest.ini是一个手动创建的配置文件,用于读取整个项目的配置信息,pytest将按此配置文件中指定的方式去运行,并可以改变pytest的默认行为。pytest.ini配置文件存放在项目的根目录,文件名称固定不可修改,配置完后通过执行pytest调用,pytest.main()的部分参数也是会生效的。文件内容如下:

python 复制代码
[pytest]
;addopts = -vs  --html ./report/report.html
#排除测试用例的路径
norecursedirs =./testcase01
#测试用例的路径                    
testpaths = ./testcase
#模块名的规则                                     
python_files = test_*.py                                   
 #类名的规则
python_classes = Test*
#方法名的规则
python_functions = test                                 
#标志规则,默认是false标记@pytest.mark.xfail的用例会根据配置来设置结果
xfail_strict=true or false              
 
#日志文件格式 时间+文件名+行数+日志等级+日志信息
log_format = %(asctime) s [%(filename) s:%(lineno)-4s] [%(levelname) 5s] %(message) s  
#asctime的时间格式,%Y-%m-%d %H:%M:%S是到秒;%Y-%m-%d %H:%M:%S.%f是到微秒.但是在linux环境可能不生效直接原样输出为2025-11-14 13:27:05.%f,未找到原因。如果想输出ms,可以如下设置log_format = %(asctime)s.%(msecs)03d
log_date_format = %Y-%m-%d %H:%M:%S.%f 
 
#首先在pytest.ini中定义分组名称,然后在用例中用@pytest.mark.smoke或@pytest.mark.test标记分组,用pytest -m "smoke or test"执行对应的分组用例 
markers=smoke
        get
        first
        last
        run

norecursedirs 和testpaths 说明

  • 当两者有冲突时,比如二者配置的一样,testpaths优先,也就是执行testpaths下的所有用例

  • testpaths包含norecursedirs,执行testpaths下除了norecursedirs的用例

  • norecursedirs包含testpaths,不执行任何用例,并给出警告

四、执行顺序

默认情况下,pytest测试用例的执行顺序是按先外层后内层(目录下的文件),再根据编写的顺序升序执行。

如果想自定义pytest测试用例的执行顺序,可以通过多种方式实现,常用的方法有:

  • 利用pytest_ordering插件,通过装饰器@pytest.mark.run(order=1)来进行控制,数字越小,越前执行。

  • 在测试方法上加装饰器@pytest.mark.first 表示第一个执行和@pytest.mark.last表示最后一个执行。优先级最高

五、用例跳过--skip

  • 有条件跳过(在用例前面加上@pytest.mark.skipif(age>=18,reason = '已成年'))
  • 无条件跳过(在用例前面加上@pytest.mark.skip)

六、标记失败用例-xfail

适用于用例写了但是代码没有实现,或者已知bug

使用方法:@pytest.mark.xfail(condition, reason="xxx" ) condition为预期失败的条件

1、assert成功。结果为xpass

python 复制代码
@pytest.mark.xfail
def test_case1():
    print("代码开发中")
    assert 1==1

2、assert失败。结果为xfail

python 复制代码
@pytest.mark.xfail
def test_case1():
    print("代码开发中")
    assert 1==2

3、condition为true,才会执行xfail判断。assert成功,结果为xpass;assert成功。结果为xfail

python 复制代码
@pytest.mark.xfail(1==1, reason="代码开发中")
def test_case1():
    print("代码开发中")
    assert 1==1

4、condition为false,不执行xfail判断。直接执行用例

python 复制代码
@pytest.mark.xfail(1==2, reason="代码开发中")
def test_case1():
    print("代码开发中")
    assert 1==1

5、将不符合预期的成功XPASS标记为失败,使用strict参数。

strict为关键字参数,默认值为False。

当strict=False时,如果用例执行失败,则结果标记为XFAIL,表示符合预期的失败。如果用例执行成功,则结果标记为XPASS,表示不符合预期的成功。

当strict=True时,如果用例执行成功,则结果将标记为FAILED,而不再标记为XPASS。

我们也可以在pytest.ini文件中配置,代码如下:

python 复制代码
[pytest]
xfail_strict = ture

如果用例的失败不是因为所期望的异常导致的,pytest将会把测试结果标记为FAILED。

七、标记参数化-parametrize

https://blog.csdn.net/qishuzdh/article/details/124808459

参数化使用场景主要是针对同样的用例步骤,输入不同数据,结合参数实现数据驱动的测试

7.1、parametrize基础用法

python 复制代码
@pytest.mark.parametrize(argnames, argvalues, ids=None, scope=None)
  • argnames(必选):字符串类型,指定测试函数的参数名列表(多个参数用逗号分隔)
  • argvalues(必选):可迭代对象(如列表、元组、生成器等),提供多组测试数据。每组数据对应一个测试用例,长度必须与 argnames中的参数数量一致
  • ids(可选):为每组测试数据生成自定义的测试用例名称(默认是数据的内存地址,可读性差)
  • scope(可选):指定参数化的作用域,默认是 "function"(每个测试函数独立参数化)。其他选项"class"/"module"/"session"
  • indirect:默认情况下,@pytest.mark.parametrize的参数化数据是直接传递 给测试函数的。而 indirect的作用是:将指定的参数名标记为"间接参数" ,这些参数的值不会直接传入测试函数,而是作为 fixture 的输入参数,调用对应的 fixture 并获取其返回值,最终将 fixture 的返回值传递给测试函数。
python 复制代码
pytest.mark.parametrize的使用。https://blog.csdn.net/qishuzdh/article/details/124808459
# 1、一个参数一个值
@pytest.mark.parametrize("input", ["输入值"])
#2、一个参数多个值
@pytest.mark.parametrize("input", ["输入值1", "输入值2", "输入值3", "输入值4", "输入值5"])
# 3、多个参数多个值
@pytest.mark.parametrize("userName,passWord",[("xiaqiang", "123456"), ("rose", "123456"), ("jone", "123456"), ("Alix", "123456")])
# 4、多个参数混个使用
data1 = [1, 2]
data2 = ["python", "java"]
data3 = ["暴", "躁", "测", "试", "君"]
@pytest.mark.parametrize("a", data1)
@pytest.mark.parametrize("b", data2)
@pytest.mark.parametrize("c", data3)
# 5、参数化,传入字典数据
json=({"username":"alex","password":"123456"},{"username":"rongrong","password":"123456"})
@pytest.mark.parametrize('json', json)
# 6、参数化集合标记的使用
@pytest.mark.parametrize("user,pwd",[("xiaoqiang", "123456"), ("rose", "123456"),pytest.param("jone", "123456", marks=pytest.mark.xfail),pytest.param("Alex", "123456", marks=pytest.mark.skip)])

indirect的语法与取值

indirect可以是以下三种类型之一:

  • 布尔值(True/False):

    • indirect=True:所有参数化参数(即 argnames中声明的参数)都会被视为间接参数,强制通过 fixture 传递。
    • indirect=False(默认值):所有参数直接传递给测试函数,不经过 fixture。
  • 字符串列表:

    指定哪些参数名需要作为间接参数(通过 fixture 传递),其他参数直接传递。例如 indirect=["browser", "env"]表示只有 browserenv会触发 fixture 调用,其他参数直接传入测试函数。

    python 复制代码
    # 定义两个 fixture,分别处理浏览器和环境
    @pytest.fixture
    def browser(request):
        return f"{request.param}_instance"  # 返回模拟实例
    
    @pytest.fixture
    def env(request):
        return f"{request.param}_env"  # 返回模拟环境
    
    # 参数化测试,indirect=["browser", "env"] 表示两个参数都通过 fixture 传递
    @pytest.mark.parametrize(
        "browser, env, test_case",
        [
            ("chrome", "dev", "case1"),
            ("firefox", "staging", "case2"),
            ("edge", "prod", "case3")
        ],
        indirect=["browser", "env"]  # browser 和 env 都走 fixture
    )
    def test_multi_indirect(browser, env, test_case):
        print(f"测试用例 {test_case}: 在 {env} 环境使用 {browser} 执行")

7.2、parametrize通过读取excel的数据来实现数据驱动

python 复制代码
import openpyxl
def get_data_excel():
    wb = openpyxl.load_workbook(r"C:\Users\liyue03\Desktop\test\data\user_password.xlsx")
    sheet = wb.worksheets[0]
    list2 = []
    for i in sheet.values:
        list2.append(i)
    else:
        return list2
python 复制代码
@pytest.mark.parametrize('username,password', get_data_excel())
def test_parm_excel(username, password):
    print(f"姓名:{username}")
    print(f"姓名:{password}")

7.3、parametrize通过读取csv的数据来实现数据驱动

python 复制代码
import csv

def get_data_csv():
    list1 = []
    c1 = csv.reader(open(r"C:\Users\liyue03\Desktop\test\data\data.csv", encoding="UTF-8"))
    for i in c1:
        list1.append(i)
    else:
        return list1
python 复制代码
@pytest.mark.parametrize('username,password', get_data_csv())
def test_parm_csv(username, password):
    print(f"姓名:{username}")
    print(f"姓名:{password}")

八、pytest前后置

前置setup和后置teardown来处理测试用例执行前的准备工作(浏览器驱动实例化,数据库连接等)以及执行后的处理工作(清理数据,关闭浏览器驱动,关闭数据库连接等),pytest中存在两种前后置方式:1、setup/teardown;2、fixture。

8.1、setup/teardown

https://www.cnblogs.com/trystudy/p/17025701.html

pytest提供了以下5个前置后置方法:

  • setup、teardown:每条用例都会执行,既可以在类中使用,也可以在类外使用--但是新版的pytest不支持setup和teardown
  • setup_class、teardown_class:类中的测试用例执行前后只执行一次
  • setup_method、teardown_method:类中的每条测试用例执行前后都执行一次
  • setup_function、teardown_function:类外的每条测试用例执行前后都执行一次
  • setup_module、teardown_module:类外的测试用例执行前后只执行一次

执行顺序:

类中前置执行顺序:setup_class > setup_method (后置执行顺序则相反)

类外前置执行顺序:setup_module > setup_function (后置执行顺序则相反)

python 复制代码
def setup_function():
    print('setup_function前置执行')
def teardown_function():
    print('teardown_function后置执行')
def setup_module():
    print('setup_module前置执行')
def teardown_module():
    print('teardown_module后置执行')

class Test_04:
    def setup_class(self):
        print('setup_class前置执行')
    def teardown_class(self):
        print('teardown_class后置执行')
    def setup_method(self):
        print('setup_method前置执行')
    def teardown_method(self):
        print('teardown_method后置执行')

    def test_01(self):
        print("-"*5,"类内用例01","-"*5)
    def test_02(self):
        print("-"*5,'类内用例02',"-"*5)

def test_case1():
    print("-"*5,"类外用例01","-"*5)
    assert 1 == 1

def test_case2():
    print("-"*5,"类外用例02","-"*5)
    assert 1 == 1

执行结果:

testcase/test_01.py::Test_04::test_01 setup_module前置执行

setup_class前置执行

setup_method前置执行

----- 类内用例01 -----

PASSEDteardown_method后置执行

testcase/test_01.py::Test_04::test_02 setup_method前置执行

----- 类内用例02 -----

PASSEDteardown_method后置执行

teardown_class后置执行

testcase/test_01.py::test_case1 setup_function前置执行

----- 类外用例01 -----

PASSEDteardown_function后置执行

testcase/test_01.py::test_case2 setup_function前置执行

----- 类外用例02 -----

PASSEDteardown_function后置执行

teardown_module后置执行

8.2、fixture 机制

8.2.1、fixture优点

fixture相对于pytest中的setup和teardown来说有以下几点优势:

  • fixure命名更加灵活,局限性比较小;
  • conftest.py 配置里面可以实现数据共享,不需要import就能自动找到一些配置;
  • scope="session"可以实现多个.py跨文件使用一个session来完成多个用例。

8.2.2、fixture语法

语法如下:

python 复制代码
fixture(callable_or_scope=None, *args, scope="function", params=None, autouse=False, ids=None, name=None)
  • scope:fixture的作用域,默认为function;scope参数可以是session, module,class,function
    • session 会话级别:是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module;
    • module 模块级别:模块里所有的用例执行前执行一次module级别的fixture;
    • class 类级别 :每个类执行前都会执行一次class级别的fixture;
    • function 函数级别:每个测试用例执行前都会执行一次function级别的fixture。
  • parmas:允许 fixture 为测试提供多组数据,每个参数值都会生成一个独立的测试用例。一般用于参数化用例,request参数可以调用parmas传入的参数。
  • autouse:默认:False,需要用例手动调用该fixture;如果是True,所有作用域内的测试用例都会自动调用该fixture;
  • name:装饰器的名称,同一模块的fixture相互调用建议写不同的name。

8.2.3、定义fixture

定义fixture,在函数上添加@pytest.fixture即可。

python 复制代码
@pytest.fixture()
def fixture_demo():
    print("这是fixture")

8.2.4、如何区分前后置

在pytest中,用yield区分前后置,即yield前面代码为前置,后面代码为后置。

python 复制代码
from selenium import webdriver
@pytest.fixture()
def open_browser_init():
    # 前置
    driver = webdriver.Chrome()
    driver.get("https://www.baidu.com")
    yield driver
    # 后置
    driver.quit()

8.2.5、 fixture调用

https://blog.csdn.net/weixin_67553250/article/details/140805658

一共存在三种调用方式:

  • 将fixture名称作为参数传入测试用例,如果fixture有返回值,那么测试用例将会接收返回值。
  • pytest装饰器调用fixture。@pytest.mark.usefixtures("XXX")
  • autouse调用fixture。
python 复制代码
@pytest.fixture()
def fixture_test():
    print("pytest fixture 前置")
    yield
    print("pytest fixture 后置")

@pytest.fixture(autouse=True)
def fixture_test_auto():
    print("pytest fixture autouse 开启前置")
    yield
    print("pytest fixture autouse 开启后置")

def test_01(fixture_test):
    print("打印test01_def")


@pytest.mark.usefixtures("fixture_test")
def test_02(fixture_test):
    print("打印test02_def")
执行结果:

testcase/test_01.py::test_01 pytest fixture autouse 开启前置
pytest fixture 前置
打印test01_def
PASSEDpytest fixture 后置
pytest fixture autouse 开启后置

testcase/test_01.py::test_02 pytest fixture autouse 开启前置
pytest fixture 前置
打印test02_def
PASSEDpytest fixture 后置
pytest fixture autouse 开启后置

parmas和request的使用

  • params参数的主要作用是为 fixture 提供参数化能力,让同一个 fixture 可以根据不同的参数值生成多个测试用例。
  • request是 pytest fixture 中的一个特殊参数,它是一个 FixtureRequest 对象,提供了访问测试上下文信息的能力。最主要的使用当使用 params参数定义 fixture 时,request.param用于获取当前的参数值。
python 复制代码
import pytest


@pytest.fixture(params=["apple", "banana", "cherry"])
def detailed_fixture(request):
    """展示 request 对象的所有重要属性"""
    print("\n" + "=" * 50)
    print("REQUEST 对象属性详情:")
    print("=" * 50)

    # 1. 参数相关属性
    print(f"request.param: {request.param}")  # 当前参数值
    print(f"request.param_index: {request.param_index}")  # 参数索引

    # 2. Fixture 相关属性
    print(f"request.fixturename: {request.fixturename}")  # fixture名称
    print(f"request.scope: {request.scope}")  # 作用域

    # 3. 测试上下文属性
    print(f"request.function: {request.function.__name__ if hasattr(request, 'function') else 'N/A'}")
    print(f"request.cls: {request.cls}")  # 测试类(如果有)
    print(f"request.module: {request.module.__name__ if hasattr(request, 'module') else 'N/A'}")

    # 4. 会话和配置
    print(f"request.config: {type(request.config)}")  # 配置对象

    return {
        "param": request.param,
        "index": request.param_index,
        "fixture_name": request.fixturename,
        "scope": request.scope
    }


def test_request_attributes(detailed_fixture):
    """测试 request 对象的属性"""
    print(f"测试接收到的fixture数据: {detailed_fixture}")
其中一个参数的输出结果如下:
testcase/test_request.py::test_request_attributes[apple]
==================================================
REQUEST 对象属性详情:
==================================================
request.param: apple
request.param_index: 0
request.fixturename: detailed_fixture
request.scope: function
request.function: test_request_attributes
request.cls: None
request.module: testcase.test_request
request.config: <class '_pytest.config.Config'>
测试接收到的fixture数据: {'param': 'apple', 'index': 0, 'fixture_name': 'detailed_fixture', 'scope': 'function'}
PASSED

8.2.6、conftest.py的作用范围

conftest.py文件名称时固定的,pytest会自动识别该文件。一个工程下可以建多个conftest.py的文件,一般在工程根目录下设置的conftest文件起到全局作用。在不同子目录下也可以放conftest.py的文件,作用范围只能在改层级以及以下目录生效。可以将fixture全部放到conftest.py

conftest.py示例

python 复制代码
import pytest

@pytest.fixture(scope='session', autouse=True)
def login():
    print('----准备登录----')
相关推荐
ragnwang1 小时前
Ubuntu /home 分区安全扩容教程
linux·运维·ubuntu
濊繵1 小时前
Linux网络--应用层自定义协议与序列化
linux·服务器·网络
zt1985q2 小时前
本地部署 Jupyter 并实现外部访问(Windows 版本)
运维·服务器·windows
爱吃泡芙的小白白2 小时前
使用某云超算平台Jupyterlab的使用方法(自用)
运维·服务器·python·学习记录
p***93032 小时前
自己编译RustDesk,并将自建ID服务器和key信息写入客户端
运维·服务器
潇凝子潇2 小时前
Linux 服务器实时监控Shell 脚本
linux·服务器·chrome
last demo2 小时前
iscsi服务器
linux·运维·服务器·php
java_logo2 小时前
GPUSTACK Docker 容器化部署指南
运维·mongodb·docker·云原生·容器·eureka·express
阿巴阿巴啊啊啊2 小时前
docker基本操作命令
运维·docker·容器