Jest 快照測(cè)試

2021-09-18 20:16 更新

每當(dāng)你想要確保你的UI不會(huì)有意外的改變,快照測(cè)試是非常有用的工具。

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

使用 Jest 進(jìn)行快照測(cè)試

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

  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. });

第一次運(yùn)行此測(cè)試時(shí),Jest 創(chuàng)建一個(gè)如下所示的快照文件

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

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

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

更新快照

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

如果我們有意更改示例中的 Link 組件指向的地址,就會(huì)出現(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ā)生變化是合理的。我們的快照測(cè)試用例失敗了,因?yàn)槲覀兏碌慕M件的快照不再匹配這個(gè)測(cè)試用例的快照工件。

為了解決這個(gè)問題,我們需要更新我們的快照工件。你可以運(yùn)行帶有標(biāo)志的 Jest,該標(biāo)志會(huì)告訴它重新生成快照:

  1. jest --updateSnapshot

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

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

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

交互式快照模式

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

Snapshot Testing - 圖2

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

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

Snapshot Testing - 圖3

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

Snapshot Testing - 圖4

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

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

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

示例:

首先,編寫一個(gè)測(cè)試,?.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. });

下次運(yùn)行 Jest 時(shí),?tree?將進(jìn)行評(píng)估,并將快照作為參數(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?。

屬性匹配器

對(duì)象中通常有您想要生成快照的字段(如 ID 和日期)。如果你想嘗試對(duì)這些對(duì)象進(jìn)行快照,它們將強(qiáng)制快照在每次運(yùn)行時(shí)失?。?/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. `;

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

  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. `;

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

  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. `;

最佳實(shí)踐

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

1. 將快照視為代碼

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

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

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

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

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

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

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

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

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

3. 使用描述性快照名稱

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

例如,比較:

  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. `;

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

  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)上自動(dòng)寫入?

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

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

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

快照測(cè)試僅適用于 React 組件嗎?

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

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

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

快照測(cè)試會(huì)取代單元測(cè)試嗎?

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

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

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

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

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

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

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

代碼覆蓋率是否適用于快照測(cè)試?

是的,以及任何其他測(cè)試。


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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)