Jest 快照測試

2021-09-18 20:16 更新

每當你想要確保你的UI不會有意外的改變,快照測試是非常有用的工具。

典型的快照測試用例呈現(xiàn) UI 組件,拍攝快照,然后將其與存儲在測試旁邊的參考快照文件進行比較。如果兩個快照不匹配,則測試將失?。焊氖且馔獾?,或者參考快照需要更新到新版本的 UI 組件。

使用 Jest 進行快照測試

在測試 React 組件時可以采用類似的方法。你可以使用測試渲染器為你的 React 樹快速生成可序列化的值,而不是渲染需要構(gòu)建整個應(yīng)用程序的圖形 UI??紤]Link組件的這個例子測試

  1. import React from 'react';
  2. import renderer from 'react-test-renderer';
  3. import Link from '../Link.react';
  4. it('renders correctly', () => {
  5. const tree = renderer
  6. .create(<Link page="http://www.facebook.com">Facebook</Link>)
  7. .toJSON();
  8. expect(tree).toMatchSnapshot();
  9. });

第一次運行此測試時,Jest 創(chuàng)建一個如下所示的快照文件

  1. exports[`renders correctly 1`] = `
  2. <a
  3. className="normal"
  4. rel="external nofollow" target="_blank"
  5. onMouseEnter={[Function]}
  6. onMouseLeave={[Function]}
  7. >
  8. Facebook
  9. </a>
  10. `;

快照工件應(yīng)與代碼更改一起提交,并作為代碼審查過程的一部分進行審查。在代碼審查期間,Jest 使用友好的格式使快照更易于閱讀。在后續(xù)的測試運行中,Jest 會將渲染的輸出與之前的快照進行比較。如果它們匹配,則測試將通過。如果它們不匹配,則測試運行程序在您的代碼中發(fā)現(xiàn)了一個應(yīng)該修復(fù)的錯誤(在本例中,它是?<Link>?組件),或者實現(xiàn)已更改并且快照需要更新。

 注意:快照直接作用于呈現(xiàn)的數(shù)據(jù)——在我們的實例中,它是由?<Link />?的組件 page prop 傳遞給它。這也意味著即使任何其他文件在?<Link />?組件中缺少 props (比如:?App.js?)。它仍然可以通過測試,因為測試不知道?<Link />?組件的用法,并且它的范圍僅限于 ?Link.react.js?。此外,在其他快照測試中使用不同的 props 渲染相同的組件是不會影響第一個,因為測試不知道彼此。

有關(guān)快照測試如何工作以及我們?yōu)楹螛?gòu)建它的更多信息,請參閱發(fā)布博客文章。我們建議你閱讀這篇博文,以了解何時應(yīng)該使用快照測試。我們還建議你觀看有關(guān)使用 Jest 進行快照測試的蛋頭視頻。

更新快照

在引入錯誤后,快照測試什么時候失敗是很容易被發(fā)現(xiàn)。發(fā)生這種情況時,請繼續(xù)解決問題并確保你的快照測試再次通過?,F(xiàn)在,讓我們談?wù)効煺諟y試由于有意的實現(xiàn)更改而失敗的情況。

如果我們有意更改示例中的 Link 組件指向的地址,就會出現(xiàn)這樣一種情況。

  1. // Updated test case with a Link to a different address
  2. it('renders correctly', () => {
  3. const tree = renderer
  4. .create(<Link page="http://www.instagram.com">Instagram</Link>)
  5. .toJSON();
  6. expect(tree).toMatchSnapshot();
  7. });

在這種情況下,Jest 將打印以下輸出:

Snapshot Testing - 圖1

由于我們剛剛更新了我們的組件以指向不同的地址,因此期望該組件的快照發(fā)生變化是合理的。我們的快照測試用例失敗了,因為我們更新的組件的快照不再匹配這個測試用例的快照工件。

為了解決這個問題,我們需要更新我們的快照工件。你可以運行帶有標志的 Jest,該標志會告訴它重新生成快照:

  1. jest --updateSnapshot

繼續(xù)并通過運行上述命令接受更改。?-u?如果您愿意,您也可以使用等效的單字符標志來重新生成快照。這將為所有失敗的快照測試重新生成快照工件。如果由于意外錯誤導(dǎo)致我們有任何其他失敗的快照測試,我們需要在重新生成快照之前修復(fù)錯誤,以避免記錄錯誤行為的快照。

如果你想限制重新生成哪些快照測試用例,可以傳遞一個額外的?--testNamePattern?標志來僅為那些與模式匹配的測試重新記錄快照。

你可以通過克隆快照示例、修改?Link?組件和運行 Jest來嘗試此功能。

交互式快照模式

失敗的快照也可以在監(jiān)視模式下交互更新:

Snapshot Testing - 圖2

一旦你進入交互式快照模式,Jest 將一次一次地引導(dǎo)你完成失敗的快照,并讓你有機會查看失敗的輸出。

從這里你可以選擇更新該快照或跳到下一個:

Snapshot Testing - 圖3

完成后,Jest 會在返回觀看模式之前為你提供摘要:

Snapshot Testing - 圖4

