ZOMBIES:我的軟體開發和測試簡便指南(一)
很久以前,在我還是一個萌新程序員的時候,我們曾經被分配一大批工作。我們每個人都被分配了一個編程任務,然後回到自己的小隔間里噼里啪啦地敲鍵盤。我記得團隊里的成員在自己的小隔間里一呆就是幾個小時,為打造無缺陷的程序而奮鬥。當時流行的思想是:能一次性做得越多,能力越強。
對於我來說,能夠長時間編寫或者修改代碼而不用中途停下來檢驗這些代碼是否有效,就像榮譽勳章一樣。那個時候我們都認為停下來檢驗代碼是否工作是能力不足的表現,菜鳥才這麼干。一個「真正的開發者」應該能一口氣構建起整個程序,中途不用停下來檢查任何東西!
然而事與願違,當我停止在開發過程中測試自己的代碼之後,來自現實的檢驗狠狠地打了我的臉。我的代碼要麼無法通過編譯,要麼構建失敗,要麼無法運行,或者不能按預期處理數據。我不得不在絕望中掙扎著解決這些煩人的問題。
避開喪屍群
如果你覺得舊的工作方式聽起來很混亂,那是因為它確實是這樣的。我們一次性處理所有的任務,在問題堆里左砍右殺,結果只是引出更多的問題。著就像是跟一大群喪屍間的戰鬥。
如今我們已經學會了避免一次性做太多的事情。在最初聽到一些專家推崇避免大批量地開發的好處時,我覺得這很反直覺,但我已經從過去的犯錯中吸取了教訓。我使用被 James Grenning 稱為 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)
我將在本系列文章中對它們進行分析講解。
最簡場景
最簡場景指可能出現的最簡單的情況。
人們傾向於最開始的時候使用硬編碼值,因為這是最簡單的方式。通過在編碼活動中使用硬編碼值,可以快速構建出一個能即時反饋的解決方案。不需要幾分鐘,更不用幾個小時,使用硬編碼值讓你能夠馬上與正在構建的系統進行交互。如果你喜歡這個交互,就朝這個方向繼續做下去。如果你發現不喜歡這種交互,你可以很容易拋棄它,根本沒有什麼可損失。
本系列文章將以構建一個簡易的購物系統的後端 API 為例進行介紹。該服務提供的 API 允許用戶創建購物筐、向購物筐添加商品、從購物筐移除商品、計算商品總價。
首先,創建項目的基本結構(將購物程序的代碼和測試代碼分別放到 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 在運行中收到請求時應該返回的值。你需要通過 using app;
聲明來告訴測試你使用的購物邏輯代碼在哪裡。
接下來,你需要 實例化 IShoppingAPI
介面:
IShoppingAPI shoppingAPI = new ShoppingAPI();
這個實例用來發送請求並接收返回的值。
現在,代碼變成了這樣:
using System;
using Xunit;
using app;
namespace tests {
public class ShoppingAPITests {
IShoppingAPI shoppingAPI = [new][3] ShoppingAPI();
[Fact]
public void NewlyCreatedBasketHas0Items() {
var expectedNoOfItems = 0;
var actualNoOfItems = shoppingAPI.NoOfItems();
Assert.Equal(expectedNoOfItems, actualNoOfItems);
}
}
}
顯然執行這個測試的結果是失敗,因為你硬編碼了一個錯誤的返回值(期望值是 0,但是返回的是 1)。
同樣的,你也可以通過將硬編碼的值從 1 改成 0 來讓測試通過,但是現在做這個是在浪費時間。現在設計的介面已經跟測試關聯上了,你剩下的職責就是編寫代碼實現預期的行為邏輯。
在編寫應用程序代碼時,你得決定用來表示購物筐得數據結構。為了保持設計的簡單,盡量選擇 C# 中表示集合的最簡單類型。第一個想到的就是 ArrayList
。它非常適合目前的使用場景——可以保存不定個數的元素,並且易於遍歷訪問。
因為 ArrayList
是 System.Collections
包的一部分,在你的代碼中需要聲明:
using System.Collections;
然後 basket
的聲明就變成這樣了:
ArrayList basket = new ArrayList();
最後將 NoOfItems()
中的因編碼值換成實際的代碼:
public int NoOfItems() {
return basket.Count;
}
這次測試能夠通過了,因為最初購物筐是空的,basket.Count
返回 0。
這也是你的第一個最簡場景測試要做的事情。
更多案例
目前的課後作業是處理一個喪屍,也就是第 0 個喪屍。在下一篇文章中,我將帶你了解單元素場景和多元素場景。不要錯過哦!
(題圖:MJ/7917bc47-5325-4c0f-a2dd-4e444f57a46c)
via: https://opensource.com/article/21/2/development-guide
作者:Alex Bunardzic 選題:lujun9972 譯者:toknow-gh 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive