修改,删除无用句子

This commit is contained in:
独奏猫
2019-08-28 10:38:51 +08:00
committed by GitHub
parent 0c70b05728
commit 3b6981504c

View File

@@ -20,11 +20,11 @@ Java是一个静态类型的语言程序员经常对一种编程语言明显
这个过程是将集成测试构建到你创建的所有代码中,并在当你每次构建你的系统时运行这些测试。这样,构建过程检查不仅仅是检查语法的错误,同时你也教它检查语义错误。
“单元”指的是测试代码中的一小部分的想法。通常,每个类都有测试检查它所有方法的行为。“系统”测试则是不同的,它检查完成的程序是否满足要求。
“单元”指的是测试代码中的一小部分的想法。通常,每个类都有测试检查它所有方法的行为。“系统”测试则是不同的,它检查程序是否满足要求。
C风格的语言特别是C++通常会认为性能比安全更重要。用Java编程比C++一般认为大概快两倍快的原因是Java的安全性网络这种特征类似于垃圾回收以及键入检查。通过将单元测试集成到构建过程中你扩大了这个安全网从而导致了更快的开发效率。当发现设计或实现的缺陷时,可以更容易、更大胆重构你的代码,并更快地生成更好的产品
C风格的语言特别是C++通常会认为性能比安全更重要。用Java编程比C++一般认为大概快两倍快的原因是Java的安全性网络这种特征类似于垃圾回收以及键入检查。通过将单元测试集成到构建过程中你扩大了这个安全网了更快的开发效率。当发现设计或实现的缺陷时,可以更容易、更大胆重构你的代码。
当我意识到,要保书中代码的正确性时我自己的测试经历就开始了。这本书通过Gradle构建系统 你需要安装JDK你可以通过输入gradlew compileJava编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目在我看来任何编程书籍的必备条件——你怎么能相信你没有编译的代码呢? 并且我发现可以利用搜索和替换在整本书大范围的修改。如果引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在系统发现了一个严重的漏洞。 编译程序是毫无疑问的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书并且发现了上面代码的缺陷)。然而我收到了来自读者反馈的语法问题。我在实现一个自动化执行测试系统的时候使用了在早期能看到效果的步骤但这是迫于出版压力与此同时我明白我的程序绝对有问题这些都会变成bug让我自食其果。我也经常收到读者的抱怨说我没有显示足够的代码输出。我需要验证程序的输出并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码许多读者就是这么做的并且从中受益。然而这种态度背后的原因是我无法保证书中的输出是正确的。从经验来看我知道随着时间的推移会发生一些事情使得输出不再正确或者一开始就没有把它弄对。为了解决这个问题我利用Python创建了一个工具你将在下载的示例中找到此工具。本书中的大多数程序都产生控制台输出该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较所以读者可以看到预期的输出并且知道这个输出已经被构建程序验证的。
当我意识到,要保书中代码的正确性时我自己的测试经历就开始了。这本书通过Gradle构建系统 你需要安装JDK你可以通过输入gradlew compileJava编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目在我看来任何编程书籍的必备条件——你怎么能相信你没有编译的代码呢? 我发现可以利用搜索和替换在整本书大范围的修改。如果引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在系统发现了一个严重的漏洞。 编译程序是毫无疑问的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书并且发现了上面代码的缺陷)。尽管我收到了来自读者反馈的语法问题。我在实现一个自动化执行测试系统的时候使用了在早期能看到效果的步骤但这是迫于出版压力与此同时我明白我的程序绝对有问题这些都会变成bug让我自食其果。我也经常收到读者的抱怨说我没有显示足够的代码输出。我需要验证程序的输出并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码许多读者就是这么做的并且从中受益。然而这种态度背后的原因是我无法保证书中的输出是正确的。从经验来看我知道随着时间的推移会发生一些事情使得输出不再正确或者一开始就没有把它弄对。为了解决这个问题我利用Python创建了一个工具你将在下载的示例中找到此工具。本书中的大多数程序都产生控制台输出该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较所以读者可以看到预期的输出并且知道这个输出已经被构建程序验证的。
#### JUnit
@@ -162,13 +162,13 @@ Cleaning up 4
**@BeforeAll** 注解是在任何其他测试操作之前运行一次的方法。 **@AfterAll** 是所有其他测试操作之后只运行一次的方法。两个方法都必须是静态的。
**@BeforeEach**注解是通常用于创建和初始化公共对象的方法,并在每次测试前运行。或者,您可以将所有这样的初始化放在test类的构造函数中尽管我认为 **@BeforeEach** 更加清晰。JUnit为每个测试创建一个对象确保测试运行之间没有副作用。然而,所有测试的所有对象都是同时创建的(而不是在测试之前创建对象),所以使用 **@BeforeEach** 和构造函数之间的唯一区别是 **@BeforeEach** 在测试前直接调用。在大多数情况下,这不是问题,如果愿意,可以使用构造函数方法。
**@BeforeEach**注解是通常用于创建和初始化公共对象的方法并在每次测试前运行。可以将所有这样的初始化放在test类的构造函数中尽管我认为 **@BeforeEach** 更加清晰。JUnit为每个测试创建一个对象确保测试运行之间没有副作用。然而所有测试的所有对象都是同时创建的(而不是在测试之前创建对象),所以使用 **@BeforeEach** 和构造函数之间的唯一区别是 **@BeforeEach** 在测试前直接调用。在大多数情况下,这不是问题,如果愿意,可以使用构造函数方法。
如果必须在每次测试后执行清理如果修改了需要恢复的静态文件打开文件需要关闭打开数据库或者网络连接etc那就用注解 **@AfterEach**.
如果必须在每次测试后执行清理如果修改了需要恢复的静态文件打开文件需要关闭打开数据库或者网络连接etc那就用注解 **@AfterEach**.
每个测试创建一个新的 **CountedListTest** 对象,任何非静态成员变量也会在同一时间创建。然后为每个测试调用 **initialize()** 于是list被分配了一个新的 **CountedList** 对象,然后用 **String“0”、“1”****“2”** 初始化。观察 **@BeforeEach** 和 **@AfterEach** 的行为,这些方法在初始化和清理测试时显示有关测试的信息。
**insert()****replace()** 演示了典型的测试方法。JUnit使用 **@Test** 注解发现这些方法,并将每个方法作为测试运行。在方法内部,可以执行任何所需的操作并使用 JUnit 断言方法(已"assert"开头)验证测试的正确性(更全面的"assert"说明可以在Junit文档里找到。如果断言失败将显示导致失败的表达式和值。这通常就足够了但是你也可以使用每个JUnit断言语句的重载版本它包含一个字符串以便在断言失败时显示。
**insert()****replace()** 演示了典型的测试方法。JUnit使用 **@Test** 注解发现这些方法,并将每个方法作为测试运行。在方法内部,可以执行任何所需的操作并使用 JUnit 断言方法(已"assert"开头)验证测试的正确性(更全面的"assert"说明可以在Junit文档里找到。如果断言失败将显示导致失败的表达式和值。这通常就足够了但是你也可以使用每个JUnit断言语句的重载版本它包含一个字符串以便在断言失败时显示。
断言语句不是必须的;你可以在没有断言的情况下运行测试,如果没有异常,则认为测试是成功的。
@@ -178,21 +178,21 @@ Cleaning up 4
**gradlew validating:test**
Gradle不运行已经运行过的测试所以如果你没有得到测试结果先运行:
Gradle不运行已经运行过的测试,所以如果你没有得到测试结果,先运行:
**gradlew validating:clean**
可以用这个命令运行本书的所有测试:
可以用这个命令运行本书的所有测试:
**gradlew test**
尽管可以用最简单的方法如CountedListTest.java所示
尽管可以用最简单的方法如CountedListTest.java所示
JUnit包含许多额外的测试业务可以在其上了解这些结构
JUnit包含许多额外的测试业务可以在其上了解这些结构
[junit.org]: junit.org.
Junit是Java最流行的单元测试框架但也有其它可以替代的。你可以通过互联网发现更适合的那一个。
Junit是Java最流行的单元测试框架但也有其它可以替代的。你可以通过互联网发现更适合的那一个。
#### 测试覆盖率的幻觉
@@ -200,9 +200,9 @@ Junit是Java最流行的单元测试框架但也有其它可以替代的。
计算覆盖率,还有有帮助的文章[Java代码覆盖工具](https://en.wikipedia.org/wiki/Java_Code_Coverage_Tools)
对于没有知识但处于控制地位的人来说很容易在没有任何了解的情况下也有概念认为100%的测试覆盖是唯一可接受的值。这一个问题因为100%并不意味着是对测试有效性的最佳测量。可以测试所有需要它的东西但是只需要65%的覆盖率。如果需要100%的覆盖,将浪费大量时间来生成剩余的代码,并且在向项目添加代码时浪费的时间更多。
对于没有知识但处于控制地位的人来说很容易在没有任何了解的情况下也有概念认为100%的测试覆盖是唯一可接受的值。这一个问题因为100%并不意味着是对测试有效性的最佳测量。可以测试所有需要它的东西但是只需要65%的覆盖率。如果需要100%的覆盖,将浪费大量时间来生成剩余的代码,并且在向项目添加代码时浪费的时间更多。
分析一个未知的代码库时测试覆盖率作为一个粗略的度量是有用的。如果覆盖率工具报告的值特别低比如少于百分之40则说明覆盖不够充分。然而一个非常高的值也同样值得怀疑这表明对编程领域了解不足的人迫使团队做出了武断的决定。覆盖工具的最佳用途是发现代码库中未测试的部分。但是不要依赖覆盖率来告诉你关于测试质量的任何信息。
当分析一个未知的代码库时测试覆盖率作为一个粗略的度量是有用的。如果覆盖率工具报告的值特别低比如少于百分之40则说明覆盖不够充分。然而一个非常高的值也同样值得怀疑这表明对编程领域了解不足的人迫使团队做出了武断的决定。覆盖工具的最佳用途是发现代码库中未测试的部分。但是不要依赖覆盖率来得到测试质量的任何信息。
<!-- Preconditions -->
@@ -252,7 +252,7 @@ at Assert1.main(Assert1.java:9)
如果你正常运行程序,没有任何特殊的断言标志,则不会发生任何事情。你需要在运行程序时显式启用断言。一种简单的方法是使用 **-ea** flag 它也可以表示为: **-enableassertion** 这将运行程序并执行任何断言语句。
输出中并没有包含多少有用的信息。另一方面,如果你使用 **information-expression** 将生成一条有用的消息作为异常堆栈跟踪的一部分。最有用的 **information-expression** 通常是一串针对程序员的文本:
输出中并没有包含多少有用的信息。另一方面,如果你使用 **information-expression** 将生成一条有用的消息作为异常堆栈跟踪的一部分。最有用的 **information-expression** 通常是一串针对程序员的文本:
```java
// validating/Assert2.java
@@ -274,9 +274,9 @@ at Assert2.main(Assert2.java:8)
*/
```
**information-expression** 可以产生任何类型的对象,因此,通常将构造一个包含对象值的更复杂的字符串,它是否与失败的断言有关。
**information-expression** 可以产生任何类型的对象,因此,通常将构造一个包含对象值的更复杂的字符串,它是否与失败的断言有关。
还可以通过类名或包名打开或关闭断言;也就是说,可以为整个包启用或禁用断言。实现这一点的详细信息在JDK的断言文档中。想要打开或关闭某些断言时,此特性对于使用断言进行工具化的大型项目非常有用。然而,日志记录(*Logging*)或者调试(*Debugging*,可能是捕获这类信息的更好工具。
还可以通过类名或包名打开或关闭断言;也就是说,可以为整个包启用或禁用断言。实现这一点的详细信息在JDK的断言文档中。想要打开或关闭某些断言时,此特性对于使用断言进行工具化的大型项目非常有用。但是,日志记录(*Logging*)或者调试(*Debugging*,可能是捕获这类信息的更好工具。
这有另一种办法控制你的断言:编程方式,通过链接到类加载器对象(**ClassLoader**)。类加载器中有几种方法允许动态启用和禁用断言,其中 **setDefaultAssertionStatus ()** ,它为之后加载的所有类设置断言状态。因此,你可以认为你像下面这样悄悄地开启了断言:
@@ -308,7 +308,7 @@ LoaderAssertions.main(LoaderAssertions.java:9)
*/
```
这消除了在运行程序时在命令行上使用 **-ea** 标志的需要,使用 **-ea** 标志启用断言可能同样简单。当交付独立产品时,可能必须设置一个执行脚本让用户能够启动程序,配置其他启动参数。这是有道理的,然而,决定在程序运行时启用断言可以使用下面的 **static** 块来实现这一点,该语句位于系统的主类中:
这消除了在运行程序时在命令行上使用 **-ea** 标志的需要,使用 **-ea** 标志启用断言可能同样简单。当交付独立产品时,可能必须设置一个执行脚本让用户能够启动程序,配置其他启动参数。这是有道理的,然而,决定在程序运行时启用断言可以使用下面的 **static** 块来实现这一点,该语句位于系统的主类中:
```java
static {
@@ -399,7 +399,7 @@ Shouldn't be null: arg s
2.通过实现某些运行时检查来保证这种行为,他将这些检查称为前置条件、后置条件和不变项。
不管你是否同意,第一条总是对的,在足够多的情况下DbC确实是一种有用的方法。我认为与任何解决方案一样它的有用性也有界限。但如果你知道这些界限你就知道什么时候去尝试。尤其是设计过程中一个有价值的部分是特定类DbC约束的表达式如果无法指定约束则可能对要构建的内容了解得不够。
不管你是否同意,第一条总是对的,在大多数情况下DbC确实是一种有用的方法。我认为与任何解决方案一样它的有用性也有界限。但如果你知道这些界限你就知道什么时候去尝试。尤其是设计过程中一个有价值的部分是特定类DbC约束的表达式如果无法指定约束则可能对要构建的内容了解得不够。
#### 检查指令
@@ -407,7 +407,7 @@ Shouldn't be null: arg s
在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是作为其中复杂的一部分。滴定完成后一个有用的检查指令是能够断定液体变蓝了。
检查指令是对你的代码进行补充,当可以测试并阐明对象或程序的状态时,应该使用它。
检查指令是对你的代码进行补充,当可以测试并阐明对象或程序的状态时,应该使用它。
#### 前置条件
@@ -435,21 +435,21 @@ assert invariant();
#### 放松DBC检查 或 非严格的DBC
尽管他强调了前置条件、后置条件和不变性的价值所在以及在开发过程中使用它们的重要性Meyer承认在一个产品中包含所有DbC代码并不总是实用的。可以放松DbC检查它基于在特定的地方你可以对代码的信任程度。以下是放松检查的顺序最安全到最不安全
尽管他强调了前置条件、后置条件和不变性的价值所在以及在开发过程中使用它们的重要性Meyer承认在一个产品中包含所有DbC代码并不总是实用的。可以放松DbC检查它基于在特定的地方你可以对代码的信任程度。以下是放松检查的顺序最安全到最不安全
**1**. 不变性检查在每个方法一开始的时候是不能进行的,因为在每个方法结束的时候进行不变性检查能保证一开始的时候对象处于有效状态。也就是说,通常情况下,你可以相信对象的状态不会在方法调用之间发生变化。这是一个非常安全的假设,你可以只在代码末尾使用不变性检查来编写代码。
**2**. 接下来禁用后置条件检查,当你进行合理的单元测试以验证方法是否返回了适当的值时。因为不变性检查是观察对象的状态,后置条件检查仅在方法期间验证计算结果,因此可能会被丢弃,以便进行单元测试。单元测试不会像运行时后置条件检查那样安全,但是它可能已经足够了,特别是如果你对代码有信心的话
**2**. 接下来禁用后置条件检查,当你进行合理的单元测试以验证方法是否返回了适当的值时。因为不变性检查是观察对象的状态,后置条件检查仅在方法期间验证计算结果,因此可能会被丢弃,以便进行单元测试。单元测试不会像运行时后置条件检查那样安全,但是它可能已经足够了,特别是当对自己的代码有信心
**3**. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管它可能没有调用 **invariant()** 那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。
**3**. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管它可能没有调用 **invariant()** 那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。
**4**. 最后,万不得已,禁用前置条件检查。这是最不安全、最不明智的选择,因为尽管你知道并且可以控制自己的代码,但是你无法控制客户端可能会传递给方法的参数。然而,**A** 在迫切需要性能和概要分析的情况下,将前置条件检查作为瓶颈,**(B)** 并且你有某种合理的保证,即客户端不会违反前置条件(如你自己编写客户端代码的情况)。禁用前置条件检查是可以接受的。
**4**. 禁用前置条件检查,但除非这是万不得已的情况下。因为这是最不安全、最不明智的选择,因为尽管你知道并且可以控制自己的代码,但是你无法控制客户端可能会传递给方法的参数。**A** 在迫切需要性能和概要分析的情况下,将前置条件检查作为瓶颈,**(B)** 并且你有某种合理的保证,即客户端不会违反前置条件(如你自己编写客户端代码的情况)。禁用前置条件检查是可以接受的。
不应该直接删除检查的代码,因为只需要禁用检查(添加注释)。这样如果发现错误,可以轻松地恢复检查以快速发现问题。
不应该直接删除检查的代码,因为只需要禁用检查(添加注释)。这样如果发现错误,可以轻松地恢复检查以快速发现问题。
#### DBC + 单元测试
下面的例子演示了将契约式设计中的概念与单元测试相结合的有效性。它显示了一个简单的先进先出(FIFO)队列,该队列实现为一个“循环”数组,即以循环方式使用的数组。当到达数组的末尾时,类将回绕到开头。
下面的例子演示了将契约式设计中的概念与单元测试相结合的有效性。它显示了一个简单的先进先出(FIFO)队列,该队列实现为一个“循环”数组,即以循环方式使用的数组。当到达数组的末尾时,将绕回到开头。
我们可以对这个队列做一些契约定义:
@@ -584,7 +584,7 @@ assert invariant();
**postcondition()** 和 **constant()** 都返回一个布尔值,因此可以在 **assert** 语句中使用它们。此外,如果出于性能考虑禁用断言,则根本不存在方法调用。**invariant()** 对对象执行内部有效性检查,如果你在每个方法调用的开始和结束都这样做,这是一个花销巨大的操作,就像 **Meyer** 建议的那样。所以, 用代码清晰地表明是有帮助的,它帮助我调试了实现。此外,如果对代码实现做任何更改,那么 **invariant()** 将确保你没有破坏代码,将不变性测试从方法调用移到单元测试代码中是相当简单的。如果的单元测试是足够的,那么你应当对不变性保持一定的信心。
**postcondition()** 和 **constant()** 都返回一个布尔值,因此可以在 **assert** 语句中使用它们。此外,如果出于性能考虑禁用断言,则根本不存在方法调用。**invariant()** 对对象执行内部有效性检查,如果你在每个方法调用的开始和结束都这样做,这是一个花销巨大的操作,就像 **Meyer** 建议的那样。所以, 用代码清晰地表明是有帮助的,它帮助我调试了实现。此外,如果对代码实现做任何更改,那么 **invariant()** 将确保你没有破坏代码,将不变性测试从方法调用移到单元测试代码中是相当简单的。如果的单元测试是足够的,那么你应当对不变性保持一定的信心。
@@ -758,9 +758,9 @@ assert invariant();
#### 使用Guava前置条件
在非严格DBC中 我指出,前置条件是DbC中你不想删除的那一部分因为它可以检查方法参数的有效性。那是你没有办法控制的事情所以你需要对其检查。因为Java在默认情况下禁用断言所以通常最好使用另外一个始终验证方法参数的库。
在非严格DBC中前置条件是DbC中你不想删除的那一部分因为它可以检查方法参数的有效性。那是你没有办法控制的事情所以你需要对其检查。因为Java在默认情况下禁用断言所以通常最好使用另外一个始终验证方法参数的库。
谷歌的Guava库包含了一组很好的前置条件测试这些测试不仅易于使用而且命名也足够好。在这里可以看到它们的简单用法。库的设计人员建议静态导入前置条件:
谷歌的Guava库包含了一组很好的前置条件测试这些测试不仅易于使用而且命名也足够好。在这里可以看到它们的简单用法。库的设计人员建议静态导入前置条件:
```java
// validating/GuavaPreconditions.java
@@ -882,9 +882,9 @@ NullPointerException
*/
```
虽然Guava的前置条件适用于所有类型但我只演示 **字符串String** 类型。**test()**方法需要一个**Consumer<String>**因此我们可以传递一个lambda表达式作为第一个参数字符串。以及作为第二个参数传递给lambda的字符串。它显示字符串以便在查看输出时确定方向然后将字符串传递给lambda表达式。try块中的第二个 **println**() 仅在lambda表达式成功时才显示; 否则catch将捕获并显示错误信息。注意 **test()** 方法消除了多少重复的代码。
虽然Guava的前置条件适用于所有类型但我只演示 **字符串String** 类型。**test()** 方法需要一个Consumer<String>因此我们可以传递一个lambda表达式作为第一个参数字符串。以及作为第二个参数传递给lambda的字符串。它显示字符串以便在查看输出时确定方向然后将字符串传递给lambda表达式。try块中的第二个 **println**() 仅在lambda表达式成功时才显示; 否则catch将捕获并显示错误信息。注意 **test()** 方法消除了多少重复的代码。
每个前置条件都有三种不同的重载形式:一个什么都没有,一个带有简单字符串消息,以及带有一个字符串和替换值。为了提高效率,只允许 **%s** (字符串类型)替换标记。在上面的例子中,演示了**checkNotNull()** 和 **checkArgument()** 这两种形式。但是它们对于所有前置条件方法都是相同的。注意 **checkNotNull()** 的返回参数, 所以可以在表达式中内联使用它。下面是如何在构造函数中使用它来防止包含 **Null **值的对象构造:
每个前置条件都有三种不同的重载形式:一个什么都没有,一个带有简单字符串消息,以及带有一个字符串和替换值。为了提高效率,只允许 **%s** (字符串类型)替换标记。在上面的例子中,演示了**checkNotNull()** 和 **checkArgument()** 这两种形式。但是它们对于所有前置条件方法都是相同的。注意 **checkNotNull()** 的返回参数, 所以可以在表达式中内联使用它。下面是如何在构造函数中使用它来防止包含 **Null** 值的对象构造:
/