Python测试框架 pytest : 从零开始的完全指南

pytest : 从零开始的完全指南

  • 一、pytest 简介
    • 1.1 pytest 的背景和发展历史
    • 1.2 pytest 的概念
    • 1.3 pytest 的特点
    • 1.4 测试阶段分类
    • 1.5 单元测试框架的主要功能
  • 二、pytest 的基本使用
    • 2.1 pytest 默认测试用例
    • 2.2 全局配置文件 pytest.ini
    • 2.3 执行 pytest
    • 2.4 跳过方法
    • 2.5 pytest 前后置方法
      • 2.5.1 使用固件实现前后置
      • 2.5.2 使用 Fixtrue 实现前后置
    • 2.6 conftest.py 文件
  • 三、pytest 进阶内容
    • 3.1 Allure 效果美化

  
image.png
  在软件开发中,测试是确保代码质量的关键步骤之一。测试不仅能帮助我们发现代码中的错误,还能保证新代码的改动不会破坏现有功能。对于 Python 开发者来说, pytest 是一个功能强大且灵活的测试框架,广泛应用于各种规模的项目中。本文将深入探讨 pytest ,帮助你全面掌握其基本用法、进阶功能和最佳实践。

一、pytest 简介

1.1 pytest 的背景和发展历史


  pytest 最初由 Holger Krekel 创建,于 2004 年首次发布,最初名为 py.test 。其目标是简化测试编写过程,同时提供强大的功能来满足复杂的测试需求。pytest 通过直观的语法自动化的测试发现机制强大的扩展性迅速获得了开发者的青睐。
发展历史

  • 2004年pytest首次发布,目标是提供一个简单的、灵活的测试框架。
  • 2009年Pytest Development Team成立,推动pytest的持续发展。
  • 2011年 引入插件系统,极大地增强了pytest的灵活性和扩展性。
  • 2015年pytest采用率显著上升,成为 Python 社区中最受欢迎的测试框架之一。
  • 2017年 发布pytest 3.0版本,引入了许多新特性和改进,进一步提升了用户体验。
  • 2019年pytest迎来了 5.0 版本,持续改进和优化性能。
  • 2022年pytest 6.0版本发布,继续增强功能和改进用户体验。

1.2 pytest 的概念


  pytest 是一个用于 Python 的测试框架,支持简单的单元测试和复杂的功能测试。它以其简单易用灵活的特点,受到了许多开发者的青睐。

pytest 框架可以轻松编写小型、可读的测试,并可以扩展支持应用程序和库的复杂功能测试。

pytest 官网:https://docs.pytest.org/en/8.2.x/

1.3 pytest 的特点


pytest 的主要特点包括:

  1. 简洁的语法:无需继承特定的测试类,只需使用简单的函数即可编写测试。
  2. 强大的断言:内置丰富的断言方法,提供详细的失败信息。
  3. 自动发现:自动发现测试文件和测试函数,无需显式地注册测试。
  4. 插件系统:丰富的插件生态系统,支持扩展和定制。

1.4 测试阶段分类


测试一般分为四个方面的测试:

  • 单元测试:称模块测试,针对软件设计中的最小单位——程序模块,进行正确性检查的测试工作
  • 集成测试:称组装测试,通常在单元测试的基础上,将所有程序模块进行有序的、递增测试,重点测试不同模块的接口部分
  • 系统测试:将整个软件系统看成一个整体进行测试,包括对功能、性能以及软件所运行的软硬件环境进行测试
  • 验收测试:按照项目任务书或合同、供需双方约定的验收依据文档进行的对整个系统的测试于评审,决定是否验收或拒收系统

1.5 单元测试框架的主要功能


  1. 发现测试用例
  2. 执行测试用例
  3. 判断测试结果
  4. 生成测试报告

requirements.txt 是一个用于管理 Python 项目的依赖包的文件。它包含了项目运行所需的所有 Python 包及其版本信息。通过这个文件,开发者可以方便地分享和安装项目的依赖包,确保项目在不同环境下的一致性。

本文所采用的依赖包

# requestment.txt文件
pytest-html
pytest-xdist
pytest-ordering
pytest-rerunfailures

