如何寫好單元測試

2021-09-24 07:18:57 字數 4530 閱讀 7818

本文主要闡述單元測試(ut)的重要性,以及解釋一些常見的困惑,以幫助我們寫出質量更高的 ut。至於類似 mocha 怎麼用,斷言庫怎麼用之類的問題,建議看官方文件。 原文在此

我發現很多朋友意識不到單元測試的重要性。在談如何寫好 ut 之前,我想先說說測試的必要性,這有利於提高我們寫 ut 的內驅力。

假如,張三負責開發介面,李四負責開發具體的業務。李四會呼叫張三開發的介面,但由於種種原因,張三開發的介面可能存在一些 bug 。

在沒有單元測試的情況下,這些 bug 往往都是由介面的使用者也就是李四發現。這無形中給李四增加了額外的工作量,因為保證介面質量的工作本該是寫介面的人也就是張三應該做的事兒。假設張三還是乙個比較粗心的人,其中額外增加的時間成本會更大(實際開發中經常遇到)。

張三可能會很委屈的說:誰能保證**永遠不會產生 bug 。

是的,沒人能夠絕對的保證**的正確性。但張三需要對他所提供的這些介面有一些起碼的保證。他需要保證這些介面的確具備介面文件中所描述的那些功能,且能正常執行。

於是乎,張三含情脈脈的望著李四的眼睛說道:i promise.

再回到上述的場景。假如張三為他所寫的那些介面寫了一些質量還不錯的測試用例。

李四可能會興奮的把張三公主抱了起來。

哈哈哈哈,原因如下:

ok,我們再換一種場景。假如張三現在是某個開源庫的作者,李四是這個開源庫的使用者,兩人相互並不認識。可以想象,一旦李四遇到什麼 bug,他和張三的溝通成本無疑會更大。這就是為什麼社群總是強調開源專案的測試覆蓋率的原因。

小結一下,寫單元測試有如下幾點好處:

此外還有一些好處在上述的例子中並未體現出來,在此不再贅述,各位自行感受:

通過跟身邊同事的交流以及些許切身的感受,我總結了如下幾個常見的關於 ut 的困惑。

ut 的本質:保證介面在具體的場景下能有符合預期的輸出(或是行為)。

解釋:由於**的本質是對實際行為的抽象。所以理論上當我們的測試用例覆蓋了專案中的所有行為以及對應的所有場景時,我們是能夠絕對確保軟體的質量的。雖然這只是理想狀態,但是這卻是我們寫 ut 的初衷。夢想總是要有的,對吧?

很多大公司都會要求專案有足夠的測試覆蓋率,在 ci(continuous integration) 的時候會有乙個測試覆蓋率的閥門,如果測試覆蓋率低於這個閥門,就不讓提交**。比如我們公司閥門是 90% 。

關於測試覆蓋率,我們大致了解下以下幾個常見的計算維度即可:

需要注意,這裡說的都是可測量的覆蓋率。另外還有一種是無法測量的覆蓋率,也就是上面所說的:測試覆蓋所有行為以及對應所有場景的比例。值得一提的是,後者才是我們真正追求的覆蓋率,當這個覆蓋率足夠高的時候,那些可觀測的覆蓋率必然也就低不了。作為一位優秀的開發者,我們需要弄清楚其中的主次關係。

關於這個問題,社群有著許多不同的看法,在 stackoverflow 上大家爭論的很火熱。在此,我也談談我自己的看法。

如下,寫或不寫,都有其各自的說法:

而在我看來,這顯然不是乙個非黑即白的問題。如果我是團隊的 leader,我絕對不會強制要求團隊必須為所有的私有方法寫 ut ,這是一件很愚蠢的事情。如果我是乙個普通的開發者,即使 leader 說我們團隊可以不用為私有方法寫 ut,我可能還是會為某些私有方法寫 ut 。

我的原則是:如果該私有方法複雜度較高且比較重要(重要這個詞的理解就仁者見仁了),那麼我會為它寫 ut 。

前面說了,ut 的本質就是預期介面在指定場景下的輸出或是行為。這裡的指定場景是我們通過 mock 的手段營造出來的。

在給某個介面寫 ut 的過程中,比較常見的情況是:輸入 a 會觸發 a 行為,輸入 b 會觸發 b 行為。這種情況比較簡單,我們只需寫兩個 case ,然後分別 mock 資料 a 和 資料 b ,然後再寫斷言語句來預期對應的行為即可。

除了 mock 輸入資料,我們常常還需要 mock 一些方法。我總結了下述兩種情況。

這裡通過具體的例子會比較好理解。假設我們需要為下述介面寫 ut 。

// index.js

const

promise = require('bluebird');

const fs = promise.promisifyall(require('fs'));

const _ = require('underscore');

exports.refreshconfiguration = function(param) )

.catch((err) => )

}複製**

