mysql重置增量_摆脱困境:在每种测试方法之前重置自动增量列

mysql重置增量

当我们为将信息保存到数据库的功能编写集成测试时,我们必须验证是否将正确的信息保存到数据库。

如果我们的应用程序使用Spring Framework,则可以为此目的使用Spring Test DbUnit和DbUnit 。

但是,很难验证是否在主键列中插入了正确的值,因为主键通常是通过使用自动增量或序列自动生成的。

这篇博客文章指出了与自动生成值的列相关的问题,并帮助我们解决了这一问题。

补充阅读:

  • 经测试的应用程序在博客文章中进行了描述,该博客文章的标题为: 摆脱困境:在DbUnit数据集中使用空值 。 我建议您阅读该博客文章,因为我不会在此博客文章上重复其内容。
  • 如果您不知道如何为存储库编写集成测试,则应阅读我的博客文章,标题为: Spring Data JPA教程:集成测试 。 它说明了如何为Spring Data JPA存储库编写集成测试,但是您可以使用与为使用关系数据库的其他Spring Powered存储库编写测试的方法相同的方法。

我们无法断言未知

让我们从为CrudRepository接口的save()方法编写两个集成测试开始。 这些测试描述如下:

  • 第一个测试确保在设置了保存的Todo对象的标题和描述时将正确的信息保存到数据库中。
  • 当仅设置了已保存的Todo对象的标题时,第二个测试将验证是否将正确的信息保存到数据库中。

这两个测试都通过使用相同的DbUnit数据集( no-todo-entries.xml )来初始化使用的数据库,该数据集如下所示:

<dataset><todos/>
</dataset>

我们的集成测试类的源代码如下所示:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {PersistenceContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class })
@DbUnitConfiguration(dataSetLoader = ColumnSensingReplacementDataSetLoader.class)
public class ITTodoRepositoryTest {private static final Long ID = 2L;private static final String DESCRIPTION = "description";private static final String TITLE = "title";private static final long VERSION = 0L;@Autowiredprivate TodoRepository repository;@Test@DatabaseSetup("no-todo-entries.xml")@ExpectedDatabase("save-todo-entry-with-title-and-description-expected.xml")public void save_WithTitleAndDescription_ShouldSaveTodoEntryToDatabase() {Todo todoEntry = Todo.getBuilder().title(TITLE).description(DESCRIPTION).build();repository.save(todoEntry);}@Test@DatabaseSetup("no-todo-entries.xml")@ExpectedDatabase("save-todo-entry-without-description-expected.xml")public void save_WithoutDescription_ShouldSaveTodoEntryToDatabase() {Todo todoEntry = Todo.getBuilder().title(TITLE).description(null).build();repository.save(todoEntry);}
}

这些不是很好的集成测试,因为它们仅测试Spring Data JPA和Hibernate是否正常工作。 我们不应该通过为框架编写测试来浪费时间。 如果我们不信任框架,则不应使用它。

如果您想学习为数据访问代码编写好的集成测试,则应该阅读标题为: 编写数据访问代码测试的教程。

DbUnit数据集( save-todo-entry-with-title-and-description-expected.xml )用于验证将已保存的Todo对象的标题和描述插入到todos表中,如下所示:

<dataset><todos id="1" description="description" title="title" version="0"/>
</dataset>

DbUnit数据集( save-todo-entry-without-description-expected.xml )用于验证是否仅将已保存的Todo对象的标题插入了todos表,如下所示:

<dataset><todos id="1" description="[null]" title="title" version="0"/>
</dataset>

当我们运行集成测试时,其中之一失败,并且我们看到以下错误消息:

junit.framework.ComparisonFailure: value (table=todos, row=0, col=id) 
Expected :1
Actual   :2

原因是todos表的id列是一个自动增量列,并且首先调用的集成测试“获取”了id1。在调用第二个集成测试时,值2保存到了id列,测试失败。

让我们找出如何解决这个问题。

快速修复赢?

有两个快速解决此问题的方法。 这些修复程序描述如下:

首先 ,我们可以使用@DirtiesContext批注注释测试类,并将其classMode属性的值设置为DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD。这将解决我们的问题,因为应用程序在加载应用程序上下文时会创建一个新的内存数据库,并且@DirtiesContext注释可确保每个测试方法都使用新的应用程序上下文。

