close

前言

你的工作中有寫測試麼?今日前端早讀課文章由 @goldbergyoni 分享。

正文從這開始~~

【第2732期】JavaScript & Node.js 的測試最佳實踐 - 第一章: 測試剖析

第二章:後端測試⚪ ️2.1 豐富您的測試組合:不局限於單元測試和測試金字塔

✅ 建議:測試金字塔,雖然已經有超過 10 年的歷史了,但是它仍是一個很好的相關模型,它提出了三種測試類型,並且影響了大多數開發人員的測試策略。與此同時,大量閃亮的新測試技術出現了,並隱藏在測試金字塔的陰影下。考慮到近 10 年來我們所看到的所有巨變 (微服務、雲、無服務器),這個非常老的模型是否仍能適用於所有類型的應用?測試界不應該考慮歡迎新的測試技術嗎?

請不要誤解,在 2019 年,測試金字塔、TDD、單測仍然是強大的技術,且對於大多數應用仍是最佳選擇。但是像其他模型一樣,儘管它有用,但是一定會在某些時候出問題。例如,我們有一個 IOT 應用,將許多事件注入一個 Kafka/RabbitMQ 這樣的消息總線中,然後這些事件流入一些數據倉庫並被分析 UI 查詢。我們真的需要花費 50% 的測試預算去為這個幾乎沒有邏輯的集成中心化的應用寫單測嗎?隨着應用類型 (機器人、密碼、Alexa-skills) 的多樣性增長,測試金字塔可能將不再是某些場景的最佳選擇了。

是時候豐富你的測試組合併了解更多的測試類型了(下一節會給你一些小建議),這些類似於測試金字塔的思維模型與你所面臨的現實問題更匹配(' 嘿,我們的 API 掛了,試試消費者驅動的合同測試!'),讓您的測試多樣化,比如建立基於風險分析的檢查模型 —— 評估可能出現問題的位置,並提供一些預防措施以減輕這些潛在風險。

需要注意的是:軟件世界中的 TDD 模型面臨兩個極端的態度,一些人鼓吹到處使用它,另一些人則認為它是魔鬼。每個說絕對的人都是錯的 :]

❌ 否則:你將錯過一些超高投入產出比的工具,比如 Fuzz、lint、mutation 這些工具只需 10 分鐘配置就能貢獻價值。

👏 正例: Cindy Sridharan 在她的文章 「測試微服務 —— 理智的方式」 中提出了一個豐富的測試組合

☺️Example: YouTube: 「Beyond Unit Tests: 5 Shiny Node.JS Test Types (2018)」 (Yoni Goldberg)

⚪ 2.2 組件化測試可能是最有效的利器

✅ 建議:應用的每個單元測試僅能覆蓋應用的一小部分,覆蓋全部會非常麻煩,而端到端測試可以很輕鬆地覆蓋大量區域,但是比較脆弱而且很慢。何不找一個平衡點:寫一些比單測大,但是比端到端測試小的測試。組件測試是測試世界的一顆遺珠 —— 它找到了兩個模式的最佳平衡點:不錯的性能和使用 TDD 模式的可能性 + 真實且強大的覆蓋率。

組件測試關注於微服務 「單元」,他們反對 API,不 mock 任何屬於微服務本身的東西(比如:真實的 DB,甚至是該 DB 的內存版本)但是 stub 所有外部的東西比如調用其他微服務。這麼做,我們測試我們部署的部分,由外而內地覆蓋應用,節省大量時間。

❌ 否則:你可能花了好幾天寫單測,卻發現僅得到了 20% 的系統覆蓋率。

👏 正例:使用 Supertest 測試 Express API (快速、覆蓋很多層)

⚪ 2.3 保證新的 release 不會破壞 API 的使用

✅ 建議:你的微服務有很多的客戶,而你為了兼容性運行着該服務的很多版本(keeping everyone happy)。當你改了某個字段後 「砰!」,依賴該字段的幾個重要的客戶炸鍋了。服務端滿足所有客戶的期望是非常難的 —— 另一方面,客戶無法發起測試,因為服務端控制着 release。Consumer-driven contracts and the framework PACT 誕生了,它以一種破壞性的方式規範了這一流程 —— 不再由服務端定義測試計劃,而是客戶端決定服務端的測試!PACT 可以記錄客戶端的期望 ——「中間人(Broker)」,並放置到共享空間,服務端可以 pull 下來這寫期望並利用 PACT 的庫在所有的版本中檢測是否有被破壞的契約 —— 有客戶端的期望沒有被滿足。通過這種方式,所有客戶端 - 服務端不匹配的 API 將會在 構建 / CI 階段被 catch 到,從而減少你大量的煩惱。

❌ 否則:所有的變更將帶來繁瑣的手動測試,導致開發者懼怕發布。

👏 正例:

⚪ 2.4 單獨測試你的中間件

✅ 建議:許多人拒絕測試中間件,是因為它們僅占據系統的一小部分而且依賴真實的 Express server。這兩個原因都不正確 —— 中間件雖然小,但是影響全部或者大部分請求,而且可以被簡單地作為純函數測試(參數為 {req,res} JS 對象)。測試中間件函數,你僅需調用它,並且 spy (比如使用 Sinon) {req,res} 的交互以保證函數執行了正確的行為。node-mock-http 庫更進一步:它還監聽了 {req,res} 對象的行為。例如,它可以斷言 res 對象上的 http 狀態是否符合預期。(看下面的例子)