內(nèi)聯(lián)快照

內(nèi)聯(lián)快照的行為與外部快照(?.snap?文件)相同,只是快照值會自動寫回到源代碼中。這意味著可以獲得自動生成快照的好處,而無需切換到外部文件以確保寫入了正確的值。

內(nèi)聯(lián)快照由Prettier提供支持。要使用內(nèi)聯(lián)快照,您必須已?prettier?在項目中安裝。寫入測試文件時,您的 Prettier 配置將得到尊重。
如果您?prettier?安裝在 Jest 找不到的位置,您可以使用"prettierPath"配置屬性告訴 Jest 如何找到它。

示例:

首先,編寫一個測試,?.toMatchInlineSnapshot()?不帶參數(shù)調(diào)用:

  1. it('renders correctly', () => {
  2. const tree = renderer
  3. .create(<Link page="https://prettier.io">Prettier</Link>)
  4. .toJSON();
  5. expect(tree).toMatchInlineSnapshot();
  6. });

下次運行 Jest 時,?tree?將進行評估,并將快照作為參數(shù)寫入?toMatchInlineSnapshot?:

  1. it('renders correctly', () => {
  2. const tree = renderer
  3. .create(<Link page="https://prettier.io">Prettier</Link>)
  4. .toJSON();
  5. expect(tree).toMatchInlineSnapshot(`
  6. <a
  7. className="normal"
  8. rel="external nofollow" target="_blank"
  9. onMouseEnter={[Function]}
  10. onMouseLeave={[Function]}
  11. >
  12. Prettier
  13. </a>
  14. `);
  15. });

這里的所有都是它的!甚至可以?--updateSnapshot?使用模式?u?鍵或使用鍵來更新快照?--watch?。

屬性匹配器

對象中通常有您想要生成快照的字段(如 ID 和日期)。如果你想嘗試對這些對象進行快照,它們將強制快照在每次運行時失?。?/p>

  1. it('will fail every time', () => {
  2. const user = {
  3. createdAt: new Date(),
  4. id: Math.floor(Math.random() * 20),
  5. name: 'LeBron James',
  6. };
  7. expect(user).toMatchSnapshot();
  8. });
  9. // Snapshot
  10. exports[`will fail every time 1`] = `
  11. Object {
  12. "createdAt": 2018-05-19T23:36:09.816Z,
  13. "id": 3,
  14. "name": "LeBron James",
  15. }
  16. `;

對于這些情況,Jest 允許為任何屬性提供非對稱匹配器。在寫入或測試快照之前檢查這些匹配器,然后將其保存到快照文件而不是接收到的值:

  1. it('will check the matchers and pass', () => {
  2. const user = {
  3. createdAt: new Date(),
  4. id: Math.floor(Math.random() * 20),
  5. name: 'LeBron James',
  6. };
  7. expect(user).toMatchSnapshot({
  8. createdAt: expect.any(Date),
  9. id: expect.any(Number),
  10. });
  11. });
  12. // Snapshot
  13. exports[`will check the matchers and pass 1`] = `
  14. Object {
  15. "createdAt": Any<Date>,
  16. "id": Any<Number>,
  17. "name": "LeBron James",
  18. }
  19. `;

任何不是匹配器的給定值都將被準確檢查并保存到快照中:

  1. it('will check the values and pass', () => {
  2. const user = {
  3. createdAt: new Date(),
  4. name: 'Bond... James Bond',
  5. };
  6. expect(user).toMatchSnapshot({
  7. createdAt: expect.any(Date),
  8. name: 'Bond... James Bond',
  9. });
  10. });
  11. // Snapshot
  12. exports[`will check the values and pass 1`] = `
  13. Object {
  14. "createdAt": Any<Date>,
  15. "name": 'Bond... James Bond',
  16. }
  17. `;

最佳實踐

快照是識別應(yīng)用程序中意外界面更改的絕佳工具——無論該界面是 API 響應(yīng)、UI、日志還是錯誤消息。與任何測試策略一樣,為了有效地使用它們,你應(yīng)該了解一些最佳實踐以及應(yīng)該遵循的指南。

1. 將快照視為代碼

提交快照并作為常規(guī)代碼審查過程的一部分進行審查。這意味著像對待項目中的任何其他類型的測試或代碼一樣對待快照。

通過保持快照的重點、簡短并使用強制執(zhí)行這些風(fēng)格約定的工具來確保您的快照可讀。

如前所述,Jest 用于pretty-format使快照具有人類可讀性,但你可能會發(fā)現(xiàn)引入其他工具很有用,例如eslint-plugin-jest使用其no-large-snapshots選項或snapshot-diff組件快照比較功能,以促進提交簡短、重點突出的斷言。

目標是讓審查拉取請求中的快照變得容易,并與在測試套件失敗時重新生成快照的習(xí)慣作斗爭,而不是檢查其失敗的根本原因。

2. 測試應(yīng)該是確定性的

你的測試應(yīng)該是確定性的。在未更改的組件上多次運行相同的測試應(yīng)該每次都產(chǎn)生相同的結(jié)果。你有責(zé)任確保生成的快照不包含特定于平臺的數(shù)據(jù)或其他非確定性數(shù)據(jù)。

