Redian新闻
>
ZOMBIES:在软件开发中定义边界和接口(三) | Linux 中国

ZOMBIES:在软件开发中定义边界和接口(三) | Linux 中国

科技
 
导读:丧尸是没有边界感的,需要为你的软件设定限制和期望。                                 
本文字数:8328,阅读时长大约:10分钟

丧尸是没有边界感的,需要为你的软件设定限制和期望。

丧尸没有边界感。它们踩倒栅栏,推倒围墙,进入不属于它们的地盘。在前面的文章中,我已经解释了为什么把所有编程问题当作一群丧尸一次性处理是错误的。

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)

在本系列的前面两篇文章中,我演示了 ZOMBIES 方法的前三部分:最简场景、单元素场景和多元素场景。第一篇文章 实现了最简场景🔗 linux.cn,它提供了代码中的最简可行路径。第二篇文章中针对单元素场景和多元素场景 运行测试🔗 linux.cn。在这篇文章中,我将带你了解边界和接口。

回到单元素场景

要想处理边界,你需要绕回来(迭代)。

首先思考下面的问题:电子商务的边界是什么?我需要限制购物框的大小吗?(事实上,我不认为这有任何意义。)

目前唯一合理的边界条件是确保购物框里的商品数量不能为负数。将这个限制表示成可运行的期望:

  1. [Fact]
  2. public void Add1ItemRemoveItemRemoveAgainHas0Items() {
  3.         var expectedNoOfItems = 0;
  4.         var actualNoOfItems = -1;
  5.         Assert.Equal(expectedNoOfItems, actualNoOfItems);
  6. }

这就是说,如果你向购物框里添加一件商品,然后将这个商品移除两次,shoppingAPI 的实例应该告诉你购物框里有零个商品。

当然这个可运行期望(微测试)不出意料地会失败。想要这个微测试能够通过,最小改动是什么呢?

  1. [Fact]
  2. public void Add1ItemRemoveItemRemoveAgainHas0Items() {
  3.         var expectedNoOfItems = 0;
  4.         Hashtable item = new Hashtable();
  5.         shoppingAPI.AddItem(item);
  6.         shoppingAPI.RemoveItem(item);
  7.         var actualNoOfItems = shoppingAPI.RemoveItem(item);
  8.         Assert.Equal(expectedNoOfItems, actualNoOfItems);
  9. }

这个期望测试依赖于 RemoveItem(item) 功能。目前的 shippingAPI 还不具备该功能,你需要增加该功能。

回到 app 文件夹,打开 IShippingAPI.cs 文件,新增以下声明:

  1. int RemoveItem(Hashtable item);

到 ShippingAPI.cs 中实现该功能:

  1. public int RemoveItem(Hashtable item) {
  2.         basket.RemoveAt(basket.IndexOf(item));
  3.         return basket.Count;
  4. }

运行,然后你会得到如下错误:

Error

系统在移除一个不在购物框的商品,这导致了系统崩溃。加一点点 防御式编程defensive programming

  1. public int RemoveItem(Hashtable item) {
  2.         if(basket.IndexOf(item) >= 0) {
  3.                 basket.RemoveAt(basket.IndexOf(item));
  4.         }
  5.         return basket.Count;
  6. }

在移除商品之前先检查它是否在购物框中。(你可能试过用捕获异常的方式来处理,但是我认为上面的处理方式更具可读性。)

更多具体的期望

在讲更多具体的期望之前,让我们先探讨一下什么是接口。在软件工程中,接口表示一种规范,或者对能力的描述。从某种程度上来说,接口类似于菜谱。它罗列出了制作蛋糕的原材料,但它本身并不能吃。我们只是按照菜谱上的说明来烤蛋糕。

与此类似,我们首先通过说明这个服务能做什么的方式来定义我们的服务。这个描述说明就是所谓的接口。但是接口本身并不能向我们提供任何功能。它只是指导我们实现指定功能的蓝图而已。

到目前为止,我们已经实现了接口(只是某部分实现了,稍后还会增加新功能)和业务处理边界(也就是购物框里的商品不能是负数)。你指导了 shoppingAPI 怎么向购物框添加商品,并通过 Add2ItemsBasketHas2Items 测试验证了该功能的有效性。

然而仅仅具备向购物框添加商品的功能还不足以使其成为一个网购应用程序。它还需要能够计算购物框里的商品的总价。现在需要增加另一个期望。

按照惯例,从最直接明了的期望开始。当你向购物框里加入一件价值 ¥10 的商品时,你希望这个购物 API 能正确地计算出总价为 ¥10。

第五个测试(伪造版)如下:

  1. [Fact]
  2. public void Add1ItemPrice10GrandTotal10() {
  3.         var expectedTotal = 10.00;
  4.         var actualTotal = 0.00;
  5.         Assert.Equal(expectedTotal, actualTotal);
  6. }

