官术网_书友最值得收藏!

1.4 讓測試通過

我們剛才寫測試的時候,暫時忽略了語法上有可能出現編譯錯誤的所有地方,只想著把自己期望的結果表達出來。這樣做合適嗎?

在剛剛開始的時候,通過極少量的代碼來擺正我們的前進方向確實是合適的,我們目前所處的正是這種剛剛開始的狀態。當然,由于還沒定義Dollar就開始使用它,因此測試會失敗。這時有人可能會來一句:“那還用說?”然而大家目前還是得稍微有一點點耐心才行,因為我們至少做到了這樣兩點:

1.我們已經完成了第一步,也就是讓第一個測試變紅(或者說,寫出了第一個失敗的測試)。對于所要實現的每一個功能來說,編寫失敗的測試都是實現該功能時的第一步,而我們現在所要實現的是整個程序的第一個功能,因此我們不僅處在這個功能的起點,而且處在整個程序的起點。

2.我們可以(而且樂意)在開發后續功能的時候,逐漸提升實現每個功能的速度。然而我們同時也知道在需要放慢腳步的時候可以慢下來。

RGR環的第二個環節是讓測試變綠(也就是令其通過)。

我們顯然需要抽象出這個名為Dollar的概念。這一節就定義如何引入此抽象和其他一些必要的抽象,讓我們的測試能夠通過。

1.4.1 Go

在money_test.go末尾添加一個空白的Dollar結構體:

這次運行測試,我們會看到一條新的錯誤消息:

不錯,有進展了!

這條錯誤消息指引我們給Dollar結構體添加名為amount的字段。我們現在就做。對于當前的目標來說,只需要用int類型設計這個字段就足夠了:

把這個字段添加到Dollar結構體之后,接下來運行測試的時候當然就會遇到這樣一條錯誤消息:

大家可以看到一條規律:如果還沒有定義某個東西(例如某個字段或方法)時就使用它,那么Go語言的運行時庫會給出undefined錯誤。以后我們會利用這條規律來提升TDD的速度,從而更快地走完每一個RGR環。現在,我們先添加名叫Times的函數。根據我們所寫的測試,這個函數必須接受一個(表示乘數的)數字,并返回另一個(表示相乘結果的)數字。

然而,這個結果應該怎樣計算呢?我們當然知道基本的算術規則,也就是說,我們知道怎樣用編程手段來計算兩數相乘之積。但是,現在只需要用最簡單的代碼讓測試通過就行了,因此我們完全可以直接返回測試所期望的那個值,即一個用來表示10美元的結構體:

再度運行測試,我們會在終端中看到一條簡短而令人開心的回應:

其中最關鍵的詞就是PASS,這表示我們的測試通過了!

1.4.2 JavaScript

打開test_money.js文件,找到const assert=require('assert');這行代碼,在它下面緊接著定義一個空白的Dollar類:

運行test_money.js時,我們會看到這樣一條錯誤消息:

有進步!這條錯誤消息清楚地告訴我們,目前還沒有給這個叫作fiver的對象定義名為times的方法。于是,我們現在就向Dollar類添加這樣的方法:

這次運行測試,我們會看到一條新的錯誤消息:

?這是Node.js v16所給出的錯誤消息,如果用的是v14,那么錯誤消息會稍有不同。

我們的測試希望times方法能夠返回一個帶有amount屬性的對象。然而剛才寫times方法時卻沒有讓該方法返回任何值,因此JavaScript會把返回值判定為undefined,這樣一個值當然沒有名叫amount的屬性(其實不單是這個屬性,它同樣沒有其他名字的屬性)。

JavaScript語言的函數與方法不會明確聲明返回值的類型。如果某個函數根本就不返回任何東西,而我們又查看了該函數的返回值,那么這樣查出來的返回值就是undefined。

怎樣才能讓測試變綠?要想做到這一點,最簡單的方法是什么?是不是可以考慮讓times函數總創建一個表示10美元的對象并返回該對象?

現在就試試看。我們添加一個constructor(構造器),用來將本對象的amount屬性初始化成指定的值,然后讓times方法總是通過調用這個構造器來創建一個表示10美元的Dollar對象:

?每當創建Dollar對象的時候,constructor都會得到調用。

?以調用方所給的參數來初始化this.amount變量。

?times方法需要接受一個參數。

?采用最簡單的辦法實現該方法,也就是讓它總是返回一個表示10美元的對象。

現在運行這段代碼,看不到任何錯誤。這說明我們的測試變綠了!

assert包中的strictEqual方法與其他方法都只在斷言失敗的情況下才給出錯誤消息,如果測試成功,那么這些方法不會有任何輸出。我們將在第6章改進這一點。

1.4.3 Python

由于Dollar還沒有得到定義,因此我們需要在test_money.py文件里面定義這樣一個類。我們把這個類寫在TestMoney類的前面:

運行代碼,我們會看到這樣一條錯誤消息:

有進展!這條錯誤消息清楚地告訴我們:目前還沒有辦法用參數來初始化Dollar對象(我們在代碼里面需要用5或10這樣的參數來初始化相應的Dollar對象)。現在,編寫一種最簡單的初始化器(initializer)來解決這個問題:

運行測試,我們發現它所產生的錯誤消息已經變了:

我們在這里發現一條規律:盡管測試依然失敗,但每次失敗的原因都略有不同。一開始是因為沒有定義Dollar類,于是我們定義這樣一個類,后來又變成沒有能夠接收參數的構造器,于是我們定義這樣一個能夠接收參數的構造器。現在,又變成了沒有times方法,每次的錯誤消息都促使我們改進現有的代碼,把它推進到一個更好的狀態之中。這正是TDD的特征,也就是以我們自己所控制的節奏穩步向前推進。

現在我們稍微提升一下速度,把兩件事放到一起做:一是定義名為times的函數,二是用最簡單的方式實現該函數,以便讓整個測試變綠。那么,什么是最簡單的方式呢?當然是讓這個函數總返回測試所要求的結果,也就是返回一個表示10美元的對象。

?只要一創建Dollar對象,__init__函數就會得到調用。

?以調用方給出的參數來初始化self.amount變量。

?讓times方法接受一個參數。

?我們用最簡單的方式實現times方法,也就是總讓該方法返回一個表示10美元的Dollar對象。

運行測試,我們會看到一段簡短而可喜的回應:

這里面寫的時間可能稍微有點夸張,運行測試所需的時長應該要比0.000s多。但是別忘了,我們的重點是OK。這表示我們的第一個測試已經變綠了!

主站蜘蛛池模板: 深州市| 正镶白旗| 五莲县| 长宁县| 江源县| 双牌县| 扶风县| 太保市| 和平区| 建始县| 荥经县| 武山县| 山阴县| 房产| 木里| 平罗县| 美姑县| 神农架林区| 凤翔县| 凯里市| 清镇市| 金堂县| 驻马店市| 中牟县| 元氏县| 阳东县| 新昌县| 朝阳市| 长沙县| 乌鲁木齐市| 太保市| 平邑县| 台湾省| 遂昌县| 澄迈县| 新巴尔虎左旗| 阜阳市| 清原| 繁峙县| 汪清县| 通城县|