...
 
Commits (5)
    https://gitcode.net/OpenDocCN/apachecn-java-zh/-/commit/42aa267300bd37a1e75e2ca2b42f94aed011d5b8 2021-09-26 22:22:11 2021-09-26T22:22:11+08:00 wizardforcel 562826179@qq.com https://gitcode.net/OpenDocCN/apachecn-java-zh/-/commit/f6f54648263530701385d8bcc73f69dafb38b9d5 2021-09-26 22:24:41 2021-09-26T22:24:41+08:00 wizardforcel 562826179@qq.com https://gitcode.net/OpenDocCN/apachecn-java-zh/-/commit/90513592cbb06b447c6550ae233c8f3b437dd24c 2021-09-26 22:27:13 2021-09-26T22:27:13+08:00 wizardforcel 562826179@qq.com https://gitcode.net/OpenDocCN/apachecn-java-zh/-/commit/34be19bdfe5a8712b82f782dd320c2081558bc87 2021-09-26 22:31:45 2021-09-26T22:31:45+08:00 wizardforcel 562826179@qq.com https://gitcode.net/OpenDocCN/apachecn-java-zh/-/commit/8757b06469224e7e5203eb8f26046092257da569 2021-09-26 22:34:41 2021-09-26T22:34:41+08:00 wizardforcel 562826179@qq.com