还是一样的老把戏,通过硬编码一个错误的值让 Add1ItemPrice10GrandTotal10 测试失败。当然前三个测试成功通过,但第四个新增的测试失败了:

  1. A total of 1 test files matched the specified pattern.
  2. [xUnit.net 00:00:00.57] tests.UnitTest1.Add1ItemPrice10GrandTotal10 [FAIL]
  3.   X tests.UnitTest1.Add1ItemPrice10GrandTotal10 [4ms]
  4.   Error Message:
  5.    Assert.Equal() Failure
  6. Expected: 10
  7. Actual: 0
  8. Test Run Failed.
  9. Total tests: 4
  10.      Passed: 3
  11.          Failed: 1
  12.  Total time: 1.0320 Seconds

将硬编码值换成实际的处理代码。首先,检查接口是否具备计算订单总价的功能。根本没有这种东西。目前为止接口中只声明了三个功能:

1. int NoOfItems();
2. int AddItem(Hashtable item);
3. int RemoveItem(Hashtable item);

它们都不具备计算总价的能力。所以需要声明一个新功能:

  1. double CalculateGrandTotal();

这个新功能应该让 shoppingAPI 具备计算总价的能力。这是通过遍历购物框中的商品并把它们的价格累加起来实现的。

修改第五个测试:

  1. [Fact]
  2. public void Add1ItemPrice10GrandTotal10() {
  3.         var expectedGrandTotal = 10.00;
  4.         Hashtable item = new Hashtable();
  5.         item.Add("00000001", 10.00);
  6.         shoppingAPI.AddItem(item);
  7.         var actualGrandTotal = shoppingAPI.CalculateGrandTotal();
  8.         Assert.Equal(expectedGrandTotal, actualGrandTotal);
  9. }

这个测试表明了这样的期望:如果向购物框里加入一件价格 ¥10 的商品,然后调用 CalculateGrandTotal() 方法,它会返回商品总价 ¥10。这是一个完全合理的期望,它完全符合商品总价计算的逻辑。

那么怎么实现这个功能呢?就像以前一样,先写一个假的实现。回到 ShippingAPI 类中,实现在接口中声明的 CalculateGrandTotal() 方法:

  1. public double CalculateGrandTotal() {
  2.                 return 0.00;
  3. }

现在先将返回值硬编码为 0.00,只是为了检验这个测试能否正常运行,并确认它是能够失败的。事实上,它能够运行,并且如预期一样失败。接下来的工作就是正确实现计算商品总价的处理逻辑:

  1. public double CalculateGrandTotal() {
  2.         double grandTotal = 0.00;
  3.         foreach(var product in basket) {
  4.                 Hashtable item = product as Hashtable;
  5.                 foreach(var value in item.Values) {
  6.                         grandTotal += Double.Parse(value.ToString());
  7.                 }
  8.         }
  9.         return grandTotal;
  10. }

运行,五个测试全部通过!

从单元素场景到多元素场景

现在是时候进入下一轮迭代了。你已经通过处理最简场景、单元素场景和边界场景迭代地构建了系统,现在需要处理稍复杂的多元素场景了。

