Merge pull request #29370 from toknow-gh/tr0513

Translated
This commit is contained in:
Xingyu.Wang 2023-05-13 13:22:31 +08:00 committed by GitHub
commit 6073cf4b9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 206 additions and 204 deletions

View File

@ -1,204 +0,0 @@
[#]: 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)
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

View File

@ -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