安装命令

pip install -r requirements.txt

二、pytest 的基本使用

2.1 pytest 默认测试用例


pytest 默认测试用例的格式:

  • 模块名:模块名(文件名)通常被统一放在一个testcases文件夹中,然后需要保证模块名以test_开头或_test结尾,例如test_demo1demo2_test
  • 类名:测试类类名必须以Test开头,并且不能带有init方法
  • 方法名:测试方法名(Case 名)必须以test_开头,例如test_demo1(self)test_demo2(self)

test_demo1.py

class TestDemo:def test_demo1(self):print("测试用例1")def test_demo2(self):print("测试用例2")

2.2 全局配置文件 pytest.ini


  我们可以在 pytest.ini 中进行一些属性的配置来修改 pytest 的默认属性,我们需要在根目录下创建,名称必须是 pytest.ini

[pytest]
#参数
addopts = ‐vs
# 默认的执行路径,它会默认执行该文件夹下所有的满足条件的测试case
testpaths = ./testcases	
# 文件命名规则
python_files = test_*.py
# 类名命名规则
python_classes = Test*
# Case命名规则
python_functions = test_*# 标记
markers =		
# 冒烟规则
smoke:冒烟用例		 
product_manage:商品管理

2.3 执行 pytest


  1. 方式一:使用命令行执行

      最简单的就是直接在 console 命令行输入 pytest,如果存在 pytest.ini,它会根据文件内容进行执行; 如果没有就按照默认格式执行。但是我们可以通过一些参数来强化 pytest 参数指令:

    # -vs: -v输出详细信息 -s输出调试信息
    pytest -vs# -n: 多线程运行(前提安装插件:pytest-xdist)
    pytest -vs -n=2# --reruns num: 失败重跑(前提安装插件:pytest-rerunfailres)
    pytest -vs --reruns=2# -x: 出现一个用例失败则停止测试
    pytest -vs -x# --maxfail: 出现几个失败才终止
    pytest -vs --maxfail=2# --html: 生成html的测试报告,后面 需要跟上所创建的文件位置及文件名称(前提安装插件:pytest-html)
    pytest -vs --html ./reports/result.html# -k: 运行测试用例名称中包含某个字符串的测试用例,我们可以采用or表示或者,采用and表示都
    pytest -vs -k "qiuluo"
    pytest -vs -k "qiuluo or weiliang"
    pytest -vs -k "qiuluo and weiliang"# -m:冒烟用例执行,后面需要跟一个冒烟名称,执行user_manage这个分组
    pytest -vs -m user_manage
    
    class TestDemo:# 我们在Case上采用@pytest.mark. + 分组名称,就相当于该方法被划分为该分组中# 注意:一个分组可以有多个方法,一个方法也可以被划分到多个分组中@pytest.mark.user_managedef test_demo1(self):print("user_manage_test1")@pytest.mark.product_managedef test_demo2(self):print("product_manage_test1")@pytest.mark.user_manage@pytest.mark.product_managedef test_demo3(self):print("manage_test1")# 执行
    pytest -vs -m user_manage
    
  2. 方式二:使用 main 方法执行

    if __name__ == '__main__':pytest.main()if __name__ == '__main__':pytest.main(["‐vs"])
    

2.4 跳过方法


  pytest 的跳过案例方法其实和 unittest 是完全相同的,我们只需采用 skip 或 skipif 方法来指定参数并贴在方法上即可跳过。

# @pytest.mark.skip(跳过原因)# @pytest.mark.skipif(跳过条件,跳过原因)# 示例
class TestDemo:workage2 = 5workage3 = 20@pytest.mark.skip(reason="无理由跳过")def test_demo1(self):print("我被跳过了")@pytest.mark.skipif(workage2<10,reason="工作经验少于10年跳过")    def test_demo2(self):print("由于经验不足,我被跳过了")@pytest.mark.skipif(workage3<10,reason="工作经验少于10年跳过")def test_demo3(self):print("由于经验过关,我被执行了")def test_demo3(self):print("我没有跳过条件,所以我被执行了")

2.5 pytest 前后置方法

