天天看點

《測試驅動的嵌入式C語言開發》——3.6節增量式前進

3.6 增量式前進

剛剛接觸tdd的人往往為這樣的早期版本代碼而感到困惑。“我們什麼也沒有測到(你可能這樣想),這隻是些寫死的傳回值”;“或者測試太小了,我們隻是在各種活動間跳來跳去”。讓我來進一步解釋。

dtsttcpw:先仿冒再建造

回首我剛剛學習極限程式設計時,kent beck在黑闆上寫下了這個很到位的縮寫:dtsttcpw。它讀起來還蠻押韻的;我希望我也能找到那麼押韻的一組詞。它是do the simplest thing that could possibly work(隻做簡單夠用的東西)的英文縮寫。當你隻寫了幾個簡單的測試時,通常最簡單的事就是仿冒它。leddriver通過寫死寫到led位址中的值來仿冒它。一旦你的測試多起來,仿冒就沒那麼容易了—可能還是真實的實作,或者部分的真實實作更簡單。

在傳統的開發方式中,放入寫死的值可以是很嚴重的問題。當這樣的捷徑方式深埋在實作當中時,它很容易被忘記。這在tdd中卻不是什麼大問題,因為你還會回到這些地方來。随着你設計出更多的測試,這些弱點就會暴露出來。如果你擔心你會忘記,那麼在測試清單中記上一筆。

當我第一次見到kent beck仿冒傳回值時,我很困惑。但我自己嘗試了一下并發現這樣可以工作。正如kent建議的那樣,我確定寫了所需的所有測試。在有了相當多的tdd經驗後,我有了一個重大的發現:盡管實作離正确還有相當大的差距,但測試是正确的!

我教過很多人tdd,并且給他們示範“仿冒它直到做出它”。他們總是問:“那什麼時候停止仿冒轉而寫真實的代碼?”我對此簡單的首要原則是,一旦去仿冒它比做出它還要麻煩時就做出它。你很快就會明白我說的是什麼意思了。

保持小而專注的測試

有幾件事值得我們注意。你可能會想,為什麼需要第二個測試用例來測試關閉led 1呢?要測試關閉led 1最簡單的做法不是給test(leddriver, turnonledone)加一點代碼就可以了嗎?但這會讓我們的測試缺乏關注。這樣就會有兩個原因能讓該測試失敗:leddriver_turnon()壞掉,或者leddriver_turnoff()壞掉。

在第二個測試中,請注意,并沒有對于leddriver_turnon()是否正常工作的檢查。(test leddriver,turnonledone)進行檢查,是以我們并不需要在這個以及後面的測試中不斷地檢查它。

剛剛做tdd的程式員往往在每個測試中放入太多東西。這會破壞可讀性和關注點。對于一個測試用例中該有多少行斷言并無限制,正如對一個函數中該有多少行代碼并無限制一樣。但是我們要保持測試可讀、小巧并且專注。四段測試模式的每一步(建立、執行、驗證和拆除)應該在每個測試用例中都清晰可見。當測試變得很大或很不清晰時,它們作為文檔的價值就不存在了。當測試變得不清晰,讀者會弄不明白它們到底要達到什麼目的。讓你的測試保持既小又專注,并且給它們起好名字,它們會在今後的幾年裡回報你。

比較理想的情況是一個單一的代碼問題隻會導緻單一的測試失敗。順便說一下,這個理想情況永遠不可能達成,但這仍不失為一個好點子。

綠了之後就重構

tdd中另一個不可分割的部分是重構。重構就是經常清理代碼和設計。我們會在第4章中繼續開發leddriver時進行重構。我們還會在第12章中深入讨論重構。

唯一可以安全地進行重構的時刻是當所有的測試都通過時。這裡進一步強調一下,不要在測試不通過的情況下重構!當有測試失敗時,你并沒有鎖住代碼的行為。結構化發生改動,當它與失敗的測試糾纏在一起時可能會非常難以重新回到所有測試都通過的狀态。讓測試保持通過就是保護網,它讓重構的雜技安全地進行。現在測試已經通過了,是以讓我們來看看我們的新代碼裡有沒有什麼問題。

當你具備了訓練有素的嗅覺時,你會在設計變壞到難以簡單改正之前就聞到一些味道。

測試代碼已經有壞味道了——重複。virtualleds在每個測試用例中都要建立一遍,每個測試用例都要調用一遍leddriver_create()。test(leddriver, ledsoffaftercreate)還是要有,因為它處理一種特殊的情況。移出在另兩個測試中的重複,如下所示:

《測試驅動的嵌入式C語言開發》——3.6節增量式前進
《測試驅動的嵌入式C語言開發》——3.6節增量式前進

測試即文檔,應當小心地命名它們。一旦測試通過後,請確定名字能表達測試的意圖。

繼續閱讀