測試簡介

已發表: 2021-06-16

歡迎來到我們關於與測試相關的所有內容的新博客系列。 希望這些博客文章能讓您大致了解我們在 Mediatoolkit 是如何編寫它們的,以及為什麼。 在深入研究示例之前,讓我們從一些基本定義、想法和編寫測試的好處開始。

那裡有什麼樣的測試?

並非所有測試都是平等的。 有不同種類的測試用於不同的目的。 雖然這篇文章主要關注單元測試,但我們應該意識到它們之間的差異並了解它們的好處。

1. 手動測試

手動測試涉及由測試人員手動執行的測試。 這些通常由質量保證部門或開發人員自己完成。 QA 應該被視為最後一道防線,而不是主要的應用程序測試人員(QA 將手動測試作為 UI 測試的主要方式是這種情況的有效例外)。

當開發人員想要在本地運行一些東西查看系統如何運行而不將任何代碼推送到暫存階段時,開發人員通常會編寫手動測試,或者上帝保佑你,生產。 然而,這些測試的目標並不是代碼的健壯性。 這些測試的唯一作用是捕捉錯誤並確保您的產品質量

2. 集成測試

系統由多個組件組成,或者至少它們應該是。 集成測試檢查這些組件的公共 API 。 不僅僅是 REST API,還有任何公開的 API,比如你的 Kafka 主題通信。

如果一個組件“說”它以某種格式輸出關於某個主題的消息,那就是它的公共 API 或合約。 集成測試正在檢查多個單獨開發的組件是否可以通信,以及它們的合同是否作為一個組匹配。

如果您只單獨測試這些組件,它們的個別行為可能會正常工作,但是當您嘗試將它們連接到更大的組中時,您會發現它們的合同不同

3.端到端(E2E)測試

端到端測試是保證最終產品的用戶體驗流程。 當今系統的複雜性很難用測試來覆蓋。 許多系統相互依賴,E2E 測試確保產品達到預期效果。 QA 通過檢查最終用戶流程並檢查所有系統是否按預期運行來驗證產品的正確性。

4.單元測試

單元測試是任何可靠軟件的支柱。 它們為其他測試奠定了基礎。 他們測試單個單元。

開發人員可能會將單元的定義誤認為是單一的方法。 單元測試應該測試可能由多個不同類組成的組件的行為。 其他組件可訪問的公共方法需要測試,不要測試受保護的方法或類。 它們是實現細節,而不是您的公共 API 的一部分

為什麼我們還要測試?

如果我們編寫好的測試並經常編寫它們,我們可以在產品出現之前確保它的質量。

隨著我們系統的老化,測試的好處變得越來越明顯。 我們的環境變得可靠,它們節省了開發時間,並在不可避免地出現問題時減少了很多麻煩。 當您的同事能夠查看您的測試並了解您的代碼應該做什麼和不應該做什麼而不需要手動運行時,他們會很感激他們。

健壯的代碼

在我們開始談論“健壯代碼”之前,我們應該如何定義它? 是什麼讓代碼健壯? 這是否意味著我們應該進行防禦性編程並考慮其他開發人員可能會如何濫用我們的代碼?

健壯的代碼總是簡單而乾淨的。 簡單是健壯的,複雜是脆弱的。 我們應該處理無效的輸入,但這並不意味著我們需要防禦性地編程並且不信任我們的團隊。

高爾定律:一個有效的複雜系統總是被發現是從一個有效的簡單系統演變而來的。

相反的命題似乎也是正確的:從零開始設計的複雜系統永遠不會工作,也無法使其工作。 你必須重新開始,從一個工作簡單的系統開始。

安全地重構

當您的代碼被測試覆蓋時,您不再害怕更改現有代碼。 每次更改後,您都可以運行測試並確保沒有破壞任何東西。 當您進行測試時,您不必進行防禦性編程。

沒有測試的重構正在走下一個滑坡,最終會在不眠之夜和周日工作中結束。 這個話題太寬泛,無法在此涵蓋,未來值得單獨發表一篇博文。

我們怎麼不寫測試?

知道如何不編寫單元測試與如何編寫單元測試同樣重要。

  • 編寫打印方法調用結果的測試不是測試,因為我們不驗證所需的結果
  • 如果您的測試從 Documents 文件夾中的文件讀取數據,則它不是真正的單元測試,因為測試不應依賴於環境。
  • 任何開發人員都應該能夠檢查您的代碼並成功運行測試,而無需執行任何其他操作。
  • 每個單元測試都應該獨立於其他測試。 這意味著測試的執行順序也不重要。
  • 如果我們不更改任何代碼,多次運行測試應該總是以相同的結果結束
  • 單元測試應該測試行為,而不是單獨的方法調用。 並非每個類和方法都需要進行測試。

行為是產生系統用戶需要的真正價值的東西。 您的用戶是否需要知道productFactory.create()在調用兩次時是否創建了相同的對象,或者是否使用某些參數調用了您的存儲庫? 可能不會,但仍然有許多開發人員編寫了這些類型的測試。

如果您的測試看起來像那樣,它們與您的實現緊密耦合。 每次您想要更改實現的細節時,您都需要更新您的測試,即使行為是相同的。 你的測試應該只在行為改變時改變,而不是實現細節。 換句話說,測試的代碼做了什麼,而不是它是怎麼做的。

我們如何編寫測試?

我們的測試必須遵循最佳代碼實踐,它們必須獨立於環境並且需要快速執行

保持測試執行時間盡可能短很重要。 每個測試不應超過幾毫秒。 當測試執行時間過長時,人們往往會跳過它們,只依賴他們的 CI 服務器,例如 Jenkins,當它無法構建他們的部署可執行文件時會大驚小怪。