❌ 否則: Express 中間件上的一個 bug === 所有或者大部分請求的 bug

👏正例:隔離地測試中間件,不發出網絡調用或喚醒整個 Express 機器

//the middleware we want to test const unitUnderTest = require('./middleware') const httpMocks = require('node-mocks-http'); //Jest syntax, equivelant to describe() & it() in Mocha test('A request without authentication header, should return http status 403', () => { const request = httpMocks.createRequest({ method: 'GET', url: '/user/42', headers: { authentication: '' } }); const response = httpMocks.createResponse(); unitUnderTest(request, response); expect(response.statusCode).toBe(403); });⚪ 2.5 使用靜態分析工具度量並指導重構

✅ 建議:使用靜態度量工具可以幫助你客觀地提升代碼質量並使其可維護。你可以將靜態分析工具放在你的 CI 中。除了普通 linting 外,它的主要賣點是結合多文件的上下文來檢查質量(例如:發現重複定義)、執行高級分析(例如:代碼複雜度)以及跟蹤 code issue 的歷史和進度。有兩個工具供你使用:Sonarqube (2,600+ stars) and Code Climate (1,500+ stars)

貢獻:: Keith Holliday

❌ 否則:由於代碼質量差,再新的庫和 feature 也無法拯救你的 bug 和性能。

👏 正例: CodeClimate —— 一個用於發現複雜方法的商業工具

⚪ 2.6 你是否準備好迎接 Node 相關的噪聲

✅ 建議:怪異的是,大部分軟件測試僅關注邏輯和數據,但是最糟糕(而且很難減輕)的往往是基礎設施問題。例如,你測試過當你的進程存儲過載、服務器 / 進程掛掉時的表現嗎?或者你的監控系統會檢測到 API 減慢 50% 了嗎?為了測試及減輕類似問題,Netflix 設立了 噪聲工程。它的目的是為我們的系統在故障問題下的健壯性提供意識、框架及工具。比如,著名的工具之一 噪聲猴子,隨機地殺掉服務以保證我們的服務仍服務於用戶,而不是僅依賴一個單獨的服務器(Kubernetes 也有一個版本 kube-monkey 用於殺掉 pods)。這些工具都是作用於服務器 / 平台層面,但如果你想測試及生產純粹的 Node 噪聲比如檢查你的 Node 進程如何處理未知錯誤、未知的 promise rejection、v8 內存超過 1.7GB 的限制以及當事件循環經常卡住後你的 UX 是否仍正常運行?為了解決上面提到的這些問題, node-chaos (alpha) 提供了各種 Node 相關的噪聲。

❌ 否則:墨菲定律一定會無情地砸中你的產品,跑不掉的。

👏 正例: Node-chaos 可以生成所有類型的 Node.js 問題,因此您可以測試您的應用程序對混亂的適應能力

⚪ 2.7 不要寫全局的 fixtures 和 seeds,而是放在每個測試中

✅ 建議:參照黃金法則,每條測試需要在它自己的 DB 行中運行避免互相污染。現實中,這條規則經常被打破:為了性能提升而在執行測試前全局初始化數據庫 (也被稱為『test fixture』)。儘管性能很重要,但是它可以通過後面講的「分組件測試」緩和。為了減輕複雜度,我們可以在每個測試中只初始化自己需要的數據。除非性能問題真的非常顯著,那麼可以做一定的妥協 —— 僅在全局放不會改變的數據(比如 query)。

❌ 否則:一部分測試掛了,我們的團隊花費大量寶貴時間後發現,是由於兩個測試同時改變了同一個 seed 數據導致的。

👎 反例:用例之間不獨立,而是依賴同一個全局鈎子來生成全局 DB 數據

before(() => { //adding sites and admins data to our DB. Where is the data? outside. At some external json or migration framework await DB.AddSeedDataFromJson('seed.json'); }); it("When updating site name, get successful confirmation", async () => { //I know that site name "portal" exists - I saw it in the seed files const siteToUpdate = await SiteService.getSiteByName("Portal"); const updateNameResult = await SiteService.changeName(siteToUpdate, "newName"); expect(updateNameResult).to.be(true); }); it("When querying by site name, get the right site", async () => { //I know that site name "portal" exists - I saw it in the seed files const siteToCheck = await SiteService.getSiteByName("Portal"); expect(siteToCheck.name).to.be.equal("Portal"); //Failure! The previous test change the name :[ });

👏 正例:每個用例操作它自己的數據集

it("When updating site name, get successful confirmation", async () => { //test is adding a fresh new records and acting on the records only const siteUnderTest = await SiteService.addSite({ name: "siteForUpdateTest" }); const updateNameResult = await SiteService.changeName(siteUnderTest, "newName"); expect(updateNameResult).to.be(true); });

關於本文原文:https://github.com/goldbergyoni/javascript-testing-best-practices/blob/master/readme-zh-CN.md

關於【實踐】相關推薦,歡迎讀者們自薦投稿,前端早讀課等你來

【第2724期】前後端數據接口協作提效實踐【第2695期】基於微前端qiankun的多頁簽緩存方案實踐

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()