CTFL(二)贯穿软件开发生存周期中的测试
贯穿软件开发生存周期中的测试
验收测试(acceptance testing),黑盒测试(black-box testing),组件集成测试(component integration testing),组件测试(component testing),确认测试(confirmation testing),功 能测试(functional testing),集成测试(integration testing),维护测试(maintenance testing),非功能测试(non-functional testing),回归测试(regression testing),左移 (shift-left),系统集成测试(system integration testing),系统测试(system testing),测 试级别(test level),测试对象(test object),测试类型(test type),白盒测试(white-box testing)。
软件开发生存周期中的测试
软件开发生存周期(SDLC)模型是对软件开发过程的抽象、概要表述。SDLC 模型定义了软件开发过程中不同开发阶段和活动类型之间的逻辑和时间关系。SDLC 模型的示例包括:顺序开发模型(例如瀑布模型、V 模型)、迭代开发模型(例如螺旋模型、原型模型)和增量开发模型(例如统一软件开发过 程)。
软件开发过程中的活动也可以通过更详细的软件开发方法和敏捷实践来描述。例如:验收测试驱动开发(ATDD)、行为驱动开发(BDD)、领域驱动设计(DDD)、极限编程(XP)、特征驱动开发 (FDD)、看板、精益管理、Scrum 和测试驱动开发(TDD)。
软件开发生存周期对测试的影响
测试必须适应软件开发生存周期才能成功。软件开发生存周期的选择会对以下几个方面产生影响:
- 测试活动的范围和时间安排(例如测试级别和测试类型)。
- 测试文档的详细程度。
- 测试技术和测试方法的选择。
- 测试自动化程度。
- 测试人员的角色和职责。
在顺序开发模型中,测试人员通常在软件开发的初期阶段参与需求评审、测试分析和测试设计。可执行代码通常在后期阶段创建,因此通常无法在软件开发生存周期早期进行动态测试。
在某些迭代和增量开发模型中,每次迭代都会提供可工作的增量原型或产品。也就是说在每个迭代 中,静态和动态测试都可以在所有测试级别上执行。频繁的增量交付需要快速反馈和全面回归测试。
敏捷软件开发假定在项目过程中都可能发生变化。因此,在敏捷项目中,更倾向于轻量级的工作产 品文档和全面测试自动化,以便更容易进行回归测试。此外,大部分人工测试往往使用基于经验的测试技术(参阅第 4.4 节),不需要事前进行大量的测试分析和设计。
软件开发生存周期与良好的测试实践
无论选择哪种软件开发生存周期模型,良好的测试实践包括以下内容:
-
每个软件开发活动,都有相应的测试活动,以便对所有开发活动进行质量控制。
-
不同测试级别(参阅第 2.2.1 章)具有特定且不同的测试目标,可以确保测试既全面又避免冗余。
-
给定测试级别的测试分析和设计始于相应的软件开发生存周期开发阶段,以便测试能够遵循早 期测试的原则(参阅第 1.3 节)。
-
相关文档的初稿完成时,测试人员立即参与评审工作产品,以便早期测试和缺陷检测,从而支持测试左移方法(参阅第 2.1.5 节)。
测试是软件开发的驱动
测试驱动开发(TDD)、验收测试驱动开发(ATDD)和行为驱动开发(BDD)是类似的开发方法,将测试定义为指导开发的手段。这些方法都实现了早期测试的原则(参阅第 1.3 节)并遵循左移方法(参 阅第 2.1.5 节),因为测试在编写代码之前定义。它们支持迭代开发模型。这些方法的具体特点如下:
- 测试驱动开发(TDD):
- 通过测试用例来指导编码(而不是详尽的软件设计)(Beck 2003)。
- 先编写测试,后编写代码以满足测试,最后对测试和代码进行重构。
- 验收测试驱动开发(ATDD)(参阅第 4.5.3 节):
- 作为系统设计过程的一部分,从验收准则中导出测试(Gärtner 2011)。
- 在部分应用程序开发前,编写测试,以满足测试的要求。
- 行为驱动开发(BDD):
- 用简化的自然语言编写测试用例来表达应用程序的期望行为,利益相关方容易理解,通常使用 Given/When/Then 格式(Chelimsky 2010)。
- 自动将测试用例转化为可执行的测试。
对于上述所有方法,通常应用自动化测试,以确保将来改写或重构的代码质量。
DevOps与测试
DevOps 是一种组织方法,旨在通过使开发(包括测试)和运维部门共同努力,实现一系列通用目标,从而实现协同效应。DevOps 要求组织内部进行文化转变,将开发和运维的职能同等看待,以弥合开发(包括测试)和运维之间的差距。DevOps 提倡团队的自主权、快速反馈、集成工具链以及持续集成 (CI)和持续交付(CD)等技术实践。通过 DevOps 交付流水线,软件团队可以更快地构建、测试和发 布高质量的代码(Kim 2016)。
从测试的角度来看,DevOps 的好处包括:
- 代码质量的快速反馈,并判断变更是否对现有代码产生不利影响。
- 持续集成(CI)通过鼓励开发人员提交高质量的代码,并辅以组件测试和静态分析,在测试中实现左移方法(参阅第 2.1.5 节)。
- 促进 CI/CD 自动化过程,有助于建立稳定的测试环境。
- 更加关注非功能性质量特性(例如性能、可靠性)。
- 交付流水线的自动化,减少人工重复测试的需求。
- 由于自动化回归测试的规模和范围,降低了回归风险。
然而,DevOps 也面临着某些风险和挑战,包括:
- 必须定义和建立 DevOps 交付流水线。
- 必须引入和维护 CI/CD 工具。
- 测试自动化需要额外资源,这些资源可能难以建立和维护。
尽管 DevOps 提供了高度自动化测试,但从用户的角度来说,仍然需要人工测试。
左移的方法
测试早期介入的原则(参阅第 1.3 节)有时被称为“左移”,这是软件开发生存周期中较早进行测 试的方法。左移建议测试应该早期进行(例如,代码实现或组件集成前开始测试),但不能因此忽视软件开发生存周期的后期测试。
许多良好的实践可以说明如何实现测试 “左移”,包括:
- 从测试的角度评审规格说明。对规格说明进行评审通常可以发现潜在的缺陷,例如规格说明表述模糊、不完整和不一致。
- 编码之前编写测试用例,在代码实现过程中通过测试用具(test harness)运行代码。
- 使用持续集成(CI)和持续交付(CD),提供快速反馈和自动化组件测试,可以在代码提交到代码库时运行源代码测试。
- 在动态测试之前或作为自动化过程的一部分对源代码进行静态分析。
- 在可能的情况下,从组件测试级别开始进行非功能性测试。这是左移形式之一,因为非功能性测试类型通常在系统完整且代表性的测试环境就绪后,在软件开发生存周期的后期执行。
左移方法可能会在过程早期增加培训、工作量和成本,但可以节省过程后期的工作量和成本。 对于左移,重要的是让利益相关方相信并接受此种方法。
回顾与过程改进
回顾会议(也称为“项目总结会议/post-project meetings”和项目回顾)作为发布的里程碑,通常在项目或迭代结束后,按需召开。回顾会议的时间和组织方式取决于所采用的特定软件开发生存周期模型。回顾会议上,参与者(不仅限于测试人员,还包括开发人员、架构师、产品负责人、业务分析师等)讨论以下内容:
- 哪些工作是成功的,应予以保留?
- 哪些工作没成功,可以改进?
- 如何整合改进并保持未来成功?
应记录结果,通常作为测试完成报告的一部分(参阅第 5.3.2 节)。回顾对于成功实施持续改进至关重要,对任何建议的改进都要进行跟踪。
测试的典型收益包括:
- 增加测试的有效性/效率(例如,实施过程改进的建议)。
- 提高测试件的质量(例如,联合评审测试过程)。
- 团队凝聚力和学习能力(例如,提出问题,列出改进点)。
- 提高测试依据的质量(例如,处理和解决需求范围和质量方面的缺陷)。
- 改善开发和测试之间的合作(例如,定期评审和优化协作)。
测试级别和测试类型
测试级别是共同组织和管理的测试活动组。每个测试级别都是测试过程的一个实例,在给定的开发阶段,从单个组件到完整系统,或在适用情况下,乃至到系统的系统(systems of systems),执行软件相关的测试过程。
测试级别与软件开发生存周期内的其他活动相关。在顺序 SDLC 模型中,测试级别通常定义为:一 个级别的出口准则是下一个级别的入口准则的一部分。在一些迭代开发模型中,这可能不适用。开发活动可能跨越多个测试级别。测试级别在时间上可能重叠。
测试类型,是与某种质量特性相关的测试活动的集合,这些测试活动中的大部分可以在每个测试级别进行。
测试级别
在本课程大纲中,将列举描述下列五个测试级别:
- 组件测试(也称为单元测试),侧重于对单独组件的测试。组件测试通常需要一些特殊的支 持,例如需要使用测试用具或者单元测试框架。组件测试通常由开发人员在他们的开发环境中 进行。
- 组件集成测试(也称为单元集成测试),侧重于对组件之间的接口及交互进行测试。组件集成 测试重度依赖于集成策略方法,例如,自底向上集成,自顶向下集成或者大爆炸集成。
- 系统测试,关注于对整个系统或产品的总体行为和能力,通常包含覆盖“端到端业务”的功能 测试以及针对非功能质量特性的测试。对于一些非功能质量特性的测试,更倾向于一个完整系统,在具有代表性的测试环境中进行测试,例如,易用性测试。使用模拟的子系统也是可能的。系统测试可以由独立测试团队执行,并且与系统规格说明有关。
- 系统集成测试,侧重于对被测系统与其他系统以及外部服务的接口的测试。系统集成测试需要 合适的测试环境,最好是与运行环境类似的测试环境。
- 验收测试,侧重于确认和展示部署准备情况,这意味着系统满足了用户的业务要求。在理想情 况下,验收测试应该由潜在用户执行。验收测试的主要形式有:用户验收测试(UAT)、运行 验收测试、合同验收测试以及法规验收测试、Alpha 测试和 Beta 测试。
测试级别可以通过以下(非详尽)属性列表来区分,以避免测试活动的重叠:
- 测试对象
- 测试目的
- 测试依据
- 缺陷和失效
- 方法和职责
测试类型
在项目中可能会应用多种测试类型。本大纲主要涉及下列四种测试类型:
功能测试是用于评估组件或系统应该执行的功能的测试。功能是测试对象应该做的事情。功能测试的主要目的是检查功能完整性、功能正确性和功能适合性。
非功能测试是用于评估组件或系统除功能特性之外的其他属性。非功能测试是测试“系统表现得多 好”。非功能测试的主要目的是检查软件的非功能质量特性。在 ISO/IEC 25010 标准中列出了不同类型 的非功能质量特性:
- 性能效率
- 兼容性
- 易用性
- 可靠性
- 信息安全性
- 维护性
- 可移植性
在开发生存周期的初期进行非功能测试有时是适当的(如,作为评审、组件测试或系统测试的一部 分)。很多非功能测试是从功能测试派生而出的,他们使用同样的功能测试,在执行功能时测试非功能 约束是否被满足,例如,通过检查执行完成某个功能所需要的时间,或者通过检查功能是否可以被移植 到新平台上。如果较晚发现非功能缺陷,将严重威胁到项目的成功。非功能测试有时需要在特定的测试 环境中进行,例如,执行易用性测试可能需要易用性测试实验室。
黑盒测试(参阅第 4.2 章节),是基于规格说明并根据测试对象外部的文档生成测试的测试技术。 黑盒测试的主要目的是检查系统行为是否与规格说明描述一致。
白盒测试(参阅第 4.3 章节),是基于结构并根据系统的实施或系统的内部结构(如代码、结构、 工作流和数据流)生成测试的测试技术。白盒测试的主要目标是通过测试将底层结构覆盖到可接受的水 平。
上述四种测试类型都可应用到所有的测试级别,尽管每个测试级别的重点有所不同。可使用不同的 测试技术为所有上述所提的测试类型导出测试条件和测试用例。
确认测试和回归测试
变更,通常指对组件和系统做出的改进,这种改进可以是增加新特征,或通过修改代码以移除缺陷进行修复。测试还应该包括确认测试和回归测试。
确认测试用于确认原有缺陷是否已经被成功修复的测试。根据风险的不同,测试人员可以对软件的缺陷修复版本进行不同的测试,包括:
- 执行先前由于存在缺陷而失败了的所有测试用例。
- 增加新的测试,以覆盖由于修复缺陷引发的任何变更。
但是,当进行缺陷修复工作的时间和预算有限时,确认测试的范围可能被严格限定,即仅执行重现原有(由缺陷引起的)失效的步骤,以检查失效是否已经消失。
回归测试确认变更未造成任何不良后果,包括已经经过确认测试的修复。这些不良后果可能会影响 进行更改的同一组件、同一系统中的其他组件,甚至其他关联的系统。回归测试可能不局限于测试对象本身,还可以与环境相关。建议首先执行影响分析以优化回归测试的范围。影响分析显示软件的哪些部分可能受到影响。
回归测试套件会被多次运行,通常回归测试用例的数量会随着每次迭代或发布而有所增加,因此可 以优先考虑自动化回归测试。这些测试的自动化应该在项目的早期开始。在使用持续集成(CI)时,比 如 DevOps(参阅第 2.1.4 节),最好也包括自动化回归测试。根据情况,可能包括不同级别的回归测试。
如果缺陷修复和/或变更发生在某些测试级别上,对测试对象进行的确认测试和/或回归测试就需要 在所有涉及的测试级别上进行。
维护测试
有多种不同类别的维护,可以是修正错误、应对环境变更、改进性能或改善维护性(详见 ISO/IEC 14764),因此,维护可以包括计划内的发布/部署和计划外的发布/部署(热修复 hot fix)。可以在 变更实施之前进行影响分析,基于系统在其他领域潜在后果的分析,帮助决定是否应该实施变更。在生 产环境中测试系统的变更,既包括评估更改实施的成功与否,也包括检查系统中保持不变的部分(通常 是系统的大部分)是否存在可能的回归错误。
维护测试范围通常依赖于:
- 变更引起的风险程度
- 现有系统的规模
- 变更的大小
维护以及维护测试的触发因素可以有以下几类:
- 修改,如计划中的改进(如,基于发布版本),修正错误产生的变更,或者热修复。
- 运行环境的升级或者迁移,如从一个平台迁移至另一个平台,可能需要进行与新运行环境有关的测试,也可能需要进行与软件变更有关的测试;或者当数据从一个应用迁移至处于维护状态 的另一个系统时,需要对数据迁移进行的测试。
- 退役,例如应用程序的生存周期即将结束。当系统退役时,如果需要长时间的数据保留,可能 需要测试数据归档。如果在归档期间需要某些数据,则可能还需要测试归档后的数据恢复和检索过程。
欢迎关注我的博客,如有疑问或建议,请随时留言讨论。
相关文章:

