接口自动化-pytest

目录

1.介绍

2.pytest对比和优势

[主流 Python 测试框架对比表](#主流 Python 测试框架对比表)

3.pytest安装

4.pytest

4.1传统的方式

4.2pytest测试

4.3pytest格式

4.4查看pytest文档

[4.5 pytest命令参数](#4.5 pytest命令参数)

4.6pytest配置文件

4.7前后置

4.8断言

4.8参数化

4.9fixture

[4.10 yield fixture](#4.10 yield fixture)

4.11fixtue带参数

[4.11conftest.py 与 @pytest.fixture 结合实现全局前后置应用](#4.11conftest.py 与 @pytest.fixture 结合实现全局前后置应用)

[4.12通过 params 实现参数化](#4.12通过 params 实现参数化)

5.总结


1.介绍

之前介绍了request,有了 requests 库,可以实现对接口发起 HTTP 请求。然而在自动化测试中,我们需要编写大量的测试用例,这些用例的组织、执行和管理则需要借助其他更强大的框架 ------pytest 框架。

requests 库专注于 HTTP 请求的发送,而 pytest 框架则提供了测试用例的组织、执行和管理功能。

pytest 是一个非常流行且高效的 Python 测试框架,它提供了丰富的功能和灵活的用法,使得编写和运行测试用例变得简单而高效

pytest 官方文档Get Started - pytest documentation

2.pytest对比和优势

主流 Python 测试框架对比表

维度 unittest(Python 内置) pytest Robot Framework
安装方式 无需安装(Python 标准库) pip install pytest pip install robotframework
语法风格 基于类(需继承**TestCase)** 函数式或面向对象(无需样板代码) 关键字驱动(表格化用例)
断言方法 **self.assertEqual()**等 原生**assert**表达式 关键字断言(如**Should Be Equal**)
参数化支持 需**subTest**或第三方库 内置**@pytest.mark.parametrize** 数据驱动(Test Template
插件生态 少(依赖扩展库如**HTMLTestRunner**) 丰富(如**pytest-htmlpytest-xdistallure-pytest**) 一般(需安装额外库如**RequestsLibrary**)
测试报告 需插件生成报告 支持多格式报告(HTML、Allure 等) 自带详细日志和报告
学习曲线 中等(需熟悉 xUnit 模式) 低(语法简洁) 高(需掌握关键字和语法)
BDD 支持 不支持 支持(通过**pytest-bdd**插件) 支持(通过**robotframework-bdd**)
适用场景 简单项目或遗留系统维护 复杂项目、高扩展性需求 团队协作、非技术人员参与
pytest 核心优势 详细说明
简单易用 语法简洁清晰,编写测试用例友好,几乎可在几分钟内上手。
强大的断言支持 支持原生**assert**表达式,无需记忆复杂的断言方法,且能智能报告断言失败的中间值,便于问题定位。
支持参数化测试 通过内置的**@pytest.mark.parametrize**装饰器,允许使用不同的参数多次运行同一个测试函数,大幅提高测试效率。
丰富的插件生态系统 拥有大量插件(如 pytest-html 生成 HTML 报告、pytest-xdist 实现并行执行、allure-pytest 生成美观报告等),可扩展覆盖测试、失败用例重跑等功能;同时支持与 Selenium、Requests、Appium 等工具结合,实现 Web、接口、App 自动化测试。
灵活的测试控制 允许跳过指定用例、标记预期失败的用例,还支持重复执行失败的用例,提升测试灵活性。
多样化的测试组织方式 支持函数式测试和面向对象测试(无需继承特定类),也可通过类对测试用例进行分组,便于测试管理。
内置实用功能 提供如临时目录(tmp_path )等内置 fixtures,方便处理测试资源;支持通过 pytest.raises 断言异常场景,满足各类测试需求。

3.pytest安装

版本要求:安装 pytest 8.3.2 需要 Python 版本在 3.8 及以上。

安装命令

复制代码
pip install pytest==8.3.2
  • 不同 pytest 版本支持的最低 Python 版本对应表

    pytest 版本 最低 Python 版本
    8.0+ 3.8+
    7.1+ 3.7+
    6.2 - 7.0 3.6+
    5.0 - 6.1 3.5+
    3.3 - 4.6 2.7、3.4+

    若当前 Python 版本低于 3.8,可根据上表选择适配的 pytest 版本进行安装。

4.pytest

4.1传统的方式

复制代码
def tes1():
    print('test1')

def tes2():
    print('test2')


def tes3():
    print('test3')

import test03

if __name__ == '__main__':
    test03.tes1()
    test03.tes2()
    test03.tes3()

4.2pytest测试

复制代码
class Testaaa():
    def test01(self):
        print("test01")

    def test02(self):
        print("test01")

    def test03(self):
        print("test01")

    def test04(seelf):
        print("test01")

pytest可以自动识别测试用例,不用再编写main函数并调用测试用例

但是用例要按照一定格式,不然识别不出测试用例。

4.3pytest格式

pytest 测试用例的命名规范如下:

  1. 文件名必须以 test_ 开头或者**_test**结尾。
  2. 测试类必须以 Test 开头,并且不能包含 **init**方法。
  3. 测试方法必须以test开头。

注意:Python类中不可以添加 init方法法

复制代码
class Test01():
    def __init__(self):
        print("init")
    def test01_01(self):
        print("test01")

由于 pytest 的测试收集机制,测试类中不可以定义 init 方法。

pytest 采用自动发现机制来收集测试用例:

  • 它会自动实例化测试类
  • 调用其所有以 test 结尾的方法作为测试用例

如果测试类中定义了 **init** 方法,那么当 pytest 实例化该类时:

  • **init**方法会被调用
  • 这可能掩盖测试类的实际测试逻辑
  • 可能引入额外的副作用,影响测试结果的准确性

为了避免使用 **init** 方法,建议在 pytest 中使用其他替代方案:

  1. 使用 **setUp ()**和 **tearDown ()**方法
  2. 使用类属性
  3. 使用fixture 函数

4.4查看pytest文档

复制代码
pytest -h

4.5 pytest命令参数

pytest 提供了丰富的命令行选项来控制测试的执行,以下是一些常用的 pytest 命令行参数及其使用说明:

命令 描述 备注
pytest 在当前目录及其子目录中搜索并运行测试
pytest -v 增加输出的详细程度
pytest -s 显示测试中的 print 语句
pytest test_module.py 运行指定的测试模块
pytest test_dir/ 运行指定目录下的所有测试
pytest -k <keyword> 只运行测试名包含指定关键字的测试
pytest -m <marker> 只运行标记为指定标记的测试
pytest -q 减少输出的详细程度
pytest --html=report.html 生成 HTML 格式的测试报告 需要安装 pytest-html 插件
pytest --cov 测量测试覆盖率 需要安装 pytest-cov 插件
复制代码
pytest .\tests\test_aaa.py 
复制代码
 pytest .\tests\test_aaa.py -sv

4.6pytest配置文件

参数 解释
addopts 指定在命令行中默认包含的选项
testpaths 指定搜索测试的目录
python_files 指定发现测试模块时使用的文件匹配模式
python_classes 指定发现测试类时使用的类名前缀或模式
python_functions 指定发现测试函数和方法时使用的函数名前缀或模式
norecursedirs 指定在搜索测试时应该避免递归进入的目录模式
markers 定义测试标记,用于标记测试用例
复制代码
[pytest]
# 默认命令行参数:详细输出 + 显示print内容
addopts = -vs

# 测试用例所在目录
testpaths = tests

# 测试文件匹配规则
python_files = test_*.py *_test.py

# 测试类命名规则
python_classes = Test*

# 测试函数命名规则
python_functions = test*

# 不搜索的目录
norecursedirs = .git venv node_modules build

# 注册测试标记
markers =
    smoke: 核心功能冒烟测试
    regression: 回归测试用例
    slow: 执行时间较长的测试
    api: 接口测试用例
目录名称 含义 为何在 pytest 中跳过搜索
.git Git 版本控制系统的核心目录,存储项目的版本历史、分支信息、提交记录等元数据 与代码功能无关,不含测试用例,搜索会浪费时间,可能误判文件为测试用例
venv Python 虚拟环境目录,用于隔离项目的依赖包(避免与系统全局依赖冲突) 包含第三方库代码(非项目自身测试用例),搜索会扫描大量无关文件,降低测试执行效率
node_modules Node.js 项目的依赖目录,存储通过 npm 或 yarn 安装的 JavaScript 依赖包 若为 Python 与前端混合开发项目,该目录仅含前端依赖,与 Python 测试用例无关,无需扫描
build 存放编译、打包生成的文件(如 Python 项目的可执行文件、临时编译产物等) 是自动生成的二进制或中间文件,不含源代码或测试用例,搜索无意义
复制代码
import pytest


@pytest.mark.api
class Testaaa():
    def test01(self):
        print("test01")

    def test02(self):
        print("test01")

    def test03(self):
        print("test01")

    def test04(seelf):
        print("test01")



@pytest.mark.slow
class Testbbb():
    def test01(self):
        print("test01")

    def test02(self):
        print("test01")

    def test03(self):
        print("test01")

    def test04(seelf):
        print("test01")

pytest -m "api" 
复制代码
 pytest -m "api and not slow"
复制代码
pytest -m "api and not slow"

4.7前后置

之前的问题:使用 pytest 框架,测试类中不可以添加 init () 方法,如何进行数据的初始化?

在测试框架中,前后置是指在执行测试用例前和测试用例后执行一些额外的操作,这些操作可以用于设置测试环境、准备测试数据等,以确保测试的可靠性。

pytest 框架提供三种方法做前后置的操作:

  1. setup_methodteardown_method:这两个方法用于类中的每个测试方法的前置和后置操作。
  2. setup_classteardown_class:这两个方法用于整个测试类的前置和后置操作。
  3. fixture:这是 pytest 推荐的方式来实现测试用例的前置和后置操作。fixture 提供了更灵活的控制和更强大的功能。(后续在 fixture 中详细讲解
复制代码
class Test05():
    def setup_method(self, method):
        print("setup_method")
    def test05_01(self):
        print("test05_01")

    def test05_02(self):
        print("test05_02")

    def test05_03(self):
        print("test05_03")

    def teardown_method(self):
        print("teardown_method")

4.8断言

断言(assert)是一种调试辅助工具,用于检查程序的状态是否符合预期。如果断言失败(即条件为假),Python 解释器将抛出一个 AssertionError 异常。断言通常用于检测程序中的逻辑错误。

pytest 允许你在 Python 测试中使用标准的 Python assert 语句来验证预期和值。

基本语法:

复制代码
assert 条件, 错误信息
  • 条件:必须是⼀个布尔表达式。

  • 错误信息:当条件为假时显示的错误信息,可选。

    def test_assert():
    #断言列表
    expect_list=[1,3.14,"hello world"]
    actual_list=[1,3.14,"hello world"]

    复制代码
      assert expect_list==actual_list
      #断言元组
      expect_list = (1, 3.14, "hello world")
      actual_list = (1, 3.14, "hello world")
    
      assert expect_list == actual_list
    
      #断言集合
      expect_set = {1, 2, 3, 'apple'}
      actual_set = {1, 2, 3, 'apple'}
      assert expect_set == actual_set
    
      def divide(a, b):
          assert b != 0, "除数不能为0"
          return a / b
      # 正常情况
      print(divide(10, 2))  # 输出 5.0
      # 触发断⾔
      print(divide(10, 0))  # 抛出 AssertionError: 除数不能为0

免费学习API资源:http://jsonplaceholder.typicode.com/

测试json结果

复制代码
import requests


def test():
    url="http://jsonplaceholder.typicode.com/posts/1"
    expect_data={
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

    r=requests.get(url=url)

    assert r.json()==expect_data

测试id结果

复制代码
import requests


def test02():
    url="http://jsonplaceholder.typicode.com/posts"
    r=requests.get(url=url)
    assert r.json()[0]["id"]==1
    assert r.json()[1]["id"] == 2
    assert r.json()[2]["id"] == 3

测试包含字段

复制代码
def test02():
    url="http://jsonplaceholder.typicode.com/posts"
    r=requests.get(url=url)
    text="qui est esse"
    assert text in r.text

4.8参数化

参数化设计是自动化设计中的一个重要组成部分,它通过定义设计参数和规则,使得设计过程更加灵活和可控。

pytest 中,内置的pytest.mark.parametrize装饰器可实现对测试函数参数的参数化,其基本格式如下:

复制代码
import pytest

@pytest.mark.parametrize("参数名1, 参数名2, ...", [(参数值1, 参数值2, ...), (参数值1, 参数值2, ...), ...])
def test_函数名(参数名1, 参数名2, ...):
    # 测试逻辑
    pass

注意:找不到包,要在设置将包设置不排除在外。

示例:

单参数:

复制代码
import pytest


@pytest.mark.parametrize("data1", (1,2,3,4))
def test03(data1):
    print(data1)

多参数:

复制代码
import pytest


@pytest.mark.parametrize("data1, data2", [(1, 4), (2, 5), (3.14, 6.23),("hello","pytest")])
def test03(data1, data2):
    print(data1, data2)

参数运算:

复制代码
import pytest


@pytest.mark.parametrize("test_input,expected ", [(1*42, 42), (2+5, 7), (4/2, 2),(1-1,0),("hello","hello")])
def test04(test_input,expected):
    assert test_input == expected


@pytest.mark.parametrize("n,expected ", [(1, 2), (3, 4)])
class Test_n:
    def test05(self,n,expected):
        assert n+1 == expected

    def test06(self,n,expected):
        assert (n*1) + 1 == expected

要对模块中的所有测试进⾏参数化,你可以将pytestmark 全局变量赋值:

复制代码
import pytest


pytestmark = pytest.mark.parametrize("n",(1, 2))

class Test_01():
    def test01(self,n):
        print(n)

    def test02(self,n):
        print(n)


class Test_02():
    def test03(self,n):
        print(n)

    def test04(self,n):
        print(n)

自定义参数化数据源:

复制代码
import pytest
def data_provider():
    return ["hello",1,1+2,3.14]

pytestmark = pytest.mark.parametrize("data",data_provider())
def test_06(data):
    print(data)

除了使用 @pytest.mark.parametrize实现测试函数的参数化外,pytest.fixture() 也支持对 fixture函数进行参数化。

4.9fixture

pytest 中的 fixture是一种强大的机制,用于提供测试函数所需的资源或上下文。它可以用于设置测试环境、准备数据等。以下是fixture 的一些核心概念和使用场景。

未调用fixture

复制代码
def fixture_01():
    print("第⼀个fixture标记的⽅法")
def test_01():
    fixture_01()
    print("第⼀个测试⽤例")

调用fixture

复制代码
import pytest

@pytest.fixture
def fixture_01():
    print("第⼀个fixture标记的⽅法")
def test_01(fixture_01):

    print("第⼀个测试⽤例")

未标记 fixture 方法的调用与 fixture 标记的方法调用完全不一样,前者需要在方法体中调用,而后者可以将函数名作为参数进行调用。

测试脚本中存在很多重复的代码公共的数据对象 时,使用 fixture 最为合适。

fixture嵌套

复制代码
import pytest
@pytest.fixture
def login():
    print("\n登陆")

def test_list(login):
    print("访问列表⻚")

def test_detail(login):
    print("访问详情⻚")
复制代码
# test_append.py的内容 import pytest
import pytest
@pytest.fixture
def first_entry():
    return "a"
# 安排
@pytest.fixture
def order(first_entry):
    return [first_entry]

def test_string(order): # ⾏动
    order.append("b")
    # 断⾔
    assert order == ["a", "b"]
复制代码
import pytest
class Fruit:
    #这里不是测试类可以用__init__
    def __init__(self, name):
        self.name = name
    def __eq__(self, other):
        return self.name == other.name
@pytest.fixture
def my_fruit():
    return Fruit("apple")
@pytest.fixture
def fruit_basket(my_fruit):
    return [Fruit("banana"), my_fruit]

def test_my_fruit_in_basket(my_fruit, fruit_basket):
    assert my_fruit in fruit_basket

4.10 yield fixture

当我们运行测试时,我们希望确保它们能够自我清理,以便它们不会干扰其他测试 (同时也避免留下大量测试数据来膨胀系统)。pytest中的fixture 提供了一个非常有用的拆卸系统,它允许我们为每个 fixture 定义具体的清理步骤。

"Yield" fixture使用yield而不是 return 。有了这些fixture ,我们可以运行一些代码,并将对象返回给请求的fixture/test ,就像其他 fixture 一样。唯一的不同是:

return 被替换为 yield

fixture 的任何拆卸代码放置在 yield 之后。

一旦 pytest 确定了 fixture 的线性顺序,它将运行每个 fixture 直到它返回或 yield ,然后继续执行列表中的下一个fixture 做同样的事情。

测试完成后,pytest 将逆向遍历 fixture列表,对于每个 yield 的 fixture ,运行yield 语句之后的代码。

示例:

不返回数据

复制代码
import pytest
@pytest.fixture
def operator():
    print("初始化操作")
    yield
    print("\n清理数据")
def test01(operator):
    print("第一个测试用例")

返回数据

复制代码
import pytest
@pytest.fixture
def operator():
    print("初始化操作")
    yield 100
    print("\n清理数据")
def test01(operator):
    print(operator+100)
    assert operator == 100
复制代码
import pytest
@pytest.fixture
def file_read():
    print("\n打开文件句柄")
    fo = open("log.txt", "r", encoding="utf-8")
    yield fo
    print("关闭文件句柄")
    fo.close()

@pytest.fixture
def file_wirte():
    print("打开文件句柄")
    fo=open("log.txt","w",encoding="utf-8")
    yield fo
    print("\n关闭文件句柄")


def test_file(file_read,file_wirte):
    w=file_wirte
    w.write("hello")
    w.close()

    r=file_read
    str=r.read()
    print(str)

4.11fixtue带参数

复制代码
pytest.fixture(scope='', params='', autouse='', ids='', name='')

**scope 参数:**用于控制 fixture 的作用范围,决定 fixture 的生命周期。可选值有:

scope 值 说明
function (默认)每个测试函数都会调用一次fixture
class 在同一个测试类中共享这个fixture
module 在同一个测试模块(一个文件里)中共享这个fixture
session 整个测试会话中共享这个 fixture。

autouse 参数:默认为 False 。若设为 True ,每个测试函数会自动调用该 fixture ,无需显式传参。
params 参数:用于参数化 fixture ,支持列表传入。每个参数值会让 fixture 执行一次,类似 for 循环。
**ids 参数:**与 params 配合,为每个参数化实例指定可读标识符(给参数取名)。
name 参数:为 fixture 显式设名。若用了 name ,测试函数需用此名称引用 fixture(给 fixture 取名)。
scope="function"

复制代码
import pytest


@pytest.fixture(scope="function")
def fixture_01():
    print("\n初始化")
    yield
    print("\n清理")


class TestCase():
    def test_01(self, fixture_01):
        print("第一个测试用例")

    def test_02(self, fixture_01):
        print("第二个测试用例")

scope="class"

复制代码
import pytest


@pytest.fixture(scope="class")
def fixture_01():
    print("\n初始化")
    yield
    print("\n清理")


class TestCase1():
    def test_01(self, fixture_01):
        print("第一个测试用例")

    def test_02(self, fixture_01):
        print("第二个测试用例")


class TestCase2():
    def test_01(self, fixture_01):
        print("第一个测试用例")

    def test_02(self, fixture_01):
        print("第二个测试用例")

scope="module"

scope 默认为function ,这里的 function可以省略不写。当 scope="function" 时,每个测试函数都会调用一次 fixture。当 scope="class" 时,在同一个测试类中,fixture 只会在类中的第一个测试函数开始前执行一次,并在类中的最后一个测试函数结束后执行清理。

  • scope="module"、scope="session" 时,可用于实现全局的前后置应用,这里需要多个文件配合。

4.11conftest.py 与 @pytest.fixture 结合实现全局前后置应用

@pytest.fixtureconftest.py文件结合使用,能够在多个测试模块(.py 文件)中共享前后置操作。这种方式可在整个测试项目中定义和维护通用的前后置逻辑,让测试代码更模块化、更易维护。

规则:

  • conftest.py 是单独存放夹具配置的文件,名称固定且不可修改。
  • 可在项目的不同目录下创建多个conftest.py文件,每个文件仅对其所在目录及子目录下的测试模块生效。
  • 在不同模块的测试中使用conftest.py 的前后置功能时,无需进行任何import导入操作。

作用:

使不同 .py 文件可以使用同一个 fixture 函数

autouse默认为 False ,即当前的fixture 需要手动显式调用,在该案例之前我们默认使用的都是 autouse =False

autouse=True 时,fixture 会在所有测试函数执行之前自动调用,无论这些测试函数是否显式地引用了该 fixture

scope="autouse"

4.12通过 params 实现参数化

复制代码
@pytest.fixture(params=["1","1+2","a","3.14"])
def data_provider(request):
    return request.param

def test_data(data_provider):
    print(data_provider)

如果测试场景主要涉及简单的参数传递,且不需要复杂的资源管理,建议使用parametrize ,因为它更简单直接;如果测试需要动态加载外部数据,或者需要管理复杂的测试资源(如数据库连接、文件操作等),建议使用fixture。在某些情况下,也可以结合使用 parametrizefixture,以充分利用两者的优点。

总结来说,parametrize 更适合简单场景,而 fixture 更适合需要动态数据和资源管理的复杂场景。

5.总结

相关推荐
m0_5982500025 分钟前
参考平面与返回电流
笔记
Forever Nore1 小时前
Nginx 学习
运维·学习·nginx
源代码•宸1 小时前
C++高频知识点(十五)
c++·经验分享
泽02022 小时前
Linux基本指令(一)
linux·运维·服务器
XIAO·宝2 小时前
PyCharm 在手,NumPy 函数实操笔记:从字符串到统计分析,一篇搞懂
笔记·pycharm·numpy
AOwhisky2 小时前
板块三章节3——NFS 服务器
运维·服务器·php
从零开始的ops生活3 小时前
【Day 18】Linux-DNS解析
linux·运维·服务器
Dontla3 小时前
Linux怎么查看时区信息?(Linux时区)(tzselect)
linux·运维·服务器