《代碼整潔之道》第9章 單元測試 - 筆記

測試驅動開發 (TDD) 是一種編寫整潔代碼的“規程”或“方法論”,而不僅僅是測試技術。

JaCoCo 在運行測試后生成詳細的覆蓋率報告的工具, maven 引用。

測試驅動開發

測試驅動開發(TDD)是什么?

TDD 不是說寫完代碼再寫測試,而是先寫測試,再寫代碼。它是一種開發流程,一個不斷循環的“節奏”:

  1. 紅燈 (Red): 寫一個針對某個新功能的自動化測試。運行這個測試,它應該失敗,因為你還沒寫對應的功能代碼。這個失敗告訴你,“我想要的功能還不存在”。
  2. 綠燈 (Green):最少量的程序代碼,讓剛才失敗的測試通過。你的目標只是讓測試變綠,代碼可能寫得不好看、效率不高都沒關系。然后運行所有的測試(包括之前寫過的),確保沒有破壞已有的功能。
  3. 重構 (Refactor): 現在所有測試都通過了,功能是正確的。這時,你可以放心地改進和優化你的程序代碼和測試代碼,讓它們更整潔、更高效、結構更好。重構過程中,要持續運行所有測試,確保改進沒有引入新的 Bug。

這個 紅 -> 綠 -> 重構 的循環非常短,可能只需要幾分鐘到十幾分鐘。你不斷地重復這個循環,逐步完善你的功能。

TDD 的三定律

  • 在你編寫一個失敗的測試之前,不能編寫任何生產代碼。
  • 在一個失敗的測試中,你不能編寫多于恰好能夠暴露失敗的測試代碼。
  • 在你編寫一個失敗的測試之外,你不能編寫多于恰好能夠使得當前失敗測試通過的生產代碼。

舉例說明:構建一個簡單的字符串計算器

假設我們要寫一個函數,能夠接收一個包含數字和逗號的字符串,并計算所有數字的和。例如 "1,2,3" 應該返回 6。

我們使用 TDD 的流程來開發這個功能。

我們要測試的函數:int add(String numbers)

步驟 1:紅燈 (Red) - 測試空字符串

  • 需求: 空字符串應該返回 0。