每個測試由3 個“A”部分(AAA 模式)組成:

  1. 安排
  2. 行為
  3. 斷言

1. 安排

在我們測試的安排部分,我們確保我們的系統在調用我們想要測試的行為之前處於特定狀態。 “系統”可能是一個對象,我們需要以特定方式設置它以產生行為、創建臨時文件或類似性質的東西。

本節中的代碼通常比其他兩個加起來要大

Object Mother 應該證明對保持這部分的小而特別有用的一種設計模式是Object Mother 。 這種設計模式與Factory非常相似,但它有更具體的方法可以為您構建預配置的對象。 雖然標準Factory可能有諸如createCar(carDescription)類的方法,但ObjectMother將具有諸如createRedFerrari()createBlackTesla()createBrokenYugo()類的方法。

2.行動

這部分測試必須有一行。 此行執行被測行為。 如果您發現自己為這一部分寫了不止一行,那麼您可能沒有正確封裝您的行為。 不應期望您的客戶以特定順序調用對象的多個方法,那麼為什麼要進行測試呢?

這一行是我們要測試的方法調用。 如果此方法返回結果,您應該將該值存儲在一個變量中,以檢查它是否是 Assert 步驟中的預期值。

3. 斷言

當我們在 Arrange 部分準備好系統並在 Act 部分執行我們的被測動作後,我們需要驗證動作的結果。 我們通常在這裡檢查方法的結果,但有時,我們的方法不返回值,但它們仍然會產生副作用。 如果我們的代碼需要改變一個對象的狀態,創建一個文件,或者從List中刪除一些東西,我們應該檢查它是否確實做到了。

存根與模擬

大多數開發人員交替使用術語模擬存根,但存在差異。

存根不能通過測試,模擬可以。

存根是基於狀態的,它們返回硬編碼值。 “我得到了什麼結果?”

模擬是基於行為的,您可以使用它們來驗證您的行為如何通過它。 “我是怎麼得到結果的?”

如果您的單元測試充滿了模擬,那麼您的測試最終會變得非常脆弱,這意味著每次您更改一個實現細節時,您都需要更新所有模擬調用。

只測試一件事。

我們需要能夠隔離一種行為並證明它有效。 如果該行為在不同的輸入下應該以不同的方式工作,我們需要為這些行為中的每一個編寫一個新的測試。 如果我們有一個同時測試多個事物的大型測試,就很難知道為什麼我們的測試失敗了。 此外,刪除我們不再需要的功能並查看我們在添加新代碼時破壞了哪些功能變得更加困難。

在測試的最後部分有多個斷言是完全可以的,只要這不需要您多次調用被測行為即可。 如果是這種情況,將很難查明錯誤行為並進行修復。

當我們在一個測試中斷言多個行為時,我們無法清楚地了解哪些行為不起作用,因為測試只會報告第一個失敗,其餘的會被跳過。 要理解哪些更改是必要的以及有多少事情沒有按預期工作變得更加困難。

命名測試

將好的測試與優秀的測試區分開來的一件事是測試名稱。 測試不僅應該告訴我們它們做了什麼,還應該告訴我們它們什麼時候做。

您可以使用許多好的命名模式,因此請選擇您認為最具描述性的一種並堅持使用。 以下是一些出色的測試名稱示例:

  • RegistrationServiceShould.createNewAccountWhenEmailIsNotTaken
  • RegistrationServiceTest.whenEmailIsFree_createNewAccount
  • RegistrationServiceTest.if_freeEmail_when_userCreatesAccount_then_create

當您編寫單元測試時,傳達您正在測試的內容比遵循最佳方法命名實踐更重要。 例如,在 Java 中,我們在編寫方法時使用 camelCase,但使用下劃線 (_) 將狀態與測試名稱中的操作分開是完全有效的。

清潔測試

您編寫的測試應遵循您應用於代碼的所有乾淨代碼實踐。 測試不是二等公民,您需要像處理其餘代碼一樣小心謹慎,以使它們具有可讀性。

測試中代碼重複的定義非常重要。 DRY 原則(不要重複自己)適用於提取出於相同原因而發生變化的行為。 測試因不同的原因而改變,所以如果它們真的沒有因為相同的原因而改變,那麼從你的測試中提取東西時要有所不同。 劇透警報,他們通常不會。

if語句不屬於測試。 if語句告訴我們,我們的測試至少做了兩個不同的事情,如果我們將測試重寫為兩個不同的測試,我們會做得更好。 如果命名正確,將更容易理解測試的作用以及所有不同的行為。

我們什麼時候應該寫測試?

在 TDD 原則的指導下,我們應該在編寫新代碼之前編寫測試。

當我們需要添加一個新特性時,我們首先將期望的行為描述為一個新的測試。 我們進行了最少的更改以通過該測試而不會破壞任何其他測試。

否則,時間越長,我們擁有的未經測試的代碼就越多,引入錯誤或過度工程的機會就會增加

此外,測試變得更加複雜,因為我們現在有更多的代碼要測試,通常發生的情況是開發人員將測試調整為代碼。 這意味著我們調整行為以匹配我們的代碼所做的事情,而不是相反。

當我們儘早編寫測試時,問題的定義會變得更小,並且比更通用和更複雜的行為更容易解決這些問題。

最後的想法

雖然編寫測試似乎是可選的事情,但從良好的基礎開始至關重要。 編碼已經很困難了,通過編寫更易於閱讀、理解和維護的可測試代碼,讓你自己和你的隊友更輕鬆。 最後,如果您在使測試符合您的意願時遇到困難,則問題更可能出現在您的代碼中而不是您的測試中。

有興趣編寫乾淨且可測試的代碼嗎?
查看我們針對高級後端開發人員的空缺職位
或向我們發送開放申請