JavaScript >> Javascript 文檔 >  >> JavaScript

JavaScript 單元測試的奇特案例

JavaScript 單元測試和一般的前端測試對我來說是一個有趣的話題。在我使用 Visual Basic.NET 開發並使用 nUnit 進行測試的工作中,我立即愛上了單元測試。不久前,我創建了 YUI Test JavaScript 測試框架(用於 YUI 2.x 和 YUI 3.x)來嘗試將相同類型的功能引入 Web 瀏覽器。在過去的幾年裡,我既寫過也談過在 JavaScript 中應用單元測試。儘管如此,它仍然是一個尚未被適當探索的領域,更不用說用最佳實踐來征服了。那麼有什麼問題呢?

問題

總結一下,問題出在瀏覽器上。或者更確切地說,問題在於瀏覽器太多,而且它們都有自己的怪癖。傳統上非常適合單元測試的語言都在一個通用且穩定的運行時環境中運行,其中某些操作的效果(和副作用)得到了很好的理解。 JavaScript 沒有那麼奢侈。

為 Web 應用程序編寫的 JavaScript 往往也有許多依賴項。單獨的 JavaScript 在 Web 上沒有用,它只有在結合 HTML 和 CSS 並通過使用 DOM 和 BOM(定義)時才有用。因此,您不僅需要擔心 JavaScript 引擎的差異(只需看看微軟與 ECMAScript 標準的差異),您還必須擔心頁面呈現方式以及訪問和操作 DOM 元素的方式的差異。僅這一點就讓測試 JavaScript 的任務變得異常艱鉅。

單元測試的精神

在它的核心,單元測試應該測試單個原子的功能“單元”,而不依賴於其他任何東西。消除依賴關係很重要,因為依賴關係中的失敗可能會錯誤地顯示為您正在測試的單元中的失敗。例如,如果調用 JSON.stringify() 返回錯誤的值,這不是你的代碼的錯。您的代碼總是需要 JSON.stringify() 正常工作並返回正確的值,如果沒有,那就是你無法控制的錯誤。

JavaScript 在瀏覽器中的依賴特性使得除了最底層的實用程序函數之外的任何東西都很難完成真正的單元測試。 JavaScript 庫實際上很容易進行單元測試,因為每個方法通常在給定一組輸入的情況下執行一個離散操作。 JavaScript 庫代碼沒有任何業務邏輯或直接了解 DOM 元素、CSS 和 JavaScript 本身之間的關係。這就是為什麼 YUI 等庫擁有如此全面的單元測試套件的原因:測試非常容易編寫和執行。

更大的問題是對運行 Web 應用程序的 JavaScript 代碼進行單元測試。由於 HTML 和 CSS 的相互關係,您開始遇到嚴重的依賴問題。 JavaScript 代碼不只是簡單地操作數據;它預計將在 Web 應用程序環境中運行。要進行真正的單元測試,您需要將整個 Web 應用程序環境存根,以便讓代碼執行。然後,你測試什麼?很多時候,您都在測試用戶界面如何響應用戶輸入,這意味著您實際上已經開始跨入功能測試領域(也稱為系統測試)。

如何幫助自己

開始 JavaScript 單元測試的最佳方式是以盡可能可測試的方式編寫代碼。我在我最近的演講 Scalable JavaScript Application Architecture (slides) 和我現在相當老的演講 Maintainable JavaScript (slides) 中談到了這一點。主要目標是盡可能消除依賴關係,您可以通過多種方式做到這一點:

  • 不要使用全局變量。 任何時候你需要測試使用全局變量的東西,你都需要重新創建它們,這樣代碼才能運行。省去你的麻煩。
  • 不要修改不屬於你的對象。 這適用於本機對象原型。再次,這會創建環境依賴項,當您想要測試代碼時需要重新創建這些依賴項。
  • **創建小塊功能。 **代碼片段越原子,它們對環境的依賴就越少。嘗試以允許您傳遞必要數據而不是期望它位於特定位置的方式將功能按邏輯分組在一起。
  • **依賴庫來實現核心功能。 **該庫充當您的代碼和環境之間的抽象,使其更容易存根或模擬功能以消除依賴關係。

在瀏覽器中運行測試

假設您已經以模塊化方式編寫代碼並且現在編寫了良好的測試,那麼下一步就是運行它們。我怎麼強調在瀏覽器中運行 JavaScript 代碼的重要性也不過分 .事實上,你可以運行測試的瀏覽器越多,你的情況就越好。我強烈建議至少從 A 級瀏覽器開始。

在使用 Rhino 在命令行上運行 JavaScript 單元測試時,我會定期得到幫助。雖然有可能,但我強烈建議反對 這樣做。如果您的 JavaScript 旨在在 Web 瀏覽器中運行,則應在 Web 瀏覽器中對其進行測試。 Rhino 是一個與任何瀏覽器完全不同的環境,事實上,它不是任何現有瀏覽器的 JavaScript 引擎(它是 SpiderMonkey 的 Java 端口,基於 C 的庫,是 3.5 版之前的 Firefox 的 JavaScript 引擎) .在 Rhino 中測試 JavaScript 代碼只會告訴你代碼在 Rhino 中可以運行,它不會告訴你代碼可以在任何瀏覽器中運行。

