漫談單元測試

2021-07-08 11:18:26 字數 4127 閱讀 7055

提起單元測試,有人覺得它沒什麼用,純屬浪費時間;有人則一頭霧水;當然也有人認為單元測試很重要,無論對專案的開發還是對程式設計師自身的提公升都大有益處。

拿自身經歷來講,之前呆過的乙個小團隊也曾推過這個東西,但沒過一段時間就不了了之;作為當事人,我當時的感受就是完全不明白這東西是幹什麼的,一頭霧水,覺得怎麼寫完程式還要寫這東西,浪費時間。但最近在嘗試參與 chromium 專案時,發現單元測試是一門必須課;而且對於很多開源專案,單元測試也都是不可獲取的一部分。單元測試真的沒用嗎?是時候靜下心來去思考一下了。

從使用的角度來講,單元測試並不複雜。但我覺得對於單元測試的學習而言,學會使用只是最基礎的,只有搞清楚了為什麼要用、單元測試能幫我們解決哪些問題、單元測試又有那些侷限這些問題,才能真正明白單元測試的真諦,真正的用好單元測試;所以,在這篇部落格中,我不會講某個單元測試框架,也不會去講單元測試的概念,而是結合自己以前的一些工作經歷去思考,為什麼我們需要單元測試?單元測試能幫我們解決那些問題,又有那些侷限,單元測試的本質。

單元測試的存在不是為了給程式設計師增加負擔,而是為了解決在軟體開發中普遍存在的一些問題。如今,當初創造單元測試的大師們是怎麼意識到這些問題並提出了單元測試這個解決方案我們不得而知;但我們可以自己去思考在自己的專案經歷中存在的一些問題。

你們的專案在測試和修復 bug 上花費了多少時間?

13 年剛進公司時我曾參與乙個遠控專案的開發,該專案從 8 月份處開始,到 12 月初結束,歷時 4 個月,而這其中花費在測試和修復 bug 上時間就有 2 個月,當時,作為開發人員我在這兩個月內的節奏基本是這樣的:

被測試人員測出乙個 bug

修復 bug

自己把出現該 bug 的功能測一下;這裡除了要測試引起該功能出現這個 bug 的場景外,往往還需要對該功能的其他使用場景進行測試,甚至還要對這個 bug 可能影響到的其他功能進行測試

測試成功後提交 bug 管理系統,等待測試人員進行回測後確認 bug 是否已解決。

測試人員進行回測,以確認該 bug 是否已修復。同樣,這個階段測試人員除了要測試引起該功能出現這個 bug 的場景外,往往也需要對該功能的其他使用情景測試,甚至測試這個 bug 可能影響到的其他功能

上面就是當時整個團隊在那段時間的節奏,我想很多人應該都有類似經歷。

那這個過程麻煩嗎?在回答這個問題之前,我們不妨先問問自己,上面這個過程在自己的專案經歷中重複了多少次?而自己每次又在第 4 步花費了多少時間?測試人員又在第 5 步花費了多少時間?

可以說,上面這個過程不麻煩;但當這個過程反覆出現時,就麻煩了;另外如果在第 4 步和第 5 步中開發人員和測試人員只是測試引起功能出現 bug 的使用場景,那還好;但實際情況是,開發人員和測試人員往往要測試出現 bug 的功能的多種使用情景,甚至再測試可能受該 bug 影響的其他功能。當上面這些情況放在一起時,你會發現花費在測試上的時間在以難以想象的速度在增長。

怎麼辦?首先,規範測試方法和測試流程是一方面,但怎樣讓這些測試自動化才是關鍵,因為無論測試方法和測試流程有多規範,但只要這些測試在重複(這無法避免)且需要手動進行,那團隊花費在測試上時間就不會減少。而單元測試就是為了測試的自動化而生,相對於手動進行測試,單元測試難以想象的快,而且可以反覆進行。

當接手了乙個老專案,覺得**很爛,想進行重構或者重寫部分**,但是你真的敢嗎?

前段時間修復乙個老專案中存在的一些 bug 時,當時覺得**質量實在不高,就想對一些**進行重寫(注意,僅僅是重寫一些**),但一想到重寫這些**可能付出的代價後,還是放棄了,轉而小心翼翼的在原有**的基礎上去做些小的修改。為什麼?因為這不是簡單的重寫部分**的問題,而是在這部分**重寫後,可能相關的測試都要來重新來一邊的問題;而且一旦在這個過程中出現問題,那所有的責任都是我的;因為至少以前這段**在大部分情況下是沒問題的;另外專案組老大估計也不會同意。為什麼?我覺得其實他也怕。

但是假如這個專案有之前積累的非常完善的單元測試呢。我想情況會稍有不同。因為在重寫後,相關的測試可以很快完成,即使出錯,通過單元測試框架提供的資訊,錯誤也能很快定位到,立刻得到解決。

這便是單元測試的另外乙個好處。當專案有非常完善的單元測試時,測試也就不在是一件耗費時間且無聊的事,而我們在修改或重構現有**時也能放下包袱,大膽嘗試。

上面說的很美好,但單元測試真的能在測試方面徹底解放我們嗎?下面讓我們結合實際來討論下這個問題。