CTFL(二)贯穿软件开发生存周期中的测试
贯穿软件开发生存周期中的测试 验收测试(acceptance testing),黑盒测试(black-box testing),组件集成测试(component integration testing),组件测试(compone…...

PMIC FS8405
FS8495 具有多个SMPS和LDO的故障安全系统基础芯片。 FS8X 大多数参数都是通过OTP寄存器设置的。 概述 FS85/FS84设备系列是按照ASIL D流程开发的,FS84具有ASIL B能力,而FS85具有ASIL D能力。所有的设备选项都是引脚到引脚和软件兼容的。 FS85/FS84是一种汽车功能安全…...

matlab建模入门指导
本文以水池中鸡蛋温度随时间的变化为切入点,对其进行数学建模并进行MATLAB求解,以更为通俗地进行数学建模问题入门指导。 一、问题简述 一个煮熟的鸡蛋有98摄氏度,将它放在18摄氏度的水池中,五分钟后鸡蛋的温度为38摄氏度&#x…...

微搭低代码入门03函数
目录 1 函数的定义与调用2 参数与返回值3 默认参数4 将功能拆分成小函数5 函数表达式6 箭头函数7 低代码中的函数总结 在用低代码开发软件的时候,除了我们上两节介绍的变量、条件语句外,还有一个重要的概念叫函数。函数是执行特定功能的代码片段…...

零基础Java第十六期:抽象类接口(二)
目录 一、接口(补) 1.1. 数组对象排序 1.2. 克隆接口 1.3. 浅拷贝和深拷贝 1.4. 抽象类和接口的区别 一、接口(补) 1.1. 数组对象排序 我们在讲一维数组的时候,使用到冒泡排序来对数组里的元素进行从小到大或从大…...

【css】html里面的图片宽度设为百分比,高度要与宽度一样
场景:展示图片列表的时候,原始图片宽高不一致。 外层div的宽度自适应,图片宽度不能固定数值,只能设置百分比。图片高度也不能设置固定数值。 如何让图片的高度与图片的宽度一样呢? html代码 : <div cl…...

前端三大组件之CSS,三大选择器,游戏网页仿写
回顾 full stack全栈 Web前端三大组件 结构(html) 样式(css) 动作/交互(js) --- 》 框架vue,安哥拉 div 常用的标签 扩展标签 列表 ul/ol order——有序号 unordered——没序号的黑点 <!DOCTYPE html> <html><head><meta charset"…...

sqlsever 分布式存储查询
当数据存储在不同的服务器上的时候怎么取出来进行正常管连呢?比如你有 A 和B 两个服务器 里面存有两个表 分别是 A_TABLE、B_TABLE 其中 他们的关联关系是 ID 互相关联 1.创建链接服务器如果在B数据库要访问A数据库 那么 就在B数据库创建 -- 创建链接服务器 EXEC sp_addlink…...

deeponet(nature原文部分重点提取)
论文链接:Learning nonlinear operators via DeepONet based on the universal approximation theorem of operators | Nature Machine Intelligence 原文部分重点提取 DeepONets 会产生小的泛化误差 隐式类型算子还可以描述我们对其形式没有任何数学知识的系统 De…...

LeetCode【0036】有效的数独
本文目录 1 中文题目2 求解方法:python内置函数set2.1 方法思路2.2 Python代码2.3 复杂度分析 3 题目总结 1 中文题目 请根据以下规则判断一个 9 x 9 的数独是否有效。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线…...

Typecho登陆与评论添加Geetest极验证,支持PJAX主题(如Handsome)
Typecho登陆与评论添加Geetest极验证,支持PJAX主题(如Handsome) 起因 最近垃圾评论比较多,为了防止一些机器人,我给博客添加了一些评论过滤机制,并为评论添加了验证码。 原本使用的插件是noisky/typecho…...

前端入门一之ES6--面向对象、够着函数和原型、继承、ES5新增方法、函数进阶、严格模式、高阶函数、闭包
前言 JS是前端三件套之一,也是核心,本人将会更新JS基础、JS对象、DOM、BOM、ES6等知识点,这篇是ES6;这篇文章是本人大一学习前端的笔记;欢迎点赞 收藏 关注,本人将会持续更新。 文章目录 JS高级 ES61、面向对象1.1…...

脑机接口、嵌入式 AI 、工业级 MR、空间视频和下一代 XR 浏览器丨RTE2024 空间计算和新硬件专场回顾
这一轮硬件创新由 AI 引爆,或许最大受益者仍是 AI,因为只有硬件才能为 AI 直接获取最真实世界的数据。 在人工智能与硬件融合的新时代,实时互动技术正迎来前所未有的创新浪潮。从嵌入式系统到混合现实,从空间视频到脑机接口&…...

RoseTTAFold MSA_emb类解读
MSA_emb 类的作用是对多序列对齐(MSA)数据进行嵌入编码,同时添加位置编码和查询编码(调用PositionalEncoding 和 QueryEncoding)以便为序列特征建模类。 源代码: class MSA_emb(nn.Module):def __init__(self, d_model=64, d_msa=21, p_drop=0.1, max_len=5000):super(…...

2411C++,C++26反射示例
参考 namespace __impl {template<auto... vals>struct replicator_type {template<typename F>constexpr void operator>>(F body) const {(body.template operator()<vals>(), ...);}};template<auto... vals>replicator_type<vals...>…...

Ubuntu上搭建Flink Standalone集群
Ubuntu上搭建Flink Standalone集群 本文部分内容转自如下链接。 环境说明 ubuntu 22.06 先执行apt-get update更新环境 第1步 安装JDK 通过apt自动拉取 openjdk8 apt-get install openjdk-8-jdk执行java -version,如果能显示Java版本号,表示安装并…...

C语言 精选真题2
题目要求:将形参s所指向的字符串转换为整数并且返回 知识点: 将字符1转化为整数1 int fun(char *s) {int flag1,n0; if(*s-) //先根据第一个符号来判断是正负;然后读取第二位{flag-1;s; }else if(*s){s;}while(*s>0&&…...

Netty篇(WebSocket)
目录 一、简介 二、特点 三、websock应用场景 四、websocket案例 1. 服务端 2. 处理器 3. 页面端处理 五、参考文献 一、简介 没有其他技术能够像WebSocket一样提供真正的双向通信,许多web开发者仍然是依赖于ajax的长轮询来 实现。(注ÿ…...

云原生-docker安装与基础操作
一、云原生 Docker 介绍 Docker 在云原生中的优势 二、docker的安装 三、docker的基础命令 1. docker pull(拉取镜像) 2. docker images(查看本地镜像) 3. docker run(创建并启动容器) 4. docker ps…...

MySQL数据库:SQL语言入门 【上】(学习笔记)
SQL(Structured Query Language)是结构化查询语言的简称,它是一种数据库查询和程序设计语言,同时也是目前使用最广泛的关系型数据库操作语言。(95%适用于所有关系型数据库) 【 SQL是关系型数据库通用的操作…...

重学 Android 自定义 View 系列(六):环形进度条
目标 自定义一个环形进度条,可以自定义其最大值、当前进度、背景色、进度色,宽度等信息。 最终效果如下(GIF展示纯色有点问题): 1. 结构分析 背景圆环:表示进度条的背景。进度圆环:表示当前…...

nodejs 020: React语法规则 props和state
props和state 在 React 中,props 和 state 是管理数据流的两种核心机制。理解它们之间的区别和用途是构建 React 应用程序的基础。 一、props 和 state的区别 特性propsstate定义方式由父组件传递给子组件的数据组件内部管理的本地数据是否可修改不可变ÿ…...

STM32问题集
这里写目录标题 一、烧录1、 Can not connect to target!【ST-LINK烧录】 一、烧录 1、 Can not connect to target!【ST-LINK烧录】 烧录突然 If the target is in low power mode, please enable “Debug in Low Power mode” option from Target->settings menu 然后就&…...

SwiftUI(十二)- 容器组件 布局与结构的基石
引言 在用户界面开发中,布局是设计一个应用程序的视觉层次和交互体验的核心之一。无论是设计简单的按钮排布,还是复杂的多层次页面,合理的布局和结构可以极大地提升用户体验。而容器组件,作为将多个视图整合、组织、排列的工具&a…...

想租用显卡训练自己的网络?AutoDL保姆级使用教程(PyCharm版)
各位小伙伴们大家好~ 不知道各位同学在科研过程中是否有这样的苦恼 电脑无显卡。难不成我要用CPU跑实验吗?救救我吧电脑显卡算力太低。训练过程慢慢慢慢慢,等半天都出不来结果电脑显卡显存不够,batchsize稍微高一点点,就要爆显存…...

LeetCode【0039】组合总和
本文目录 1 中文题目2 求解方法:回溯法2.1 方法思路2.2 Python代码2.3 复杂度分析 3 题目总结 1 中文题目 给定一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#…...

AscendC从入门到精通系列(一)初步感知AscendC
1 什么是AscendC Ascend C是CANN针对算子开发场景推出的编程语言,原生支持C和C标准规范,兼具开发效率和运行性能。基于Ascend C编写的算子程序,通过编译器编译和运行时调度,运行在昇腾AI处理器上。使用Ascend C,开发者…...

PostgreSQL中的COPY命令:高效数据导入与导出
在PostgreSQL数据库中,数据导入和导出是日常工作中常见的操作。传统的插入(INSERT)方法虽然可以实现数据的导入,但在处理大量数据时效率较低。而COPY命令则提供了一个快速、高效的方式来完成这一任务。COPY命令不仅可以用于将数据…...

【HAL库】STM32F105VCTx多通道ADC+DMA方式的【STM32CubeMX】配置及代码实现
相关代码编写 配置好后点击生成代码,在生成代码的adc.c文件中的初始化函数MX_ADC1_Init中添加如下代码: HAL_ADCEx_Calibration_Start(&hadc1); /* 校准ADC */HAL_ADC_Start_DMA(&hadc1,(uint32_t*)ADC_Value,ADC_DMA_…...

[SaaS] 数禾科技 AIGC生成营销素材
https://zhuanlan.zhihu.com/p/923637935https://zhuanlan.zhihu.com/p/923637935...