[譯]從新思考單元測試斷言

原文地址:https://medium.com/javascript-scene/rethinking-unit-test-assertions-55f59358253fjavascript

做者:Eric Elliottjava

「斷言」是編程術語,表示爲一些布爾表達式,程序員相信在程序中的某個特定點該表達式值爲真,能夠在任什麼時候候啓用和禁用斷言驗證,所以能夠在測試時啓用斷言而在部署時禁用斷言。一樣,程序投入運行後,最終用戶在遇到問題時能夠從新啓用斷言。git

每當測試失敗的時候,靠譜的自動化測試總能生成一份優秀的錯誤報告(bug report),可是不多有開發者花時間去思考一個好的錯誤報告須要哪些信息。程序員

在此以前,我已經詳細地敘述過 每一個單元測試必須回答的 5 個問題 ,因此此次咱們將它們一筆帶過。github

  1. 被測單元是什麼(模塊,函數,類,等等)?
  2. 它將作什麼?
  3. 實際輸出是什麼?
  4. 指望的輸出是什麼?
  5. 如何將失敗重現?

許多測試框架容許你忽略這些問題中的一個或者多個,這會致使錯誤報告並不實用。shell

讓咱們看一下使用一個虛擬測試框架的示例,該框架提供經常使用的 pass() 以及 fail() 斷言。npm

describe('addEntity()', async ({ pass, fail }) => {
  const myEntity  = { id: 'baz', foo: 'bar' };
  try {
    const response = await addEntity(myEntity);
    const storedEntity = await getEntity(response.id);
    pass('should add the new entity');
  } catch(err) {
    fail('failed to add and read entity', { myEntity, error });
  }
});

咱們走在正確的軌道上,可是咱們遺漏了一些信息。讓咱們嘗試使用此測試中提供的數據回答 5 個問題:編程

  1. 被測單元是什麼? addEntity()
  2. 它將作什麼? should add the new entity
  3. 實際輸出是什麼? 哎呀,咱們不知道。咱們沒有將這些數據提供給測試框架。
  4. 指望的輸出是什麼? 咱們再一次的不知道。咱們這裏沒有測試返回值。相反,咱們假設它不拋出,一切都按照預期運行——可是若是沒有呢?若是函數返回一個值或者是 promise ,咱們應該測試結果值。
  5. 如何將失敗重現? 咱們能夠在測試設置中看到這一點,但咱們能夠更明確地說明這一點。例如,對你輸入的東西進行簡單的描述以便讓咱們更好地理解測試用例的意圖。

滿分爲 5 分的狀況下,個人得分爲 2.5 分。這項測試沒有完成它應盡的職責。顯然沒有回答每一個單元測試必須回答的 5 個問題。api

大多數測試框架的問題在於它們的功能太過強大,你能夠輕鬆地使用它們提供的各類 「方便(convenient)」 斷言,以致於忘記了在測試失敗時實現測試的最大價值。promise

在失敗階段,編寫測試問題讓咱們更加容易弄清楚出了什麼問題。

每一個單元測試必須回答的 5 個問題 ,我這樣寫道:

equal() 是我最喜歡的斷言。若是每一個測試套件中惟一可用的斷言是 equal(),那麼世界上幾乎全部的測試套件都會更好。

自從我寫這篇文章以來的幾年裏,我一直堅持着個人這一信念。雖然測試框架忙於添加更多 「方便」 斷言,但我卻在 Tape(譯者注:一個開源測試框架) 上進行了一層簡單的封裝,使它只暴露了一個深度的相等斷言。換句話說,我最低程度地使用了 Tape 庫,並刪除了一些功能,以提升測試體驗。

在 RITE Way 測試原則的影響下,我將封裝庫稱爲 RITEway。RITE Way 測試應該是這樣的:

  • 可讀( Readable )
  • 隔離( Isolated )(用於單元測試)或集成( Integrated )(用於功能或集成測試,測試應該隔離而且集成組件 / 模塊)
  • 完全( Thorough )
  • 明確( Explicit )

RITEway 強制你編寫可讀,隔離以及完全的測試,由於這是你使用 API 惟一的方法。因爲編寫測試斷言是如此簡單,以致於你將沉迷於編寫測試,這使得你更容易進行完全的測試。

這是 RITEway 中 assert() 的 函數簽名:

assert({
  given: Any,
  should: String,
  actual: Any,
  expected: Any
}) => Void

斷言必須位於一個 describe() 塊中,它的第一個參數將做爲單元測試的一個標籤。完整的測試以下:

describe('sum()', async assert => {
  assert({
    given: 'no arguments',
    should: 'return 0',
    actual: sum(),
    expected: 0
  });
});

它的運行結果以下所示:

TAP version 13
# sum()
ok 1 Given no arguments: should return 0

讓咱們再看看上面的 2.5 分的測試,看看咱們可否提升咱們的分數:

describe('addEntity()', async assert => {
  const myEntity  = { id: 'baz', foo: 'bar' };
  const given =  'an entity';
  const should = 'read the same entity from the api';
  try {
    const response = await addEntity(myEntity);
    const storedEntity = await getEntity(response.id);
    assert({
      given,
      should,
      actual: storedEntity,
      expected: myEntity
    });
  } catch(error) {
    assert({
      given,
      should,
      actual: error,
      expected: myEntity
    });
  }
});
  1. 被測單元是什麼? addEntity()
  2. 它將作什麼? given an entity: should read the same entity from the api
  3. 實際輸出是什麼? { id: 'baz', foo: 'bar' }
  4. 指望的輸出是什麼? { id: 'baz', foo: 'bar' }
  5. 如何將失敗重現? 如今,消息中更明確地說明了如何重現測試:提供 given 以及描述。

很好!如今咱們經過了測試的測試。

一個深度相等斷言已經足夠了嗎?

在過去的一年半中的幾個大型項目中,我幾乎天天都使用 RITEway。經過界面的簡單化,咱們將其提高了一些,可是我歷來沒有想過另外的斷言,咱們的測試套件是我在整個職業生涯中見過的最簡單,最易讀的測試套件。

我認爲是時候與世界其餘地方分享這項創新了。若是你想開始使用 RITEway

npm install --save-dev riteway

它會改變你對測試軟件的見解。

簡而言之:

測試越簡單越好(Simple tests are better tests)

附:我在本文中一直使用 「單元測試」 這個術語,這僅僅是由於它比 「自動化軟件測試」 或 「單元測試、功能測試以及集成測試」 更容易寫,可是我在本文中所說的關於單元測試的全部內容都適用於我能想到的每一個自動化軟件測試。我也喜歡這些比 Cucumber/Gherkin 更好的測試。

下一步

EricElliottJS.com 的會員能夠得到有關測試驅動開發的視頻課程,若是你還不是會員,請當即前往 註冊

Eric Elliott「Programming JavaScript Applications」O’Reilly)的做者,也是軟件導師平臺 DevAnywhere.io 的創始人。 他爲 Adobe Systems,Zumba Fitness,華爾街日報,ESPN,BBC 以及包括 Usher,Frank Ocean,Metallica 等在內的頂級錄音軟件的用戶體驗作出了傑出貢獻。

相關文章
相關標籤/搜索