2.5.1 使用固件实现前后置


  • 前后置就是针对不同层级方法执行前和执行后所需要执行的步骤进行封装并执行
  • 这个层级通常被划分为:文件层、类层、方法层(比如说要执行打印日志操作)
    • 方法层:它会在每个方法执行前后去执行该操作

      # 方法执行之前def setUp(self):print("方法执行之前")pass
      # 方法执行之后def tearDown(self):print("每个测试方法执行之后都会执行")pass
      
    • 类层:它会在调用这个类内所有方法的前后去执行该操作,无论类的方法执行多少次,它只会调用一次,它是一个类方法

      # 类中所有方法之前@classmethoddef setUpClass(cls):print("类中所有方法之前")pass
      # 类中所有方法之后@classmethoddef tearDownClass(cls):print("类中所有方法之后")pass
      
    • 文件层:也叫模块层,在每个代码文件执行前后去执行该操作,模块级别的需要卸载类的外边直接定义函数即可

      # 代码文件之前def setup_module():print("代码文件之前")pass
      # 代码文件之后def teardown_module():print("代码文件之后")pass
      

类似于 Srping AOP 里面的前置、后置通知。

示例:用户账户登录

import unittestclass TestLogin(unittest.TestCase):# 在执行该类前所需要调用的方法@classmethoddef setUpClass(cls) -> None:print('------打开浏览器')# 在执行该类后所需要调用的方法@classmethoddef tearDownClass(cls) -> None:print('------关闭浏览器')# 每个测试方法执行之前都会先调用的方法def setUp(self):print('输入网址......')# 每个测试方法执行之后都会调用的方法def tearDown(self) -> None:print('关闭当前页面......')# 测试Case1def test_1(self):print('输入正确用户名密码验证码,点击登录 1')# 测试Case2def test_2(self):print('输入错误用户名密码验证码,点击登录 2')

结果
image.png

