diff --git a/docs/high-perform-app-java9/3.md b/docs/high-perform-app-java9/3.md index 056caca388f6d043baa6bdc6ae3268c79e063f84..bf32426acfaec19ae9ac7d665cffc593244155b5 100644 --- a/docs/high-perform-app-java9/3.md +++ b/docs/high-perform-app-java9/3.md @@ -1144,7 +1144,7 @@ Oracle 还建议使用 G1 及其默认设置,然后使用`-XX:MaxGCPauseMillis * 使用`-Xmx`调出堆的大小。但请确保它不超过 RAM 的物理大小。更好的是,为其他应用程序留出一些 RAM 空间。 * 使用`-XX:ConcGCThreads`显式增加并发标记线程数。 * 如果大量对象占用了太多的堆(注意显示大量区域旁边数字的`gc+heap=info`日志记录),请尝试使用`-XX: G1HeapRegionSize`增加区域大小。 -* 观察 GC 日志并修改代码,这样应用程序创建的几乎所有对象都不会移动到年轻一代(垂死的年轻一代)之外。 +* 观察 GC 日志并修改代码,这样应用程序创建的几乎所有对象都不会移动到新生代(垂死的新生代)之外。 * 一次添加或更改一个选项,这样您就可以清楚地了解 JVM 行为更改的原因。 这几个步骤将帮助您创建一个试错周期,让您更好地了解所使用的平台、应用程序的需求以及 JVM 和所选 GC 对不同选项的敏感性。有了这些知识,您就能够满足非功能性能需求,无论是通过更改代码、调优 JVM 还是重新配置硬件。 diff --git a/docs/java11-cb/09.md b/docs/java11-cb/09.md index e90b825478ace015a3aed226afe29ca14a0e232c..6c83a28b3c2068d1d75d6e3e414bc47a71d617a7 100644 --- a/docs/java11-cb/09.md +++ b/docs/java11-cb/09.md @@ -151,7 +151,7 @@ create database sample; # 创建人员表 -运行以下 SQL 语句以使用新创建的数据库并创建简单的 person 表: +运行以下 SQL 语句以使用新创建的数据库并创建简单的`person`表: ```java create table person( @@ -181,7 +181,7 @@ values('David', 'John', 'Delhi'); # 怎么做。。。 -1. 创建一个模型类`com.packt.boot_db_demo.Person`,以表示一个人。我们将使用 Lombok 注释为我们生成获取器和 setter: +1. 创建一个模型类`com.packt.boot_db_demo.Person`,以表示一个人。我们将使用 Lombok 注释为我们生成获取器和设置器: ```java @Data @@ -493,7 +493,7 @@ values('David', 'John', 'Delhi'); } ``` -您可以在`Chapter09/3_boot_rest_demo`找到完整的代码。您可以从项目文件夹中使用`mvn spring-boot:run`启动应用程序。应用程序启动后,导航至`http://localhost:8080/api/persons`查看 person 表中的所有数据。 +您可以在`Chapter09/3_boot_rest_demo`找到完整的代码。您可以从项目文件夹中使用`mvn spring-boot:run`启动应用程序。应用程序启动后,导航至`http://localhost:8080/api/persons`查看`person`表中的所有数据。 为了测试其他 API,我们将使用用于 Google Chrome 的 Postman REST 客户端应用程序。 @@ -513,7 +513,7 @@ values('David', 'John', 'Delhi'); 首先,让我们看看`PersonMapper`接口是如何发现要执行的 SQL 语句的。如果你看`src/main/resources/mappers/PersonMapper.xml`,你会发现```namespace`属性是`org.packt.boot_rest_demo.PersonMapper`。这是一个要求,`namespace`属性的值应该是映射器接口的完全限定名,在我们的例子中,它是`org.packt.boot_rest_demo.PersonMapper`。 -接下来,在``、``、``和``中定义的各个 SQL 语句的`id`属性应该与映射器接口中的方法名称匹配。例如,`PersonMapper`接口中的`getPersons()`方法查找带有`id="getPersons"`的 SQL 语句。 现在,MyBatis 库通过读取`mybatis.mapper-locations`属性的值来发现这个映射器 XML 的位置。 @@ -886,7 +886,7 @@ Heroku 提供了一个名为 Heroku cli([cli.Heroku.com](http://cli.heroku.com 6. 更新`src/main/resources/application.properties`文件,将`spring.profiles.active`属性的值替换为`heroku`。 -7. 提交更改并将其推送到 Heroku remote: +7. 提交更改并将其推送到 Heroku 远程: ```java $ git commit -am"using heroky mysql addon" @@ -1226,7 +1226,7 @@ management.endpoints.web.base-path=/metrics management.endpoints.web.exposure.include=prometheus,health,info,metrics ``` -即使我们启用了Prometheus,我们也需要在类路径上拥有`micrometer-registry-prometheus`库。只有这样,我们才能查看Prometheus格式的度量。因此,我们在 pom 中添加了以下依赖项: +即使我们启用了Prometheus,我们也需要在类路径上拥有`micrometer-registry-prometheus`库。只有这样,我们才能查看Prometheus格式的度量。因此,我们在 POM 中添加了以下依赖项: ```java diff --git a/docs/java11-cb/11.md b/docs/java11-cb/11.md index 213e86af620449a14d0437486c9d63907e69705c..154fad4bbbcdcf77bc38cf65d3a0a0b1789f5737 100644 --- a/docs/java11-cb/11.md +++ b/docs/java11-cb/11.md @@ -35,9 +35,9 @@ GC 堆和堆栈使用两个内存区域。第一个用于 JVM 分配内存和存 # 了解 G1 垃圾收集器 -先前的 GC 实现包括**串行 GC**、**并行 GC**和**并发标记扫描**(**CMS**)收集器。他们将堆分为三个部分:年轻一代、老年代或终身一代,以及巨大的区域,用于容纳标准区域大小 50% 或更大的对象。年轻一代包含大多数新创建的对象;这是最具活力的区域,因为大多数对象都是短期的,并且很快(随着年龄的增长)就有资格收集。期限是指对象存活下来的收集周期数。年轻一代有三个收集周期——一个*伊甸空间*和两个幸存者空间,如幸存者 0(`S0`)和幸存者 1(`S1`)。对象会在其中移动(根据其年龄和其他一些特征),直到最终被丢弃或放置在老年代中。 +先前的 GC 实现包括**串行 GC**、**并行 GC**和**并发标记扫描**(**CMS**)收集器。他们将堆分为三个部分:新生代、老年代或终身一代,以及巨大的区域,用于容纳标准区域大小 50% 或更大的对象。新生代包含大多数新创建的对象;这是最具活力的区域,因为大多数对象都是短期的,并且很快(随着年龄的增长)就有资格收集。期限是指对象存活下来的收集周期数。新生代有三个收集周期——一个*伊甸空间*和两个幸存者空间,如幸存者 0(`S0`)和幸存者 1(`S1`)。对象会在其中移动(根据其年龄和其他一些特征),直到最终被丢弃或放置在老年代中。 -老年代包含的对象比某个年龄段的对象旧。这个区域比年轻一代大,因此,这里的垃圾收集成本更高,而且不像年轻一代那样频繁。 +老年代包含的对象比某个年龄段的对象旧。这个区域比新生代大,因此,这里的垃圾收集成本更高,而且不像新生代那样频繁。 永久生成包含描述应用程序中使用的类和方法的元数据。它还存储字符串、库类和方法。 @@ -65,7 +65,7 @@ G1 GC 算法类似于 CMS 收集器。首先,它同时标识堆中所有被引 G1 的主要受益者是需要大堆(6GB 或更高)且不允许长时间暂停(0.5 秒或更短)的应用程序。如果应用程序遇到暂停太多和/或太长的问题,可以从 CMS 或并行 GC(特别是老年代的并行 GC)切换到 G1 GC。如果不是这样,那么在使用 JDK 9 或更高版本时,不需要切换到 G1 收集器。 -G1 GC 从年轻一代集合开始,使用停止世界进行疏散(将年轻一代内部的对象移动到老年代)。当老年代的入住率达到一定阈值后,也会被收集。旧代中的某些对象的收集是同时进行的,某些对象是使用“停止世界”进行收集的。这些步骤包括: +G1 GC 从新生代集合开始,使用停止世界进行疏散(将新生代内部的对象移动到老年代)。当老年代的入住率达到一定阈值后,也会被收集。旧代中的某些对象的收集是同时进行的,某些对象是使用“停止世界”进行收集的。这些步骤包括: * 幸存者区域(根区域)的初始标记(可能有对旧代中对象的引用)使用“停止世界”完成 * 在应用程序继续运行时,同时扫描幸存者区域以查找对旧代的引用 @@ -73,9 +73,9 @@ G1 GC 从年轻一代集合开始,使用停止世界进行疏散(将年轻 * 备注步骤完成活动对象的标记,使用“停止世界”完成 * 清理过程计算活动对象的年龄,释放区域(使用“停止世界”),并将其返回到“可用”列表(同时) -前面的序列中可能夹杂着年轻一代的撤离,因为大多数物体都是短暂的,更频繁地扫描年轻一代更容易释放大量内存。 +前面的序列中可能夹杂着新生代的撤离,因为大多数物体都是短暂的,更频繁地扫描新生代更容易释放大量内存。 -当 G1 收集年轻人和老年人已经标记为主要垃圾的区域时,也有一个混合阶段,当大型物体被移动到或从巨大区域撤离时,也有巨大的分配。 +当 G1 收集新生代和老年代已经标记为主要垃圾的区域时,也有一个混合阶段,当大型物体被移动到或从巨大区域撤离时,也有巨大的分配。 在某些情况下,使用“停止世界”执行完全 GC: @@ -114,7 +114,7 @@ G1 GC 从年轻一代集合开始,使用停止世界进行疏散(将年轻 如您所见,它创建了 99888999 个对象并将它们添加到`List list`集合中。您可以通过减少最大对象数(`max`来调整它,以匹配计算机的配置。 -2. g1gc 是自 Java9 以来的默认收集器,因此,如果它对您的应用程序足够好,则不必设置任何内容。不过,您可以通过在命令行上提供`-XX:+UseG1GC`来显式启用 G1: +2. G1GC 是自 Java9 以来的默认收集器,因此,如果它对您的应用程序足够好,则不必设置任何内容。不过,您可以通过在命令行上提供`-XX:+UseG1GC`来显式启用 G1: ```java java -XX:+UseG1GC -cp ./cookbook-1.0.jar @@ -135,7 +135,7 @@ G1 GC 从年轻一代集合开始,使用停止世界进行疏散(将年轻 ![](img/559f97f3-d6c4-40f6-8656-ba6b3a7f2834.png) -如您所见,GC 完成了我们描述的大部分步骤。它从收集年轻一代开始。然后,当`List list`对象(参见前面的代码)变得太大(超过年轻一代区域的 50%)时,它的内存被分配到*庞大的*区域。您还可以看到初始标记步骤、下面的备注以及前面描述的其他步骤。 +如您所见,GC 完成了我们描述的大部分步骤。它从收集新生代开始。然后,当`List list`对象(参见前面的代码)变得太大(超过新生代区域的 50%)时,它的内存被分配到*庞大的*区域。您还可以看到初始标记步骤、下面的备注以及前面描述的其他步骤。 每一行以 JVM 运行的时间(以秒为单位)开始,以每一步所用的时间(以毫秒为单位)结束。在屏幕截图的底部,我们看到`time`实用程序打印的三行内容: @@ -164,14 +164,14 @@ G1 GC 从年轻一代集合开始,使用停止世界进行疏散(将年轻 * `-XX:+G1UseAdaptiveIHOP`:表示起始堆占用应该是自适应的 * `-XX:InitiatingHeapOccupancyPercent=45`:设置前几个采集周期;G1 将使用老年代的 45% 的占有率作为标记开始阈值 * `-XX:G1HeapRegionSize=`:根据初始堆大小和最大堆大小保存堆区域大小(默认情况下,因为堆包含大约 2048 个堆区域,所以堆区域的大小可以从 1 到 32 MB 不等,并且必须是 2 的幂) -* `-XX:G1NewSizePercent=5`和`-XX:XX:G1MaxNewSizePercent=60`:定义年轻一代的总大小,在这两个值之间变化为当前使用的 JVM 堆的百分比 +* `-XX:G1NewSizePercent=5`和`-XX:XX:G1MaxNewSizePercent=60`:定义新生代的总大小,在这两个值之间变化为当前使用的 JVM 堆的百分比 * `-XX:G1HeapWastePercent=5`:以百分比形式保存候选集合集中允许的未回收空间(如果候选集合中的可用空间低于该百分比,G1 停止空间回收) * `-XX:G1MixedGCCountTarget=8`:保存多个集合中空间回收阶段的预期长度) * `-XX:G1MixedGCLiveThresholdPercent=85`:保存老年代区域的活动对象占用百分比,在此百分比之后,在此空间回收阶段将不会收集区域 一般来说,默认配置中的 G1 目标是“*在高吞吐量下提供相对较小、均匀的暂停*”(来自 G1 文档)。如果这些默认设置不适合您的应用程序,您可以更改暂停时间(使用`-XX:MaxGCPauseMillis`和最大 Java 堆大小(使用`-Xmx`选项)。不过,请注意,实际暂停时间在运行时并不完全匹配,但 G1 将尽最大努力实现这一目标。 -如果要增加吞吐量,请减少暂停时间目标或请求更大的堆。要提高响应能力,请更改“暂停时间”值。但是请注意,限制年轻一代的大小(使用`-Xmn`、`-XX:NewRatio`或其他选项)可能会妨碍暂停时间控制,因为“年轻一代的大小是 G1 满足暂停时间的主要手段”(来自 G1 文档)。 +如果要增加吞吐量,请减少暂停时间目标或请求更大的堆。要提高响应能力,请更改“暂停时间”值。但是请注意,限制新生代的大小(使用`-Xmn`、`-XX:NewRatio`或其他选项)可能会妨碍暂停时间控制,因为“新生代的大小是 G1 满足暂停时间的主要手段”(来自 G1 文档)。 性能差的第一个可能原因之一是老年代中堆占用率过高而触发的完全 GC。此情况可通过日志中存在*暂停已满(分配失败)*来检测。它通常发生在快速连续创建过多对象(并且不能足够快地收集)或无法及时分配许多大型(庞大)对象时。有几种建议的方法来处理这种情况: @@ -184,7 +184,7 @@ G1 GC 从年轻一代集合开始,使用停止世界进行疏散(将年轻 * 异常系统或实时使用 * 引用处理时间太长 -* 仅限年轻人的收藏花费的时间太长 +* 仅限新生代的收藏花费的时间太长 * 混合收藏花费的时间太长 * 高更新 RS 和扫描 RS 次数 @@ -299,7 +299,7 @@ java -Xlog:help ![](img/f3cfeb83-7d29-427b-a784-3905350fbd00.png) 5. 尝试使用两个通过`+`符号组合的标签(例如`-Xlog:gc+heap`。它只显示具有两个标记的消息(类似于二进制`AND`操作)。请注意,通配符不能与`+`符号一起使用(例如`-Xlog:gc*+heap`不起作用)。 -6. 您还可以选择输出类型和装饰器。实际上,decorator 级别似乎信息量不大,可以通过只显式列出所需的 decorator 而轻松忽略。考虑以下示例: +6. 您还可以选择输出类型和装饰器。实际上,装饰器级别似乎信息量不大,可以通过只显式列出所需的装饰器而轻松忽略。考虑以下示例: ```java java -Xlog:heap*,cpu*::uptime,tags -cp ./cookbook-1.0.jar @@ -322,7 +322,7 @@ java -Xlog:help 新记录系统最有用的方面是标记选择。它可以更好地分析每个 JVM 组件及其子系统的内存演变,或者找到性能瓶颈,分析每个收集阶段花费的时间,这两个阶段对 JVM 和应用程序调优都至关重要。 -# 对 JVM 使用 jcmd 命令 +# 对 JVM 使用`jcmd`命令 如果您打开 Java 安装的`bin`文件夹,您可以在那里找到相当多的命令行实用程序,它们可以用于诊断问题和监视使用**Java 运行时环境**(**JRE**)部署的应用程序。他们使用不同的机制来获取他们报告的数据。这些机制特定于**虚拟机**(**VM**)实现、操作系统和版本。通常,只有一部分工具适用于给定问题。 @@ -431,7 +431,7 @@ jcmd Chapter11Memory GC.heap_info ![](img/abfd96d8-ab1b-4ff2-8991-f21ef5f8d76a.png) -它显示了堆的总大小和使用量,年轻一代中的区域大小和分配的区域数量,以及参数`Metaspace`和`class space`。 +它显示了堆的总大小和使用量,新生代中的区域大小和分配的区域数量,以及参数`Metaspace`和`class space`。 3. 如果您正在寻找失控的线程或希望了解幕后的其他情况,则以下命令非常有用: @@ -547,7 +547,7 @@ Java9 引入的新的*资源试用*语句通过允许有效地使用最终变量 如您所见,它更加简洁和集中,无需重复编写关闭资源的琐碎代码。没有更多的`finally`和额外的`try...catch`。 -2. 如果连接也被传入,也可以将其放入同一个 try 块中,并在不再需要时立即关闭: +2. 如果连接也被传入,也可以将其放入同一个`try`块中,并在不再需要时立即关闭: ```java void execute(Connection conn, Statement st, String sql) { @@ -796,7 +796,7 @@ Arrays.stream(new Throwable().getStackTrace()) } ``` -从`Chapter11Memory`类的 main 方法调用此方法: +从`Chapter11Memory`类的`main`方法调用此方法: ```java public class Chapter11Memory { @@ -810,7 +810,7 @@ Arrays.stream(new Throwable().getStackTrace()) ![](img/60ec130e-737d-49db-831f-f14f3fd5e717.png) -`Do something`消息从`Clazz01`传递并在`Clazz03`中打印出来。然后`Clazz02`将 null 传递给`Clazz03`,并在`action.toString()`行`NullPointerException`引起的堆栈跟踪之前打印出`Throw the exception`消息。 +`Do something`消息从`Clazz01`传递并在`Clazz03`中打印出来。然后`Clazz02`将`null`传递给`Clazz03`,并在`action.toString()`行`NullPointerException`引起的堆栈跟踪之前打印出`Throw the exception`消息。 # 它是如何工作的。。。 @@ -955,7 +955,7 @@ class SomeOtherClass { } ``` -在前面的示例中,`Calculator`对象一经创建就是一个单例,应用程序中只存在它的一个实例。如果我们知道将始终使用 calculator 属性,那么就不需要延迟初始化。在 Java 中,我们可以在任何应用程序线程首次加载类时利用静态属性初始化: +在前面的示例中,`Calculator`对象一经创建就是一个单例,应用程序中只存在它的一个实例。如果我们知道将始终使用`calculator`属性,那么就不需要延迟初始化。在 Java 中,我们可以在任何应用程序线程首次加载类时利用静态属性初始化: ```java class SomeOtherClass { @@ -1005,7 +1005,7 @@ class LazyInitExample { } ``` -如您所见,我们可以同步对`getData()`方法的访问,但在创建对象后不需要这种同步,并且在高度并发的多线程环境中可能会造成瓶颈。类似地,我们可以在同步块内只进行一次 null 检查,但在对象初始化后不需要进行此同步,因此我们用另一次 null 检查将其包围起来,以减少出现瓶颈的可能性。 +如您所见,我们可以同步对`getData()`方法的访问,但在创建对象后不需要这种同步,并且在高度并发的多线程环境中可能会造成瓶颈。类似地,我们可以在同步块内只进行一次`null`检查,但在对象初始化后不需要进行此同步,因此我们用另一次`null`检查将其包围起来,以减少出现瓶颈的可能性。 3. 不要忘记清理缓存并删除不必要的条目。 @@ -1208,7 +1208,7 @@ void weakReference() { } ``` -在前面的代码中,我们将映射(缓存)包装在`WeakReference`类中,这意味着我们告诉 JVM,只要没有对该对象的引用,它就可以收集该对象。然后,在 for 循环的每次迭代中,我们创建`new Double[1024]`对象并将其保存在列表中。我们这样做是为了更快地使用所有可用内存。然后我们将相同的对象放入缓存。当我们运行此代码时,它很快会得到以下输出: +在前面的代码中,我们将映射(缓存)包装在`WeakReference`类中,这意味着我们告诉 JVM,只要没有对该对象的引用,它就可以收集该对象。然后,在`for`循环的每次迭代中,我们创建`new Double[1024]`对象并将其保存在列表中。我们这样做是为了更快地使用所有可用内存。然后我们将相同的对象放入缓存。当我们运行此代码时,它很快会得到以下输出: ```java Cache=4582, used memory=25 MB @@ -1324,12 +1324,12 @@ String s2 = "this string takes as much memory as another one"; 了解您的垃圾收集器是如何工作的(请参阅“了解 G1 垃圾收集器”配方),不要忘记使用 JVM 日志记录(在“针对 JVM 的统一日志记录”配方中描述)。 -之后,您可能需要调优 JVM 和垃圾收集器。下面是一些常用的`java`命令行参数(默认情况下,大小以字节为单位指定,但可以附加字母 k 或 k 表示千字节,m 或 m 表示兆字节,g 或 g 表示千兆字节): +之后,您可能需要调优 JVM 和垃圾收集器。下面是一些常用的`java`命令行参数(默认情况下,大小以字节为单位指定,但可以附加字母`k`或`k`表示千字节,`m`或`m`表示兆字节,`g`或`g`表示千兆字节): * `-Xms size`:此选项允许我们设置初始堆大小(必须大于 1MB,并且是 1024 的倍数)。 * `-Xmx size`:此选项允许我们设置最大堆大小(必须大于 2MB 和 1024 的倍数)。 -* `-Xmn size`或`-XX:NewSize=size`和`-XX:MaxNewSize=size`的组合:此选项允许我们设置年轻一代的初始和最大大小。对于高效 GC,它必须低于`-Xmx size`。Oracle 建议将其设置为堆大小的 25% 以上,50% 以下。 -* `-XX:NewRatio=ratio`:此选项允许我们设置年轻一代和老年代之间的比率(默认为两代)。 +* `-Xmn size`或`-XX:NewSize=size`和`-XX:MaxNewSize=size`的组合:此选项允许我们设置新生代的初始和最大大小。对于高效 GC,它必须低于`-Xmx size`。Oracle 建议将其设置为堆大小的 25% 以上,50% 以下。 +* `-XX:NewRatio=ratio`:此选项允许我们设置新生代和老年代之间的比率(默认为两代)。 * `-Xss size`:此选项允许我们设置线程堆栈大小。以下是不同平台的默认值: * Linux/ARM(32 位):320 KB * Linux/ARM(64 位):1024KB diff --git a/docs/master-soft-test-junit5/6.md b/docs/master-soft-test-junit5/6.md index 0fb05334081b8a968314f648fee2cdb480d689de..196da5f130b0f0835c132b8aacec4baef43a1507 100644 --- a/docs/master-soft-test-junit5/6.md +++ b/docs/master-soft-test-junit5/6.md @@ -135,7 +135,7 @@ Eclipse4.7 中 EclEmma 的测试覆盖率(氧气) 因此,据说软件系统中不存在缺陷是无法证明的。这是由计算机科学先驱 Edsger W.Dijkstra 所说的(见本章开头的引文)。因此,测试充其量只是抽样,必须在任何软件项目中进行,以降低系统故障的风险(参见第 1 章、“软件质量和 Java 测试回顾”、回顾软件缺陷分类法)。因为我们不能测试所有的东西,所以我们需要正确地测试。在本节中,我们将回顾一组编写高效测试用例的最佳实践,即: * **测试应该简单**:编写测试的软件工程师(打电话给他或她的测试人员、程序员、开发人员或其他人)应该避免尝试测试他或她的程序。关于测试,问题“是谁在看看守员?”的正确答案应该是无名小卒。我们的测试逻辑应该足够简单,以避免任何类型的元测试,因为这将导致任何逻辑的递归问题。间接地说,如果我们保持测试简单,我们还可以获得另一个理想的特性:测试将易于维护。 -* **不要实现简单测试**:一件事是进行简单测试,另一件非常不同的事情是实现伪代码,比如获取器或 setter。正如前面介绍的,测试充其量只是抽样,我们不能浪费宝贵的时间来评估代码库的这类部分。 +* **不要实现简单测试**:一件事是进行简单测试,另一件非常不同的事情是实现伪代码,比如获取器或设置器。正如前面介绍的,测试充其量只是抽样,我们不能浪费宝贵的时间来评估代码库的这类部分。 * **易读**:第一步是为我们的测试方法提供一个有意义的名称。此外,由于 JUnit 5`@DisplayName`注释,我们可以提供丰富的文本描述,它在没有 Java 命名约束的情况下定义了测试的目标。 * **单一责任原则**:这是计算机编程的一般原则,规定每个类都应该对单一功能负责。它与内聚度密切相关。这一原则在编写测试代码时非常重要:单个测试只应引用给定的系统需求。 * **测试数据是关键**:如前一节所述,SUT 的预期结果是测试的核心部分。正确管理这些数据对于创建有效的测试至关重要。幸运的是,JUnit 5 提供了一个丰富的工具箱来处理测试数据(参见第 4 章“使用高级 JUnit 特性简化测试”中的“参数化测试”。