天天看點

《測試驅動的嵌入式C語言開發》——3.5節先測試驅動接口再測試驅動内部實作

3.5 先測試驅動接口再測試驅動内部實作

好的接口對于設計良好的子產品來講很關鍵。前面幾個測試會驅動接口設計。關注于接口意味着我們是從外向内開發代碼的。測試作為接口的首個使用者,從調用者(或用戶端代碼)的角度給出了開發代碼的使用方式。從使用者的角度出發會産生可用性更強的接口。

我通常也會讓前面的幾個測試來檢驗一些産品代碼的邊界條件。選擇一個帶邊界檢查的簡單用例。

《測試驅動的嵌入式C語言開發》——3.5節先測試驅動接口再測試驅動内部實作

為了消除這個編譯錯誤,在子產品的接口聲明頭檔案中增加這個接口函數原型:

《測試驅動的嵌入式C語言開發》——3.5節先測試驅動接口再測試驅動内部實作
《測試驅動的嵌入式C語言開發》——3.5節先測試驅動接口再測試驅動内部實作

寫出并且通過這些測試能幫助我們實作以下目标:它定義了驅動程式的一個接口函數并確定我們的做法在硬體上可行。但我保證你肯定還是很困惑。

實作是錯的

正如很多工程師一樣,你很可能會對寫死一些明顯錯誤的東西感到很不安。這樣的最終實作也應該隻能夠設定最低的那一位。但是,你仔細想想,到目前我們寫的測試已經通過。如果我們不是在練習tdd,那麼還要寫更多的測試,否則這樣的錯誤實作會導緻軟體缺陷。但我們正是在使用tdd并且還會寫更多的測試來暴露這些弱點。

我很難想象這樣的實作,即讓我們測試清單中的測試都通過之後還能在原來的位置保持這些錯誤。但如果你發現寫死了一些東西并且不能被現有的測試清單所覆寫,那麼馬上寫一個測試來暴露這個弱點或者在測試清單中加上一個新條目。

測試是對的

在實作還不完整的時候,你可能會認為什麼也沒有測試到。當然有!現在的測試確定将變量置為1!

請嘗試從另一個角度來思考這個問題。測試是對的!它們是tdd産生的非常有價值的副産品。這些簡單的實作測試了我們的測試用例。測試用例失敗表明這些測試可以發現錯誤的結果。把正确答案寫死進去能證明測試用例可以發現正确的結果。這些測試是正确的并且具有價值,盡管産品代碼還不完整。稍後,随着實作的演進,這些看似瑣碎的測試用例會測試重要的行為和邊界條件。從根本上來講,我們是在收緊一個夾在被測代碼上的老虎鉗,把代碼的行為緊緊夾住。

别擔心,産品代碼不會一直這樣寫死很久的。一旦你需要打開一個不同的led,就不需要寫死了。真實的實作并不是很難,但我要求你堅持抵制住不去寫那些不是現在的測試所需要的代碼。我們在演進設計。增加那些不是現在的測試所需要的代碼的問題是,你大概就不會再去寫能讓代碼不産生bug所需的所有測試了。

在有測試要求之前就增加代碼會增加複雜度。有時你對需求的了解可能是錯的,其結果是引入了不必要的複雜度。并且,“我會用到這個”的想法永無止境。那麼什麼時候才要停下來?在tdd實踐中,在目前的測試不再對代碼有新的要求時我們就會停下來。大概的終點列舉在測試清單中。

tdd是一種結構化的延遲。它推遲産生正确的産品代碼,直到由測試強迫我們完成實作。其最終的目标在所有正确的測試都就位之後才實作。

選擇下一個測試

下一步要測試什麼?我們當然可以寫一個新測試來迫使我們移除由簡單思維而生成的實作。但我更傾向于進一步演進接口以使在建的子產品的樣子更清楚些。讓我們把剛剛打開的led關掉。打開和關閉剛好互相補充,并且可以很友善地在後面驗證led的操作時不會互相影響。另外,我們甚至可以真的把這個代碼釋出到目标系統中,如果我們需要做的隻是打開led 1。就這麼定了——讓我們寫個測試來關閉led 1。

《測試驅動的嵌入式C語言開發》——3.5節先測試驅動接口再測試驅動内部實作

現在所有的測試又都通過了。

此時此刻,led驅動程式的接口已經初具規模。我們有了三個測試和一個驅動程式的骨架實作。我們會在第4章再回到這段代碼上來。但首先讓我們來讨論一下我們做的這幾個小的步驟。

繼續閱讀