我们的测试类的配置如下所示:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {PersistenceContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class })
@DbUnitConfiguration(dataSetLoader = ColumnSensingReplacementDataSetLoader.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class ITTodoRepositoryTest {}

这看起来很干净,但不幸的是,它可能会破坏我们的集成测试套件的性能,因为它会在调用每个测试方法之前创建一个新的应用程序上下文。 这就是为什么我们不应该使用@DirtiesContext批注,除非它是绝对必要的

但是,如果我们的应用程序只有少量的集成测试,则@DirtiesContext批注引起的性能损失可能是可以容忍的。 我们不应该仅仅因为它会使我们的测试变慢而放弃该解决方案。 有时这是可以接受的,并且在这种情况下,使用@DirtiesContext注释是一个很好的解决方案。

补充阅读:

  • @DirtiesContext注释的Javadoc
  • @ DirtiesContext.ClassMode枚举的Javadoc

其次 ,我们可以从数据集中省略todos元素的id属性,并将@ExpectedDatabase批注的assertionMode属性的值设置为DatabaseAssertionMode.NON_STRICT 。 这将解决我们的问题,因为DatabaseAssertionMode.NON_STRICT意味着将忽略数据集文件中不存在的列和表。

该断言模式是一个有用的工具,因为它使我们有可能忽略其信息不会被测试代码更改的表。 但是, DatabaseAssertionMode.NON_STRICT不是解决此特定问题的正确工具,因为它迫使我们编写用于验证很少内容的数据集。

例如,我们不能使用以下数据集:

<dataset><todos id="1" description="description" title="title" version="0"/><todos description="description two" title="title two" version="0"/>
</dataset>

如果我们使用DatabaseAssertionMode.NON_STRICT ,则数据集的每个“行”必须指定相同的列。 换句话说,我们必须将数据集修改为如下所示:

<dataset><todos description="description" title="title" version="0"/><todos description="description two" title="title two" version="0"/>
</dataset>

没什么大不了的,因为我们可以相信Hibernate将正确的id插入todos表的id列中。

但是,如果每个待办事项条目都可以包含0 .. *标签,那么我们将会遇到麻烦。 假设我们必须编写一个集成测试,该测试将两个新的todo条目插入数据库并创建一个DbUnit数据集,以确保

  • 标题为“ title one”的待办事项条目具有名为“ tag one”的标签。
  • 标题为“标题二”的待办事项条目具有名为“标签二”的标签。

我们的最大努力如下所示:

<dataset><todos description="description" title="title one" version="0"/><todos description="description two" title="title two" version="0"/><tags name="tag one" version="0"/><tags name="tag two" version="0"/>
</dataset>

我们无法创建有用的DbUnit数据集,因为我们不知道保存到数据库中的待办事项条目的ID。

我们必须找到更好的解决方案。

寻找更好的解决方案

我们已经为我们的问题找到了两种不同的解决方案,但是它们都产生了新的问题。 第三种解决方案基于以下思想:

如果我们不知道插入到自动递增列中的下一个值,则必须在调用每个测试方法之前重置自动递增列。

我们可以按照以下步骤进行操作:

  1. 创建一个用于重置指定数据库表的自动增量列的类。
  2. 修复我们的集成测试。

让我们弄脏双手。

创建可以重置自动增量列的类

我们可以按照以下步骤创建类,该类可以重置指定数据库表的自动增量列:

  1. 创建一个名为DbTestUtil最终类,并通过向其添加私有构造函数来防止其实例化。
  2. 公共静态void resetAutoIncrementColumns()方法添加到DbTestUtil类。 此方法采用两个方法参数:
    1. ApplicationContext对象包含已测试应用程序的配置。
    2. 必须重置其自动增量列的数据库表的名称。
  3. 通过执行以下步骤来实现此方法:
    1. 获取对DataSource对象的引用。
    2. 使用键“ test.reset.sql.template”从属性文件( application.properties )中读取SQL模板。
    3. 打开数据库连接。
    4. 创建调用SQL语句并调用它们。

DbTestUtil类的源代码如下所示:

import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;public final class DbTestUtil {private DbTestUtil() {}public static void resetAutoIncrementColumns(ApplicationContext applicationContext,String... tableNames) throws SQLException {DataSource dataSource = applicationContext.getBean(DataSource.class);String resetSqlTemplate = getResetSqlTemplate(applicationContext);try (Connection dbConnection = dataSource.getConnection()) {//Create SQL statements that reset the auto increment columns and invoke //the created SQL statements.for (String resetSqlArgument: tableNames) {try (Statement statement = dbConnection.createStatement()) {String resetSql = String.format(resetSqlTemplate, resetSqlArgument);statement.execute(resetSql);}}}}private static String getResetSqlTemplate(ApplicationContext applicationContext) {//Read the SQL template from the properties fileEnvironment environment = applicationContext.getBean(Environment.class);return environment.getRequiredProperty("test.reset.sql.template");}
}

附加信息:

  • ApplicationContext接口的Javadoc
  • DataSource接口的Javadoc
  • 环境接口的Javadoc
  • String.format()方法的Javadoc

让我们继续前进,找出如何在集成测试中使用此类。

修复我们的集成测试

我们可以按照以下步骤修复集成测试:

  1. 将重置SQL模板添加到示例应用程序的属性文件中。
  2. 在调用我们的测试方法之前,请重置todos表的自动增量列( id )。

首先 ,我们必须将重置SQL模板添加到示例应用程序的属性文件中。 此模板必须使用String类的format()方法支持的格式 。 因为我们的示例应用程序使用了H2内存数据库,所以我们必须将以下SQL模板添加到属性文件中:

test.reset.sql.template=ALTER TABLE %s ALTER COLUMN id RESTART WITH 1

附加信息:

  • 我们的示例应用程序的应用程序上下文配置类
  • String.format()方法的Javadoc
  • 重置H2中的自动增量
  • 如何重置MySQL自动增量列
  • PostgreSQL 9.3文档:ALTER SEQUENCE

其次 ,在调用我们的测试方法之前,我们必须重置todos表的自动增量列( id )。 我们可以通过对ITTodoRepositoryTest类进行以下更改来做到这一点:

  1. 将包含我们示例应用程序配置的ApplicationContext对象注入到测试类中。
  2. 重置待办事项表的自动增量列。

我们的固定集成测试类的源代码如下所示(突出显示了更改):

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.DbUnitConfiguration;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;import java.sql.SQLException;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {PersistenceContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,DbUnitTestExecutionListener.class })
@DbUnitConfiguration(dataSetLoader = ColumnSensingReplacementDataSetLoader.class)
public class ITTodoRepositoryTest {private static final Long ID = 2L;private static final String DESCRIPTION = "description";private static final String TITLE = "title";private static final long VERSION = 0L;@Autowiredprivate ApplicationContext applicationContext;@Autowiredprivate TodoRepository repository;@Beforepublic void setUp() throws SQLException {DbTestUtil.resetAutoIncrementColumns(applicationContext, "todos");}@Test@DatabaseSetup("no-todo-entries.xml")@ExpectedDatabase("save-todo-entry-with-title-and-description-expected.xml")public void save_WithTitleAndDescription_ShouldSaveTodoEntryToDatabase() {Todo todoEntry = Todo.getBuilder().title(TITLE).description(DESCRIPTION).build();repository.save(todoEntry);}@Test@DatabaseSetup("no-todo-entries.xml")@ExpectedDatabase("save-todo-entry-without-description-expected.xml")public void save_WithoutDescription_ShouldSaveTodoEntryToDatabase() {Todo todoEntry = Todo.getBuilder().title(TITLE).description(null).build();repository.save(todoEntry);}
}

附加信息:

  • @Autowired注释的Javadoc
  • ApplicationContext接口的Javadoc
  • @Before注释的Javadoc

当我们第二次运行集成测试时,它们通过了。

让我们继续并总结从这篇博客文章中学到的知识。

摘要

这个博客教会了我们三件事:

  • 如果我们不知道插入到其值自动生成的列中的值,我们将无法编写有用的集成测试。
  • 如果我们的应用程序没有很多集成测试,那么使用@DirtiesContext注释可能是一个不错的选择。
  • 如果我们的应用程序有很多集成测试,则必须在调用每种测试方法之前重置自动增量列。

您可以从Github获得此博客文章的示例应用程序 。

翻译自: https://www.javacodegeeks.com/2014/11/spring-from-the-trenches-resetting-auto-increment-columns-before-each-test-method.html

mysql重置增量

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

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

相关文章

oracle itpub论坛,Oracle Service

本帖最后由 sunyunyi 于 2018-11-10 10:33 编辑目前就职海天起点&#xff0c;服务于电力行业&#xff0c;致力于帮助客户解决生产过程中出现的问题&#xff0c;提高生产效率, 爱好书法&#xff0c;周易&#xff01;愿结交志同道合之士&#xff01;共同进步&#xff01; 微信号&…

Linux C语言结构体

前面学习了c语言的基本语法特性&#xff0c;本节进行更深入的学习。预处理程序。 编译指令: 预处理, 宏定义&#xff0c;建立自己的数据类型&#xff1a;结构体&#xff0c;联合体&#xff0c;动态数据结构c语言表达式工具 逻辑运算符&#xff1a; & | ^ ~ << >&g…

ejb构建_如何使用单例EJB,Ehcache和MBean构建和清除参考数据缓存

ejb构建在本文中&#xff0c;我将介绍如何使用单例EJB和Ehcache在Java EE中构建简单的参考数据缓存。 高速缓存将在给定的时间段后重置自身&#xff0c;并且可以通过调用REST端点或MBean方法“手动”清除。 这篇文章实际上是建立在以前的文章中如何建立和清除与单EJB和MBean的一…

oracle中enqueue,ORACLE: Enqueue 烂笔头

Oracle 的enqueue 包含以下模式&#xff1a;模式代码解释1Null mode2Sub-Share3Sub-Exclusive4Share5Share/Sub-Exclusive6ExclusiveOracle的enqueue有如下类型&#xff1a;Enqueue 缩写缩写解释BLBuffer Cache managementBRBackup/RestoreCFControlfile transactionCICross-in…

Linux C语言编程基本原理与实践

重识C语言C语言是一种通用的, 面向过程的编程语言, 在系统与应用软件的开发应用较广是人类和计算机交流的一种方式ANSI C&#xff1a; 是C语言的标准, 为了避免各开发商用的C语言语法的差异C语言的特点: 简单, 快速, 高性能, 兼容性好, 功能强大, 易于学习C语言适合做什么Linux…

listview属性_属性提取器:获取ListView即时更新其元素的最佳方法

listview属性这篇文章是关于如何处理JavaFX ListViews和TableViews的&#xff0c;以及如何通过这些控件了解所包含元素的更改内容。 我想知道为什么在相关书籍中没有找到关于以下模式的任何信息&#xff0c;因为这是一个非常关键的机制。 那里的许多帖子建议通过调用以下命令来…

linux6.5安装oracle,linux [CentOS 6.5]下安装oracle

一&#xff0c;安装oracle依赖包# yum install binutils compat-libstdc-33 compat-libstdc-33.i686 elfutils-libelf elfutils-libelf-devel gcc gcc-c glibc glibc.i686 glibc-common glibc-devel glibc-devel.i686 glibc-headers ksh libaio libaio.i686 libaio-devel libai…

30分钟了解C 11新特性

什么是C 11C 11是曾经被叫做C 0x&#xff0c;是对目前C 语言的扩展和修正&#xff0c;C 11不仅包含核心语言的新机能&#xff0c;而且扩展了C 的标准程序库&#xff08;STL&#xff09;&#xff0c;并入了大部分的C Technical Report 1&#xff08;TR1&#xff09;程序库(数学的…

外链分享已取消无法下载怎么办_微信升级外链规范:“砍一刀”、“帮我加速吧”将被封禁...

北京商报讯(记者魏蔚)朋友圈、微信群遍布的购物分享链接&#xff0c;再度受到微信严控。10月18日晚间&#xff0c;微信宣布&#xff0c;外链规范即将进行更新升级&#xff0c;新增和细化多项外链规则&#xff0c;包括不可违规使用用户头像&#xff1b;不可诱导、误导下载/跳转&…

ef ddl生成不了脚本_如何使用Hibernate从Play生成DDL脚本! 框架项目

ef ddl生成不了脚本好的&#xff0c;因此您一直在使用hibernate属性名称“ hibernate.hbm2ddl.auto ” value “ 更新 ”来不断更新数据库模式&#xff0c; 但是现在您需要一个完整的DDL脚本吗&#xff1f; 从您的Global Class onStart中使用此方法来导出DDL脚本。 只需为其提…

vue php企业站案例,vue 开发企业微信整合案例分析

本文实例讲述了vue 开发企业微信整合。分享给大家供大家参考&#xff0c;具体如下&#xff1a;概述手机端程序可以和企业微信进行整合&#xff0c;我们也可以使用企业微信JSSDK功能&#xff0c;实现一些原生的功能。整合步骤在整合之前需要阅读 整合步骤。1.引入JSSDKnpm i -S …

Invalid Gradle JDK configuration found_带你了解Gradle编译速度是如何提升70%的

前言Gradle作为一款基于Groovy语言的构建工具&#xff0c;已经吸引众多的ant&#xff0c;maven使用者转投gradle的怀抱&#xff0c;和Gradle相比&#xff0c;ant显得冗余复杂&#xff0c;maven显得有些死板落后&#xff0c;而gradle基于DSL语法&#xff0c;特点明显&#xff1a…

【游戏开发】C 游戏编程实例

网络游戏开发分为&#xff1a;服务器编程、客户端编程、人工智能、数据库管理、游戏策划、美工设计、音乐特效等。大型游戏往往需要团队合作开发&#xff0c;因此面向对象的编程思想在网络游戏中得到了广泛应用。游戏开发基本流程&#xff1a;游戏初始化——游戏实现——游戏结…

jax-rs/jersey_使用JAX-RS(Jersey)的HTTP状态错误消息响应中的自定义原因短语

jax-rs/jersey在我最近的一些工作中&#xff0c;我收到了在发生错误时在HTTP状态响应中生成自定义“原因短语”的请求&#xff0c;并将其传递给使用我们REST API的客户端之一。 在这篇文章中&#xff0c;我将演示如何使用Jersey来实现这一目标。 1.定义检查的异常和异常映射器…

linux内核3.14.4,Linux内核4.14.14,4.9.77,4.4.112和3.18.92更新发布

原标题&#xff1a;Linux内核4.14.14&#xff0c;4.9.77&#xff0c;4.4.112和3.18.92更新发布导读正如所承诺的&#xff0c;Linux内核维护者Greg Kroah-Hartman今天发布了针对长期支持的Linux 4.14,4.9,4.4和3.18内核系列的一系列新更新。这些新内核在他们之前发布的一个星期后…

springboot 获取登录浏览器_java项目部署到linux服务器,微信小程序后台springboot项目部署到云服务器(图文详解)...

前面给大家讲了一个点餐系统的开发&#xff0c;包括java点餐后台和微信点餐小程序。可是都是教大家如何在本地把项目跑起来。今天就来教大家如何把这个点餐系统部署到服务器&#xff0c;实现商用。传送门点餐系统的开发&#xff0c;java后台微信小程序&#xff1a;https://blog…

spring体系结构_了解Spring Web应用程序体系结构:经典方法

spring体系结构每个开发人员必须了解两件事&#xff1a; 架构设计是必要的。 花哨的体系结构图没有描述应用程序的真实体系结构。 真正的体系结构是从开发人员编写的代码中找到的&#xff0c;如果不设计应用程序的体系结构&#xff0c;最终将得到一个具有多个体系结构的应用…

C 网络库都干了什么?

虽然市面上已经有很多成熟的网络库&#xff0c;但是编写一个自己的网络库依然让我获益匪浅&#xff0c;这篇文章主要包含&#xff1a;TCP 网络库都干了些什么&#xff1f;编写时需要注意哪些问题&#xff1f;CppNet 是如何解决的。首先&#xff0c;大家都知道操作系统原生的soc…

linux脚本登录启动失败,linux – 在X上运行shell脚本失败登录尝试

Alrighty.我想到了.要小心不要搞砸,因为如果你做错了,可能会搞砸你的系统.如果您对此感到不舒服,最好先在虚拟机中进行尝试.教程>创建脚本.在本教程中,我将把脚本放在/var/myscript.sh中.您可以安全地将脚本路径替换为脚本所在的位置.在任何情况下,请确保脚本不会以0以外的任…

iphone屏幕录制_iPhone怎么内录声音?怎么录制苹果手机内部声音?

有时我们想要对苹果手机上播放的声音进行录音&#xff0c;却不知道该如何操作。苹果手机上自带的录音软件只可以对手机外部声音进行录制&#xff0c;却无法录制自身播放的声音。其实我们可以先将苹果手机屏幕及声音先投放到电脑上&#xff0c;再通过支持内录的软件进行录音就可…