首先我們來介紹乙個單元測試框架 googletest。googletest 是由 google 開發的開源 c++ 單元測試框架。也許你之前沒有接觸過 googletest,甚至從來沒有接觸過單元測試。沒關係,這裡不需要糾結什麼概念;往下看,你會發現,單元測試的思想很簡單,googletest 的核心東西也很簡單。

簡單來講,googletest 其實就是給我們提供了一系列的判斷真假的巨集,如 assert_eq(expected, actual) 用於判斷 expected 和 actual 是否相等;assert_gt(val1,val2) 用於判斷 val1 是否大於 val2;assert_streq(expected_str, actual_str) 用於判斷 expected_str 和 actual_str 這兩個字串是否相等。這些巨集就是 googletest 的核心,直接決定著 googletest 能用來做什麼;至於 googletest 提供的如 test fixtures 之類的特性,只是它為我們提供的在使用這些巨集時遇到的一些常見需求(如,不同 test 之間共享資源)的通用解決方案,錦上添花而已。

而為專案寫單元測試,其實主要就是用這些巨集去測試程式中的函式是否正確。我們通過指定的引數去呼叫程式中的函式,然後用這些巨集比較函式的實際返回值與正確的返回值是否相等;如果相等,說明函式就是正確的;如果不相等,就說明函式就 bug。

比如我們的專案中有乙個下面乙個函式:

// returns n! (the factorial of n).  for negative n, n! is defined to be 1.

int factorial(int n)

return result;

}

那我們為這個函式所寫的單元測試大致就是下面這樣的:

expect_eq(1, factorial(-5));

expect_eq(1, factorial(-1));

expect_gt(factorial(-10), 0);

通過用不同的引數去呼叫 factorial 然後比較它的返回值和正確的返回值,來測試 factorial 在不同情況是否都能正確執行。當 factorial 通過這些測試時,基本就可以說 factorial 肯定是沒問題的。

這便是單元測試。很簡單不是嗎!但看到這裡,我想此時此刻肯定會有很多人會疑問,這真的有用嗎?我覺得,在給自己乙個答案之前,先不妨想一下自己專案組的 bug 列表裡,有多少 bug 是因為函式自身的實現就存在問題而導致的,而定位和解決這些 bug 又花費了多長時間。下面講講我的理解。

軟體由功能組成;而功能主要由函式組成。那如果能保證組成乙個功能的所有函式都是正確的,能不能說這個功能在 90% 的情況下是沒有 bug 的呢;進一步講,如果這個軟體的所有函式都通過了單元測試,那能不能說這個軟體在 90% 的情況下是沒有 bug 的。這就是單元測試的思想,通過測試每乙個函式的正確性,來保證軟體功能的正確性,來保證整個軟體的正確性。

的確,單元測試的核心很簡單,就是一些判斷真假的巨集;但重要的不是這些巨集,而是這種 1 + 1 > 2 的思想和單元測試的可重複性。

當然,不得不說單元測試的也是侷限的,單元測試的實現方式注定著它無法檢測程式中的一些不僅僅是保證**正確就能避免的 bug,如由程式自身的設計缺陷而引起的 bug。記得在之前的乙個遠控專案中,當時程式在內網進行測試時沒有任何問題,但一旦拿到公網下,在客戶端需要往管理器返回大量資料時,就會出現異常;而出現該問題的本質原因就是客戶端的設計存在缺陷;當時客戶端傳送資料時使用非同步操作,而且在傳送資料時不會等待上次傳送操作結束後在傳送,而是立即傳送,這樣在資料量非常大的情況下,就會導致儲存未傳送的非同步資料的空間用完,從而導致程式丟擲異常。就這個 bug 而言,通過單元測試肯定是檢測不出來的。

但是,單元測試依舊為我們解決了絕大部分的問題,不是嗎?

乙個工具再好,不會用,也能被用爛了。單元測試就是這樣,這篇部落格也只是講了自己的一些想法而已,有興趣的同學可以更深入學習,這裡強烈推薦《你好,單元測試!》這篇文章。

另外,其實我們不僅僅是在思考單元測試,而是在思考軟體開發,思考軟體開發過程中的存在的常見問題和這些問題的通用解決方案,思考怎麼改善這個這個過程。我們是程式設計師,我們寫**,但我們也不僅僅寫**,我們也要嘗試從乙個更高的角度看問題。

單元測試 單元測試文章收藏

前言 前段時間公司計畫做自動化測試,自己也打算圍繞幾個點做相關調研,現在想想呢?其實對自動化測試的概念都還不是十分清晰,當時主要還是圍繞 單元測試 向qa小夥伴學習了一段時間,現由於公司重組,學習中斷,這裡簡單記錄一些單元測試好文,留待後續參考.什麼叫自動化測試?自動化測試覆蓋率?覆蓋率如何做到的?...

單元測試之Django單元測試

每個應用,自帶tests.py 整合在django的專案檔案裡,更多是開發人員寫django自動的測試執行 3.1 前後置方法執行特點 django.test.testcase類主要由前 後置處理方法和test開頭的方法組成 特點 繼承於django.test.testcase 測試用例都是test...

單元測試(三) 建立多執行緒單元測試

junit本是不支援多執行緒的,乙個單元測試case主程序跑完,其他new出來的執行緒都會gg思密達。此篇mark乙份在junit中執行多執行緒的方法。net.sourceforge.groboutils groboutils core 5test slf4j public class device...