跳至主要內容
版本:29.7

快照測試

當您想要確保使用者介面不會意外變更時,快照測試會是一個非常有用的工具。

典型的快照測試案例會呈現使用者介面元件,取得快照,然後將其與儲存在測試旁的參考快照檔案進行比較。如果兩個快照不匹配,測試就會失敗:變更可能是意外的,或者參考快照需要更新為使用者介面元件的新版本。

使用 Jest 進行快照測試

在測試 React 元件時,可以採取類似的方法。您可以使用測試呈現器快速產生 React 樹的可序列化值,而無需呈現圖形使用者介面,這需要建置整個應用程式。請考慮這個 範例測試,用於 連結元件

import renderer from 'react-test-renderer';
import Link from '../Link';

it('renders correctly', () => {
const tree = renderer
.create(<Link page="https://127.0.0.1">Facebook</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});

第一次執行此測試時,Jest 會建立一個 快照檔案,如下所示

exports[`renders correctly 1`] = `
<a
className="normal"
href="https://127.0.0.1"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;

快照成品應與程式碼變更一起提交,並在您的程式碼檢閱過程中進行檢閱。Jest 使用 pretty-format 在程式碼檢閱期間讓快照易於閱讀。在後續測試執行中,Jest 會將呈現的輸出與先前的快照進行比較。如果它們匹配,測試就會通過。如果它們不匹配,表示測試執行器在您的程式碼中找到一個錯誤(在本例中為 <Link> 元件),應該修正這個錯誤,或者實作已變更,需要更新快照。

快照直接涵蓋您要呈現的資料範圍,在我們的範例中,是傳遞了 page 屬性的 <Link> 元件。這表示即使任何其他檔案在 <Link> 元件中遺失屬性(例如 App.js),它仍會通過測試,因為測試不知道 <Link> 元件的用法,而且它只涵蓋 Link.js。此外,在其他快照測試中使用不同屬性呈現同一個元件,也不會影響第一個元件,因為測試彼此不知道。

資訊

有關快照測試運作方式和我們為何建立它的更多資訊,請參閱 發佈部落格文章。我們建議閱讀 這篇部落格文章,以了解您應該何時使用快照測試。我們也建議觀看這個 egghead 影片,了解 Jest 的快照測試。

更新快照

在引入錯誤後,很容易發現快照測試何時失敗。發生這種情況時,請繼續修正問題,並確保您的快照測試再次通過。現在,讓我們來討論由於故意的實作變更而導致快照測試失敗的情況。

如果我們故意變更範例中 Link 元件指向的網址,可能會出現這種情況。

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

在這種情況下,Jest 會列印此輸出

由於我們剛剛更新元件以指向不同的網址,因此可以合理預期此元件的快照會變更。我們的快照測試案例失敗,因為我們更新的元件快照不再與此測試案例的快照人工製品相符。

為了解決此問題,我們需要更新快照人工製品。您可以使用一個旗標執行 Jest,它會指示 Jest 重新產生快照

jest --updateSnapshot

繼續執行上述命令來接受變更。如果您願意,也可以使用等效的單一字元 -u 旗標來重新產生快照。這將為所有失敗的快照測試重新產生快照人工製品。如果我們有任何其他由於意外錯誤而導致的快照測試失敗,我們需要在重新產生快照之前修正錯誤,以避免記錄錯誤行為的快照。

如果你想限制重新生成哪些快照測試案例,你可以傳遞一個額外的 --testNamePattern 旗標,以僅重新記錄與該模式相符的那些測試的快照。

你可以透過複製 快照範例、修改 Link 元件,並執行 Jest 來試用此功能。

互動快照模式

失敗的快照也可以在監控模式中互動更新

一旦你進入互動快照模式,Jest 將一次一個測試引導你完成失敗的快照,並讓你審閱失敗的輸出。

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

完成後,Jest 將在你返回監控模式之前提供摘要

內嵌快照

內嵌快照的行為與外部快照(.snap 檔案)相同,除了快照值會自動寫回原始碼。這表示你可以獲得自動生成快照的好處,而無需切換到外部檔案來確保寫入正確的值。

範例

首先,你寫一個測試,呼叫 .toMatchInlineSnapshot() 而沒有參數

it('renders correctly', () => {
const tree = renderer
.create(<Link page="https://example.com">Example Site</Link>)
.toJSON();
expect(tree).toMatchInlineSnapshot();
});

下次執行 Jest 時,將會評估 tree,並且會寫入快照作為 toMatchInlineSnapshot 的參數

it('renders correctly', () => {
const tree = renderer
.create(<Link page="https://example.com">Example Site</Link>)
.toJSON();
expect(tree).toMatchInlineSnapshot(`
<a
className="normal"
href="https://example.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Example Site
</a>
`);
});

就這樣!你甚至可以使用 --updateSnapshot 或在 --watch 模式中使用 u 鍵來更新快照。

預設情況下,Jest 會處理將快照寫入你的原始碼。但是,如果你在專案中使用 prettier,Jest 會偵測到這一點,並將工作委派給 prettier(包括尊重你的設定)。

屬性比對器

通常在你要快照的物件中,有些欄位是產生的(例如 ID 和日期)。如果你嘗試快照這些物件,它們會強制快照在每次執行時都失敗

it('will fail every time', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};

expect(user).toMatchSnapshot();
});

// Snapshot
exports[`will fail every time 1`] = `
{
"createdAt": 2018-05-19T23:36:09.816Z,
"id": 3,
"name": "LeBron James",
}
`;

對於這些情況,Jest 允許為任何屬性提供非對稱比對器。這些比對器會在寫入或測試快照之前檢查,然後儲存到快照檔案中,而不是接收到的值

it('will check the matchers and pass', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};

expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
id: expect.any(Number),
});
});

// Snapshot
exports[`will check the matchers and pass 1`] = `
{
"createdAt": Any<Date>,
"id": Any<Number>,
"name": "LeBron James",
}
`;

任何不是比對器的給定值都將被精確檢查並儲存到快照中

it('will check the values and pass', () => {
const user = {
createdAt: new Date(),
name: 'Bond... James Bond',
};

expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
name: 'Bond... James Bond',
});
});

// Snapshot
exports[`will check the values and pass 1`] = `
{
"createdAt": Any<Date>,
"name": 'Bond... James Bond',
}
`;
提示

如果案例涉及的不是物件而是字串,則在測試快照之前,你需要自行替換該字串的隨機部分。
你可以使用例如 replace()正規表示式

const randomNumber = Math.round(Math.random() * 100);
const stringWithRandomData = `<div id="${randomNumber}">Lorem ipsum</div>`;
const stringWithConstantData = stringWithRandomData.replace(/id="\d+"/, 123);
expect(stringWithConstantData).toMatchSnapshot();

其他方法是使用 快照序列化器模擬 負責產生你正在快照的程式碼隨機部分的函式庫。

最佳實務

快照是識別應用程式內部意外介面變更的絕佳工具,無論該介面是 API 回應、UI、記錄或錯誤訊息。與任何測試策略一樣,有一些最佳實務你應該知道,以及你應該遵循的準則,才能有效地使用它們。

1. 將快照視為程式碼

提交快照並在定期程式碼檢閱流程中檢閱它們。這表示將快照視為你專案中任何其他類型的測試或程式碼。

透過保持快照的重點、簡短,以及使用強制執行這些風格慣例的工具,來確保你的快照具有可讀性。

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

目標是讓大家可以輕鬆檢閱拉取要求中的快照,並對抗在測試套件失敗時重新產生快照的習慣,而不是檢查失敗的根本原因。

2. 測試應具有確定性

您的測試應具有確定性。在未變更的元件上多次執行相同的測試,每次都應產生相同的結果。您有責任確保產生的快照不包含特定於平台或其他非確定性資料。

例如,如果您有一個使用 Date.now()Clock 元件,從此元件產生的快照會在每次執行測試案例時都不同。在這種情況下,我們可以 模擬 Date.now() 方法,讓它在每次執行測試時都傳回一致的值

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

現在,每次執行快照測試案例時,Date.now() 都會持續傳回 1482363367071。這將導致為此元件產生相同的快照,無論何時執行測試。

3. 使用具描述性的快照名稱

始終盡量為快照使用具描述性的測試和/或快照名稱。最佳名稱會描述預期的快照內容。這讓審閱者在審閱期間更容易驗證快照,也讓任何人可以在更新過時快照前知道它是否為正確的行為。

例如,比較

exports[`<UserName /> should handle some test case`] = `null`;

exports[`<UserName /> should handle some other test case`] = `
<div>
Alan Turing
</div>
`;

exports[`<UserName /> should render null`] = `null`;

exports[`<UserName /> should render Alan Turing`] = `
<div>
Alan Turing
</div>
`;

由於後者精確描述輸出中預期的內容,因此當它錯誤時更容易看出

exports[`<UserName /> should render null`] = `
<div>
Alan Turing
</div>
`;

exports[`<UserName /> should render Alan Turing`] = `null`;

常見問題

快照會在持續整合 (CI) 系統上自動寫入嗎?

不會,在 Jest 20 中,如果在 CI 系統中執行 Jest 而沒有明確傳遞 --updateSnapshot,Jest 中的快照不會自動寫入。預期所有快照都是 CI 上執行的程式碼的一部分,而且由於新的快照會自動通過,因此它們不應通過 CI 系統上的測試執行。建議始終提交所有快照,並將它們保留在版本控制中。

快照檔案是否應該提交?

是的,所有快照檔案都應該與它們涵蓋的模組及其測試一起提交。它們應被視為測試的一部分,類似於 Jest 中任何其他斷言的值。事實上,快照表示來源模組在任何給定時間點的狀態。這樣一來,當來源模組被修改時,Jest 可以看出與前一個版本相比有哪些變更。它還可以在程式碼審查期間提供許多額外的內容,讓審查者可以更深入地研究你的變更。

快照測試是否只適用於 React 元件?

ReactReact Native 元件是快照測試的良好使用案例。然而,快照可以擷取任何可序列化值,並且應該在任何目標是測試輸出是否正確時使用。Jest 儲存庫包含許多測試 Jest 本身輸出、Jest 斷言函式庫輸出以及 Jest 程式碼庫各個部分的記錄訊息的範例。請參閱 快照 CLI 輸出 在 Jest 儲存庫中的範例。

快照測試和視覺回歸測試之間的差異是什麼?

快照測試和視覺回歸測試是測試使用者介面的兩種不同方式,它們服務於不同的目的。視覺回歸測試工具擷取網頁的螢幕截圖,並逐像素比較產生的影像。使用快照測試,值會被序列化、儲存在文字檔中,並使用 diff 演算法進行比較。有不同的權衡需要考慮,我們在 Jest 部落格 中列出了建立快照測試的原因。

快照測試是否取代單元測試?

快照測試只是 Jest 附帶的 20 多個斷言之一。快照測試的目標不是取代現有的單元測試,而是提供額外的價值並使測試變得輕鬆。在某些情況下,快照測試有可能消除對特定功能集(例如 React 元件)進行單元測試的需要,但它們也可以一起使用。

快照測試在速度和產生檔案大小方面的效能如何?

Jest 已針對效能進行改寫,快照測試也不例外。由於快照儲存在文字檔案中,因此這種測試方式快速且可靠。Jest 會為呼叫 toMatchSnapshot 比對器的每個測試檔案產生一個新檔案。快照的大小相當小:作為參考,Jest 程式碼庫中所有快照檔案的大小小於 300 KB。

我該如何解決快照檔案中的衝突?

快照檔案必須始終代表其涵蓋模組的目前狀態。因此,如果您正在合併兩個分支,並在快照檔案中遇到衝突,您可以手動解決衝突或透過執行 Jest 並檢查結果來更新快照檔案。

是否可以使用快照測試來應用測試驅動開發原則?

雖然可以手動撰寫快照檔案,但通常無法使用。快照有助於找出測試涵蓋模組的輸出是否已變更,而不是在第一時間提供指導來設計程式碼。

程式碼覆蓋率是否適用於快照測試?

是的,與其他任何測試一樣。