......@@ -199,7 +199,7 @@ public void whenSomethingThenResultIsSomethingElse() {
有许多不同的方式来命名测试方法。 我的首选方法是使用 BDD 场景中使用的`given/when/then`语法来命名它们。 `Given`描述(前置)条件,`When`描述动作,`Then`描述预期结果。 如果测试没有先决条件(通常使用`@Before``@BeforeClass`注释设置),则可以跳过`Given`
Do not rely only on comments to provide information about test objectives. Comments do not appear when tests are executed from your favorite IDE, nor do they appear in reports generated by the CI or build tools.
不要仅仅依靠注释来提供有关测试目标的信息。 从您喜欢的 IDE 执行测试时不会出现注释,也不会出现在 CI 或构建工具生成的报告中。
除了编写测试,您还需要运行它们。由于我们使用的是 Gradle,因此可以从命令提示符下运行它们:
......@@ -240,7 +240,7 @@ public class TicTacToeSpec {
}
```
When a piece is placed anywhere outside the *x*-axis, then `RuntimeException` is thrown.
当一块被放置在`x`轴之外的任何地方时,就会抛出`RuntimeException`
在这个测试中,我们定义当调用`ticTacToe.play(5, 2)`方法时,`RuntimeException`是预期的。这是一个很短很容易的测试,让它通过也应该很容易。我们所要做的就是创建`play`方法,并确保当`x`参数小于 1 或大于 3(电路板为 3x3)时,它抛出`RuntimeException`。您应该运行此测试三次。第一次,它应该失败,因为`play`方法不存在。一旦添加,它应该会失败,因为没有抛出`RuntimeException`。第三次,它应该是成功的,因为与此测试对应的代码已完全实现。
......@@ -280,7 +280,7 @@ public void whenYOutsideBoardThenRuntimeException() {
}
```
When a piece is placed anywhere outside the *y*-axis, then `RuntimeException` is thrown.
当一块被放置在`y`轴之外的任何地方时,就会抛出`RuntimeException`
# 实施
......@@ -313,7 +313,7 @@ public void whenOccupiedThenRuntimeException() {
}
```
When a piece is placed on an occupied space, then `RuntimeException` is thrown.
当一块被放置在一个被占用的空间时,就会抛出`RuntimeException`
就这样,;这是我们最后一次测试。一旦实现完成,我们就可以认为第一个需求已经完成。
......@@ -378,7 +378,7 @@ private void setBox(int x, int y) {
现在,是时候研究一下该轮到哪个球员上场了。
There should be a way to find out which player should play next.
应该有办法找出下一个应该玩哪个玩家。
我们可以将此要求分为三个测试:
......@@ -398,11 +398,11 @@ import static org.junit.Assert.*;
现在是编写这些测试和实现的时候了。
Write the test before writing the implementation code .
在编写实现代码之前编写测试。
The benefits of doing this are as follows—it ensures that testable code is written and ensures that every line of code gets tests written for it.
这样做的好处如下——它确保编写了可测试的代码,并确保每一行代码都为它编写了测试。
By writing or modifying the test first, the developer is focused on requirements before starting to work on a code. This is the main difference when compared to writing tests after the implementation is done. An additional benefit is that with tests first, we are avoiding the danger that the tests work as quality checking instead of quality assurance.
通过首先编写或修改测试,开发人员在开始处理代码之前专注于需求。 与在实现完成后编写测试相比,这是主要区别。 另一个好处是,首先进行测试,我们避免了测试作为质量检查而不是质量保证的危险。
# 测试–X 首先播放
......@@ -415,7 +415,7 @@ public void givenFirstTurnWhenNextPlayerThenX() {
}
```
The first turn should be played by Player `X`.
第一回合应该由玩家`X`玩。
这个测试应该是不言自明的。我们期待`nextPlayer`方法返回`X`。如果您尝试运行这个,您将看到代码甚至没有编译。那是因为`nextPlayer`方法根本不存在。我们的任务是编写`nextPlayer`方法并确保它返回正确的值。
......@@ -441,7 +441,7 @@ public void givenLastTurnWasXWhenNextPlayerThenO() {
}
```
If the last turn was played by `X`, then the next turn should be played by `O`.
如果上一回合由`X`打,那么下一回合应该由`O`打。
# 实施
......@@ -471,7 +471,7 @@ public char nextPlayer() {
最后,我们可以检查`X`的回合是否在`O`播放之后。
If the last turn was played by `O`, then the next turn should be played by `X`.
如果上一回合由`O`打,那么下一回合应该由`X`打。
没有什么可以做来完成这个测试,因此,这个测试是无用的,应该被丢弃。如果你写这个测试,你会发现它是一个假阳性。它将在不改变实施的情况下通过;试试看。编写此测试,如果它成功而没有编写任何实现代码,则放弃它。
......@@ -481,7 +481,7 @@ If the last turn was played by `O`, then the next turn should be played by `X`.
是时候按照游戏规则努力赢得比赛了。与前面的代码相比,这一部分的工作变得有点乏味。我们应该检查所有可能获胜的组合,如果其中一个组合成功,则宣布获胜。
A player wins by being the first to connect a line of friendly pieces from one side or corner of the board to the other.
玩家首先将一排友好棋子从棋盘的一侧或角落连接到另一侧,从而获胜。
为了检查一行友好的片段是否连接,我们应该验证水平线、垂直线和对角线。
......@@ -497,7 +497,7 @@ public void whenPlayThenNoWinner() {
}
```
If no winning condition is fulfilled, then there is no winner.
如果不满足获胜条件,则没有获胜者。
# 实施
......@@ -529,7 +529,7 @@ public void whenPlayAndWholeHorizontalLineThenWinner() {
}
```
The player wins when the whole horizontal line is occupied by his pieces.
当整个水平线被他的棋子占据时,玩家获胜。
# 实施
......@@ -606,7 +606,7 @@ public void whenPlayAndWholeVerticalLineThenWinner() {
}
```
The player wins when the whole vertical line is occupied by his pieces.
当整个垂直线被他的棋子占据时,玩家获胜。
# 实施
......@@ -642,7 +642,7 @@ public void whenPlayAndTopBottomDiagonalLineThenWinner() {
}
```
The player wins when the whole diagonal line from the top-left to bottom-right is occupied by his pieces.
当从左上角到右下角的整条对角线被他的棋子占据时,玩家获胜。
# 实施
......@@ -707,7 +707,7 @@ public void whenPlayAndBottomTopDiagonalLineThenWinner() {
}
```
The player wins when the whole diagonal line from the bottom-left to top-right is occupied by his pieces.
当从左下角到右上角的整个对角线都被他的棋子占据时,玩家获胜。
# 实施
......@@ -765,7 +765,7 @@ private boolean isWin() {
唯一缺少的是如何处理平局结果。
The result is a draw when all the boxes are filled.
当所有方框都填满时,结果是平局。
# 测试–处理平局情况
......
......@@ -130,7 +130,7 @@ TestNG 提供了更多的特性,并且比 JUnit 更高级。在本章中,我
想象一下,一艘军舰被安置在地球海洋的某个地方。由于现在是 21 世纪,我们可以远程控制这艘船。
Our job will be to create a program that can move the ship around the seas.
我们的工作将是创建一个可以在海上移动船舶的程序。
因为这是一本 TDD 书籍,本章的主题是单元测试,所以我们将使用 TDD 方法开发一个应用程序,重点是单元测试。在上一章第 3 章*红绿重构——从失败到成功,直到完美*中,您学习了红绿重构过程的理论和实践经验。我们将在此基础上进一步学习如何有效地使用单元测试。具体地说,我们将试着专注于我们正在开发的单元,并学习如何隔离和忽略单元可能使用的依赖项。不仅如此,我们还将努力一次只关注一个需求。出于这个原因,您只收到了高级别的需求;我们应该能够移动位于地球某处的遥控飞船。
......@@ -180,7 +180,7 @@ Our job will be to create a program that can move the ship around the seas.
我们需要知道船的当前位置,以便能够移动它。此外,我们还应该知道它所面临的方向:北、南、东或西。因此,第一个要求如下:
You are given the initial starting point (*x*, *y*) of a ship and the direction (*N*, *S*, *E*, or *W*) it is facing.
您将获得一艘船的初始起点`(x, y)`及其面向的方向(`N``S``E``W`)。
在开始处理这个需求之前,让我们先看看可以使用的帮助器类。`Point`类保存`x``y`坐标。它具有以下构造函数:
......@@ -286,7 +286,7 @@ public class ShipSpec {
现在我们知道我们的船在哪里了,让我们试着移动它。首先,我们应该能够前进和后退。
Implement commands that move the ship forwards and backwards (*f* and *b*).
执行使船向前和向后移动的命令(`f``b`)。
`Location`helper 类已经有了实现此功能的`forward``backward`方法:
......@@ -321,11 +321,11 @@ public void givenEastWhenMoveForwardThenXIncreases() {
当在指定的单元中包含外部代码时,至少在本例中,我们应该考虑到外部代码已经过测试这一事实。我们知道它是有效的,因为每次对代码进行任何更改时,我们都会运行所有测试。
Rerun all the tests every time the implementation code changes. 
每次实现代码更改时重新运行所有测试。
This ensures that there is no unexpected side-effect caused by code changes.
这可确保不会因代码更改而导致意外的副作用。
Every time any part of the implementation code changes, all tests should be run. Ideally, tests are fast to execute and can be run by a developer locally. Once code is submitted to the version control, all tests should be run again to ensure that there was no problem due to code merges. This is especially important when more than one developer is working on the code. CI tools, such as Jenkins, Hudson, Travind, Bamboo, and Go-CD should be used to pull the code from the repository, compile it, and run tests.
每次实现代码的任何部分更改时,都应该运行所有测试。 理想情况下,测试可以快速执行并且可以由开发人员在本地运行。 一旦代码提交给版本控制,所有的测试都应该重新运行,以确保没有由于代码合并引起的问题。 当有多个开发人员在处理代码时,这一点尤其重要。 应该使用 CI 工具,例如 Jenkins、Hudson、Travind、Bamboo 和 Go-CD,从存储库中提取代码、编译它并运行测试。
这种方法的另一个问题是,如果外部代码发生更改,将有更多的规范需要更改。理想情况下,我们应该被迫只更改与要修改的单元直接相关的规范。搜索调用该单元的所有其他位置可能非常耗时且容易出错。
......@@ -383,7 +383,7 @@ public boolean moveBackward() {
只把船来回移动我们走不了多远。我们应该能够通过向左或向右旋转船舶来改变方向。
Implement commands that turn the ship left and right (*l* and *r*).
执行使船左右转动的命令(`l``r`)。
在实现了前面的需求之后,这个需求应该非常容易,因为它可以遵循相同的逻辑。`Location`helper 类已经包含了`turnLeft``turnRight`方法,它们可以精确地执行此需求所要求的内容。我们需要做的就是将它们集成到`Ship`类中。
......@@ -464,9 +464,9 @@ public void whenReceiveCommandsFThenForward() {
我们已经谈到了编写通过规范的最简单的代码的重要性。
Write the simplest code to pass the test. This ensures a cleaner and clearer design and avoids unnecessary features 
编写最简单的代码来通过测试。 这确保了更清晰、更清晰的设计,并避免了不必要的功能
The idea is that the simpler the implementation, the better and easier it is to maintain the product. The idea adheres to the KISS principle. It states that most systems work best if they are kept simple rather than made complex; therefore, simplicity should be a key goal in design and unnecessary complexity should be avoided.
这个想法是实施越简单,维护产品就越好、越容易。 这个想法遵循 KISS 原则。 它指出,大多数系统如果保持简单而不是变得复杂,则效果最佳; 因此,简单应该是设计的一个关键目标,应该避免不必要的复杂性。
这是一个应用此规则的好机会。您可能倾向于编写类似以下内容的代码:
......@@ -535,11 +535,11 @@ public void receiveCommands(String commands) {
如果您试图自己编写规范和实现,并且遵循简单性规则,那么您可能需要多次重构代码以获得最终解决方案。简单性是关键,重构通常是受欢迎的必要条件。重构时,请记住,所有规范必须始终通过。
Refactor only after all the tests have passed. 
只有在所有测试都通过后才能重构。
Benefits: refactoring is safe.
好处:重构是安全的。
If all the implementation code that can be affected has tests and if they are all passing, it is relatively safe to refactor. In most cases, there is no need for new tests; small modifications to existing tests should be enough. The expected outcome of refactoring is to have all the tests passing both before and after the code is modified.
如果所有可能受影响的实现代码都有测试并且都通过了,那么重构相对安全。 在大多数情况下,不需要新的测试; 对现有测试进行小的修改就足够了。 重构的预期结果是让所有测试在修改代码之前和之后都通过。
[此需求的完整来源可在`tdd-java-ch04-ship`存储库的`req04-commands`分支中找到](https://bitbucket.org/vfarcic/tdd-java-ch04-ship/branch/req04-commands)
......@@ -547,7 +547,7 @@ If all the implementation code that can be affected has tests and if they are al
地球和其他任何行星一样,都是一个球体。当地球以地图的形式呈现时,到达一个边缘将我们包裹到另一个边缘;例如,当我们向东移动,到达太平洋最远的点时,我们被包裹在地图的西侧,继续向美国移动。此外,为了使移动更容易,我们可以将地图定义为网格。该网格的长度和高度应表示为*x*轴和*y*轴。该网格应具有最大长度(x)和高度(y)。
Implement wrapping from one edge of the grid to another.
实现从网格的一个边缘到另一个边缘的环绕。
# 规范-行星信息
......@@ -674,7 +674,7 @@ public boolean moveForward() {
尽管地球大部分地区被水覆盖(约 70%),但仍有大陆和岛屿可被视为我们遥控船的障碍。我们应该有办法检测我们的下一步行动是否会遇到这些障碍之一。如果发生这种情况,移动应中止,船舶应停留在当前位置并报告障碍物。
Implement surface detection before each move to a new position. If a command encounters a surface, the ship aborts the move, stays on the current position, and reports the obstacle.
在每次移动到新位置之前实施表面检测。 如果命令遇到水面,船舶将中止移动,停留在当前位置,并报告障碍物。
该需求的规范和实现与我们之前所做的非常相似,我们将留给您。
......
......@@ -83,7 +83,7 @@
Connect 4 是一款流行的、非常容易玩的棋盘游戏。规则是有限和简单的。
Connect 4 is a two-player connection game in which the players first choose a color and then take turns dropping colored discs from the top into a seven column, six row, vertically suspended grid. The pieces fall straight down, occupying the next available space within the column. The objective of the game is to connect four of your own discs of the same color next to one another vertically, horizontally, or diagonally, before your opponent connects four of theirs.
Connect 4 是一种两人连接游戏,玩家首先选择一种颜色,然后轮流将彩色圆盘从顶部放入七列六行垂直悬挂的网格中。 碎片直接落下,占据列内的下一个可用空间。 游戏的目标是在您的对手连接四个相同颜色的圆盘之前,将您自己的四个相同颜色的圆盘垂直、水平或对角地相互连接起来。
有关游戏的更多信息,请访问[维基百科](http://en.wikipedia.org/wiki/Connect_Four)
......@@ -116,7 +116,7 @@ Connect 4 is a two-player connection game in which the players first choose a co
让我们从第一个要求开始。
The board is composed of seven horizontal and six vertical empty positions.
棋盘由七个横空位和六个纵空位组成。
这个需求的实现非常简单。我们只需要一个空位置的表示和数据结构来保存游戏。请注意,玩家使用的颜色也有定义:
......@@ -153,7 +153,7 @@ public class Connect4 {
这个要求介绍了游戏的部分逻辑。
Players introduce discs on the top of the columns. The introduced disc drops down the board if the column is empty. Future discs introduced in the same column will stack over the previous ones.
玩家在列的顶部介绍光盘。 如果列是空的,则引入的圆盘会从板子上掉下来。 在同一列中引入的未来光盘将叠加在以前的光盘上。
在这一部分中,董事会界限变得相关。我们需要标记已经采取的立场,使用`Color.RED`来表示这些立场。最后,创建了第一个`private`方法。这是一种辅助方法,用于计算给定列中引入的光盘数量:
......
......@@ -26,9 +26,9 @@ TDD 是关于速度的。我们希望快速演示一个想法、概念或实现
在关注单元的同时,我们必须尝试删除单元可能使用的所有依赖项。这些依赖项的删除是通过设计和模拟的组合来完成的。
The benefits of using mocks include reduced code dependency and faster text execution.
使用模拟的好处包括减少代码依赖性和更快的文本执行。
Mocks are prerequisites for the fast execution of tests and the ability to concentrate on a single unit of functionality. By mocking dependencies external to the method that is being tested, the developer is able to focus on the task at hand without spending time setting them up. In a case of bigger or multiple teams working together, those dependencies may not even be developed. Also, the execution of tests without mocks tends to be slow. Good candidates for mocks are databases, other products, services, and so on.
模拟是快速执行测试和专注于单个功能单元的能力的先决条件。 通过模拟正在测试的方法外部的依赖关系,开发人员能够专注于手头的任务,而无需花时间设置它们。 在更大或多个团队一起工作的情况下,甚至可能不会开发这些依赖项。 此外,没有模拟的测试的执行往往很慢。 模拟的良好候选对象是数据库、其他产品、服务等。
在我们深入研究嘲讽之前,让我们先来解释为什么人们会首先使用它们。
......@@ -240,9 +240,9 @@ public void whenInstantiatedThenMongoHasNameGame() {
}
```
Use setup and teardown methods. The benefits of these allow preparation or setup and disposal or teardown code to be executed before and after the class or each test method. 
使用设置和拆卸方法。 这些的好处允许在类或每个测试方法之前和之后执行准备或设置和处置或拆卸代码。
In many cases, some code needs to be executed before the test class or each method in a class. For this purpose, JUnit has the `@BeforeClass` and `@Before` annotations that should be used in the setup phase. The `@BeforeClass` executes the associated method before the class is loaded (before the first test method is run). `@Before` executes the associated method before each test is run. Both should be used when there are certain preconditions required by tests. The most common example is setting up test data in the (hopefully in-memory) database. On the opposite end are the `@After` and `@AfterClass` annotations, which should be used as the teardown phase. Their main purpose is to destroy the data or state created during the setup phase or by tests themselves. Each test should be independent from others. Moreover, no test should be affected by the others. The teardown phase helps maintain the system as if no test were previously executed.
在很多情况下,一些代码需要在测试类或类中的每个方法之前执行。 为此,JUnit 具有应该在设置阶段使用的`@BeforeClass``@Before`注释。 `@BeforeClass`在加载类之前(在运行第一个测试方法之前)执行关联的方法。 `@Before` 在每个测试运行之前执行关联的方法。 当测试需要某些先决条件时,应同时使用两者。 最常见的例子是在(希望在内存中)数据库中设置测试数据。 另一端是`@After``@AfterClass`注释,它们应该用作拆卸阶段。 它们的主要目的是销毁在设置阶段或通过测试本身创建的数据或状态。 每个测试都应该独立于其他测试。 此外,任何测试都不应受到其他测试的影响。 拆卸阶段有助于维护系统,就好像之前没有执行过任何测试一样。
现在,让我们做一些模拟,间谍和验证!
......@@ -254,9 +254,9 @@ In many cases, some code needs to be executed before the test class or each meth
正如您可能已经猜到的,以这种方式编写的单个测试涉及大量的工作。此外,编写此类测试不仅需要投入工作。执行时间会增加很多。运行一个与数据库通信的测试不会花费很长时间。运行十个测试通常仍然很快。运行数百或数千次可能需要相当多的时间。当运行所有单元测试需要很多时间时会发生什么?人们失去耐心,开始将他们分成小组,或者一起放弃 TDD。将测试分成小组意味着我们对没有任何东西被破坏这一事实失去信心,因为我们只持续测试它的一部分。放弃 TDD。。。这不是我们想要达到的目标。然而,如果运行测试需要很多时间,那么期望开发人员不想等到运行完成后再转到下一个规范是合理的,而这正是我们停止执行 TDD 的时候。允许单元测试运行的合理时间是多少?没有一刀切的规则来定义这一点;然而,根据经验,如果时间超过 10-15 秒,我们应该开始担心,并花时间优化它们。
Tests should run quickly. The benefits are that the tests are used often.
测试应该快速运行。 好处是经常使用这些测试。
If it takes a lot of time to run tests, developers will stop using them or run only a small subset related to the changes they are making. One benefit of fast tests, besides fostering their usage, is fast feedback. The sooner the problem is detected, the easier it is to fix it. Knowledge about the code that produced the problem is still fresh. If a developer has already started working on the next feature while waiting for the completion of the execution of tests, they might decide to postpone fixing the problem until that new feature is developed. On the other hand, if they drops their current work to fix the bug, time is lost in context switching.
如果运行测试需要大量时间,开发人员将停止使用它们或仅运行与他们所做更改相关的一小部分。 除了促进使用之外,快速测试的好处之一是快速反馈。 越早发现问题,就越容易解决。 关于产生问题的代码的知识仍然新鲜。 如果开发人员在等待测试执行完成时已经开始处理下一个功能,他们可能会决定推迟修复问题,直到开发新功能。 另一方面,如果他们放弃当前的工作来修复错误,时间就会浪费在上下文切换中。
如果使用 LiveDB 来运行单元测试不是一个好的选择,那么替代方案是什么?嘲笑和刺探!在我们的示例中,我们知道应该调用第三方库的哪个方法。我们还投入了足够的时间来信任这个库(除了稍后将执行的集成测试)。一旦我们知道如何使用该库,我们就可以将我们的工作限制为验证是否正确调用了该库。
......@@ -336,9 +336,9 @@ mongoCollection.save(Turn: 3; X: 2; Y: 1; Player: Y);
Mockito 告诉我们,根据我们的规范,我们希望调用`mongoCollection.save`方法,但预期没有实现。由于测试仍然失败,我们需要返回并完成实现。TDD 最大的罪过之一是测试失败,转而做其他事情。
All tests should pass before a new test is written. The benefits of this are that the focus is maintained on a small unit of work, and implementation code is (almost) always in a working condition.
在编写新测试之前,所有测试都应该通过。 这样做的好处是将焦点保持在一个小的工作单元上,并且实现代码(几乎)始终处于工作状态。
It is sometimes tempting to write multiple tests before the actual implementation. In other cases, developers ignore problems detected by the existing tests and move towards new features. This should be avoided whenever possible. In most cases, breaking this rule will only introduce technical debt that will need to be paid with interest. One of the goals of TDD is ensuring that the implementation code is (almost) always working as expected. Some projects, due to pressures to reach the delivery date or maintain the budget, break this rule and dedicate time to new features, leaving the fixing of the code associated with failed tests for later. Those projects usually end up postponing the inevitable.
在实际实现之前编写多个测试有时很诱人。 在其他情况下,开发人员会忽略现有测试检测到的问题并转向新功能。 应尽可能避免这种情况。 在大多数情况下,违反此规则只会引入需要支付利息的技术债务。 TDD 的目标之一是确保实现代码(几乎)总是按预期工作。 一些项目,由于要达到交付日期或维持预算的压力,打破了这一规则并将时间投入到新功能上,将与失败测试相关的代码的修复留到以后。 这些项目通常最终会推迟不可避免的事情。
让我们也修改实现,例如,以下内容:
......
......@@ -22,10 +22,11 @@ TDD 可能无法立即调整为旧代码。你可能需要对这些步骤稍加
我们如何检测遗留代码?虽然传统代码通常等同于糟糕的代码,但 Michael Feathers 在他的书《有效地处理传统代码》*(Dorling Kindsley(印度)私人有限公司)(1993 年)中透露了一些气味。*
***Code smell**.
Smells are certain structures in the code that indicate violation of fundamental design principles and negatively impact design quality.
***代码气味**
Code smells are usually not bugs—they are not technically incorrect and do not currently prevent the program from functioning. Instead, they indicate weaknesses in design that may be slowing down development or increasing the risk of bugs or failures in the future. 
气味是代码中的某些结构,表明违反基本设计原则并对设计质量产生负面影响。
代码异味通常不是错误——它们在技术上不是错误的,目前不会阻止程序运行。 相反,它们表明设计中的弱点可能会减慢开发速度或增加未来出现错误或失败的风险
[来源](http://en.wikipedia.org/wiki/Code_smell)
......@@ -193,9 +194,9 @@ public class BirthdayGreetingService {
修改代码库始终是可能的回归的来源,因此应该谨慎地进行。重构需要测试,除非不可能。
The Legacy Code Dilemma. 
遗留代码困境。
When we change code, we should have tests in place. To put tests in place, we often have to change code.
当我们更改代码时,我们应该进行测试。 为了使测试到位,我们经常需要更改代码。
# 遗留代码更改算法
......@@ -452,7 +453,7 @@ Alexandria 是一个用 Java 编写的后端项目,它使用 RESTAPI 将信息
我们稍后将使用这些知识来实现新特性。
Black-box testing is a method of software testing that examines the functionality of an application without peering into its internal structures or workings. This type of test can be applied to virtually every level of software testing: unit, integration, system, and acceptance. It typically most if not all higher-level testing, but can dominate unit testing as well. 
黑盒测试是一种软件测试方法,它在不查看应用程序内部结构或工作的情况下检查应用程序的功能。 这种类型的测试几乎可以应用于软件测试的每个级别:单元、集成、系统和验收。 它通常是大多数(如果不是全部)更高级别的测试,但也可以主导单元测试。
[来源](http://en.wikipedia.org/wiki/Black-box_testing)
......@@ -634,11 +635,13 @@ PO 确认通过`status`搜索书籍现在已经改变,它还允许搜索任何
[产品网站在这里](https://www.getpostman.com/)。这也可以通过名为 [curl](http://curl.haxx.se/) 的工具实现。
What is curl?  
curl is a command-line tool and library for transferring data with URL syntax, supporting `[...] HTTP`, `HTTPS, [...]`, `HTTP POST`, `HTTP PUT`, and `[...]`.
什么是 curl?
curl 是一个命令行工具和库,用于使用 URL 语法传输数据,支持`[...] HTTP``HTTPS, [...]``HTTP POST``HTTP PUT``[...]`
curl 有什么用?
What's curl used for?
curl is used in command lines or scripts to transfer data.
curl 在命令行或脚本中用于传输数据。
[来源](http://curl.haxx.se/)
......@@ -670,11 +673,11 @@ curl is used in command lines or scripts to transfer data.
为了使测试更有价值,它们应该是自动化的、详尽的。目前,它们不是,所以我们认为它们是尖峰。它们将在未来实现自动化。
Each and every single test that we perform is not automated. In this case, the tests from the Postman interface are much faster to write than the automated ones. Also, the experience is far more representative of what production use would be like. The test client (thankfully, in this case) could introduce some problems with the production one, and therefore not return trusted results. 
我们执行的每一个测试都不是自动化的。 在这种情况下,来自 Postman 界面的测试比自动化的测试要快得多。 此外,经验更能代表生产用途。 测试客户端(谢天谢地,在这种情况下)可能会给生产客户端带来一些问题,因此不会返回可信结果。
In this particular case, we have found that the Postman tests are a better investment because, even after writing them, we will throw them away. They give very rapid feedback on the API and results. We also use this tool for prototyping the REST APIs, as its tools are both effective and useful. 
在这种特殊情况下,我们发现 Postman 测试是更好的投资,因为即使在编写它们之后,我们也会将它们扔掉。 他们对 API 和结果提供非常快速的反馈。 我们还使用此工具为 REST API 进行原型设计,因为它的工具既有效又实用。
The general idea here is this: depending on whether you want to save those tests for the future or not, use one tool or another. This also depends on how often you want to execute them, and in which environment.
这里的一般想法是:根据您是否想为将来保存这些测试,使用一种或另一种工具。 这还取决于您想要执行它们的频率以及在哪种环境中。
写下所有请求后,这些是我们在应用程序中找到的状态,由状态图表示:
......@@ -1036,7 +1039,7 @@ public class Books {
*`status`更改为`statuses`
* 消除对`status`的原始困扰(可选)
The smell: Primitive obsession involves using primitive data types to represent domain ideas. For example, we use a string to represent a message, an integer to represent an amount of money, or a struct/dictionary/hash to represent a specific object. 
气味:原始痴迷涉及使用原始数据类型来表示领域思想。 例如,我们使用字符串表示消息,使用整数表示金额,或使用结构/字典/哈希表示特定对象。
[来源](http://c2.com/cgi/wiki?PrimitiveObsession)
......
......@@ -75,8 +75,9 @@ Gradle 和 Maven 等构建工具期望源目录分离以及命名约定。
然而,在所有情况下,除了我们在整个重构练习中使用的那些之外,我们更喜欢后缀`Spec`。它有助于明确区分测试方法主要是作为一种指定将要开发的内容的方式创建的。测试是这些规范的重要子产品。
Use descriptive names for test methods.
Benefits: It helps in understanding the objective of tests.
对测试方法使用描述性名称。
好处:它有助于理解测试的目标。
当试图找出某些测试失败的原因,或者当覆盖率应该随着更多测试而增加时,使用描述测试的方法名称是有益的。测试前应明确设置的条件、执行的操作以及预期结果。
......@@ -104,18 +105,21 @@ Benefits: It helps in understanding the objective of tests.
TDD 过程是一组核心实践。TDD 的成功实施取决于本节所述的实践。
Write a test before writing the implementation code.
Benefits: It ensures that testable code is written; it ensures that every line of code gets tests written for it.
在编写实现代码之前先编写一个测试。
好处:确保编写可测试的代码; 它确保每一行代码都有为其编写的测试。
通过首先编写或修改测试,开发人员在开始处理实现代码之前,会关注需求。这是与实现完成后编写测试相比的主要区别。另外一个好处是,通过先编写测试,我们避免了测试作为**质量检查****质量控制**)而不是**质量保证****质量保证**)工作的危险。我们正在努力确保质量是内在的,而不是稍后检查我们是否达到了质量目标。
Only write new code when the test is failing.
Benefits: It confirms that the test does not work without the implementation.
仅在测试失败时编写新代码。
好处:它确认测试在没有实现的情况下不起作用。
如果测试通过而不需要编写或修改实现代码,那么要么功能已经实现,要么测试有缺陷。如果确实缺少新功能,那么测试总是通过,因此是无用的。由于预期的原因,测试应该失败。尽管无法保证测试验证的是正确的,但由于预期的原因,首先失败,验证正确的信心应该很高。
Rerun all tests every time the implementation code changes.
Benefits: It ensures that there are no unexpected side effects caused by code changes.
每次实现代码更改时重新运行所有测试。
好处:它确保没有由代码更改引起的意外副作用。
每次实现代码的任何部分发生更改时,都应该运行所有测试。理想情况下,测试可以快速执行,并且可以由开发人员在本地运行。一旦代码提交到版本控制,所有测试都应该再次运行,以确保不会因为代码合并而出现问题。当不止一个开发人员在处理代码时,这一点尤为重要。应使用**持续集成****CI**工具)从存储库中提取代码、编译代码并运行测试,例如:
......@@ -124,13 +128,15 @@ Benefits: It ensures that there are no unexpected side effects caused by code ch
* [Travis](https://travis-ci.org/)
* [Bamboo](https://www.atlassian.com/software/bamboo)
All tests should pass before a new test is written.
Benefits: The focus is maintained on a small unit of work; implementation code is (almost) always in working condition.
在编写新测试之前,所有测试都应该通过。
好处:焦点保持在一个小的工作单元上; 实现代码(几乎)始终处于工作状态。
有时,在实际实现之前编写多个测试是很有诱惑力的。在其他情况下,开发人员忽略现有测试检测到的问题,转而使用新功能。应尽可能避免这种情况。在大多数情况下,违反这一规则只会导致需要支付利息的技术债务。TDD 的目标之一是实现代码(几乎)总是按预期工作。一些项目,由于达到交付日期或维持预算的压力,打破了这一规则,将时间花在新特性上,将修复与失败测试相关的代码的任务留给以后完成。这些项目通常会推迟不可避免的事情。
Refactor only after all tests are passing.
Benefits: This type of refactoring is safe.
只有在所有测试都通过后才能重构。
优点:这种重构是安全的。
如果所有可能受到影响的实现代码都有测试并且都通过了,那么重构是相对安全的。在大多数情况下,不需要进行新的测试。对现有测试进行小的修改就足够了。重构的预期结果是使所有测试在代码修改前后都通过。
......@@ -201,33 +207,38 @@ public final void whenAddIsUsedThenItWorks() {
这个测试有很多断言。目前还不清楚该功能是什么,如果其中一个出现故障,则不知道其余功能是否正常。当通过某些 CI 工具执行此测试时,可能很难理解失败。
Do not introduce dependencies between tests.
Benefits: The tests work in any order independently, whether all or only a subset is run.
不要在测试之间引入依赖关系。
优点:测试以任何顺序独立运行,无论是全部运行还是仅运行一个子集。
每个测试都应该独立于其他测试。开发人员应该能够执行任何单个测试、一组测试或所有测试。通常,由于测试运行程序的设计,无法保证测试将按任何特定顺序执行。如果测试之间存在依赖关系,那么随着新测试的引入,它们可能很容易被打破。
Tests should run fast.
Benefits: These tests are used often.
测试应该运行得很快。
优点:这些测试经常使用。
如果运行测试需要很多时间,开发人员将停止使用测试,或者只运行与所做更改相关的一小部分测试。快速测试的好处是,除了提高使用率外,还可以提供快速反馈。问题越早被发现,就越容易解决。关于产生问题的代码的知识仍然是新鲜的。如果开发人员在等待测试执行完成时已经开始开发下一个特性,他们可能会决定推迟解决问题,直到开发出新特性。另一方面,如果他们放弃当前的工作来修复 bug,那么在上下文切换中就会浪费时间。
测试应该很快,开发人员可以在每次更改后运行所有测试,而不会感到无聊或沮丧。
Use test doubles.
Benefits: This reduces code dependency and test execution will be faster.
使用测试替身。
好处:这减少了代码依赖性并且测试执行会更快。
模拟是快速执行测试和集中于单个功能单元的能力的先决条件。通过模拟正在测试的方法外部的依赖关系,开发人员可以专注于手头的任务,而无需花费时间进行设置。在大型团队的情况下,这些依赖关系甚至可能无法开发。此外,没有模拟的测试执行往往很慢。数据库、其他产品、服务等是 mock 的最佳候选对象。
Use setup and teardown methods.
Benefits: This allows setup and teardown code to be executed before and after the class or each method.
使用设置和拆卸方法。
优点:这允许在类或每个方法之前和之后执行设置和拆卸代码。
在许多情况下,一些代码需要在测试类之前或类中的每个方法之前执行。为此,JUnit 有`@BeforeClass``@Before`注释,应该用作设置阶段。`@BeforeClass`在加载类之前(在运行第一个测试方法之前)执行关联的方法。
`@Before`在每个测试运行之前执行相关的方法。当测试需要某些先决条件时,应使用这两种方法。最常见的例子是在数据库(希望在内存中)中设置测试数据。
另一端是`@After``@AfterClass`注释,应该用作拆卸阶段。它们的主要目的是销毁在设置阶段或测试本身创建的数据或状态。如前一个实践中所述,每个测试应独立于其他测试。此外,任何测试都不应受到其他测试的影响。拆卸阶段有助于维护系统,就像以前没有执行测试一样。
Do not use base classes in tests.
Benefits: It provides test clarity.
不要在测试中使用基类。
优点:它提供了测试的清晰度。
开发人员通常以与实现相同的方式处理测试代码。一个常见的错误是创建通过测试扩展的基类。这种做法以牺牲测试清晰度为代价避免了代码重复。如果可能,应避免或限制用于测试的基类。为了理解测试背后的逻辑,必须从测试类导航到其父类、父类的父类等等,这通常会带来不必要的混乱。测试的清晰性应该比避免代码重复更重要。
......@@ -235,8 +246,9 @@ Benefits: It provides test clarity.
TDD、编码和测试通常严重依赖于其他工具和过程。以下是一些最重要的问题。它们中的每一个都是本书无法探讨的大主题,因此将仅对它们进行简要描述。
Code coverage and CI.
Benefits: It gives assurance that everything is tested.
代码覆盖率和 CI。
好处:它保证一切都经过测试。
在确定所有代码、分支和复杂性都经过测试时,代码覆盖率实践和工具非常有价值。其中一些工具如下:
......@@ -251,8 +263,9 @@ CI 工具是除最琐碎的项目外所有项目的必备工具。一些最常
* [Travis](https://travis-ci.org/)
* [Bamboo](https://www.atlassian.com/software/bamboo)
Use TDD together with BDD.
Benefits: Both developer unit tests and functional customer facing tests are covered.
将 TDD 与 BDD 一起使用。
优点:涵盖了开发人员单元测试和面向客户的功能测试。
虽然带有单元测试的 TDD 是一种很好的实践,但在许多情况下,它并不能提供项目所需的所有测试。TDD 开发速度快,有助于设计过程,并通过快速反馈提供信心。另一方面,BDD 更适合于集成和功能测试,通过叙述为需求收集提供了更好的过程,并且是通过场景与客户沟通的更好方式。两者都应该使用,它们一起提供一个涉及所有利益相关者和团队成员的完整流程。TDD(基于单元测试)和 BDD 应该驱动开发过程。我们建议使用 TDD 进行高代码覆盖率和快速反馈,使用 BDD 进行自动验收测试。虽然 TDD 主要面向白盒,但 BDD 通常针对黑盒测试。TDD 和 BDD 都试图将重点放在 QA 而不是 QC 上。
......