很久以前,当我还是一个刚入门的计算机程序员时,我们过去常常大批量工作。我们每个人都被分配了一个编程任务,然后我们会离开并躲在我们的隔间里敲击键盘。我记得我的团队成员花费数小时孤立地工作,我们每个人都在自己的隔间里,与挑战作斗争,以创建无缺陷的应用程序。当时的理论是,批量越大,就越能证明我们是出色的问题解决者。
对我来说,看看我能写多久的新代码或修改现有代码,然后才停下来检查我所做的事情是否有效,是一种荣誉的象征。那时,我们很多人认为停下来验证我们的代码是否有效是软弱的表现,是菜鸟程序员的标志。“真正的开发人员”应该能够一口气完成整个应用程序,而无需停下来检查任何东西!
然而,当我停下来测试我的代码时,无论多么不情愿,我通常都会受到现实的检验。要么我的代码无法编译,要么无法构建,要么无法运行,要么只是无法按照我的意图处理数据。不可避免地,我会拼命地解决我发现的所有讨厌的问题。
避免僵尸群
如果旧的工作方式听起来很混乱,那是因为它确实如此。我们一次性处理所有任务,砍杀问题,结果却被更多问题淹没。这就像与一大群僵尸战斗。
今天,我们已经学会了避免大批量。听到一些专家赞扬避免大批量的优点,起初听起来完全违反直觉,但我从过去的错误中吸取了很多教训。恰如其分地,我正在使用 James Grenning(https://www.agilealliance.org/resources/speakers/james-grenning/)称之为 ZOMBIES 的系统来指导我的软件开发工作。
ZOMBIES 来救援!
ZOMBIES 没什么神秘之处。它是一个首字母缩略词,代表
Z – 零 (Zero)
O – 一 (One)
M – 多 (Many)(或更复杂)
B – 边界行为 (Boundary behaviors)
I – 接口定义 (Interface definition)
E – 执行异常行为 (Exercise exceptional behavior)
S – 简单场景,简单解决方案 (Simple scenarios, simple solutions)
我将在本系列文章中为你详细解释。
零的实践!
Zero 代表最简单的情况。
解决方案之所以最简单,是因为每个人最初都倾向于使用硬编码值。通过使用硬编码值开始编码会话,你可以快速创建一个为你提供即时反馈的情况。无需等待几分钟甚至几小时,硬编码值即可提供关于你是否喜欢与你正在构建的内容交互的即时反馈。如果你发现你喜欢与之交互,那就太好了!朝着那个方向继续前进。如果你发现,由于某种原因,你不喜欢与之交互,那么就没有任何重大损失。你可以轻松地放弃它;你甚至没有任何损失需要削减。
例如,构建一个简单的后端购物 API。此服务允许用户获取购物车、向购物车添加商品、从购物车中移除商品以及从 API 获取订单总额。
创建必要的基础设施(将 shipping 应用程序隔离到 app
文件夹中,并将测试隔离到 tests
文件夹中)。此示例使用开源 xUnit 测试框架。
卷起袖子,看看零原则的实践!
[Fact]
public void NewlyCreatedBasketHas0Items() {
var expectedNoOfItems = 0;
var actualNoOfItems = 1;
Assert.Equal(expectedNoOfItems, actualNoOfItems);
}
此测试是伪造的,因为它正在测试硬编码值。当新创建购物车时,它不包含任何商品;因此,购物车中商品的预期数量为 0。通过比较预期值和实际值的相等性,将此期望付诸测试(或断言)。
当测试运行时,它会产生以下结果
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
[...]
测试失败的原因很明显:你期望商品数量为 0,但实际商品数量硬编码为 1。
当然,你可以通过将分配给实际变量的硬编码值从 1 修改为 0 来快速纠正该错误
[Fact]
public void NewlyCreatedBasketHas0Items() {
var expectedNoOfItems = 0;
var actualNoOfItems = 0;
Assert.Equal(expectedNoOfItems, actualNoOfItems);
}
正如预期的那样,当此测试运行时,它会成功通过
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
Test Run Successful.
Total tests: 1
Passed: 1
Total time: 1.0950 Seconds
你可能不认为测试你正在强制使其失败的代码是值得的,但无论测试多么简单,至少看到它失败一次是绝对必要的。这样,你可以放心,如果某些无意的更改破坏了你的处理逻辑,测试稍后会提醒你。
现在是停止伪造零情况并将该硬编码值替换为将由正在运行的 API 提供的值的时候了。既然你知道你有一个可靠的失败测试,它期望空购物车有 0 件商品,那么现在是时候编写一些应用程序代码了。
与软件中的任何其他建模练习一样,首先要设计一个简单的接口。在解决方案的 app
文件夹中创建一个新文件,并将其命名为 IShoppingAPI.cs
(按照惯例,在每个接口名称前加上大写 I)。在接口中,声明方法 NoOfItems()
以返回商品数量作为 int
。以下是接口的列表
using System;
namespace app {
public interface IShoppingAPI {
int NoOfItems();
}
}
当然,在实现此接口之前,此接口无法执行任何工作。在 app
文件夹中创建另一个文件,并将其命名为 ShoppingAPI
。将 ShoppingAPI
声明为实现 IShoppingAPI
的公共类。在类的主体中,将 NoOfItems
定义为返回整数 1
using System;
namespace app {
public class ShoppingAPI : IShoppingAPI {
public int NoOfItems() {
return 1;
}
}
}
你在上面可以看到,你再次通过将返回值硬编码为 1 来伪造处理逻辑。现在这样做很好,因为你想让一切都超级简单。现在还不是(至少还不是)开始思考你将如何实现此购物车的时候。将该问题留到以后!现在,你正在使用零情况,这意味着你想看看你是否喜欢当前的安排。
为了确定这一点,请将硬编码的预期值替换为当你的购物 API 运行时并收到请求时将传递的值。你需要通过声明你正在使用 app
文件夹来让测试知道 shipping 代码的位置。
接下来,你需要实例化 IShoppingAPI
接口
IShoppingAPI shoppingAPI = new ShoppingAPI();
此实例用于在代码运行后发送请求并接收实际值。
现在列表看起来像
using System;
using Xunit;
using app;
namespace tests {
public class ShoppingAPITests {
IShoppingAPI shoppingAPI = new ShoppingAPI();
[Fact]
public void NewlyCreatedBasketHas0Items() {
var expectedNoOfItems = 0;
var actualNoOfItems = shoppingAPI.NoOfItems();
Assert.Equal(expectedNoOfItems, actualNoOfItems);
}
}
}
当然,当此测试运行时,它会失败,因为你硬编码了不正确的返回值(测试期望 0,但应用程序返回 1)。
同样,你可以通过将硬编码值从 1 修改为 0 来轻松地使测试通过,但此时这将是浪费时间。既然你已经将适当的接口连接到你的测试,那么编写导致预期代码行为的编程逻辑是你的责任。
对于应用程序代码,你需要决定使用哪种数据结构来表示购物车。为了保持最基本的功能,请努力识别 C# 中集合的最简单表示形式。立即想到的就是 ArrayList
。此集合非常适合这些目的——它可以接受无限数量的商品,并且易于简单地遍历。
在你的应用程序代码中,声明你正在使用 System.Collections
,因为 ArrayList
是该包的一部分
using System.Collections;
然后声明你的 basket
ArrayList basket = new ArrayList();
最后,将 NoOfItems()
中的硬编码值替换为实际运行代码
public int NoOfItems() {
return basket.Count;
}
这次,测试通过了,因为你实例化的购物车是空的,所以 basket.Count
返回 0 件商品。
这正是你的第一个零测试所期望的。
更多示例
你的作业是暂时只处理一个僵尸,那就是第零个僵尸。在下一篇文章中,我将研究 One 和 Many。保持坚强!
评论已关闭。