2.5.2 使用 Fixtrue 实现前后置


  Fixtrue 所实现的功能基本和固件所实现的功能是一样的,但是会更加方便。语法格式:@pytest.fixture(scope=None,autouse=False,params=None,ids=None ,name=None)

  1. scope:作用范围,参数主要有三种:function函数、class类、package/session包

    1. function:在函数层面上执行前后置,我们通常采用 yield 进行前后置划分,yield 前是前置,yield 后是后置。

      @pytest.fixture(scope="function")def exe_database_sql():print("执行SQL查询")yieldprint("关闭数据库连接")
      

      我们还可以 通过yieldreturn 去返回一些参数在方法中使用。注意: yield 返回参数后后置仍旧可以执行,但是 return 返回参数后后置操作无法执行。

      @pytest.fixture(scope="function")def exe_database_sql():print("执行SQL查询")yield "success"# return "success" 执行后无法执行后置操作print("关闭数据库连接")
      

      我们的方法在调用时,可以直接使用 exe_database_sql 表示返回信息进行输出

      def test_2(self,exe_database_sql):print(exe_database_sql)
      
    2. class:在类之前和之后执行

      @pytest.fixture(scope="class")def exe_database_sql():print("执行SQL查询")yieldprint("关闭数据库连接")
      
    3. package/session:在整个项目会话之前和之后执行

      @pytest.fixture(scope="session")def exe_database_sql():print("执行SQL查询")yieldprint("关闭数据库连接")
      
  2. autouse:是否自动启动,该参数默认为 False ,我们可以将其修改为 True,该参数的功能主要在判断该固件是否在自定义范围内可以自动启动,若自动启动,则所有方法在执行时都会自动执行该前后置;若为 False,则我们需要手动启动

    # 1. 自动启动,则我们无需关心任何参数,我们的所有方法都会自动调用@pytest.fixture(scope="function",autoues=True)def exe_database_sql():print("执行SQL查询")yieldprint("关闭数据库连接")# 2. 关闭自动启动,我们在不同的scope下有不同的调用方法@pytest.fixture(scope="function",autoues=Flase)def exe_database_sql():print("执行SQL查询")yieldprint("关闭数据库连接")# 2.1 scope = function:我们需要在方法后加上该Fixture方法名def test_2(self,exe_database_sql):print(exe_database_sql)# 2.2 scope = class:我们需要在对应的类上添加@pytest.mark.usefixtures("exe_database_sql")装饰器调用
    @pytest.mark.usefixtures("exe_database_sql")
    class TestDemo:pass# 2.3 scope = session: 一般会结合conftest.py文件来实现
    

    autouse 仅限于在自己的类中使用上述方法,如果要跨类使用,那我们也需要在conftest.py 中配置

  3. params:实现参数化配置

    通常我们的脚本都是根据导出的 yaml 文件进行属性填充,params 通常后面跟上具体的数据(列表,元组等),然后我们在调用时有固定的写法。
    首先我们需要在 Fixture 方法参数中定义一个request,然后使用 request.param 来使用我们传递的 params

    class TestDemo:def read_yaml(self):return ["张三","李四","王五"]# 首先我们的参数需要获取数据:params=read_yaml()@pytest.fixture(scope="function",autouse=False,params=read_yaml(None))# 然后我们的Fixture方法需要一个request参数def exe_database_sql(self, request):print("执行SQL查询")# 我们通过request.param获取数据,可以采用yield返回该数据yield request.paramprint("关闭数据库连接")
    
  4. ids:参数别名id

    不能单独使用,必须和 params 一起使用, 作用时对参数其别名,我们在采用 pytest 进行测试数据输出时会有对应的方法调用 n 次,该 n 次采用不同的 params 参数,这个 ids 就是修改了 console 控制台展示数据

    class TestDemo2:def read_yaml(self):return ["张三","李四","王五"]@pytest.fixture(scope="function", autouse=False, params=read_yaml(None), ids=["1", "2", "3"])def exe_database_sql(self, request):print("执行SQL查询")logging.info("打印")yield request.paramprint("关闭数据库连接")
    
  5. name:Fixture 别名

    作用时给 fixture 起别名,一旦使用了别名,那么 fixtrue 的名称就不能再用了,只能用别名

    class TestDemo:# 如果我们在这里使用到了别名@pytest.fixture(scope="function",name="exe_datebase_sql_name")def exe_database_sql(request):print("执行SQL查询")yield print("关闭数据库连接")# 我们这里就需要使用别名进行操作,之前的名称无法使用def test_2(self,exe_datebase_sql_name):print(exe_database_sql)    
    

2.6 conftest.py 文件


  该文件主要就是用来存储我们的 Fixture,然后我们会根据该文件的不同位置来判断可以使用的方法,conftest 可以在不同的目录级别下创建,如果我们在根目录下创建,那么所有case都会使用到该Fixture,但是如果我们在testcases文件夹下的某个模块文件下创建 conftest.py,那么它的作用范围就只包含在该目录下

  1. 在根目录创建conftest.py,我们在该目录下的conftest文件里写的所有fixture可以在任意测试类下执行

    import pytest
    @pytest.fixture(scope="function",name="exe_datebase_sql_name")
    def exe_database_sql():print("全部方法运行前均可以执行")yield print("全部方法运行后均可以执行")
    
  2. testcases文件夹下的usercases文件夹下创建的conftest.py,我们在该目录下创建的conftest文件里写的所有fixture仅可以在该目录下的测试类中使用,在其他测试类中使用会出现报错

    import pytest
    @pytest.fixture(scope="function",name="usercases_fixture")
    def exe_database_sql():print("usercases方法运行前均可以执行")yield print("usercases方法运行后均可以执行")
    # testcases文件下的usercases文件夹下的测试类
    import pytest
    class TestUserCases1:# 测试Case1def test_1(self,usercases_fixture):print('输入正确用户名密码验证码,点击登录 1' + usercases_fixture)
    

前后置执行顺序优先级:fixture_session > fixture_class > setup_class > fixture_function > setup

前后置执行的一个总体逻辑顺序:

  • 查询当前目录下的conftest.py文件
  • 查询当前目录下的pytest.ini文件并找到测试用例的位置
  • 查询用例目录下的conftest.py文件
  • 查询测试用例的py文件中是否有setup,teardown,setup_class,teardown_class
  • 再根据pytest.ini文件的测试用例的规则去查找用例并执行

