mirror of
https://github.com/LCTT/TranslateProject.git
synced 2026-08-23 04:03:29 +08:00
Merge branch 'LCTT:master' into 12-trans
This commit is contained in:
@@ -1,21 +1,22 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (toknow-gh)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-15808-1.html)
|
||||
[#]: subject: (My handy guide to software development and testing)
|
||||
[#]: via: (https://opensource.com/article/21/2/development-guide)
|
||||
[#]: author: (Alex Bunardzic https://opensource.com/users/alex-bunardzic)
|
||||
|
||||
我的软件开发和测试简便指南
|
||||
ZOMBIES:我的软件开发和测试简便指南(一)
|
||||
======
|
||||
编程过程有时候就像一场与丧尸群之间的战斗。
|
||||
在这个系列文章中,我将带你了解怎样将 ZOMBIES 方法应用到实际工作中。
|
||||
![Gears above purple clouds][1]
|
||||
|
||||
很久以前,在我还是一个萌新程序员的时候,我们常常一次性完成大量的工作。我们每个人都被分配编程任务,然后回到自己的小隔间里噼里啪啦地敲键盘。我记得团队里的成员在自己的小隔间里一呆就是几个小时,为打造无缺陷的程序而奋斗。当时流行的思想是:做得越多,能力越强。
|
||||
> 编程过程有时候就像一场与丧尸群之间的战斗。在这个系列文章中,我将带你了解怎样将 ZOMBIES 方法应用到实际工作中。
|
||||
|
||||
对于我来说,能够长时间编写或者修改代码而不用中途停下来检验这些代码是否有效就像荣誉勋章一样。那个时候我们都认为停下来检验代码是能力不足的表现,菜鸟才这么干。一个“真正的开发者”应该能一口气构建起整个程序,中途不用停下来检查任何东西!
|
||||
![][0]
|
||||
|
||||
很久以前,在我还是一个萌新程序员的时候,我们曾经被分配一大批工作。我们每个人都被分配了一个编程任务,然后回到自己的小隔间里噼里啪啦地敲键盘。我记得团队里的成员在自己的小隔间里一呆就是几个小时,为打造无缺陷的程序而奋斗。当时流行的思想是:能一次性做得越多,能力越强。
|
||||
|
||||
对于我来说,能够长时间编写或者修改代码而不用中途停下来检验这些代码是否有效,就像荣誉勋章一样。那个时候我们都认为停下来检验代码是否工作是能力不足的表现,菜鸟才这么干。一个“真正的开发者”应该能一口气构建起整个程序,中途不用停下来检查任何东西!
|
||||
|
||||
然而事与愿违,当我停止在开发过程中测试自己的代码之后,来自现实的检验狠狠地打了我的脸。我的代码要么无法通过编译,要么构建失败,要么无法运行,或者不能按预期处理数据。我不得不在绝望中挣扎着解决这些烦人的问题。
|
||||
|
||||
@@ -23,20 +24,19 @@
|
||||
|
||||
如果你觉得旧的工作方式听起来很混乱,那是因为它确实是这样的。我们一次性处理所有的任务,在问题堆里左砍右杀,结果只是引出更多的问题。着就像是跟一大群丧尸间的战斗。
|
||||
|
||||
如今我们已经学会了避免一次性做太多的事情。在最初听到一些专家推崇避免大批量地开发的好处时,我觉得这很反直觉,但我已经从过去的犯错中吸取了教训。我使用被 James Grenning (<https://www.agilealliance.org/resources/speakers/james-grenning/>) 称为 ZOMBIES 的方法来指导我的软件开发工作。
|
||||
如今我们已经学会了避免一次性做太多的事情。在最初听到一些专家推崇避免大批量地开发的好处时,我觉得这很反直觉,但我已经从过去的犯错中吸取了教训。我使用被 [James Grenning][1a] 称为 **ZOMBIES** 的方法来指导我的软件开发工作。
|
||||
|
||||
### ZOMBIES 方法来救援!
|
||||
|
||||
ZOMBIES 表示以下首字母缩写:
|
||||
|
||||
**Z** – 最简场景(Zero)
|
||||
**O** – 单元素场景(One)
|
||||
**M** – 多元素场景(Many or more complex)
|
||||
**B** – 边界行为(Boundary behaviors)
|
||||
**I** – 接口定义(Interface definition)
|
||||
**E** – 处理特殊行为(Exercise exceptional behavior)
|
||||
**S** – 简单场景用简单的解决方案(Simple scenarios, simple solutions)
|
||||
|
||||
- **Z** – 最简场景(Zero)
|
||||
- **O** – 单元素场景(One)
|
||||
- **M** – 多元素场景(Many or more complex)
|
||||
- **B** – 边界行为(Boundary behaviors)
|
||||
- **I** – 接口定义(Interface definition)
|
||||
- **E** – 处理特殊行为(Exercise exceptional behavior)
|
||||
- **S** – 简单场景用简单的解决方案(Simple scenarios, simple solutions)
|
||||
|
||||
我将在本系列文章中对它们进行分析讲解。
|
||||
|
||||
@@ -46,13 +46,12 @@ ZOMBIES 表示以下首字母缩写:
|
||||
|
||||
人们倾向于最开始的时候使用硬编码值,因为这是最简单的方式。通过在编码活动中使用硬编码值,可以快速构建出一个能即时反馈的解决方案。不需要几分钟,更不用几个小时,使用硬编码值让你能够马上与正在构建的系统进行交互。如果你喜欢这个交互,就朝这个方向继续做下去。如果你发现不喜欢这种交互,你可以很容易抛弃它,根本没有什么可损失。
|
||||
|
||||
本系列文章将以构建一个简易的购物系统的后端 API 为例进行介绍。该服务提供的 API 允许用户创建购物框、向购物框添加商品、从购物框移除商品、计算商品总价。
|
||||
本系列文章将以构建一个简易的购物系统的后端 API 为例进行介绍。该服务提供的 API 允许用户创建购物筐、向购物筐添加商品、从购物筐移除商品、计算商品总价。
|
||||
|
||||
首先,创建项目的基本结构(将购物程序的代码和测试代码分别放到 `app` 和 `tests` 目录下)。我们的例子中使用开源的 [xUnit][2] 测试框架。
|
||||
|
||||
现在撸起你的袖子,在实践中了解最简场景吧!
|
||||
|
||||
|
||||
```
|
||||
[Fact]
|
||||
public void NewlyCreatedBasketHas0Items() {
|
||||
@@ -62,11 +61,10 @@ public void NewlyCreatedBasketHas0Items() {
|
||||
}
|
||||
```
|
||||
|
||||
这是一个伪测试,它测试的是硬编码值。新创建的购物框是空的,所以购物框中预期的商品数是 0。通过比较期望值和实际值是否相等,这个预期被表示成一个测试(或者称为断言)。
|
||||
这是一个伪测试,它测试的是硬编码值。新创建的购物筐是空的,所以购物筐中预期的商品数是 0。通过比较期望值和实际值是否相等,这个预期被表示成一个测试(或者称为断言)。
|
||||
|
||||
运行该测试,输出结果如下:
|
||||
|
||||
|
||||
```
|
||||
Starting test execution, please wait...
|
||||
|
||||
@@ -84,7 +82,6 @@ Actual: 1
|
||||
|
||||
当然,你可以马上把硬编码的值从 1 改成 0,这样测试就能通过了:
|
||||
|
||||
|
||||
```
|
||||
[Fact]
|
||||
public void NewlyCreatedBasketHas0Items() {
|
||||
@@ -96,8 +93,6 @@ public void NewlyCreatedBasketHas0Items() {
|
||||
|
||||
与预想的一样,运行测试,测试通过:
|
||||
|
||||
|
||||
|
||||
```
|
||||
Starting test execution, please wait...
|
||||
|
||||
@@ -111,11 +106,10 @@ Total tests: 1
|
||||
|
||||
你也许会认为执行一个被强迫失败的测试完全没有意义,但是不管一个测试多么简单,确保它的可失败性是绝对有必要的。只有这样才能够保证如果在后续工作中不小心破坏了程序的处理逻辑时该测试能够给你相应的警告。
|
||||
|
||||
现在停止伪造数据,将硬编码的值替换成从 API 中获取的值。我们已经构造了一个能够可靠地失败的测试,它期望一个空的购物框中有 0 个商品,现在是时候编写一些应用程序代码了。
|
||||
现在停止伪造数据,将硬编码的值替换成从 API 中获取的值。我们已经构造了一个能够可靠地失败的测试,它期望一个空的购物筐中有 0 个商品,现在是时候编写一些应用程序代码了。
|
||||
|
||||
就跟常见的软件建模活动一样,我们先从构造一个简单的接口开始。在 `app` 目录下新建文件 `IShoppingAPI.cs`(习惯上接口名一般以大写 I 开头)。在该接口中声明一个名为 `NoOfItems()` 的方法,它以 `int` 类型返回商品的数量。下面是接口的代码:
|
||||
|
||||
|
||||
```
|
||||
using System;
|
||||
|
||||
@@ -128,7 +122,6 @@ namespace app {
|
||||
|
||||
当然这个接口什么事也做不了,在你需要实现它。在 `app` 目录下创建另一个文件 `ShoppingAPI`。在其中将 `ShoppingAPI` 声明为一个实现了 `IShoppingAPI` 的公有类。在类中定义方法 `NoOfItems` 返回整数 1:
|
||||
|
||||
|
||||
```
|
||||
using System;
|
||||
|
||||
@@ -141,22 +134,20 @@ namespace app {
|
||||
}
|
||||
```
|
||||
|
||||
从上面代码中你发现自己又在通过返回硬编码值 1 的方式来伪造代码逻辑。现阶段这是一件好事,因为你需要保持一切超级无敌简单。现在还不是仔细构想如何实现购物框的处理逻辑时候。这些工作后续再做!到目前为止,你只是通过构建最简场景来检验自己是否满意现在的设计。
|
||||
从上面代码中你发现自己又在通过返回硬编码值 1 的方式来伪造代码逻辑。现阶段这是一件好事,因为你需要保持一切超级无敌简单。现在还不是仔细构想如何实现购物筐的处理逻辑时候。这些工作后续再做!到目前为止,你只是通过构建最简场景来检验自己是否满意现在的设计。
|
||||
|
||||
为了确定这一点,将硬编码值换成这个 API 在运行中收到请求时应该返回的值。你需要通过 `using app;` 声明来告诉测试你使用的购物逻辑代码在哪里。
|
||||
|
||||
接下来,你需要 <ruby>实例化 <rt>instantiate</rt></ruby> `IShoppingAPI` 接口:
|
||||
|
||||
接下来,你需要 <ruby>实例化<rt>instantiate</rt></ruby> `IShoppingAPI` 接口:
|
||||
|
||||
```
|
||||
`IShoppingAPI shoppingAPI = new ShoppingAPI();`
|
||||
IShoppingAPI shoppingAPI = new ShoppingAPI();
|
||||
```
|
||||
|
||||
这个实例用来发送请求并接收返回的值。
|
||||
|
||||
现在,代码变成了这样:
|
||||
|
||||
|
||||
```
|
||||
using System;
|
||||
using Xunit;
|
||||
@@ -180,51 +171,53 @@ namespace tests {
|
||||
|
||||
同样的,你也可以通过将硬编码的值从 1 改成 0 来让测试通过,但是现在做这个是在浪费时间。现在设计的接口已经跟测试关联上了,你剩下的职责就是编写代码实现预期的行为逻辑。
|
||||
|
||||
在编写应用程序代码时,你得决定用来表示购物框得数据结构。为了保持设计的简单,尽量选择 C# 中表示集合的最简单类型。第一个想到的就是 `ArrayList`。它非常适合目前的使用场景——可以保存不定个数的元素,并且易于遍历访问。
|
||||
在编写应用程序代码时,你得决定用来表示购物筐得数据结构。为了保持设计的简单,尽量选择 C# 中表示集合的最简单类型。第一个想到的就是 `ArrayList`。它非常适合目前的使用场景——可以保存不定个数的元素,并且易于遍历访问。
|
||||
|
||||
因为 `ArrayList` 是 `System.Collections` 包的一部分,在你的代码中需要声明:
|
||||
|
||||
|
||||
```
|
||||
`using System.Collections;`
|
||||
using System.Collections;
|
||||
```
|
||||
|
||||
然后 `basket` 的声明就变成这样了:
|
||||
|
||||
|
||||
```
|
||||
`ArrayList basket = new ArrayList();`
|
||||
ArrayList basket = new ArrayList();
|
||||
```
|
||||
|
||||
最后将 `NoOfItems()` 中的因编码值换成实际的代码:
|
||||
|
||||
|
||||
```
|
||||
public int NoOfItems() {
|
||||
return basket.Count;
|
||||
}
|
||||
```
|
||||
|
||||
这次测试能够通过了,因为最初购物框是空的,`basket.Count` 返回 0。
|
||||
这次测试能够通过了,因为最初购物筐是空的,`basket.Count` 返回 0。
|
||||
|
||||
这也是你的第一个最简场景测试要做的事情。
|
||||
|
||||
### 更多案例
|
||||
|
||||
目前的课后作业是处理一个丧尸,也就是第 0 个丧尸。在下一篇文章中,我将带你了解单元素场景和多元素场景。不要错过哦!
|
||||
|
||||
*(题图:MJ/7917bc47-5325-4c0f-a2dd-4e444f57a46c)*
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/2/development-guide
|
||||
|
||||
作者:[Alex Bunardzic][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/toknow-gh)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
译者:[toknow-gh](https://github.com/toknow-gh)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/alex-bunardzic
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/chaos_engineer_monster_scary_devops_gear_kubernetes.png?itok=GPYLvfVh (Gears above purple clouds)
|
||||
[1a]: https://www.agilealliance.org/resources/speakers/james-grenning/
|
||||
[2]: https://xunit.net/
|
||||
[3]: http://www.google.com/search?q=new+msdn.microsoft.com
|
||||
[0]: https://img.linux.net.cn/data/attachment/album/202305/13/135130gn78t5b5kippl5nu.jpg
|
||||
@@ -3,20 +3,24 @@
|
||||
[#]: author: "Abhishek Prakash https://itsfoss.com/author/abhishek/"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: "geekpi"
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
[#]: reviewer: "wxy"
|
||||
[#]: publisher: "wxy"
|
||||
[#]: url: "https://linux.cn/article-15809-1.html"
|
||||
|
||||
终端基础 #6:在 Linux 中删除文件和文件夹
|
||||
终端基础:在 Linux 中删除文件和文件夹
|
||||
======
|
||||
|
||||
在终端基础系列的前几章中,你学习了[创建新文件][1]和目录(文件夹)。
|
||||
![][0]
|
||||
|
||||
> 你已经学会了创建文件和目录。现在是时候学习如何在命令行中删除文件和文件夹了。
|
||||
|
||||
在终端基础系列的前几章中,你学习了 [创建新文件][1] 和 [目录][1a](文件夹)。
|
||||
|
||||
现在让我们看看如何在 Linux 终端中删除文件和文件夹。
|
||||
|
||||
### 删除文件
|
||||
|
||||
要删除文件,你可以按以下方式使用 rm 命令:
|
||||
要删除文件,你可以按以下方式使用 `rm` 命令:
|
||||
|
||||
```
|
||||
rm filename_or_path
|
||||
@@ -40,7 +44,7 @@ rm file1 file2 file3
|
||||
|
||||
#### 🏋️练习文件删除
|
||||
|
||||
让我们练习一下刚刚学到的东西。创建一个名为 practice_delete 的目录并切换到该目录:
|
||||
让我们练习一下刚刚学到的东西。创建一个名为 `practice_delete` 的目录并切换到该目录:
|
||||
|
||||
```
|
||||
mkdir practice_delete && cd practice_delete
|
||||
@@ -52,19 +56,19 @@ mkdir practice_delete && cd practice_delete
|
||||
touch file1 file2 file3
|
||||
```
|
||||
|
||||
删除 file3:
|
||||
删除 `file3`:
|
||||
|
||||
```
|
||||
rm file3
|
||||
```
|
||||
|
||||
现在,让我们做一些额外的事情。运行此命令并更改 file2 的权限:
|
||||
现在,让我们做一些额外的事情。运行此命令并更改 `file2` 的权限:
|
||||
|
||||
```
|
||||
chmod u-w file1 file2
|
||||
```
|
||||
|
||||
现在尝试删除 file2:
|
||||
现在尝试删除 `file2`:
|
||||
|
||||
```
|
||||
rm file2
|
||||
@@ -72,7 +76,7 @@ rm file2
|
||||
|
||||
你是否看到消息 “**remove write protected file**”? 那是因为你从这个文件中删除了写权限(用于修改)。
|
||||
|
||||
你可以**按 Y 或回车键确认删除或按 N 拒绝删除。**
|
||||
你可以**按 `Y` 或回车键确认删除或按 `N` 拒绝删除。**
|
||||
|
||||
如果你不想看到这条消息并仍然删除它,你可以使用强制删除选项 `-f`。通过删除 `file1` 试试:
|
||||
|
||||
@@ -98,7 +102,7 @@ rm -i filename
|
||||
|
||||
当你根据特定模式删除多个文件时,这很有用。
|
||||
|
||||
这是一个示例,其中我以交互方式删除名称中匹配 file_ 模式的所有文件。我删除了一些并在交互模式下保留了一些。
|
||||
这是一个示例,其中我以交互方式删除名称中匹配 `file_` 模式的所有文件。我删除了一些并在交互模式下保留了一些。
|
||||
|
||||
![Deleting files in interactive mode][5]
|
||||
|
||||
@@ -106,25 +110,24 @@ rm -i filename
|
||||
|
||||
### 删除目录
|
||||
|
||||
在 Linux 中有专门的 rmdir 命令来删除目录。
|
||||
在 Linux 中有专门的 `rmdir` 命令来删除目录。
|
||||
|
||||
```
|
||||
rmdir dir_name
|
||||
```
|
||||
|
||||
但是,它只能删除空目录。如果目录中有任何文件或子目录,rmdir 命令将抛出错误。
|
||||
但是,它只能删除空目录。如果目录中有任何文件或子目录,`rmdir` 命令将抛出错误。
|
||||
|
||||
```
|
||||
[email protected]:~/practice_delete$ rmdir dir2
|
||||
$ rmdir dir2
|
||||
rmdir: failed to remove 'dir2': Directory not empty
|
||||
```
|
||||
|
||||
这使得它在大多数情况下用处不大。
|
||||
|
||||
那么,如何删除非空文件夹呢? 好吧,使用与之前删除文件相同的 rm 命令。
|
||||
|
||||
是的,相同的 rm 命令,但带有递归选项 `-r`:
|
||||
那么,如何删除非空文件夹呢? 好吧,使用与之前删除文件相同的 `rm` 命令。
|
||||
|
||||
是的,相同的 `rm` 命令,但带有递归选项 `-r`:
|
||||
|
||||
```
|
||||
rm -r dir_name
|
||||
@@ -134,19 +137,19 @@ rm -r dir_name
|
||||
|
||||
让我们练习你学到的东西。
|
||||
|
||||
如果你还没有,请切换到 practice_delete 文件夹。现在,创建两个目录 dir1 和 dir2。
|
||||
如果你还没有,请切换到 `practice_delete` 文件夹。现在,创建两个目录 `dir1` 和 `dir2`。
|
||||
|
||||
```
|
||||
mkdir dir1 dir2
|
||||
```
|
||||
|
||||
在 dir2 中创建一个文件:
|
||||
在 `dir2` 中创建一个文件:
|
||||
|
||||
```
|
||||
touch dir2/file
|
||||
```
|
||||
|
||||
现在尝试使用 rmdir 命令删除目录:
|
||||
现在尝试使用 `rmdir` 命令删除目录:
|
||||
|
||||
```
|
||||
rmdir dir1
|
||||
@@ -156,7 +159,7 @@ rmdir dir1
|
||||
rmdir dir2
|
||||
```
|
||||
|
||||
由于 dir2 不为空,rmdir 命令将失败。相反,使用带有递归选项的 rm 命令:
|
||||
由于 `dir2` 不为空,`rmdir` 命令将失败。相反,使用带有递归选项的 `rm` 命令:
|
||||
|
||||
```
|
||||
rm -r dir2
|
||||
@@ -166,7 +169,7 @@ rm -r dir2
|
||||
|
||||
![Deleting folders in Linux][6]
|
||||
|
||||
> 💡 交互式删除模式在使用 rm 命令的递归选项删除目录时更有帮助:
|
||||
> 💡 交互式删除模式在使用 `rm` 命令的递归选项删除目录时更有帮助:
|
||||
|
||||
```
|
||||
rm-ri dir_name
|
||||
@@ -189,7 +192,7 @@ rm-ri dir_name
|
||||
└── file
|
||||
```
|
||||
|
||||
基本上,你在当前目录 (practice_delete) 中创建一个名为 file 的文件和三个目录 dir1、dir2 和 dir3。然后在 dir1 中创建文件 file1、file2 和 file3。
|
||||
基本上,你在当前目录(`practice_delete`)中创建一个名为 `file` 的文件和三个目录 `dir1`、`dir2` 和 `dir3`。然后在 `dir1` 中创建文件 `file1`、`file2` 和 `file3`。
|
||||
|
||||
现在执行以下操作:
|
||||
|
||||
@@ -198,8 +201,6 @@ rm-ri dir_name
|
||||
- 删除 dir1 的所有内容,但不删除目录本身。
|
||||
- 列出 `dir` 的内容。
|
||||
|
||||
我鼓励你在 [It's FOSS 社区论坛][7]中讨论练习题。
|
||||
|
||||
一切进展顺利。你已经学习了一些基本知识,例如切换目录、检查目录内容、创建和删除文件和目录。在下一章中,你将学习如何在终端中复制文件和文件夹。敬请关注!
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -209,16 +210,18 @@ via: https://itsfoss.com/delete-files-folders-linux/
|
||||
作者:[Abhishek Prakash][a]
|
||||
选题:[lkxed][b]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://itsfoss.com/author/abhishek/
|
||||
[b]: https://github.com/lkxed/
|
||||
[1]: https://itsfoss.com/create-files/
|
||||
[1]: https://linux.cn/article-15643-1.html
|
||||
[1a]: https://linux.cn/article-15595-1.html
|
||||
[2]: https://itsfoss.com/content/images/2023/03/delete-files-linux-terminal.png
|
||||
[3]: https://itsfoss.com/content/images/2023/03/remove-multiple-files-linux-terminal.png
|
||||
[4]: https://itsfoss.com/content/images/2023/03/file-delete-example.svg
|
||||
[5]: https://itsfoss.com/content/images/2023/03/interactive-delete-example.svg
|
||||
[6]: https://itsfoss.com/content/images/2023/03/folder-delete-example.svg
|
||||
[7]: https://itsfoss.community/?ref=itsfoss.com
|
||||
[0]: https://img.linux.net.cn/data/attachment/album/202305/13/140619izbrl7owh9dr55lb.jpg
|
||||
@@ -1,204 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (How I build and expand application development and testing)
|
||||
[#]: via: (https://opensource.com/article/21/2/build-expand-software)
|
||||
[#]: author: (Alex Bunardzic https://opensource.com/users/alex-bunardzic)
|
||||
|
||||
How I build and expand application development and testing
|
||||
======
|
||||
Start development simply, by writing and testing your code with One
|
||||
element and then expand it out to Many.
|
||||
![Security monster][1]
|
||||
|
||||
In my [previous article][2], I explained why tackling coding problems all at once, as if they were hordes of zombies, is a mistake. I also explained the first **ZOMBIES** principle, **Zero**. In this article, I'll demonstrate the next two principles: **One** and **Many**.
|
||||
|
||||
**ZOMBIES** is an acronym that stands for:
|
||||
|
||||
**Z** – Zero
|
||||
**O** – One
|
||||
**M** – Many (or more complex)
|
||||
**B** – Boundary behaviors
|
||||
**I** – Interface definition
|
||||
**E** – Exercise exceptional behavior
|
||||
**S** – Simple scenarios, simple solutions
|
||||
|
||||
In the previous article, you implemented Zero, which provides the simplest possible path through your code. There is absolutely no conditional processing logic anywhere to be found. Now it's time for you to move into **O**ne.
|
||||
|
||||
Unlike with **Z**ero, which basically means nothing is added, or we have an empty case, nothing to take care of, **O**ne means we have a single case to take care of. That single case could be one item in the collection, or one visitor, or one event that demands special treatment.
|
||||
|
||||
With **M**any, we are now dealing with potentially more complicated cases. Two or more items in the collection, two or more events that demand special treatment, and so on.
|
||||
|
||||
### One in action
|
||||
|
||||
Build on the code from the previous article by adding something to your virtual shopping basket. First, write a fake test:
|
||||
|
||||
|
||||
```
|
||||
[Fact]
|
||||
public void Add1ItemBasketHas1Item() {
|
||||
var expectedNoOfItems = 1;
|
||||
var actualNoOfItems = 0;
|
||||
Assert.Equal(expectedNoOfItems, actualNoOfItems);
|
||||
}
|
||||
```
|
||||
|
||||
As expected, this test fails because you hard-coded an incorrect value:
|
||||
|
||||
|
||||
```
|
||||
Starting test execution, please wait...
|
||||
|
||||
A total of 1 test files matched the specified pattern.
|
||||
[xUnit.net 00:00:00.57] tests.UnitTest1.NewlyCreatedBasketHas0Items [FAIL]
|
||||
X tests.UnitTest1.NewlyCreatedBasketHas0Items [4ms]
|
||||
Error Message:
|
||||
Assert.Equal() Failure
|
||||
Expected: 0
|
||||
Actual: 1
|
||||
[...]
|
||||
```
|
||||
|
||||
Now is the time to think about how to stop faking it. You already created an implementation of a shopping basket (an `ArrayList` to hold items). But how do you implement an _item_?
|
||||
|
||||
Simplicity should always be your guiding principle, and not knowing much about the actual item, you could fake it a little by implementing it as another collection. What could that collection contain? Well, because you're mostly interested in calculating basket totals, the item collection should, at minimum, contain a price (in any currency, but for simplicity, use dollars).
|
||||
|
||||
A simple collection can hold an ID on an item (a pointer to the item, which may be kept elsewhere on the system) and the associated price of an item.
|
||||
|
||||
A good data structure that can easily capture this is a key/value structure. In C#, the first thing that comes to mind is `Hashtable`.
|
||||
|
||||
In the app code, add a new capability to the `IShoppingAPI` interface:
|
||||
|
||||
|
||||
```
|
||||
`int AddItem(Hashtable item);`
|
||||
```
|
||||
|
||||
This new capability accepts one item (an instance of a `Hashtable`) and returns the number of items found in the shopping basket.
|
||||
|
||||
In your tests, replace the hard-coded value with a call to the interface:
|
||||
|
||||
|
||||
```
|
||||
[Fact]
|
||||
public void Add1ItemBasketHas1Item() {
|
||||
var expectedNoOfItems = 1;
|
||||
Hashtable item = [new][3] Hashtable();
|
||||
var actualNoOfItems = shoppingAPI.AddItem(item);
|
||||
Assert.Equal(expectedNoOfItems, actualNoOfItems);
|
||||
}
|
||||
```
|
||||
|
||||
This code instantiates `Hashtable` and names it `item`, then invokes `AddItem(item)` on the shopping interface, which returns the actual number of items in the basket.
|
||||
|
||||
To implement it, turn to the `ShoppingAPI` class:
|
||||
|
||||
|
||||
```
|
||||
public int AddItem(Hashtable item) {
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
You are faking it again just to see the results of your tests (which are the first customers of your code). Should the test fail (as expected), replace the hard-coded values with actual code:
|
||||
|
||||
|
||||
```
|
||||
public int AddItem(Hashtable item) {
|
||||
basket.Add(item);
|
||||
return basket.Count;
|
||||
}
|
||||
```
|
||||
|
||||
In the working code, add an item to the basket, and then return the count of the items in the basket:
|
||||
|
||||
|
||||
```
|
||||
Test Run Successful.
|
||||
Total tests: 2
|
||||
Passed: 2
|
||||
Total time: 1.0633 Seconds
|
||||
```
|
||||
|
||||
So now you have two tests passing and have pretty much covered **Z** and **O**, the first two parts of **ZOMBIES**.
|
||||
|
||||
### A moment of reflection
|
||||
|
||||
If you look back at what you've done so far, you will notice that by focusing your attention on dealing with the simplest possible **Z**ero and **O**ne scenarios, you have managed to create an interface as well as define some processing logic boundaries! Isn't that awesome? You now have the most important abstractions partially implemented, and you know how to process cases where nothing is added and when one thing is added. And because you are building an e-commerce API, you certainly do not foresee placing any other boundaries that would limit your customers when shopping. Your virtual shopping basket is, for all intents and purposes, limitless.
|
||||
|
||||
Another important (although not necessarily immediately obvious) aspect of the stepwise refinement that **ZOMBIES** offers is a reluctance to leap head-first into the brambles of implementation. You may have noticed how sheepish this is about implementing anything. For starters, it's better to fake the implementation by hard-coding the values. Only after you see that the interface interacts with your test in a sensible way are you willing to roll up your sleeves and harden the implementation code.
|
||||
|
||||
But even then, you should always prefer simple, straightforward constructs. And strive to avoid conditional logic as much as you can.
|
||||
|
||||
### Many in action
|
||||
|
||||
Expand your application by defining your expectations when a customer adds two items to the basket. The first test is a fake. It expects 2, but force it to fail by hard-coding 0 items:
|
||||
|
||||
|
||||
```
|
||||
[Fact]
|
||||
public void Add2ItemsBasketHas2Items() {
|
||||
var expectedNoOfItems = 2;
|
||||
var actualNoOfItems = 0;
|
||||
Assert.Equal(expectedNoOfItems, actualNoOfItems);
|
||||
}
|
||||
```
|
||||
|
||||
When you run the test, two of them pass successfuy (the previous two, the **Z** and **O** tests), but as expected, the hard-coded test fails:
|
||||
|
||||
|
||||
```
|
||||
A total of 1 test files matched the specified pattern.
|
||||
[xUnit.net 00:00:00.57] tests.UnitTest1.Add2ItemsBasketHas2Items [FAIL]
|
||||
X tests.UnitTest1.Add2ItemsBasketHas2Items [2ms]
|
||||
Error Message:
|
||||
Assert.Equal() Failure
|
||||
Expected: 2
|
||||
Actual: 0
|
||||
|
||||
Test Run Failed.
|
||||
Tatal tests: 3
|
||||
Passed: 2
|
||||
Failed: 1
|
||||
```
|
||||
|
||||
Replace the hard-coded values with the call to the app code:
|
||||
|
||||
|
||||
```
|
||||
[Fact]
|
||||
public void Add2ItemsBasketHas2Items() {
|
||||
var expectedNoOfItems = 2;
|
||||
Hashtable item = [new][3] Hashtable();
|
||||
shoppingAPI.AddItem(item);
|
||||
var actualNoOfItems = shoppingAPI.AddItem(item);
|
||||
Assert.Equal(expectedNoOfItems, actualNoOfItems);
|
||||
}
|
||||
```
|
||||
|
||||
In the test, you add two items (actually, you're adding the same item twice) and then compare the expected number of items to the number of items from the `shoppingAPI` instance after adding the item the second time.
|
||||
|
||||
All tests now pass!
|
||||
|
||||
### Stay tuned
|
||||
|
||||
You have now completed the first pass of the **ZOM** part of the equation. You did a pass on **Z**ero, on **O**ne, and on **M**any. In the next article, I'll take a look at **B** and **I**. Stay vigilant!
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/2/build-expand-software
|
||||
|
||||
作者:[Alex Bunardzic][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/alex-bunardzic
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/security_password_chaos_engineer_monster.png?itok=J31aRccu (Security monster)
|
||||
[2]: https://opensource.com/article/21/1/zombies-zero
|
||||
[3]: http://www.google.com/search?q=new+msdn.microsoft.com
|
||||
@@ -0,0 +1,206 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (toknow-gh)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (How I build and expand application development and testing)
|
||||
[#]: via: (https://opensource.com/article/21/2/build-expand-software)
|
||||
[#]: author: (Alex Bunardzic https://opensource.com/users/alex-bunardzic)
|
||||
|
||||
软件开发和测试中的构建与拓展
|
||||
======
|
||||
在开发初期只对单个元素进行编码和测试,
|
||||
之后再拓展到多个元素上。
|
||||
![Security monster][1]
|
||||
|
||||
在 [上一篇文章][2] 中我已经解释了为什么把所有编程问题当作一群丧尸一次性处理是错误的。我也解释了 ZOMBIES 方法中的第一条:最简场景。本文中我将进一步介绍接下来的两条:单元素场景和多元素场景。
|
||||
|
||||
ZOMBIES 表示以下首字母缩写:
|
||||
|
||||
**Z** – 最简场景(Zero)
|
||||
**O** – 单元素场景(One)
|
||||
**M** – 多元素场景(Many or more complex)
|
||||
**B** – 边界行为(Boundary behaviors)
|
||||
**I** – 接口定义(Interface definition)
|
||||
**E** – 处理特殊行为(Exercise exceptional behavior)
|
||||
**S** – 简单场景用简单的解决方案(Simple scenarios, simple solutions)
|
||||
|
||||
在上一篇文章中,通过应用了最简场景,你在代码里构建了一条最简可行通路。这个代码里没有任何业务处理逻辑。现在是时候向系统中添加一个元素了。
|
||||
|
||||
最简场景表示系统中什么也没有,这是一个空的用例,我们什么也不用关心。单元素场景代表我们有一个元素需要关心考虑。这个单一元素可能是集合中的一个元素、一个访问着或者一个需要处理的事件。
|
||||
|
||||
对于多元素场景,我们需要处理更复杂的情况,比如两个或更多的集合元素或事件。
|
||||
|
||||
### 单元素场景
|
||||
|
||||
在上一篇文章的代码基础上,向虚拟购物框里添加一些商品。首先,写一个伪测试:
|
||||
|
||||
|
||||
```
|
||||
[Fact]
|
||||
public void Add1ItemBasketHas1Item() {
|
||||
var expectedNoOfItems = 1;
|
||||
var actualNoOfItems = 0;
|
||||
Assert.Equal(expectedNoOfItems, actualNoOfItems);
|
||||
}
|
||||
```
|
||||
|
||||
不出所料,这个测试失败了,因为硬编码了一个错误的值:
|
||||
|
||||
|
||||
```
|
||||
Starting test execution, please wait...
|
||||
|
||||
A total of 1 test files matched the specified pattern.
|
||||
[xUnit.net 00:00:00.57] tests.UnitTest1.NewlyCreatedBasketHas0Items [FAIL]
|
||||
X tests.UnitTest1.NewlyCreatedBasketHas0Items [4ms]
|
||||
Error Message:
|
||||
Assert.Equal() Failure
|
||||
Expected: 0
|
||||
Actual: 1
|
||||
[...]
|
||||
```
|
||||
|
||||
现在是时候停止伪造了。现在你已经用 `ArrayList` 实现了购物框。那么应该怎么实现商品呢?
|
||||
|
||||
简洁性应该一直是你的指导原则。在不了解商品的太多信息的情况下,你可以先用另一个集合来实现它。这个表示商品的集合应该包含些什么呢?由于你多半会关心计算购物框中的商品总价,所以对商品的表示至少需要包含价格(可以是任意货币,为简单起见,不妨假设是人民币)。
|
||||
|
||||
(我们需要)一个简单的集合类型,它包含一个商品 ID(可以在系统中的其它地方使用 ID 来指向该商品)和这个商品的价格。
|
||||
|
||||
键值对类型的数据结构可以很容易满足这个需求。在 C# 中最先被想到的数据结构就是 `Hashtable`。
|
||||
|
||||
在购物应用的代码中给 `IShoppingAPI` 增加一个新功能:
|
||||
|
||||
|
||||
```
|
||||
`int AddItem(Hashtable item);`
|
||||
```
|
||||
这个新功能以一个用 `Hashtable` 表示的商品为输入,返回购物框中的商品数量。
|
||||
|
||||
将测试代码中硬编码的值提替换为对接口的调用:
|
||||
|
||||
|
||||
```
|
||||
[Fact]
|
||||
public void Add1ItemBasketHas1Item() {
|
||||
var expectedNoOfItems = 1;
|
||||
Hashtable item = [new][3] Hashtable();
|
||||
var actualNoOfItems = shoppingAPI.AddItem(item);
|
||||
Assert.Equal(expectedNoOfItems, actualNoOfItems);
|
||||
}
|
||||
```
|
||||
|
||||
在上面的代码中实例化了一个 `Hashtable` 并命名为 `item`,然后调用购物接口中的 `AddItem(item)`方法,该方法会返回购物框中实际的商品数量。
|
||||
|
||||
转到 `ShoppingAPI` 类中,实现这个方法:
|
||||
|
||||
|
||||
```
|
||||
public int AddItem(Hashtable item) {
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
这里再次通过写假代码来检验测试的效果(测试是业务代码的第一个调用者)。如果测试失败,将硬编码值换成实际的代码:
|
||||
|
||||
|
||||
```
|
||||
public int AddItem(Hashtable item) {
|
||||
basket.Add(item);
|
||||
return basket.Count;
|
||||
}
|
||||
```
|
||||
|
||||
在上面的代码中,向购物框里添加了一件商品,然后返回购物框中的商品数量:
|
||||
|
||||
|
||||
```
|
||||
Test Run Successful.
|
||||
Total tests: 2
|
||||
Passed: 2
|
||||
Total time: 1.0633 Seconds
|
||||
```
|
||||
|
||||
|
||||
到目前为止,你通过了两个测试,同时也基本里解了 ZOMBIES 方法中的最简场景和单元素场景两部分。
|
||||
|
||||
### 反思总结
|
||||
|
||||
回顾前面所做的工作,你会发现通过将注意力集中到处理最简场景和单元素场景上,你在构建接口的同时也定义了一些业务逻辑边界!这不是很棒吗?现在你已经部分地实现了最关键的抽象逻辑,并且能够处理什么也没有和只有一个元素的的情况。因为你正在构建的是一个电子交易 API,所以你不能对顾客的购物行为预设其它限制。总而言之,虚拟购物框应该是无限大的。
|
||||
|
||||
ZOMBIES 提供的逐步优化思路的另一个重要方面(虽然不是很明显)是从大概思路到具体实现的阻力。你也许已经注意到了,要具体实现某个东西总是苦难重重。倒不如先用硬编码值来构造一个伪实现。只有看到接口与测试之间以一种合理的方式交互之后,你才会愿意开始完善实现代码。
|
||||
|
||||
即便如此,你也应该采用简单直接的代码结构,尽可能避免条件逻辑分支。
|
||||
|
||||
### 多元素场景
|
||||
|
||||
通过定义顾客向购物框里添加两件商品时的期望来拓展应用程序。首先构造一个伪测试。它的期望值为 2,但是现在将实际值硬编码为 0,强制让测试失败:
|
||||
|
||||
|
||||
```
|
||||
[Fact]
|
||||
public void Add2ItemsBasketHas2Items() {
|
||||
var expectedNoOfItems = 2;
|
||||
var actualNoOfItems = 0;
|
||||
Assert.Equal(expectedNoOfItems, actualNoOfItems);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
执行测试,前两个测试用例通过了(针对最简场景和单元素场景的测试),而硬编码的测试不出所料地失败了:
|
||||
|
||||
|
||||
```
|
||||
A total of 1 test files matched the specified pattern.
|
||||
[xUnit.net 00:00:00.57] tests.UnitTest1.Add2ItemsBasketHas2Items [FAIL]
|
||||
X tests.UnitTest1.Add2ItemsBasketHas2Items [2ms]
|
||||
Error Message:
|
||||
Assert.Equal() Failure
|
||||
Expected: 2
|
||||
Actual: 0
|
||||
|
||||
Test Run Failed.
|
||||
Tatal tests: 3
|
||||
Passed: 2
|
||||
Failed: 1
|
||||
```
|
||||
|
||||
将硬编码值替换为实际的代码调用:
|
||||
|
||||
|
||||
```
|
||||
[Fact]
|
||||
public void Add2ItemsBasketHas2Items() {
|
||||
var expectedNoOfItems = 2;
|
||||
Hashtable item = [new][3] Hashtable();
|
||||
shoppingAPI.AddItem(item);
|
||||
var actualNoOfItems = shoppingAPI.AddItem(item);
|
||||
Assert.Equal(expectedNoOfItems, actualNoOfItems);
|
||||
}
|
||||
```
|
||||
|
||||
在这个测试中,你向购物框中添加了两件商品(实际上是将同一件商品添加了两次),然后比较期望的商品数量和第二次添加商品后调用 `shoppingAPI` 返回的商品数量是否相等。
|
||||
|
||||
现在所有测试都能够通过!
|
||||
|
||||
### 敬请期待
|
||||
|
||||
现在你已经了解了最简场景、单元素场景和多元素场景。我将下一篇文章中介绍边界行为和接口定义。敬请期待!
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/2/build-expand-software
|
||||
|
||||
作者:[Alex Bunardzic][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[toknow-gh](https://github.com/toknow-gh)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/alex-bunardzic
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/security_password_chaos_engineer_monster.png?itok=J31aRccu (Security monster)
|
||||
[2]: https://opensource.com/article/21/1/zombies-zero
|
||||
[3]: http://www.google.com/search?q=new+msdn.microsoft.com
|
||||
Reference in New Issue
Block a user