快照測試
當您想要確保使用者介面不會意外變更時,快照測試會是一個非常有用的工具。
典型的快照測試案例會呈現使用者介面元件,取得快照,然後將其與儲存在測試旁的參考快照檔案進行比較。如果兩個快照不匹配,測試就會失敗:變更可能是意外的,或者參考快照需要更新為使用者介面元件的新版本。
使用 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>
元件),應該修正這個錯誤,或者實作已變更,需要更新快照。
有關快照測試運作方式和我們為何建立它的更多資訊,請參閱 發佈部落格文章。我們建議閱讀 這篇部落格文章,以了解您應該何時使用快照測試。我們也建議觀看這個 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 元件?
React 和 React Native 元件是快照測試的良好使用案例。然而,快照可以擷取任何可序列化值,並且應該在任何目標是測試輸出是否正確時使用。Jest 儲存庫包含許多測試 Jest 本身輸出、Jest 斷言函式庫輸出以及 Jest 程式碼庫各個部分的記錄訊息的範例。請參閱 快照 CLI 輸出 在 Jest 儲存庫中的範例。
快照測試和視覺回歸測試之間的差異是什麼?
快照測試和視覺回歸測試是測試使用者介面的兩種不同方式,它們服務於不同的目的。視覺回歸測試工具擷取網頁的螢幕截圖,並逐像素比較產生的影像。使用快照測試,值會被序列化、儲存在文字檔中,並使用 diff 演算法進行比較。有不同的權衡需要考慮,我們在 Jest 部落格 中列出了建立快照測試的原因。
快照測試是否取代單元測試?
快照測試只是 Jest 附帶的 20 多個斷言之一。快照測試的目標不是取代現有的單元測試,而是提供額外的價值並使測試變得輕鬆。在某些情況下,快照測試有可能消除對特定功能集(例如 React 元件)進行單元測試的需要,但它們也可以一起使用。
快照測試在速度和產生檔案大小方面的效能如何?
Jest 已針對效能進行改寫,快照測試也不例外。由於快照儲存在文字檔案中,因此這種測試方式快速且可靠。Jest 會為呼叫 toMatchSnapshot
比對器的每個測試檔案產生一個新檔案。快照的大小相當小:作為參考,Jest 程式碼庫中所有快照檔案的大小小於 300 KB。
我該如何解決快照檔案中的衝突?
快照檔案必須始終代表其涵蓋模組的目前狀態。因此,如果您正在合併兩個分支,並在快照檔案中遇到衝突,您可以手動解決衝突或透過執行 Jest 並檢查結果來更新快照檔案。
是否可以使用快照測試來應用測試驅動開發原則?
雖然可以手動撰寫快照檔案,但通常無法使用。快照有助於找出測試涵蓋模組的輸出是否已變更,而不是在第一時間提供指導來設計程式碼。
程式碼覆蓋率是否適用於快照測試?
是的,與其他任何測試一樣。