三、pytest 进阶内容

3.1 Allure 效果美化


 我们在使用Pytest所生成的页面往往不够美观且展示信息杂乱不好分析,所以我们通常搭载allure来实现界面美化:

  • Allure框架是一个灵活轻量级多语言测试报告工具
  • 它不仅可以以WEB的方式展示简介的测试结果,而且允许参与开发过程的每个人从日常执行的测试中最大限度的提取有用信息

步骤:

  1. 下载 allure 并配置好环境变量,下载地址 https://github.com/allure-framework/allure2/releases

  2. 安装 allure-pytest

    pip install allure-pytest
    
  3. 生成 allure 临时 json 文件,--alluredir 为文件目录

    pytest --alluredir=./allure-results
    
  4. 启动 allure

    allure serve ./allure-results  
    

    image.png

  5. 结果

    image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/37355.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.SQL注入-数字型

SQL注入-数字型(post) 查询1的时候发现url后面的链接没有传入1的参数。验证为post请求方式&#xff0c;仅显示用户和邮箱 通过图中的显示的字段&#xff0c;我们可以猜测传入数据库里面的语句&#xff0c;例如&#xff1a; select 字段1,字段2 from 表名 where id1; 编辑一个…

SOAP vs REST介绍

SOAP&#xff08;简单对象访问协议&#xff09; 定义&#xff1a;SOAP是一种基于XML的通信协议&#xff0c;用于在网络中交换结构化信息&#xff0c;特别是在分布式环境和需要中介&#xff08;如网关或防火墙&#xff09;的环境中。它通过HTTP、SMTP等多种传输协议传输信息&…

示例:WPF中推荐一个Diagram开源流程图控件

一、目的&#xff1a;分享一个自研的开源流程图控件 二、使用方法 1、引用Nuget包&#xff1a; 2、添加节点列表和绘图控件 <DockPanel><ItemsControl DockPanel.Dock"Left"><h:GeometryNodeData Text"节点"/></ItemsControl><…

mysql8.0其他数据库日志

概述 我们在讲解数据库事务时&#xff0c;讲过两种日志:重做日志、回滚日志。 对于线上数据库应用系统&#xff0c;突然遭遇数据库宕机怎么办?在这种情况下&#xff0c;定位宕机的原因就非常关键。可以查看数据库的错误日志。因为日志中记录了数据库运行中的诊断信息&#xff…

基于STM32的智能家用电力管理系统

目录 引言环境准备智能家用电力管理系统基础代码实现&#xff1a;实现智能家用电力管理系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统实现4.4 用户界面与数据可视化应用场景&#xff1a;电力管理与优化问题解决方案与优化收尾与总结 1. 引言 智能家用电力管理系统通…

【漏洞复现】I doc view——任意文件读取

声明&#xff1a;本文档或演示材料仅供教育和教学目的使用&#xff0c;任何个人或组织使用本文档中的信息进行非法活动&#xff0c;均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 I doc view 在线文档预览是一个用于查看、编辑、管理文档的工具…

JS(JavaScript)事件处理(事件绑定)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

心理辅导平台系统

摘 要 中文本论文基于Java Web技术设计与实现了一个心理辅导平台。通过对国内外心理辅导平台发展现状的调研&#xff0c;本文分析了心理辅导平台的背景与意义&#xff0c;并提出了论文研究内容与创新点。在相关技术介绍部分&#xff0c;对Java Web、SpringBoot、B/S架构、MVC模…

云顶之弈数据网站

摘要&#xff1a;随着云顶之弈游戏的广泛流行&#xff0c;玩家对于游戏数据的查询和最新资讯的获取需求呈现出显著增长的趋势。设计一款云顶之弈数据网站&#xff0c;为玩家提供便捷、高效的数据查询和资讯浏览服务&#xff0c;能满足玩家对于游戏数据的快速查询和实时资讯获取…

已解决java.security.acl.LastOwnerException:无法移除最后一个所有者的正确解决方法,亲测有效!!!