這是乙個更新配置資訊的介面。當引數都正常時,**執行成功,我們能夠獲取到最新的配置資訊,這個配置資訊包含了 param 中已有的配置資訊和指定檔案中的配置資訊。於是便有了如下這個測試用例。

// mock/config.json

// test.js

const _ = require('underscore');

const assert = require('chai').assert;

const testmodule = require('./index');

describe('refreshconfiguration', () =>

};it('should be seccessed when everything is right', () => , fakeparam.config));

});});

});複製**

如上,我們需要為這個測試用例新建乙個測試檔案。但是,你可能覺得新建檔案很麻煩。於是,我們通過 mockfs.readfileasync()方法來實現同樣的目的。

//  rewire 是為 nodejs 提供 mock 功能的第三方庫 

const rewire = require('rewire');

const _ = require('underscore');

const assert = require('chai').assert;

const

promise = require('bluebird');

const fs = promise.promisifyall(require('fs'));

const testmodule = rewire('./index');

describe('refreshconfiguration', () =>

};fakefileconfig = ;

});it('should be seccessed when everything is right', () =>

});testmodule.refreshconfiguration(fakeparam)

.then((ret) => );

});});複製**

我們只為這個介面寫了乙個 case 。另外還有一中可能就是如果獲取配置檔案失敗後,這個介面應該 catch 到對應的 error 。你不妨動手試著寫寫這種情況下的 case 。

還是上面的那個例子,refreshconfiguration介面中有使用第三方模組underscore和 node 內建的fs模組。對於兩種模組對應的介面,如果沒有 mock 的必要,我們可以不用 mock ,因為我們預設他們是安全的有保障的。

但是,如果待測試的方法中有呼叫其他業務模組的介面,理論上來說,我們必須 mock 這些介面。這涉及到 ut 中很重要的乙個概念:隔離(isolation)。我們需要隔離與當前測試用例無關的方法,這樣的好處有兩點:

減少了寫 ut 的複雜度,只需專注於具體某個場景下的執行邏輯,其餘全部 mock 。

避免了各個測試模組間的相互依賴。這樣就不會出現某個介面出現了乙個 bug ,導致一大堆的測試用例都跑不過的情況。

實際的開發中,由於種種原因我們可能沒法如此嚴格的遵守隔離的原則,但必須理解它,盡可能的避免一些「壞味道」。

在習慣於寫 ut 之後,我才深切感受到 tdd 這種開發模式非常值得嘗試。

先簡單介紹下 tdd 是什麼。tdd(test dirven development),又叫測試驅動開發。其特點是先寫測試用例,再進行開發。我最初聽說 tdd 的時候覺得非常的不可思議。先寫測試?再寫開發?這是效率是有多低啊?

哈哈哈,我也是有點後知後覺。

同樣,我們需要辯證的去看待 tdd 。它只是提供了一種思路:在某些情況下,我們可以先寫測試再進行具體的開發。而不是說我寫任意一行業務**之前都需要先把對應的測試用例給寫了。

那麼,先測試後開發的開發模式有什麼好處呢?

在我們具體開發某個介面時,如果介面不是特別的簡單的話,我們是不會一股腦的就開始寫**,而是先在腦中或是紙上設計**。我們會分析這個介面有那些場景,有那些可能性,每一種場景對應的行為是什麼樣。這句話是不是有點熟悉(上文有提過,不熟悉的話你肯定沒認真看)。是的,我們可以將這些設計的過程在 ut 中體現出來,或者說,ut 能夠更好的實踐我們的**設計

所以,對於某些複雜度較高的介面(甚至是一些私有方法),我們可以使用 tdd 進行開發。我建議所以的介面都通過 tdd 進行開發,反正這些介面的 ut 你是躲不掉的,就是早寫晚寫的問題。

如何寫好單元測試 php程式猿

phpunit單元測試 demo 很多沒寫個單元測試的朋友,總覺得單元測試很難,還增加了工作了,或者把單元測試環境搭好了,也寫了很多單元測試,越寫越累,感覺 質量沒提高,工作量反而提高很多。我們一起來學習下如何寫好單元測試。1.為什麼要些單元測試?2.單元測試與整合測試的區別?3.先些 還是先寫單元...

我該如何寫單元測試?

在我的團隊中,單元測試是較難推行的敏捷實踐之一,我思考後覺得有以下原因 1 主觀上覺得會加大工作量,影響進度 2 從未接觸junit等單元測試框架,害怕接觸新事物 3 團隊形式上要求 形式上開展,但是未能結合培訓 code review等方式持續推行 其實單元測試是個相當簡單的技術,當然,要做的完美...

如何寫好測試報告

最近讀cem kaner,james bach,bret pettichord合著的 軟體測試經驗與教訓 受益頗多,因此根據文中的部份內容總結出來與大家共享,希望能達到知識交流與共享的目的。如果感興趣,也可以閱讀原書。測試報告是產品部與技術部進行溝通的主要手段,測試報告的好壞直接影響bug的修改速度...