Scrum敏捷軟體開發之技術實踐——測試驅動開發TDD

Richaaaard發表於2015-06-17

重複無聊的定義

測試驅動開發,英文全稱Test-Driven Development,簡稱TDD,是一種不同於傳統軟體開發流程的新型的開發方法。它要求在編寫某個功能的程式碼之前先編寫測試程式碼,然後只編寫使測試通過的功能程式碼,通過測試來推動整個開發的進行。這有助於編寫簡潔可用和高質量的程式碼,並加速開發過程。(來源百度百科)

 

重複無聊的過程

測試驅動開發的基本過程如下:
  1. 快速新增一個測試(編者注:並非快速)
  2. 執行所有的測試(有時候只需要執行一個或一部分),發現新增的測試不能通過
  3. 做一些小小的改動,儘快地讓測試程式可執行,為此可以在程式中使用一些不合情理的方法
  4. 執行所有的測試,並且全部通過
  5. 重構程式碼,以消除重複設計,優化設計結構
(來源百度百科)

然而來自維基百科上的過程

解釋稍有不同,卻十分重要

1.Add a test

In test-driven development, each new feature begins with writing a test. To write a test, the developer must clearly understand the feature's specification and requirements. The developer can accomplish this through use cases and user stories to cover the requirements and exception conditions, and can write the test in whatever testing framework is appropriate to the software environment. It could be a modified version of an existing test. This is a differentiating feature of test-driven development versus writing unit tests after the code is written: it makes the developer focus on the requirements before writing the code, a subtle but important difference.

2.Run all tests and see if the new one fails

This validates that the test harness is working correctly, that the new test does not mistakenly pass without requiring any new code, and that the required feature does not already exist. This step also tests the test itself, in the negative: it rules out the possibility that the new test always passes, and therefore is worthless. The new test should also fail for the expected reason. This step increases the developer's confidence that the unit test is testing the correct constraint, and passes only in intended cases.

3.Write some code

The next step is to write some code that causes the test to pass. The new code written at this stage is not perfect and may, for example, pass the test in an inelegant way. That is acceptable because it will be improved and honed in Step 5.

At this point, the only purpose of the written code is to pass the test; no further (and therefore untested) functionality should be predicted nor 'allowed for' at any stage.

4. Run tests

If all test cases now pass, the programmer can be confident that the new code meets the test requirements, and does not break or degrade any existing features. If they do not, the new code must be adjusted until they do.

5. Refactor code

The growing code base must be cleaned up regularly during test-driven development. New code can be moved from where it was convenient for passing a test to where it more logically belongs. Duplication must be removed. Object, class, module, variable and method names should clearly represent their current purpose and use, as extra functionality is added. As features are added, method bodies can get longer and other objects larger. They benefit from being split and their parts carefully named to improve readability and maintainability, which will be increasingly valuable later in the software lifecycle. Inheritance hierarchies may be rearranged to be more logical and helpful, and perhaps to benefit from recognised design patterns. There are specific and general guidelines for refactoring and for creating clean code.[6][7] By continually re-running the test cases throughout each refactoring phase, the developer can be confident that process is not altering any existing functionality.

The concept of removing duplication is an important aspect of any software design. In this case, however, it also applies to the removal of any duplication between the test code and the production code—for example magic numbers or strings repeated in both to make the test pass in Step 3.

Repeat

Starting with another new test, the cycle is then repeated to push forward the functionality. The size of the steps should always be small, with as few as 1 to 10 edits between each test run. If new code does not rapidly satisfy a new test, or other tests fail unexpectedly, the programmer should undo or revert in preference to excessive debugging. Continuous integration helps by providing revertible checkpoints. When using external libraries it is important not to make increments that are so small as to be effectively merely testing the library itself,[4] unless there is some reason to believe that the library is buggy or is not sufficiently feature-complete to serve all the needs of the software under development.

為什麼測試驅動開發(TDD)是無價之寶

 它能確保系統中所有程式碼都可以被測試。如果必須寫下所有的程式碼來應付一個失敗的測試,那麼即使我們什麼也不做,至少也使用TDD達到了100%的程式碼覆蓋率。

 反對

“我們在開發一個非常複雜的系統,我需要事先做一些架構工作。”

    作為一個微觀層面的實踐,TDD從來就沒有說過,不能有效地和少量前期架構考慮結合起來。

    “設計:有意的而又是湧現式的”

    問題在於,如果可能,在意識上取得平衡之後,還需要事前進行什麼程度的架構上的考慮。

 

“總是先寫測試必定會花更多的時間,我沒有那麼多時間去浪費。”

    資料表明,做TDD比不做TDD多花15%的時間(George 和 Williams 2003)。但是,資料

還表明,TDD會帶來更少的缺陷。微軟的兩個研究表明:因為使用TDD,發現的缺陷數下降了

24%和38%(George 和 Williams 2003)。的確,TDD在開始的時候會花費更多的時間,

但是,通過降低缺陷修改和維護的時間,這些時間都能賺回來。

 

 我-曾經的TDD懷疑論者

 “做TDD比不做TDD多花15%的時間”

    在初級階段,我曾學習TDD並同時嘗試把TDD應用到實際產品開發的過程中,發現實際多花的時間遠遠不止15%。

因為影響該指數的因素有很多:

  • TDD的過程熟悉
  • 學習如何寫合適的測試程式碼
  • 學習如何做重構
  • 學習如何在特定技術棧下引進合適的測試框架等

    筆者認為,這取決於第一次嘗試TDD的工程師的成熟度和技術習得能力。對於首次接觸此概念的程式設計師可能需要付出比原來多出100%的時間,

而對處於有成熟TDD體系環境下的程式設計師,在首次建立TDD的所有測試用例的時間,大概處於10%-40%的範圍,具體數值仍然取決於個體成熟度

以及專案本身的特性。然而,我們所增加的時間並不只是建立TDD所有測試用例的時間,維護TDD的所產生的測試用例也是團隊所付出的時間成本。

“因為使用TDD,發現的缺陷數下降了24%和38%”

    筆者研究過微軟的對於TDD研究的兩份Paper,本身實驗設計有缺陷,不能完全證明缺陷數下降的數值,而只是經驗值,環境因素也對此結果有

非常大的影響。但是,隨著程式設計師的成熟度提升,經驗上,TDD是能夠幫助程式設計師在交付給QA之前發現更多的問題,或者說能將缺陷扼殺在TDD循

環過程中。

總之

    通常情況下,專案本身開發的時間遠小於其長期的運營使用時間,期間會隨著業務發展不斷髮生需求變更,隨著程式設計師越來越熟練的使用TDD技

術以及團隊敏捷成熟度提高,通過降低缺陷修改和維護的時間,這些時間都能夠賺回來。

 

相關文章