已解决java.security.acl.LastOwnerException&#xff1a;无法移除最后一个所有者的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 出现问题的场景 报错原因 解决思路 解决方法 1. 检查当前所有者数量 2. 添加新的所有者 3. 维…

【C语言】--数据类型和变量

&#x1f617;个人主页: 起名字真南 &#x1f619;个人专栏:【数据结构初阶】 【C语言】 目录 1 数据类型介绍1.1 字符型1.2 整形1.3 浮点型1.4 布尔型1.5 各种数据类型的长度1.5.1 sizeof 操作符1.5.2 数据类型长度1.5.3 sizeof 中表达式不计算 2 signed 和 unsigned3 数据类型…

1978Springboot在线维修预约服务应用系统idea开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot在线维修预约服务应用系统是一套完善的信息系统&#xff0c;结合springboot框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发 &#xff09;&#xff0c;系统具有完整的源代码和…

“Hello, World!“ 历史由来

布莱恩W.克尼汉&#xff08;Brian W. Kernighan&#xff09;—— Unix 和 C 语言背后的巨人 布莱恩W.克尼汉在 1942 年出生在加拿大多伦多&#xff0c;他在普林斯顿大学取得了电气工程的博士学位&#xff0c;2000 年之后取得普林斯顿大学计算机科学的教授教职。 1973 年&#…

Windows server 2016.2019 .NET Framework 3.5安装包、安装步骤

windows server2019 操作系统 安装 sqlserver2008时提示缺少 .NET Frameword 3.5&#xff0c; 在功能里选择 .NET Frameword 3.5安装报错&#xff0c; 下载安装包&#xff0c;下载地址 https://download.csdn.net/download/qq445829096/89450429这里指定备份源路径 安装包解…

OpenGL3.3_C++_Windows(22)

材质&#xff1a; 决定物体在渲染过程中最终视觉呈现的关键因素之一&#xff0c;它通过一系列光学&#xff08;投光物&#xff09;和物理参数&#xff08;反光度&#xff0c;反照率、金属度&#xff0c;折射率……&#xff09;准确模拟现实世界中的材料特性&#xff0c;从而增…

【MySQL】InnoDB的存储结构

InnoDB的存储结构&#xff1a;每个表都会生成一个表空间文件&#xff0c;这个文件里面最小结构就是行&#xff0c;存储的真正的数据&#xff0c;一个页来管理若干行&#xff0c;一个区来管理若干页&#xff0c;一个区组来管理若干区。段并不是真正的物理存储结构&#xff0c;它…

汇总大语言模型LLM的评测基准数据集(BenchMarks)

文章目录 0. 引言1. 知识与语言理解1.1 MMLU1.2 ARC1.3 GLUE1.4 Natural Questions1.5 LAMBADA1.5 HellaSwag1.6 MultiNLI1.7 SuperGLUE1.8 TriviaQA1.9 WinoGrande1.10 SciQ 2. 推理能力2.1 GSM8K2.2 DROP2.3 CRASS2.4 RACE2.5 BBH2.6 AGIEval2.7 BoolQ 3. 多轮开放式对话3.1 …

vue3中获取Excel和csv文件中的内容

1.效果 2.安装 npm install xlsxyarn add xlsx 3.引入使用 <el-upload ref"uploadRef" :on-change"changeFile" :show-file-list"false" class"mr10" accept".csv, .xlsx, .xls"action"#" :auto-upload&quo…

滚动表格(vue版本)【已验证可正常运行】

演示图 注&#xff1a;以下代码来自于GPT4o&#xff1a;国内官方直连GPT4o 代码 <template><div><div class"alarmList-child" ref"alarmList" mouseenter.stop"autoRoll(1)" mouseleave.stop"autoRoll()"><div…

基于DPU的Ceph存储解决方案

1. 方案背景和挑战 Ceph是一个高度可扩展、高性能的开源分布式存储系统&#xff0c;设计用于提供优秀的对象存储、块存储和文件存储服务。它的几个核心特点是&#xff1a; 弹性扩展&#xff1a;Ceph能够无缝地水平扩展存储容量和性能&#xff0c;只需添加新的存储节点即可&am…