寫測試:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;// 我們要測試的類,也就是生產類
class StringCalculator {// 我們還沒寫這個 add 方法// public int add(String numbers) { ... }
}class StringCalculatorTest {@Testvoid shouldReturnZeroForEmptyString() {StringCalculator calculator = new StringCalculator();// 斷言:調用 add("") 應該返回 0assertEquals(0, calculator.add(""));}
}
  • 運行測試: 測試會編譯失敗(因為 StringCalculator 類可能不存在,或者 add 方法不存在),或者運行時失敗(比如 add 方法拋異常)。測試失敗了,紅燈!

步驟 2:綠燈 (Green) - 讓測試通過

  • 目標:shouldReturnZeroForEmptyString 測試通過。

寫最少量代碼:

class StringCalculator {public int add(String numbers) {// 最簡單讓上面測試通過的代碼就是判斷是不是空字符串if (numbers == null || numbers.isEmpty()) {return 0;}// 現在還不能處理非空字符串,先隨便返回個東西或拋異常 (這里暫不處理非空)// 實際上,為了讓測試盡快通過,我們可能直接返回 0return 0; // !!! 讓 shouldReturnZeroForEmptyString 通過的最少量代碼 !!!}
}
  • 運行測試:shouldReturnZeroForEmptyString 測試通過了。如果之前有其他測試,也運行一下。所有測試都通過了,綠燈!

步驟 3:重構 (Refactor) - 改進代碼

  • 代碼量很少,暫時不需要大的重構。可能把 if 判斷寫得更清晰一點,或者給類和方法加注釋。這里跳過大的重構。

步驟 4:紅燈 (Red) - 測試單個數字

  • 需求: 輸入 "1" 應該返回 1。

寫測試:

class StringCalculatorTest {// ... shouldReturnZeroForEmptyString 測試 ...@Testvoid shouldReturnNumberForSingleNumberString() {StringCalculator calculator = new StringCalculator();// 斷言:調用 add("1") 應該返回 1assertEquals(1, calculator.add("1"));// 斷言:調用 add("5") 應該返回 5assertEquals(5, calculator.add("5"));}
}
  • 運行測試:shouldReturnNumberForSingleNumberString 測試會失敗(因為 add("1") 仍然返回 0)。shouldReturnZeroForEmptyString 應該仍然通過。測試失敗,紅燈!

步驟 5:綠燈 (Green) - 讓測試通過

  • 目標:shouldReturnNumberForSingleNumberString 通過。

寫最少量代碼:

class StringCalculator {public int add(String numbers) {if (numbers == null || numbers.isEmpty()) {return 0;}// !!! 添加處理單個數字的代碼 !!!// 嘗試將字符串轉換為整數return Integer.parseInt(numbers); // !!! 讓測試通過的最少量代碼 !!!}
}
  • 運行測試:shouldReturnZeroForEmptyStringshouldReturnNumberForSingleNumberString 都通過了。所有測試都通過了,綠燈!

步驟 6:重構 (Refactor) - 改進代碼

  • Integer.parseInt 可能會拋出 NumberFormatException,雖然當前測試沒有覆蓋到無效數字字符串,但為了健壯性,可以在這里考慮異常處理(或者等寫了相關測試后再處理)。這里暫時不展開。

步驟 7:紅燈 (Red) - 測試兩個數字

  • 需求: 輸入 "1,2" 應該返回 3。

寫測試:

class StringCalculatorTest {// ... shouldReturnZeroForEmptyString 測試 ...// ... shouldReturnNumberForSingleNumberString 測試 ...@Testvoid shouldReturnSumForTwoNumbersSeparatedByComma() {StringCalculator calculator = new StringCalculator();// 斷言:調用 add("1,2") 應該返回 3assertEquals(3, calculator.add("1,2"));// 斷言:調用 add("5,7") 應該返回 12assertEquals(12, calculator.add("5,7"));}
}
  • 運行測試:shouldReturnSumForTwoNumbersSeparatedByComma 測試會失敗(因為 add("1,2") 會因為無法直接解析 "1,2" 而拋出 NumberFormatException)。測試失敗,紅燈!

步驟 8:綠燈 (Green) - 讓測試通過

  • 目標:shouldReturnSumForTwoNumbersSeparatedByComma 通過。

寫最少量代碼:

class StringCalculator {public int add(String numbers) {if (numbers == null || numbers.isEmpty()) {return 0;}// !!! 添加處理逗號分隔的代碼 !!!String[] numberArray = numbers.split(","); // 按逗號分割if (numberArray.length == 1) {// 如果分割后只有一個元素 (處理單個數字的情況)return Integer.parseInt(numberArray[0]);} else {// 如果分割后有兩個元素 (處理兩個數字的情況)int num1 = Integer.parseInt(numberArray[0]);int num2 = Integer.parseInt(numberArray[1]);return num1 + num2; // 求和}// 注意:這段代碼現在還不能處理三個或更多數字,甚至無效數字字符串// 但它讓當前的測試通過了}
}
  • 運行測試: 所有三個測試都應該通過。所有測試都通過了,綠燈!

步驟 9:重構 (Refactor) - 改進代碼

現在的代碼有點簡陋,只能處理空字符串、一個數字或兩個數字。我們可以重構它,讓它能處理任意數量的數字(注意這里違反了第三條定律):

(需求就是實現測試,目前已經完成了,這里是重構,不過重構不建議加新功能哈,這里不加新功能沒啥好重構的了hhhh)

class StringCalculator {public int add(String numbers) {if (numbers == null || numbers.isEmpty()) {return 0;}// 重構:處理任意數量的數字String[] numberArray = numbers.split(","); // 按逗號分割int sum = 0;for (String numberStr : numberArray) {// 這里應該加上 NumberFormatException 的處理,但為了例子簡潔暫不加sum += Integer.parseInt(numberStr); // 累加每個數字}return sum;}
}
  • 運行測試: 再次運行所有測試,確保重構沒有破壞功能。它們都應該通過。

這個過程會一直進行下去,每次只添加一點點功能(比如處理換行符分隔、處理負數、忽略大于 1000 的數字等等),為每個新功能寫一個測試,讓測試通過,然后重構。

這就是 TDD 的基本流程。它通過小步快跑、頻繁測試和重構,確保你構建的功能是正確的,并且代碼保持整潔。

測試的整潔

整潔測試三要素:可讀性、可讀性和可讀性。

核心思想: 好的測試和好的生產代碼一樣重要,它們必須是整潔且易于維護的。

整潔測試的五大原則 (F.I.R.S.T.):

F - Fast (快速):

  • 什么意思: 你的測試應該運行得非常快。
  • 為什么重要: 如果測試運行得慢,開發者就不會頻繁地運行它們(比如在每次修改代碼后)。不頻繁運行測試,測試的價值就大打折扣,無法及時發現問題。快速的測試才能融入到小步快跑的 TDD 循環中。

I - Independent (獨立):

  • 什么意思: 每個測試用例都應該是獨立的,它們不應該相互依賴。一個測試的通過或失敗不應該影響到其他測試的運行結果。
  • 為什么重要: 如果測試相互依賴,當一個測試失敗時,可能會導致一系列其他測試也跟著失敗(級聯失敗),讓你很難判斷是哪個測試真正發現了問題,調試會非常困難。獨立性也意味著你可以隨意調整測試的運行順序,或者只運行某個特定的測試,而不用擔心遺漏依賴項。

R - Repeatable (可重復):

  • 什么意思: 在任何環境(你的開發機、測試服務器、CI/CD 環境)下,無論何時運行,測試都應該給出相同的結果。
  • 為什么重要: 如果測試的結果不可重復(有時通過,有時失敗),你就無法信任你的測試套件。它可能是因為外部因素(如網絡、時間、文件狀態)或測試本身的設計問題導致的不穩定(Flaky Test)。不可重復的測試是最大的障礙,會讓人失去對測試的信心。

S - Self-validating (自我驗證): 就是用斷言,控制臺通過或報錯,而不是看控制臺輸出

  • 什么意思: 測試的輸出必須是明確的“通過”或“失敗”。它應該通過自動化斷言(Assert)來判斷結果,而不是需要人工去查看日志、比較文件或觀察程序行為來判斷是否正確。
  • 為什么重要: 自動化測試的目的就是減少人工干預。測試運行完畢后,你只需要看一個簡單的報告(比如綠條或紅條)就知道代碼是否工作正常,不需要花費時間去分析結果。

T - Timely (及時):

  • 什么意思: 測試應該在正確的時間編寫。在 TDD 中,正確的時間就是恰好在需要實現對應功能之前
  • 為什么重要: 及時編寫測試(先于代碼)是 TDD 方法論的核心,它驅動你思考代碼如何使用,促進更好的設計,并確保不會遺漏測試。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/903376.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903376.shtml
英文地址,請注明出處:http://en.pswp.cn/news/903376.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

openGauss新特性 | DataKit支持PostgreSQL到openGauss的遷移能力

Postgresql-\>openGauss遷移工具debezium-connector-postgres 可獲得性 本特性自openGauss 7.0.0-RC1版本開始引入。 特性簡介 debezium-connector-postgres工具是一個基于Java語言的Postgresql到openGauss的復制工具。該工具提供了初始全量數據及對象(視圖、…

在MySQL Shell里 重啟MySQL 8.4實例

前一段時間看到MySQL官方視頻的Oracle工程師在mysql shell里面重啟mysql實例,感覺這個操作很方便,所以來試試,下面為該工程師的操作截圖 1.MySQL Shell 通過root用戶連上mysql,shutdown mysql實例 [rootmysql8_3 bin]# mysqlshMy…

truffle

文章目錄 truffle目錄結構各文件作用在本地測試合約 truffle 項目來自https://github.com/Dapp-Learning-DAO/Dapp-Learning/blob/main/basic/04-web3js-truffle/README-CN.md Truffle 是基于 Solidity 語言的一套開發框架,它簡化了去中心化應用(Dapp&…

SpringCloud核心組件Eureka菜鳥教程

關于Spring Cloud Eureka的核心概念 Eureka 是 Netflix 開源的一款基于 REST 的服務發現工具,主要用于中間層服務器的云端負載均衡。它通過維護一個服務注冊表來實現服務之間的通信1。在 Spring Cloud 中,Eureka 提供了一個高可用的服務注冊與發現機制&a…

職業教育新形態數字教材的建設與應用:重構教育生態的數字化革命

教育部新時代職業學校名師(名匠)名校長培養計劃專題 四川省第四批職業學校名師(名匠)培養計劃專題 在某職業院校的智能制造課堂上,學生佩戴VR設備,通過數字教材中的虛擬工廠完成設備裝配訓練,系統實時生成操作評分與改進建議。這一場景折射出…

基于Python的攜程國際機票價格抓取與分析

一、項目背景與目標 攜程作為中國領先的在線旅行服務平臺,提供了豐富的機票預訂服務。其國際機票價格受多種因素影響,包括季節、節假日、航班時刻等。通過抓取攜程國際機票價格數據,我們可以進行價格趨勢分析、性價比評估以及旅行規劃建議等…

Windows 圖形顯示驅動開發-初始化WDDM 1.2 和 PnP

(WDDM) 1.2 及更高版本顯示微型端口驅動程序的所有 Windows 顯示驅動程序都必須支持以下行為,以響應即插即用 (PnP) 基礎結構啟動和停止請求。 根據驅動程序返回成功或失敗代碼,或者系統硬件是基于基本輸入/輸出系統 (BIOS) 還是統一可擴展固件接口 (UEF…

【1區SCI】Fusion entropy融合熵,多尺度,復合多尺度、時移多尺度、層次 + 故障識別、診斷-matlab代碼

引言 2024年9月,研究者在數學領域國際頂級SCI期刊《Chaos, Solitons & Fractals》(JCR 1區,中科院1區 Top)上以“Fusion entropy and its spatial post-multiscale version: Methodology and application”為題發表最新科學研…

高并發架構設計之緩存

一、引言 緩存技術作為高并發架構設計的基石之一,通過數據暫存和快速訪問機制,在提升系統性能、降低后端負載方面發揮著不可替代的作用。優秀的緩存設計能夠將系統吞吐量提升數個數量級,將響應時間從秒級降至毫秒級,甚至成為系統…

Unity AI-使用Ollama本地大語言模型運行框架運行本地Deepseek等模型實現聊天對話(一)

一、Ollama介紹 官方網頁:Ollama官方網址 中文文檔參考:Ollama中文文檔 相關教程:Ollama教程 Ollama 是一個開源的工具,旨在簡化大型語言模型(LLM)在本地計算機上的運行和管理。它允許用戶無需復雜的配置…

Docker Python 鏡像使用指南

1. 使用 Python 鏡像創建容器 docker run -itd -v /data:/data python:latest 作用:創建一個基于 python:latest 鏡像的容器,并后臺運行。 參數說明: -itd:交互式后臺運行(-i 交互模式,-t 分配偽終端&…

matlab中Simscape的調用-入門

Simscape 是由 MathWorks 公司開發的一款基于物理建模的仿真工具,它建立在 MATLAB/Simulink 平臺之上,專門用于建模和仿真多領域物理系統。 主要特點 多領域建模:Simscape 提供了豐富的物理元件庫,涵蓋了機械、電氣、液壓、氣動…

Flowable7.x學習筆記(十三)查看部署流程圖

前言 Flowable 的流程圖是 Flowable Modeler 或 Process Editor 中,使用拖拽和屬性面板基于 BPMN 2.0 元素(如任務、網關、事件、序列流等)渲染出的業務流程圖形界面?。 一、將圖形導出可查看的作用 ① 可視化建模 幫助業務分析師和開發者…

Bootstrap 模態框

Bootstrap 模態框 Bootstrap 模態框(Modal)是 Bootstrap 框架中的一個組件,它允許你在一個頁面中創建一個模態對話框,用于顯示內容、表單、圖像或其他信息。模態框通常覆蓋在當前頁面上,提供了一種不離開當前頁面的交…

python-69-基于graphviz可視化軟件生成流程圖

文章目錄 1 Graphviz可視化軟件1.1 graphviz簡介1.2 安裝部署2 基于python示例應用2.1 基本示例2.2 解決中文顯示亂碼2.3 顯示多個輸出邊2.4 顯示輸出引腳名稱2.5 從左至右顯示布局2.6 設置節點為方形3 參考附錄1 Graphviz可視化軟件 1.1 graphviz簡介 Graphviz(Graph Visua…

AJAX 介紹

一、什么是AJAX ? AJAX 是 異步的 JavaScript 和 XML(Asynchronous JavaScript And XML) 的縮寫,是一種實現瀏覽器與服務器進行數據通信的技術。其核心是通過 XMLHttpRequest 對象在不重新刷新頁面的前提下,與服務器交換數據并更…

新ubuntu物理機開啟ipv6讓外網訪問

Ubuntu 物理機 SSH 遠程連接與 IPv6 外網訪問測試指南 1. 通過 SSH 遠程連接 Ubuntu 物理機 1.1 安裝 SSH 服務 sudo apt update sudo apt install openssh-server1.2 檢查 SSH 服務狀態 sudo systemctl status ssh確認出現 active (running)。 1.3 獲取物理機 IP 地址 i…

linux系統上使用nginx訪問php文件返回File not found錯誤處理方案

linux系統上使用nginx訪問php文件返回File not found錯誤處理方案 第一種情況第二種情況 第一種情況 可以在你的location php 里面添加當文件不存在時返回404而不是交給php-fpm進行處理 location ~ \.php$ { ... #文件不存在轉404 try_files $uri 404; ... }然后&#xff0c…

基于 SpringBoot 與 Redis 的緩存預熱案例

文章目錄 “緩存預熱” 是什么?項目環境搭建創建數據訪問層預熱數據到 Redis 中創建緩存服務類測試緩存預熱 “緩存預熱” 是什么? 緩存預熱是一種優化策略,在系統啟動或者流量高峰來臨之前,將一些經常訪問的數據提前加載到緩存中…

java—11 Redis

目錄 一、Redis概述 二、Redis類型及編碼 三、Redis對象的編碼 1. 類型&編碼的對應關系 2. string類型常用命令 (1)string類型內部實現——int編碼 (2)string類型內部實現——embstr編碼 ?編輯 (3&#x…