例如,如果你有一個使用的Clock組件?Date.now()?,則每次運行測試用例時,從該組件生成的快照都會不同。在這種情況下,我們可以模擬 Date.now() 方法以在每次測試運行時返回一致的值:

  1. Date.now = jest.fn(() => 1482363367071);

現(xiàn)在,每次快照測試用例運行時,?Date.now()?都會?1482363367071?一致地返回。無論何時運行測試,這都會導(dǎo)致為此組件生成相同的快照。

3. 使用描述性快照名稱

始終努力為快照使用描述性測試和/或快照名稱。最好的名稱描述了預(yù)期的快照內(nèi)容。這使得審閱者在審閱期間更容易驗證快照,并且任何人都可以在更新之前知道過時的快照是否是正確的行為。

例如,比較:

  1. exports[`<UserName /> should handle some test case`] = `null`;
  2. exports[`<UserName /> should handle some other test case`] = `
  3. <div>
  4. Alan Turing
  5. </div>
  6. `;

到:

  1. exports[`<UserName /> should render null`] = `null`;
  2. exports[`<UserName /> should render Alan Turing`] = `
  3. <div>
  4. Alan Turing
  5. </div>
  6. `;

由于后者準確地描述了輸出中的預(yù)期內(nèi)容,因此更清楚地看到什么時候出錯:

  1. exports[`<UserName /> should render null`] = `
  2. <div>
  3. Alan Turing
  4. </div>
  5. `;
  6. exports[`<UserName /> should render Alan Turing`] = `null`;

經(jīng)常問的問題

快照是否在持續(xù)集成 (CI) 系統(tǒng)上自動寫入?

不,從 Jest 20 開始,當 Jest 在 CI 系統(tǒng)中運行時沒有明確傳遞?--updateSnapshot?. 預(yù)計所有快照都是在 CI 上運行的代碼的一部分,并且由于新快照會自動通過,因此它們不應(yīng)通過在 CI 系統(tǒng)上運行的測試。建議始終提交所有快照并將它們保留在版本控制中。

應(yīng)該提交快照文件嗎?

是的,所有快照文件都應(yīng)該與它們所涵蓋的模塊及其測試一起提交。它們應(yīng)該被視為測試的一部分,類似于 Jest 中任何其他斷言的值。事實上,快照代表了源模塊在任何給定時間點的狀態(tài)。這樣,當修改源模塊時,Jest 可以分辨出與之前的版本相比發(fā)生了什么變化。它還可以在代碼審查期間提供許多額外的上下文,審查人員可以在其中更好地研究你的更改。

快照測試僅適用于 React 組件嗎?

ReactReact Native組件是快照測試的一個很好的用例。但是,快照可以捕獲任何可序列化的值,并且應(yīng)該在目標是測試輸出是否正確的任何時候使用。Jest 存儲庫包含許多測試 Jest 本身輸出的示例、Jest 斷言庫的輸出以及來自 Jest 代碼庫各個部分的日志消息。請參閱Jest 存儲庫中對CLI 輸出進行快照的示例。

快照測試和視覺回歸測試有什么區(qū)別?

快照測試和視覺回歸測試是測試 UI 的兩種不同方式,它們用于不同的目的。視覺回歸測試工具獲取網(wǎng)頁的屏幕截圖并逐個像素地比較生成的圖像。使用 Snapshot 測試值被序列化,存儲在文本文件中,并使用 diff 算法進行比較。需要考慮不同的權(quán)衡,我們在Jest 博客中列出了構(gòu)建快照測試的原因。

快照測試會取代單元測試嗎?

快照測試只是 Jest 附帶的 20 多個斷言之一。快照測試的目的不是取代現(xiàn)有的單元測試,而是提供附加價值并使測試無痛。在某些情況下,快照測試可以潛在地消除對特定功能集(例如 React 組件)進行單元測試的需要,但它們也可以一起工作。

快照測試在生成文件的速度和大小方面的性能如何?

Jest 在重寫時考慮了性能,快照測試也不例外。由于快照存儲在文本文件中,因此這種測試方式既快速又可靠。Jest 為調(diào)用?toMatchSnapshot?匹配器的每個測試文件生成一個新文件??煺盏拇笮》浅P。鹤鳛閰⒖迹琂est 代碼庫本身中所有快照文件的大小都小于 300 KB。

如何解決快照文件中的沖突?

快照文件必須始終代表它們所覆蓋的模塊的當前狀態(tài)。因此,如果您正在合并兩個分支并在快照文件中遇到?jīng)_突,您可以手動解決沖突或通過運行 Jest 并檢查結(jié)果來更新快照文件。

是否可以將測試驅(qū)動的開發(fā)原則應(yīng)用于快照測試?

雖然可以手動寫入快照文件,但這通常是不可行的??煺沼兄诖_定測試覆蓋的模塊的輸出是否已更改,而不是首先提供設(shè)計代碼的指導(dǎo)。

代碼覆蓋率是否適用于快照測試?

是的,以及任何其他測試。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號