[譯]每一個單元測試必須回答的5個問題

原文:
5 Questions Every Unit Test Must Answer

每一個開發者都知道咱們應該編寫單元測試以防程序上到生產環境才檢測到錯誤。javascript

大多數開發者都不知道編寫單元測試的基本要素。我沒法開始計算我看過單元測試失敗的次數,只有通過調查我才發現我徹底不知道開發人員試圖測試哪些功能,更不用說它出了什麼問題或者爲何它很重要。java

在最近的一個項目中,咱們讓一大堆單元測試進入測試單元,徹底沒有描述測試目的。咱們有一隻很棒的團隊,因此我放鬆了警戒。結果?咱們仍然有大量的單元測試,只有做者才能真正理解。git

幸運的是,咱們正在從新設計API,咱們將把整個測試單元從新開始,不然,這將成爲個人修復列表中的第一優先級任務。github

不要讓這發生在你身上。併發

是什麼影響了測試的規則?

你的測試是你抵禦軟件缺陷的第一道防線。你的測試比linting和靜態分析(它只能找到一個錯誤的子類,而不是你的實際程序邏輯的問題)更重要。測試與實現自己一樣重要。模塊化

單元測試結合了許多功能,使它們成爲應用程序成功的祕密武器。函數

  1. 設計幫助: 首先編寫測試可讓您更清楚地瞭解理想的API設計
  2. 功能文檔(針對開發人員): 每一個實現的功能需求都包含在代碼中的測試說明。
  3. 測試開發人員的理解程度: 開發人員是否對這個問題理解到位,從代碼中全部關鍵部分的闡述便能看出。
  4. 質量保證: 手動QA容易出錯。根據個人經驗,在更改、添加或刪除功能後,開發人員不可能記住全部須要測試的功能。
  5. 持續交付: 自動化QA提供了自動防止部署到生產環境中破損構建的機會。

單元測試不須要曲解操縱來知足全部這些普遍的目標。相反,單元測試的本質是知足全部這些需求。這些好處都是一個精心編寫的並有良好覆蓋率的測試單元的反作用。單元測試

TDD科學(Test-driven development)

證據代表測試

  • TDD能夠減小bug的密度
  • TDD能夠鼓勵更多的模塊化設計(加強軟件敏捷性/團隊開發速度)
  • TDD能夠下降代碼的複雜度

先寫測試

來自微軟研究院,IBM和Springer的研究測試了,先編寫測試和後編寫測試的效果,並始終發現,測試優先的流程比稍後添加測試能產生更好的效果。它很是明確:在編寫代碼以前,先編寫測試

什麼是良好的單元測試?

怎樣才能編寫一個良好的單元測試?

咱們將從一個真正的項目看一個很是簡單的例子來探索這個過程:來自Stamp Specificationcompose()函數。.net

咱們將使用tape進行單元測試,由於它足夠清晰簡單。

在咱們可以回答編寫好的單元測試以前,首先咱們必須瞭解如何使用單元測試:

  • 設計幫助: 在編寫代碼以前的設計階段編寫
  • 功能文檔&測試開發者的理解程度: 測試應該提供被測功能的清晰描述
  • 質量保證/持續交付: 測試應當在發生故障時中止交付管道,並在失敗時產生一份好的錯誤報告。

將單元測試做爲測試報告

當測試失敗,那個測試失敗報告一般是您確切發現錯誤的第一個也是作好的線索-快速追蹤根本緣由的祕密是知道從哪裏開始尋找。當你有一個很是清晰的錯誤報告時,這個過程變得更容易。

什麼是好的測試失敗報告

  1. 你在測試什麼?
  2. 它應該作什麼?
  3. 輸出是什麼(實際行爲)?
  4. 指望輸出是什麼(指望行爲)?

測試報告

從回答"你在測試什麼?"開始

  • 你在測試組件的什麼方面?
  • 該功能應該作什麼?你正在測試什麼特定的行爲要求?

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是你的新的默認的斷言。它是每一個優秀測試單元的主要組成部分。全部擁有數百種花哨不一樣斷言的斷言庫正在破壞你的測試質量。

一個挑戰

但願在編寫單元測試時更好?下一週,嘗試使用equaldeepEqual來編寫每個斷言,或者在你的斷言庫中有差很少的選擇。不要擔憂它的質量會影響你的單元測試。個人收入告訴你這種訓練會顯著的提升你的單元測試。

下面的代碼看起來像什麼?

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';

你能夠構建一個斷言,而不用專門賦值給名爲actualexpected的變量,可是我最近開始在每一個測試中專門爲變量actualexpected賦值,並發現它使個人測試更易於閱讀。

看這個它是如何使的這個斷言清晰的?

assert.equal(actual, expected, 
  'compose() should returns a function';
)

它在測試代碼中,分離了how和what

  • 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();
});

還有不少關於單元測試的使用案例,但知道如何編寫一個好的測試還有很長的路要走。

相關文章
相關標籤/搜索