快捷提示:由于我们一直在针对单个元素场景、多元素场景和边界行为这三点上对软件进行迭代改进,一些读者可能会认为我们同样应该对接口进行改进。我们稍后就会发现,接口已经完全满足需要了,目前没有新增功能的必要。请记住,应该保持接口的简洁。(盲目地)扩增接口不会带来任何好处,只会引入噪音。我们要遵循 奥卡姆剃刀(Occam's Razor) 原则:如无必要,勿增实体。 现在我们已经基本完成了接口功能描述的工作,是时候改进实现了。

通过上一轮的迭代,系统已经能够处理购物框里有超过一件商品的情况了。现在我么来让系统具备购物框里有超过一件商品时计算总价的能力。首先写可执行期望:

  1. [Fact]
  2. public void Add2ItemsGrandTotal30() {
  3.         var expectedGrandTotal = 30.00;
  4.         var actualGrandTotal = 0.00;
  5.         Assert.Equal(expectedGrandTotal, actualGrandTotal);
  6. }

硬编码所有值,尽量让期望测试失败。

测试确实失败了,现在得想办法让它通过。向购物框添加两件商品,然后调用 CalculateGrandTotal() 方法:

  1. [Fact]
  2. public void Add2ItemsGrandTotal30() {
  3. var expectedGrandTotal = 30.00;
  4. Hashtable item = new Hashtable();
  5. item.Add("00000001", 10.00);
  6. shoppingAPI.AddItem(item);
  7. Hashtable item2 = new Hashtable();
  8. item2.Add("00000002", 20.00);
  9. shoppingAPI.AddItem(item2);
  10. var actualGrandTotal = shoppingAPI.CalculateGrandTotal();
  11. Assert.Equal(expectedGrandTotal, actualGrandTotal);
  12. }

测试通过。现在共有六个可以通过的微测试,系统回到了稳态。

设定期望

作为一个认真负责的工程师,你希望确保当用户向购物框添加一些商品然后又移除一些商品后系统仍然能够计算出正确出总价。下面是这个新的期望:

  1. [Fact]
  2. public void Add2ItemsRemoveFirstItemGrandTotal200() {
  3.         var expectedGrandTotal = 200.00;
  4.         var actualGrandTotal = 0.00;
  5.         Assert.Equal(expectedGrandTotal, actualGrandTotal);
  6. }

这个期望表示将两件商品加入到购物框,然后移除第一件后期望的总价是 ¥200。硬编码行为失败了。现在设计更具体的正面测试样例,然后运行代码:

  1. [Fact]
  2. public void Add2ItemsRemoveFirstItemGrandTotal200() {
  3.         var expectedGrandTotal = 200.00;
  4.         Hashtable item = new Hashtable();
  5.         item.Add("00000001", 100.00);
  6.         shoppingAPI.AddItem(item);
  7.         Hashtable item2 = new Hashtable();
  8.         item2.Add("00000002", 200.00);
  9.         shoppingAPI.AddItem(item2);
  10.         shoppingAPI.RemoveItem(item);
  11.         var actualGrandTotal = shoppingAPI.CalculateGrandTotal();
  12.         Assert.Equal(expectedGrandTotal, actualGrandTotal);
  13. }

在这个正面测试样例中,先向购物框加入第一件商品(编号为 00000001,价格为 ¥100),再加入第二件商品(编号为 00000002,价格为 ¥200)。然后将第一件商品移除,计算总价,比较计算值与期望值是否相等。

运行期望测试,系统正确地计算出了总价,满足这个期望测试。现在有七个能顺利通过的测试了。系统运行良好,无异常!

  1. Test Run Successful.
  2. Total tests: 7
  3.      Passed: 7
  4.  Total time: 0.9544 Seconds

敬请期待

现在你已经学习了 ZOMBIES 方法中的 ZOMBI 部分,下一篇文章将介绍处理特殊行为。到那个时候,你可以试试自己的测试!

(题图:MJ/c4eb23b5-84aa-4477-a6b9-7d2a6d1aeee4)


via: https://opensource.com/article/21/2/boundaries-interfaces

作者:Alex Bunardzic 选题:lujun9972 译者:toknow-gh 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

LCTT 译者 :Xiangbin Ma
🌟🌟
翻译: 6.0 篇
|
贡献: 3254 天
2014-07-01
2023-05-29
https://linux.cn/lctt/toknow-gh
欢迎遵照 CC-BY-SA 协议规定转载,
如需转载,请在文章下留言 “转载:公众号名称”,
我们将为您添加白名单,授权“转载文章时可以修改”。


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
重磅 | 第九届中国人民大学国际统计论坛主报告预告(三)​大模型时代,CodeArts定义软件开发新范式终端基础:在 Linux 中删除文件和文件夹 | Linux 中国ZOMBIES:我的软件开发和测试简便指南(一) | Linux 中国AI 大模型重塑软件开发,有哪些落地前景和痛点?| ArchSummitLinus Torvalds:我是那些“清醒的共产主义者”之一 | Linux 中国SpringBoot 接口快速开发神器(接口可视化界面实现)测评适用于 Linux 中 Wayland 的最佳屏幕录制软件 | Linux 中国QCon 全球软件开发大会广州站优秀出品人与明星讲师名单公布ZOMBIES:为什么简洁性是交付健壮软件的关键(五) | Linux 中国时序图,UML给软件开发带来的唯一好处B站成立交易生态中心;选秀爱豆利路修等入淘开播;小红书旗下公司新增AI软件开发业务 | 一周简讯年薪中位数16.5万!加州这里拥有全美最多的软件开发人员AIGC 企业落地实践,就在 QCon全球软件开发大会(广州站)LLM 赋能的研发效能:如何探索软件开发新工序?软件开发进度不及预期,沃尔沃再度推迟EX90电动SUV量产时间历史小说《黄裳元吉》第七十二章 雪藏退休四年纪念!我在这些音乐平台发歌了敏捷软件开发,需要消亡zt physical limits uplift soul专访瑞声科技应用软件开发总监陆其明:当一名老兵决定重新上路AI 大模型重塑软件开发,有哪些落地前景和痛点?此一句甚妙,记存DevSecOps,将安全性集成到软件开发的每一个阶段坦克大战 the war of tanks如何在 Rocky Linux 9 / AlmaLinux 9 上安装 KVM | Linux 中国微软Build 2023:人工智能重新定义软件开发与工作的未来Reminders:一个漂亮的开源 Linux 应用,可帮助你完成工作 | Linux 中国Bash 基础知识系列 #3:传递参数和接受用户输入 | Linux 中国Zombies won't win10 个最佳 Linux 虚拟化软件 | Linux 中国risiOS:一个易于使用的基于 Fedora 的 Linux 发行版 | Linux 中国TUXEDO Stellaris 16(Gen5)是目前所能找到的终极 Linux 笔记本电脑 | Linux 中国Agustín Hernández:中美洲建筑背景下的未来主义巨构匿名功能遭举报,知乎周源回应;苹果推出 visionOS 软件开发包;跟随索尼,XBox 订阅服务涨价|极客早知道LLM 与架构新纪元:适应代码生成模式,突破软件开发瓶颈
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。