你真的須要單元測試嗎?

單元測試不是用來找Bug的

當你看到網上諸多關於單元測試的讚美時,仔細看看你就會發現不少說的實際上是TDD(Test-Driven Development,測試驅動開發),不幸的是大多數人並無注意區分這兩個概念。在Writing Great Unit Tests: Best and Worst Practices中,Steven Sanderson強烈表達了本身的觀點:Unit testing is not about finding bugs。簡單來講,當先寫代碼後寫單元測試的時候,單元測試就成了一種發現Bug的手段,但做者根據其幾十年的開發經驗指出這種手段實際上是十分低效的,由於即便每一個功能模塊都能正常工做,可是仍然不能保證模塊之間、模塊與用戶環境之間能正確交互,然後者每每是Bug的主要來源。單元測試或許能找到一些Bug,但相比集成測試和系統測試就顯得十分低效了。
既然如此,那麼單元測試爲什麼又備受追捧呢?在How Google Tests Software中,三位谷歌的專家介紹了谷歌的軟件測試之道,總而言之就是谷歌會在開發之初設計好單元測試(實際上是用代碼表達需求),在開發中不斷迭代以經過所有的測試(實際上是完成所有需求),最終交付給測試人員的軟件已經通過一輪測試,若是還有集成後的Bug,就能夠交給專業的測試人員發現了。這是一種典型的敏捷開發,能夠看到單元測試扮演更多的是驅動開發的角色。
做爲技術標杆的谷歌已經全面引入了單元測試,那麼我做爲一個普通開發者爲何還要提出一番質疑呢?請看下一節。android

單元測試的邊際收益

在經濟學領域,有一個著名的邊際收益遞減規律,指在投入生產要素後,每單位生產要素所能提供的產量增長髮生遞減(二階導數爲負)的現象。在本文討論的場景中,投入產出以下(引自:軟件開發過程當中值不值得寫單元測試? - voidint's blog):git

成本(投入)github

  • 編寫單元測試用例所額外付出的時間,短時間內會拖慢項目進度。

收益(產出)數據庫

  • 提高代碼質量。監督開發人員寫出更加易於測試和可維護的代碼。
  • 提高開發團隊內部的協做效率。其餘開發人員能夠經過閱讀單元測試用例來理解代碼原做者的意圖。
  • 保證功能實現的長期穩定。代碼一旦發生與原功能意圖不相符的變化,經過跑單元測試能夠體現出來,便可以防止功能被無心識地破壞。
  • 提升自動化測試佔比,下降其餘測試方式上的投入。

在經濟學中,邊際收益遞減現象常出現於產量的短時間分析中。結合對同事的諮詢以及本身的調研,這個現象在軟件開發領域一樣適用。當咱們須要寫原型或者開發一個短時間緊急需求的時候,(產品、運營人員)每每要求快速交付,而且因爲代碼規模有限也每每不會有太多Bug,在這種短時間開發中若是引入單元測試每每會拔苗助長,投入了雙倍的時間卻沒有明顯的附加收益。而分析How Google Tests Software一書中最多說起的幾個項目(Chrome,Android,Gmail)能夠發現,單元測試(更準確說是Test-Driven Development)的成功案例每每都是一些架構設計良好,處於長期迭代開發,基本沒有短時間臨時緊急需求的產品,項目初期的單元測試每每在幾年後還能使用,複用率極高(私覺得複用率某種程度上能夠做爲是否值得引入單元測試的標準)。而若是一個項目一開始沒有引入單元測試、過期和糟糕的代碼沒有及時重構、臨時短時間需求偏多,每每就沒有引入單元測試的必要了。後端

Jake Wharton也頭疼的單元測試

Jake Wharton何許人也?答:諸多著名開源項目的做者,Android社區的旗幟人物:
Jake Wharton Github Screenshot
Jake Wharton對於Android平臺的單元測試也十分頭痛(Against Android Unit Tests),其緣由也是我調研並寫下本文的緣由。Android相對於其餘開發環境有如下幾個特色:網絡

  1. 大多數代碼是在與Android環境(SDK)交互,邏輯每每放在後端
  2. UI與邏輯容易耦合,雖然MVP等模式的出如今嘗試緩解這個問題
  3. 版本衆多(不一樣ROM),使用環境複雜(用戶行爲、網絡波動等)致使具體環境的Bug遠多於單個模塊的Bug

能夠想象,當你投入大量精力,使用RobolectricMockito等框架模擬出一個將數據庫數據發日後臺的單元測試並經過測試用例後,用戶卻由於切換網絡等小几率場景觸發了Bug,你會不會感嘆我要這單測有何用?相似Android這種終端環境,其邊際收益遞減的臨界點每每更容易達到,引入單元測試猶需謹慎。架構

你的代碼能作單元測試嗎

結合上面的分析,哪些場景不適合作單元測試已經顯而易見了,[When is unit testing inappropriate or unnecessary? [duplicate]](https://softwareengineering.s...app

  1. The code has no branches is trivial. A getter that returns 0 doesn't need to be tested, and changes will be covered by tests for its consumers.
  1. The code simply passes through into a stable API. I'll assume that the standard library works properly.
  2. The code needs to interact with other deployed systems; then an integration test is called for.
  3. If the test of success/fail is something that is so difficult to quantify as to not be reliably measurable, such as steganography being unnoticeable to humans.
  4. If the test itself is an order of magnitude more difficult to write than the code.
  5. If the code is throw-away or placeholder code. If there's any doubt, test.

Definition of brittle unit tests中也有詳細總結,都有必定參考價值。
此外,有了適合單元測試的場景並不表明就有適合單元測試的代碼。在TDD模式中,測試先於開發,因此開發部分的代碼接口每每須要通過良好的設計和定義,最好能解耦各個模塊,如此開發代碼將可以完美匹配測試代碼。但這種開發模式每每對開發經驗、設計能力要求很高。能都達成此境界的已是TDD的行家了。然而事實是對於沒有單元測試經驗的開發人員而言,每每沒有意識到本身寫的代碼「不可測試」。如下面僞代碼爲例:框架

object processObject(Object object) {
    if (object == objectA) {
        log.i('error 1 ....')
        return object;
    }
    if (object == objectB) {
        log.i('error 1 ....')
        return object;
    }
    .....
    return object;
}

開發人員在Debug的時候,能根據log信息快速定位問題,但對於測試來講就十分不友好了:返回值都同樣。若是想要領會單元測試的優越性,短時間的鎮痛與適應彷佛是不可避免的。post

寫在最後

本文沒有討論TDD的各類優點,也沒有討論單元測試的最佳實踐,是我的的一些總結,討論的是單元測試的一些侷限之處,或許有不足、有遺漏,又或者徹底錯誤,歡迎拍磚。譬如,在stackoverflow上有一個關因而否值得作單元測試的問題就由於其爭議性而被關閉回答,而又由於有其存在的歷史意義而被一直鎖定(locked),感興趣能夠看看:


連接:Is Unit Testing worth the effort? - Stack Overflow

本文討論的只是單元測試(TDD)的侷限性,在合適的場景中其做用是巨大的,尤爲是輪子級、框架級和開源項目中,瞭解單元測試也大有裨益。好比,我在爲ChatteBot提交代碼時,就由於當時不瞭解單元測試的做用,只修改了代碼Bug而沒有修改測試代碼的錯誤(測試代碼寫錯真是沒救了)。我也所以失去了成爲一個5000+ star開源項目貢獻者的機會.....

參考

相關文章
相關標籤/搜索