有些人在嘗試將命令行 JavaScript 單元測試引入世界時遇到了很多麻煩。 John Resig 創建了 env.js,這是一個 JavaScript 實用程序,它在 Rhino 中構建了許多常見的瀏覽器環境。有趣的是,您再次處理了一個不存在於野外的瀏覽器環境。我已經看到測試在所有瀏覽器中都運行良好,但在 env.js 驅動的 Rhino 環境中卻慘遭失敗。在最終不會部署到的環境中測試代碼沒有真正的價值。

更可怕的是 Crosscheck,這是一個基於 Java 的系統,它聲稱可以在多個瀏覽器中測試您的代碼,而無需實際使用瀏覽器。 Crosscheck 由 The Frontside Software, Inc. 創建,試圖用 Java 重新創建 Internet Explorer 6、Firefox 1 和 Firefox 1.5 的瀏覽器環境。正如你所預料的那樣,Crosscheck 依賴 Rhino 作為它的 JavaScript 引擎,然後繼續構建每個瀏覽器環境。當然,這是一個雄心勃勃的想法,但現在你離真相更遠了:你依賴於別人對瀏覽器怪癖的理解來作為測試的基礎。我從事 Web 開發已經很長時間了,但即使是我也無法坐下來列出每個瀏覽器的怪癖。結果是您在幾個與現實沒有真正關聯的神話般的瀏覽器環境中進行測試。

我再說一遍,設計用於在 Web 瀏覽器中運行的 JavaScript 代碼應該在 Web 瀏覽器中進行測試。所有代碼都應在要部署的環境中進行測試。如果您的 JavaScript 代碼將部署到 Rhino,那麼請務必在 Rhino 中進行測試。但這是您應該在 Rhino(或任何其他命令行 JavaScript 引擎)中測試 JavaScript 代碼的唯一原因。

這是自動化,愚蠢

命令行工具不斷嘗試出現的真正原因是為了自動化。當開發人員坐在他的電腦前並在瀏覽器中運行測試時,單元測試過程非常簡單。但這是非常多餘的,當然,也很無聊。如果測試定期自動運行並記錄結果會容易得多。確實,命令行的訴求是將測試運行集成到持續集成(CI)系統中。

我聽到最多的兩個 CI 系統是 CruiseControl 和 Hudson。兩者都以類似的方式工作,定期運行與您的構建相關的一系列任務。他們能夠簽出代碼、運行腳本,當然還有執行命令行操作。命令行實用程序非常適合這些系統,因為可以輕鬆監控輸出的完成和錯誤。這代表了一個主要問題,因為人們使用的大多數瀏覽器都是基於 GUI 的(不過,Lynx 仍然存在)。

幸運的是,JavaScript 測試還有另一項運動,專注於命令行啟動但仍然基於瀏覽器的測試。引領潮流的是 Selenium,主要用於功能測試的工具通常很有用,因為它可以從命令行運行,並且可以在瀏覽器中執行 JavaScript。這意味著,從命令行,您可以使用 Selenium 啟動瀏覽器、導航到特定頁面、運行 JavaScript 命令並檢查頁面發生的情況。更重要的是,您可以使用 Selenium Remote Control 啟動任意數量的瀏覽器並執行相同的測試。這些結果可以傳回命令行界面,創建與 CI 系統的無縫集成。這是我目前正在做更多研究的領域。敬請期待!

最近出現的另一個有趣的工具是 TestSwarm。 TestSwarm 的方法與 Selenium 不同。 TestSwarm 不是手動啟動瀏覽器並將它們導航到頁面,而是依賴於已經設置好的瀏覽器並附加到 TestSwarm 服務器。然後瀏覽器可以輪詢服務器以查看是否有任何必須處理的新作業。優點是您只需打開瀏覽器並將其指向 TestSwarm 服務器即可添加新瀏覽器。由於瀏覽器與系統的耦合非常鬆散,因此升級以包含新瀏覽器非常簡單。

TestSwarm 還支持眾包測試。任何想要幫助測試產品的人都可以加入一個群體並自願讓瀏覽器保持打開狀態進行測試。

未來

JavaScript 單元測試的問題目前還沒有真正接近解決。作為一個整體,Web 開發人員在過去兩年中在將單元測試帶到 Web 開發討論的最前沿方面取得了重大進展。在這一點上,所有主要的 JavaScript 庫都附帶了 JavaScript 單元測試框架,這是一個很好的跡象。不過,少量的工具很好地表明了這個學科目前正處於起步階段。

正如我之前所說,這是我目前在雅虎內外都在大力研究的一個領域。我希望在明年取得重大進展,並與大家分享我的發現。


Tutorial JavaScript 教程
  1. React Portal:靈活的模式實現

  2. 抓取無限加載(即:Product Hunt)頁面!不使用ajax!

  3. 帶有過濾器的 vue v-for 給出錯誤

  4. 構建時間 CSS-in-JS:解釋

  5. npmbomb 有什麼新功能?

  6. 我討厭做開發者的 40 個理由

  7. Supabase:八個月的建設。

  1. 在 Vue.js 中單擊 JSON 呈現列表上的事件過濾

  2. 使用 ajax 使用 JSON 數據發出 POST 請求是否安全?

  3. 使用 React 在前端進行 AB 測試

  4. 生產環境中的 Node.js 監控 - 電子書

  5. 如何阻止對我的 JavaScript 文件的直接訪問?

  6. 在一篇博客中使用 TypeScript

  7. 15 個以上的 Chrome 擴展,你應該在你的口袋裡。

  1. JavaScript 傳播語法 |帶有可迭代對象的示例

  2. 捕獲 Discord 網關意圖

  3. Unicode 和 UTF-8 簡介

  4. 道具和狀態有什麼區別?