原文:
5 Questions Every Unit Test Must Answer
每一個開發者都知道咱們應該編寫單元測試以防程序上到生產環境才檢測到錯誤。javascript
大多數開發者都不知道編寫單元測試的基本要素。我沒法開始計算我看過單元測試失敗的次數,只有通過調查我才發現我徹底不知道開發人員試圖測試哪些功能,更不用說它出了什麼問題或者爲何它很重要。java
在最近的一個項目中,咱們讓一大堆單元測試進入測試單元,徹底沒有描述測試目的。咱們有一隻很棒的團隊,因此我放鬆了警戒。結果?咱們仍然有大量的單元測試,只有做者才能真正理解。git
幸運的是,咱們正在從新設計API,咱們將把整個測試單元從新開始,不然,這將成爲個人修復列表中的第一優先級任務。github
不要讓這發生在你身上。併發
你的測試是你抵禦軟件缺陷的第一道防線。你的測試比linting和靜態分析(它只能找到一個錯誤的子類,而不是你的實際程序邏輯的問題)更重要。測試與實現自己一樣重要。模塊化
單元測試結合了許多功能,使它們成爲應用程序成功的祕密武器。函數
單元測試不須要曲解操縱來知足全部這些普遍的目標。相反,單元測試的本質是知足全部這些需求。這些好處都是一個精心編寫的並有良好覆蓋率的測試單元的反作用。單元測試
證據代表測試
來自微軟研究院,IBM和Springer的研究測試了,先編寫測試和後編寫測試的效果,並始終發現,測試優先的流程比稍後添加測試能產生更好的效果。它很是明確:在編寫代碼以前,先編寫測試
怎樣才能編寫一個良好的單元測試?
咱們將從一個真正的項目看一個很是簡單的例子來探索這個過程:來自Stamp Specification的compose()
函數。.net
咱們將使用tape進行單元測試,由於它足夠清晰簡單。
在咱們可以回答編寫好的單元測試以前,首先咱們必須瞭解如何使用單元測試:
當測試失敗,那個測試失敗報告一般是您確切發現錯誤的第一個也是作好的線索-快速追蹤根本緣由的祕密是知道從哪裏開始尋找。當你有一個很是清晰的錯誤報告時,這個過程變得更容易。
compose()
函數使用任意數量的郵票(可組合的工廠函數)並生成一個新郵票。
爲了編寫這個測試,咱們將從任何單個測試的最終目標開始向後工做:測試特定的行爲要求。爲了經過這個測試,代碼必須產生什麼樣的特定行爲?
我喜歡從寫一個字符串開始。沒有分配給任何東西。沒有傳入任何函數。只是清楚地記關注組件必須知足的特定要求。在這種狀況下,咱們將從compose()
函數應該返回一個函數的事實開始。
一個簡單的,可測試的要求
'compose() should return a function.'
如今咱們將跳過一些東西,並充實剩下的測試。這個字符串是咱們的目標。事先陳述有助於咱們保持對最終結果的關注。
組件各個方面的含義因測試而異,具體取決於爲測試組件提供足夠覆蓋度所需的粒度。
在上面的例子中,咱們將測試compose()
函數的返回類型,以因確保它返回正確的類型,而不是在運行時拋出'undefined'或什麼都不拋出。
讓咱們將這個問題轉換爲測試代碼。答案進入測試描述。這一步也是咱們進行函數調用並將回調函數傳遞給測試運行器時,它在測試運行時會調用回調函數的地方。
test('<What component aspect are we testing?>', assert => {});
在這個例子中,咱們將測試compose
函數的輸出
test('Compose function output type.', assert => { })
咱們固然也須要咱們最開始的描述。它將放進咱們的回調函數:
test('Compose function output type', assert => { 'compose() should return a function.' })
equal()
是我最喜歡的斷言。若是每一個測試單元惟一可能的斷言是equal()
,那麼世界上幾乎全部的測試單元都會更好。爲何?
由於equal()
,天然的回答每一個測試單元都回答的兩個重要問題,可是大多數沒有:
若是你完成了測試但沒有回答這兩個問題,你並無在進行一個真正的單元測試。你只是有一個馬虎的,不成熟的測試。
若是你從這篇文章中只看出一件事,那這件事就是: Equal
是你的新的默認的斷言。它是每一個優秀測試單元的主要組成部分。全部擁有數百種花哨不一樣斷言的斷言庫正在破壞你的測試質量。
但願在編寫單元測試時更好?下一週,嘗試使用equal
或deepEqual
來編寫每個斷言,或者在你的斷言庫中有差很少的選擇。不要擔憂它的質量會影響你的單元測試。個人收入告訴你這種訓練會顯著的提升你的單元測試。
下面的代碼看起來像什麼?
const actual = '<what is the actual output?>'; const expected = '<what is the expected output?>';
第一個問題確實是在測試失敗中承擔雙重責任。經過回答這個問題,你的代碼也能夠回答另一個問題:
const actual = '<how is the test reproduced>';
須要注意的很重要的點是,actual
值必須在使用組件公共API時被生產出來。不然,測試毫無心義。
讓咱們回到例子:
const actual = typeof compose(); const expected = 'function';
你能夠構建一個斷言,而不用專門賦值給名爲actual
和expected
的變量,可是我最近開始在每一個測試中專門爲變量actual
和expected
賦值,並發現它使個人測試更易於閱讀。
看這個它是如何使的這個斷言清晰的?
assert.equal(actual, expected, 'compose() should returns a function'; )
它在測試代碼中,分離了how和what
閱讀測試的結果應該和閱讀一個高質量的bug報告同樣。
咱們來看看上下文中的全部內容:
import test from 'tape'; import compose from '../source/compose'; test('Compose function output type', assert => { const actual = typeof compose(); const expected = 'function'; assert.equal(actual, expected, 'compose() should return a function'); assert.end(); })
下一次,你編寫測試代碼,記得回答下面全部的問題:
最後的問題由用於導出actual
值得代碼回答。
import test from 'tape'; // For each unit test your write, // answser these questions test('What component aspect are you testing?', assert => { const actual = 'What is the actual output?'; const expected = 'What is the expected output?'; assert.equal(actual, expected, 'what should the feature do?'); assert.end(); });
還有不少關於單元測試的使用案例,但知道如何編寫一個好的測試還有很長的路要走。