iT邦幫忙

2025 iThome 鐵人賽

0
Software Development

重啟挑戰:老派軟體工程師的測試修練系列 第 31

Day 31 - 重啟挑戰的測試修練總結:從基礎到實戰的 30 天回顧與 AI 時代的開發與測試模式轉變的想法

  • 分享至 

  • xImage
  •  

前言:一個老派工程師的 30 天測試修練心得

當我決定重新整理和分享這 30 天的測試學習歷程時,其實心裡有些忐忑。作為一個在軟體領域打滾了二十多年的開發者,我並不是什麼測試大師或技術領袖,只是一個在日常工作中不斷踩雷、學習、調整的普通工程師。

但正因為是這樣的身份,我更能體會在實際專案中導入測試的各種挑戰:時程壓力、團隊技能差異、既有程式碼的包袱、還有最近這幾年 AI 工具帶來的衝擊和變化。

為什麼選擇這個題目?參加鐵人賽的動機

我平常就有寫部落格文章,雖然這幾年的產量不多,因為大部分的寫作都是在公司裡寫技術文件,所以下班後就沒有多餘的心力再去寫文章。

其實一直有個想法就是將我對新人做教育訓練的單元測試和整合測試的教材做個整理然後寫出一系列的部落格文章,但就一直缺少動力。

直到今年四月時,在公司裡有個要寫出有關單元測試的教育訓練文件的機會,也有把訓練文件完成了基礎的部分,在寫的時候就有個想法,不如也趁這個機會去將這些內容做個整理,然後寫出一系列的文章,因為無法直接把公司那份已經寫好的文件帶出來然後直接使用。

就剛好藉著這次 ITHome 2025 鐵人賽的機會,以此為動力將這系列的文章給寫了出來。

AI 時代測試技能的重要性

又因為 AI 的進展再加上像 GitHub Copilot 這類的工具功能的增強與進步,讓過往不曾學習過單元測試的開發者,或是對於單元測試不熟悉,或是不知道怎麼寫單元測試的開發者,可以藉由這些工具就能夠自動產出單元測試的程式碼。

但這些開發人員因為對於單元測試的知識與技術不足,也就缺少了**「識讀」**這些 AI 產生的測試程式碼的能力,而導致無法判斷這些 AI 所寫的單元測試的正確性,也無法去確認這些測試程式碼是否真的有測試到使用情境、是否能夠依據規格達到驗證的目的,這些開發者只知道執行測試都通過就好。

然後等到系統在驗收或是上線後出現 BUG 或是需求變更時,這些測試程式碼就無法達到防止改 A 壞 B 的情況,也無法達到輔助開發的目的,反而會出現這些測試程式碼妨礙開發者進行程式修改的情況。

因為開發者還是要具備能夠寫出單元測試程式碼的能力,才有辦法去識讀並判斷 AI 所產生的測試程式碼。

這篇文章不是要總結什麼測試理論,而是想跟大家分享這 30 天來的學習重點,還有一些在實務上的心得。更重要的是,在這個 AI 工具越來越強大的時代,我們該如何看待和應用測試技術。


第一章:30 天測試修練回顧

基礎篇 (Day01-Day06):重新認識測試的價值

Day 01:老派工程師的測試啟蒙 - 為什麼我們需要測試?

內容大綱:

  • 前言:AI 時代,為什麼還要談測試?
  • 老派工程師的自白
  • 測試的本質:信心與品質的保證
  • 測試金字塔:測試策略
  • FIRST 原則:優質單元測試的五大支柱
  • 測試的三個層次:3A Pattern
  • 單元測試方法的命名規範
  • AI 時代的測試挑戰
  • 今日實作:建立第一個符合 FIRST 原則的測試

文章連結:

Day 02:xUnit 框架深度解析 - 從生態概觀到實戰專案

內容大綱:

  • 前言:選擇測試框架的重要性
  • .NET 測試框架生態概觀
  • 為什麼選擇 xUnit?
  • xUnit 基本結構與核心概念
  • Fact vs Theory:測試的兩種基本形式
  • Theory 的進階用法:MemberData 與 ClassData
  • 其他實用的 xUnit 屬性
  • 老派工程師的框架選擇經驗談
  • 實戰:建立第一個 xUnit 測試專案

文章連結:

Day 03:xUnit 進階功能與測試資料管理

內容大綱:

  • 前言:從基礎到進階的躍進
  • Theory 進階資料提供機制
  • MemberData:靜態成員提供測試資料
  • ClassData:專用資料類別
  • PropertyData:屬性驅動的測試資料
  • 測試資料重複使用策略
  • xUnit 進階資源管理:Fixture 深度應用
  • IClassFixture 與 ICollectionFixture 應用
  • xUnit 並行執行與性能優化

文章連結:

Day 04:AwesomeAssertions 基礎應用與實戰技巧

內容大綱:

  • 前言:現代測試 Assertions 的核心價值
  • Fluent Assertions 商業授權變化分析
  • AwesomeAssertions 完整介紹
  • 安裝與基本設定
  • 基本 Assertions 語法與核心功能
  • 物件、字串、數值、集合 Assertions
  • 實戰應用範例
  • 最佳實踐與常見陷阱

文章連結:

Day 05:AwesomeAssertions 進階技巧與複雜情境應用

內容大綱:

  • 今日目標與學習重點
  • 進階 Assertions 技巧與實戰應用
  • Object Graph 進階比對技巧
  • 進階非同步 Assertions 技巧
  • 例外 Assertions 進階技巧
  • 自訂 Assertions 擴展與最佳實踐
  • 效能最佳化 Assertions
  • 快速排除更新欄位技巧
  • 動態屬性排除策略

文章連結:

Day 06:Code Coverage 程式碼涵蓋範圍實戰指南

內容大綱:

  • 前言:我的測試夠嗎?
  • 學習目標
  • Code Coverage 基本概念
  • Code Coverage 工具介紹
  • Fine Code Coverage 擴充套件
  • Fine Code Coverage 涵蓋率報告分析
  • VS Code 測試覆蓋率功能
  • 程式碼複雜度與測試策略
  • Code Coverage 的正確使用方式

文章連結:

基礎篇學習心得回顧

這一天我們談到了一個很重要的概念:AI 可以幫你寫程式碼,但不能幫你思考。 現在很多開發者習慣讓 AI 產生測試程式碼,但卻不知道這些測試在驗證什麼、有沒有意義。

當時我提到的一個觀點現在看來更加重要:在 AI 時代,理解測試的本質比會寫測試程式碼更重要。這不是說程式碼不重要,而是說如果你不知道為什麼要這樣測試,那麼再多的測試程式碼都可能只是自欺欺人。

我們也介紹了測試金字塔的概念:單元測試作為基礎、整合測試作為中堅、E2E 測試作為頂層。這個分層不只是技術問題,更是策略思考的體現。

在 .NET 的測試框架選擇上,我們最終選擇了 xUnit。不是因為它最流行,而是因為它的設計理念更符合現代測試的需求:每個測試都是獨立的實例、支援並行執行、有豐富的擴展機制。

現在回頭看,這個選擇是對的。在後面討論整合測試和並行執行時,xUnit 的這些特性就顯得格外重要。

Theory 和 InlineData 的組合讓我們可以用更簡潔的方式測試多種情境。Fact 和 Theory 的差別不只是語法,更代表了不同的測試策略:Fact 用於測試單一明確的行為,Theory 用於測試相同邏輯的不同輸入。

選擇 AwesomeAssertions 而不是 FluentAssertions 主要是考慮到授權問題。但使用下來發現,AwesomeAssertions 的表達力一點也不輸人。

result.Should().Be(expected) 這樣的語法讓測試的意圖變得更清楚。更重要的是,當測試失敗時,錯誤訊息也更容易理解。這在實際開發中特別重要,因為你不可能記住每個測試的細節。

Code Coverage 的學習讓我們理解到:測試覆蓋率不是萬能的,但是一個有用的輔助指標。重點是要知道如何正確解讀和使用這些指標,而不是盲目追求 100% 的覆蓋率。

延伸學習資源推薦

人稱敏捷三叔公的柯仁傑老師於這次 ithome 2025 鐵人賽,有寫《如何利用實例化需求在 GenAI 時代下自我升級》的系列文(在30 天內帶你用範例說人話,搞懂需求、搞定驗收,走出專案混亂的輪迴),是可以讓開發者或是開發團隊的 PM、SA、SD、QA 都能夠從中有所收穫。

系列文章連結:如何利用實例化需求在 GenAI 時代下自我升級

這個系列特別適合解決測試情境制定的問題,因為它教你如何從需求中萃取出具體的使用場景,而這些場景正是測試案例的重要來源。

工具篇 (Day07-Day12):自動化的威力

Day 07:依賴替代入門 - 使用 NSubstitute

內容大綱:

  • 前言:從基礎到現實世界的測試挑戰
  • 本日學習目標:測試替身的五大類型與應用
  • 為什麼需要測試替身?外部依賴的困擾
  • 為什麼多數開發者無法跨越單元測試這道門檻?
  • 依賴替換工具介紹:NSubstitute vs Moq
  • Test Double 類型解析:Dummy、Fake、Stub、Spy、Mock
  • 實戰案例:檔案備份服務重構
  • NSubstitute 實戰應用:基本語法與設定
  • NSubstitute 核心功能詳解:回傳值設定與行為驗證
  • 完整測試案例:OrderService 整合範例
  • 常見陷阱與最佳實踐
  • Mock vs Stub 的實戰差異

文章連結:

Day 08:測試輸出與記錄 - xUnit ITestOutputHelper 與 ILogger

內容大綱:

  • 前言:測試診斷與可觀測性的重要性
  • 本日學習目標:掌握測試輸出與記錄技術
  • 測試輸出需求分析:為何需要測試輸出與記錄?
  • xUnit ITestOutputHelper 深度應用
  • ITestOutputHelper 生命週期管理與最佳實踐
  • 結構化測試輸出設計
  • ILogger 測試驗證策略
  • ILogger 擴充方法的測試挑戰
  • 進階診斷技術與問題定位
  • 實戰案例:複雜業務邏輯的診斷測試

文章連結:

Day 09:測試私有與內部成員 - Private 與 Internal 的測試策略

內容大綱:

  • 前言:封裝與測試需求的平衡
  • 本日學習目標:正確的封裝測試思維
  • 為什麼要討論私有與內部成員的測試?
  • 封裝原則 vs 測試需求的平衡
  • 設計優先的測試思維
  • Internal 成員測試的四種可見性技術
  • InternalsVisibleTo 屬性應用
  • 條件編譯與除錯組態策略
  • 反射技術測試私有方法
  • 替代設計模式改善可測試性

文章連結:

Day 10:AutoFixture 基礎:自動產生測試資料

內容大綱:

  • 前言:從 Test Data Builder 到自動化資料產生
  • 本日學習目標:掌握 AutoFixture 核心概念
  • 為什麼需要 AutoFixture?傳統測試資料準備的痛點
  • AutoFixture 核心概念與設計理念
  • 基本型別的自動建構
  • 複雜物件的自動建構
  • 與 xUnit 的整合應用
  • 匿名值產生策略
  • 實務應用場景與最佳實踐

文章連結:

Day 11:AutoFixture 進階:自訂化測試資料產生策略

內容大綱:

  • AutoFixture 進階自訂化技術
  • Customization 機制與擴展策略
  • 特殊型別的處理方式
  • 自訂建構邏輯
  • 複雜物件關聯的處理
  • 效能最佳化與記憶體管理

文章連結:

Day 12:結合 AutoData:xUnit 與 AutoFixture 的整合應用

內容大綱:

  • xUnit AutoData 整合深度應用
  • Theory 與 AutoData 的完美結合
  • 自訂 AutoData 屬性
  • 參數化測試的進階技巧
  • 測試資料驗證策略

文章連結:

工具篇學習心得回顧

這個階段我們進入了測試的核心:如何處理外部依賴。我印象深刻的是當時提到的一個觀點:測試替身不是為了模擬,而是為了隔離。

很多人以為 Mock 就是要完全模擬真實的外部系統,但實際上我們要的是隔離待測系統,專注測試核心邏輯。這個思維的轉換對我後來的測試設計影響很大。

ITestOutputHelper 看起來是個小工具,但在實際除錯時特別有用。當測試失敗時,能夠看到詳細的執行過程,這對定位問題非常重要。這一天也讓我理解到:測試不只是驗證功能,也是建立系統可觀測性的重要環節。

Day09 討論的問題很實際:要不要測試私有方法?我學到的重要觀念是:如果你經常需要測試私有方法,可能是設計出了問題。 這不是技術問題,而是設計問題。好的設計自然就有好的可測試性。

AutoFixture 是個遊戲規則改變者。以前我們花大量時間在準備測試資料,現在可以讓 AutoFixture 自動產生。但更重要的是,它讓我們專注在測試邏輯本身,而不是被測試資料的準備工作分散注意力。當然,知道什麼時候要自訂 AutoFixture 的行為也很重要。

將 AutoFixture 和 NSubstitute 結合使用,可以大大簡化測試的準備工作。AutoData 屬性讓參數化測試變得更加簡潔,這在實際專案中特別有用。

抽象化篇 (Day13-Day16):處理難測試的元素

Day 13:NSubstitute 與 AutoFixture 的整合應用

內容大綱:

  • Mock 物件的自動產生
  • AutoFixture 與 NSubstitute 整合策略
  • 複雜依賴關係的自動處理
  • 整合測試中的 Mock 管理
  • 實戰案例:服務層測試自動化

文章連結:

Day 14:Bogus 入門:與 AutoFixture 的差異比較

內容大綱:

  • Bogus 核心概念與特色
  • 真實感測試資料產生
  • Bogus vs AutoFixture 比較分析
  • 不同場景的工具選擇策略
  • Bogus 基本語法與應用

文章連結:

Day 15:AutoFixture 與 Bogus 的整合應用

內容大綱:

  • 兩大工具的優勢互補
  • 整合應用策略
  • 複雜測試場景的解決方案
  • 效能與維護性考量
  • 實戰案例:電商系統測試資料管理

文章連結:

Day 16:測試日期與時間:Microsoft.Bcl.TimeProvider 取代 DateTime

內容大綱:

  • 時間測試的挑戰與傳統解決方案的問題
  • Microsoft.Bcl.TimeProvider 介紹
  • TimeProvider 基本用法與核心概念
  • FakeTimeProvider 測試應用
  • 時間相關邏輯的測試策略
  • 實戰案例:排程系統與時間敏感功能測試
  • 最佳實踐與常見陷阱

文章連結:

抽象化篇學習心得回顧

這個階段讓我深刻理解到:有些東西看起來難測試,其實是設計問題,不是測試問題。

AutoFixture 與 NSubstitute 的整合讓我看到了自動化測試的另一個層次。當你可以自動產生 Mock 物件和測試資料時,測試的準備工作幾乎可以忽略不計,讓你真正專注在測試邏輯本身。

Bogus 與 AutoFixture 的比較讓我學會了工具選擇的思考方式:不是哪個工具更好,而是哪個工具更適合當前的場景。AutoFixture 適合快速建立大量測試資料,Bogus 適合需要精確控制資料格式的場景。

時間是測試中最難處理的元素之一。DateTime.Now 讓測試結果變得不可預測。Microsoft.Bcl.TimeProvider 提供了一個優雅的解決方案。TimeProvider.System 用於正常執行,FakeTimeProvider 用於測試,這樣的設計讓時間相關的邏輯變得可測試。

這個系列讓我理解到抽象化不只是為了測試,更是為了程式碼的可維護性。好的抽象化設計讓程式碼既容易測試,也容易理解和修改。

整合測試篇 (Day17-Day26):從單元到系統

Day 17:檔案與 IO 測試:使用 System.IO.Abstractions 模擬檔案系統

內容大綱:

  • 檔案系統測試的挑戰與傳統困境
  • System.IO.Abstractions 介紹與核心概念
  • IFileSystem 介面的使用與依賴注入
  • MockFileSystem 測試應用
  • 檔案操作的測試策略與最佳實踐
  • 實戰案例:日誌文件處理與備份系統測試
  • 記憶體檔案系統的效能考量

文章連結:

Day 18:驗證測試:FluentValidation Test Extensions

內容大綱:

  • FluentValidation 測試策略概述
  • 驗證規則的單元測試方法
  • FluentValidation Test Extensions 深度應用
  • 複雜驗證邏輯的測試策略
  • 驗證錯誤訊息的測試技巧
  • 實戰案例:業務實體驗證測試
  • 驗證測試的維護性與可讀性

文章連結:

Day 19:整合測試入門:基礎架構與應用場景

內容大綱:

  • 整合測試的定義與價值
  • 整合測試 vs 單元測試的思維差異
  • 整合測試的範圍與層級
  • .NET 整合測試基礎架構
  • WebApplicationFactory 的應用
  • 測試資料庫的設置與管理
  • 整合測試的最佳實踐與常見陷阱

文章連結:

Day 20:Testcontainers 初探:使用 Docker 架設測試環境

內容大綱:

  • Testcontainers 概念與優勢
  • Docker 容器在測試中的應用
  • Testcontainers.NET 基本設定
  • 常用容器的快速設置
  • 容器生命週期管理
  • 並行測試的容器策略
  • 效能最佳化與資源管理

文章連結:

Day 21:Testcontainers 整合測試:MSSQL + EF Core 以及 Dapper 基礎應用

內容大綱:

  • SQL Server 容器的設置與配置
  • EF Core 整合測試策略
  • Dapper 測試的特殊考量
  • 資料庫初始化與資料準備
  • 交易管理與資料隔離
  • 實戰案例:資料存取層完整測試
  • 效能最佳化技巧

文章連結:

Day 22:Testcontainers 整合測試:MongoDB 及 Redis 基礎到進階

內容大綱:

  • NoSQL 資料庫的測試挑戰
  • MongoDB 容器設置與連線管理
  • Redis 快取層的測試策略
  • 文件資料庫的測試資料準備
  • 快取邏輯的驗證技巧
  • 實戰案例:複合資料存儲系統測試
  • 多容器協作的測試策略

文章連結:

Day 23:整合測試實戰:WebApi 服務的整合測試

內容大綱:

  • Web API 整合測試架構設計
  • WebApplicationFactory 進階應用
  • HTTP 客戶端測試策略
  • 認證與授權的測試處理
  • 中介軟體的測試技巧
  • API 回應格式與錯誤處理測試
  • 實戰案例:RESTful API 完整測試套件

文章連結:

Day 24:.NET Aspire Testing 入門基礎介紹

內容大綱:

  • .NET Aspire 概述與測試架構
  • Aspire Testing 套件介紹
  • 分散式應用程式的測試挑戰
  • Aspire Host 與測試整合
  • 服務發現與配置的測試
  • 實戰案例:微服務通訊測試

文章連結:

Day 25:.NET Aspire 整合測試實戰:從 Testcontainers 到 .NET Aspire Testing

內容大綱:

  • 從傳統容器測試遷移到 Aspire Testing
  • Aspire 與 Testcontainers 的整合策略
  • 分散式系統的端到端測試
  • 服務間通訊的驗證技巧
  • 配置管理與環境變數測試
  • 實戰案例:完整微服務系統測試

文章連結:

Day 26:xUnit 升級指南:從 2.9.x 到 3.x 的轉換

內容大綱:

  • xUnit 3.x 新功能概述
  • 破壞性變更分析與影響評估
  • 升級準備與相容性檢查
  • 段階式升級策略
  • 常見升級問題與解決方案
  • 新功能的實際應用
  • 團隊升級的最佳實踐

文章連結:

整合測試篇學習心得回顧

從單元測試跳到整合測試,不只是技術的改變,更是思維的轉換。單元測試關注個別元件的行為,整合測試關注元件之間的協作。這個轉換過程中,我學會了如何平衡測試的範圍和複雜度。

檔案 I/O 是另一個測試難點。System.IO.Abstractions 讓我們可以在測試中使用記憶體檔案系統,避免了真實檔案操作的複雜性。這也讓我理解到抽象化不只是為了測試,更是為了程式碼的可維護性。

Testcontainers 是整合測試的利器。可以在測試中啟動真實的資料庫、快取、訊息佇列,讓測試環境更接近正式環境。但也學到了容器管理的重要性:資源清理、並行執行的考量、測試執行時間的最佳化。

EF Core、Dapper 的測試策略各有不同。.NET Aspire 的整合測試模式提供了一個現代化的解決方案,特別適合微服務架構。這個系列讓我看到了測試技術的發展趨勢:從單體應用到微服務、從手動測試到自動化、從隔離測試到真實環境模擬。

並行測試執行、效能最佳化、疑難排解,這些都是在實際專案中會遇到的挑戰。最重要的是建立一套系統性的方法來處理這些問題。

實戰進階篇 (Day27-Day30):AI 應用以及認識新的測試框架

Day 27:GitHub Copilot 測試實戰:AI 輔助測試開發指南

內容大綱:

  • AI 輔助測試開發的現況與挑戰
  • GitHub Copilot 在測試開發中的應用
  • AI 產生測試程式碼的品質評估
  • 測試邏輯思考 vs 程式碼產生的平衡
  • Copilot 測試產生的最佳實踐
  • 實戰案例:AI 輔助複雜業務邏輯測試
  • AI 時代的測試開發者技能要求

文章連結:

Day 28:TUnit 入門 - 下世代 .NET 測試框架探索

內容大綱:

  • TUnit 測試框架概述與特色
  • TUnit vs xUnit 比較分析
  • 現代 .NET 測試框架的發展趨勢
  • TUnit 基本語法與核心功能
  • Source Generator 在測試框架中的應用
  • 效能優勢與技術創新
  • 實戰案例:TUnit 基礎測試建立

文章連結:

Day 29:TUnit 進階應用:資料驅動測試與依賴注入深度實戰

內容大綱:

  • 資料驅動測試進階技巧:MethodDataSource、ClassDataSource、Matrix Tests 實戰應用
  • 測試生命週期與依賴注入:Properties 屬性標記、生命週期管理、TUnit 原生 DI 功能
  • 建立強健的測試基礎設施:從建構式到 Dispose 的完整資源管理
  • 實作真正的 TUnit 依賴注入:使用原生 DI 功能取代手動 Mock 建立
  • 複雜測試資料管理:從檔案載入、AutoFixture 整合到組合測試策略

文章連結:

Day 30:TUnit 進階應用:執行控制與測試品質和 ASP.NET Core 整合測試實戰

內容大綱:

  • 執行控制與測試品質管理:Retry 機制、Timeout 控制、DisplayName 最佳實踐
  • ASP.NET Core 整合測試實作:WebApplicationFactory 整合、效能測試完整方案
  • 複雜測試基礎設施編排:TUnit + Testcontainers 建立多服務整合測試環境
  • 實戰疑難排解技巧:解決 TUnit 常見問題,提升開發效率
  • 專業測試策略:整合測試在真實專案中的應用模式

文章連結:

實戰進階篇學習心得回顧

這個階段讓我思考了測試技術的未來發展方向。

AI 輔助測試開發是個有趣的話題。GitHub Copilot 可以幫你寫測試程式碼,但重要的問題是:它知道要測試什麼嗎?這讓我更加確信:在 AI 時代,理解測試的本質比會寫測試程式碼更重要。

TUnit 代表了新一代測試框架的發展方向:更快的執行速度、更好的開發者體驗、更現代的架構設計。雖然 xUnit 仍然是主流選擇,但了解這些新技術有助於理解測試工具的演進趨勢。

特別是在 Day29 和 Day30 中,我們學習了了如何使用 TUnit 的進階功能:

  • 資料驅動測試:MethodDataSource 的靈活性、ClassDataSource 的重用性、Matrix Tests 的組合威力,每種工具都有其適用場景
  • 生命週期管理:從建構式到 Dispose 的完整流程,TUnit 提供了比傳統框架更精確的控制機制
  • 依賴注入:TUnit 原生的 DI 支援讓測試設計更接近實際應用程式的架構
  • 整合測試:與 ASP.NET Core 和 Testcontainers 的整合展現了現代測試的完整解決方案

第二章:個人開發與測試實務心得

單元測試在真實專案中的挑戰與收穫

理想與現實的差距

在學習測試理論時,一切看起來都很美好:寫測試、重構、持續整合,彷彿只要按照步驟做就能擁有高品質的程式碼。但真正在專案中實施時,才發現現實遠比理論複雜。

最大的挑戰通常不是技術問題,而是人的問題。團隊成員對測試的認知不同、時程壓力下的優先順序考量、既有程式碼的技術債等等。我遇過開發者抱怨「寫測試比寫功能還花時間」,也遇過 PM 質疑「測試不能直接交付給客戶,為什麼要花時間寫?」

但經過這幾年的實踐,我發現測試帶來的價值遠超過投入的成本。最明顯的改變是重構的信心:當你要修改一段複雜的邏輯時,有測試保護讓你敢於大膽調整;當你發現 bug 時,先寫個失敗的測試,然後修復到測試通過,這樣可以確保同樣的問題不會再出現。

測試覆蓋率的迷思

剛開始推動測試時,團隊很容易陷入覆蓋率的迷思。看到覆蓋率報告上 90% 的數字就覺得安心,但實際上可能都是一些沒有意義的測試。

我並沒有整個專案的 Code Coverage 的迷思,不會去追求那個數字要達到多少,也不會執著於每個方法都要有測試去覆蓋到。對於重要的、複雜的邏輯,我就會盡量去增加測試,這麼做不是為了要提高 Code Coverage 的數字,而是從逐漸增加測試去驗證這些邏輯,讓我能夠有信心去確保我所寫出來的程式碼是在對的方向,而 Code Coverage 的提升只是附加作用而已,並不是重點。

我現在更注重測試的品質而不是數量。一個設計良好的測試,能清楚表達業務邏輯、容易維護、失敗時能快速定位問題,這比十個只是為了提高覆蓋率而寫的測試更有價值。

比如說,測試 getter/setter 方法可能可以提高覆蓋率,但對品質改善沒什麼幫助。相反地,測試一個複雜的業務規則驗證邏輯,即使只有一個測試,也比前面十個 getter/setter 測試更有價值。

對新人的引導策略

在對新人的教育訓練時,因為新人常常執著於程式的實作,而測試的效益對新人來說還不會有那麼深刻的體會,畢竟開發經驗的影響也是有絕大的關係。新人總是會問到,要寫多少的測試才足夠?或是會問 Code Coverage 要達到多少才是標準?

我的回答是:你的每次簽入後的 Code Coverage 要能夠比上次的數據還要多一些,既使多 0.01% 也可以。

這樣的引導方式有幾個好處:避免新人被絕對數字嚇到、建立漸進改善的習慣、讓新人專注在有意義的測試上,而不是為了達到某個覆蓋率目標而寫無意義的測試。重點是培養持續改善的心態,而不是追求完美的數字。

團隊導入的實務經驗與反思

回顧我當初在團隊導入單元測試的經驗,這確實對開發團隊來說是增加了寫程式的工作,也是增加了開發流程。但我並沒有強制每個專案都必須要導入,而是採取漸進式的推動策略。

對於有導入單元測試的專案團隊,我也不會把 Code Coverage 作為 KPI 的項目,因為我相當反對這種 KPI 的制訂。正如董大偉老師所說的:「有導入就會有抗拒,有推動就會有阻力」,這句話我經常引用,因為它深刻地點出了推動任何新技術或流程時的根本挑戰。

當初資訊單位的主管真的是有提出這種加入 KPI 項目的要求,而我當時是極力反對的,甚至對主管說:「如果真的要列入 KPI 項目的話,那還是不要導入單元測試。」

這個立場可能看起來很極端,但我深知一旦將 Code Coverage 當作 KPI,開發者就會為了衝數字而寫沒有意義的測試,完全失去了測試的本質。測試應該是為了建立信心、保證品質,而不是為了達成某個數字目標。

這個經驗讓我更加確信:推動測試文化需要的是耐心和正確的引導,而不是強制的數字指標。

關於 TDD 的個人想法

TDD(Test-Driven Development)是測試領域經常被討論的話題。我知道有很多開發者是 TDD 的實踐者和擁護者,他們在專案中成功運用 TDD 並獲得了良好的效果。

就我個人的工作經驗而言,我採用的是「即時測試補強」的方式:並不是等整個類別或專案完成後才寫測試,而是當一個方法完成後,或是方法實作到一個階段後,就立即為其補上測試。這樣的做法能讓我確保方法的實作保持在需求規格範圍內,也能及時驗證實作的正確性和符合需求程度。

這種方式介於傳統的「測試後行」和 TDD 之間,既保有了實作時的彈性,又能即時透過測試來驗證和約束程式碼的品質。我認為不管是 TDD、「測試後行」還是「即時測試補強」,最重要的是要有測試。每個團隊和專案都有不同的特性,重點是找到適合自己的方式,並且持續改善程式碼品質。

團隊導入測試文化的經驗

推動測試文化最困難的是改變習慣。我嘗試過幾種方法:

漸進式導入:不要一開始就要求 100% 的測試覆蓋率,從新功能開始要求寫測試,讓團隊慢慢習慣。

Pair Programming:讓有測試經驗的開發者和新手一起寫測試,這比單純的教育訓練更有效。

工具支援:建立好的 CI/CD 流程,讓測試執行變得簡單,測試結果一目了然。

分享成功案例:當測試幫助快速定位問題或防止回歸錯誤時,在團隊中分享這些成功經驗。

最重要的是,身為團隊中的資深成員,要以身作則。如果連你自己都不寫測試,很難期待其他人會重視測試。

推動測試文化的現實挑戰與反思

說實話,要跟大家分享一個可能比較沮喪的事實:我過去在前一家公司推動單元測試以及做教育訓練有將近 10 年,但最終的結果是只有部分認同的開發者才有在專案裡加入單元測試,而加入整合測試更是少之又少。

更讓人無奈的是,之前的公司甚至最後是有些單位主管表示不想讓開發人員去寫單元測試,因為他覺得那些寫測試的時間還不如拿去消化更多的需求,他要的是趕快做、趕快上線,有錯再趕快改。這種短視近利的思維,完全忽略了測試帶來的長期價值。

而我現在的團隊則是只有我一個人有寫單元測試,其他團隊雖然也有寫,但是單元測試的品質很差,看不出測試的目的與意義,就只是拿來驗證程式碼的可行性,而沒有情境,這些測試無法作為程式碼的說明書。

這些經驗讓我深刻體會到:推動測試文化不只是技術問題,更是組織文化和管理思維的問題。 當組織追求的是短期交付速度而不是長期程式碼品質時,再好的技術推廣都可能徒勞無功。

但我並不因此感到完全失望,因為我相信:

  • 總會有認同測試價值的開發者,他們會成為種子
  • 當系統變得越來越複雜時,沒有測試的痛苦會越來越明顯
  • 新一代的開發者對測試的接受度會更高
  • 技術發展會讓測試變得更容易導入

重要的是保持耐心,持續以身作則,並且在適當的時機分享測試帶來的價值。

測試案例制定的根本困難與解決方法

接下來我要說的是過去對新人做教育訓練,甚至於專案開發時其他開發人員都會遇到的問題:不知道要寫那些測試,或是不知道有哪些是測試情境,不知道測試案例從何而來、也不知道怎麼訂定測試案例。

或許是開發經驗比較多了一些,所以當我接到需求或是看到規格時,我大致上就能夠知道會有哪些測試情境要做,也能夠訂定出有多少個測試案例。隨著程式實作逐漸完整,原本的測試案例也會去做調整,可能變得更多,變少也有可能,因為需求規格總是會有變化。

有 SD 或是 SA 能夠給測試案例是更好,但常常所遇到的是只有規格而沒有測試案例。所以很多開發人員就會對於要寫什麼測試案例就會很迷惘,因為他們不會從需求規格去抓出有哪些測試情境。

新人教育訓練的實務做法

對新人做教育訓練時,在單元測試的訓練時,我會採用這樣的步驟:

  1. 先教測試框架的使用:讓他們熟悉基本的測試語法和操作
  2. 禁止立即寫測試:對於自己的訓練用專案先不能去寫單元測試
  3. 情境分析練習:帶他們做一次怎麼將自己所寫的方法去找出測試情境
  4. 檢視與調整:將測試情境交給我檢視

在檢視的過程中,我會發現到可能是他們原本程式設計有瑕疵或是缺少一些邏輯判斷,就會要他們去調整程式,也會讓他們再去修改測試情境。等到所有測試情境和測試案例都讓我檢視完成後,我才會讓他們去實作出單元測試。

開發者的思維盲點

測試案例對於很多開發人員是個相當棘手的項目,因為就如同我前面所說的,開發者對於自己已經實作完成的程式碼,無法訂定出要怎麼驗證的測試案例。對於開發者來說,他們認為的實作完成就表示做好了、沒問題、等到別人使用時有 BUG 再來改。

這個問題的根源在於:開發者往往從實作的角度看程式碼,而不是從使用者或需求的角度看功能。 他們知道程式碼怎麼運作,但不一定知道這個功能在什麼情況下會被使用,以及可能遇到哪些異常狀況。

整合測試的踩雷經驗與學習

環境配置的複雜性

整合測試最大的挑戰是環境配置。單元測試在記憶體中執行,速度快、隔離性好。但整合測試需要真實的資料庫、快取、外部服務等等,複雜度立刻提升好幾個層級。

我遇過最頭痛的問題是測試環境的不一致性。開發者的本機環境、CI 伺服器、測試環境,每個地方的配置都可能不同,導致測試在某些地方會過、某些地方會失敗。

Testcontainers 在很大程度上解決了這個問題。透過 Docker 容器,我們可以在測試中啟動一致的環境,不管是在開發者的本機還是 CI 伺服器上都一樣。但這也帶來了新的挑戰:Docker 環境的設定、容器的生命週期管理、資源的清理等等。

測試資料的管理策略

整合測試中的測試資料管理是另一個大坑。不像單元測試可以用 AutoFixture 產生假資料,整合測試需要在真實的資料庫中建立測試資料。

我試過幾種策略:

每個測試都建立獨立的資料:這樣隔離性最好,但會讓測試執行時間變得很長。

共享測試資料:執行速度快,但測試之間可能會互相影響。

還原交易:在測試結束時還原交易 (rollback),這樣可以保持資料庫的乾淨,但不是所有情況都適用。

資料庫重建:每次測試前都重建資料庫,確保一致性,但執行時間會很長。

現在我傾向於混合策略:基礎資料使用共享的 Seed Data,測試特定的資料在測試中建立,並在測試結束時清理。

並行執行的效能考量

整合測試的執行時間通常比單元測試長很多,所以並行執行看起來很誘人。但實務上,我的經驗是:

整合測試不建議使用並行:雖然並行執行可以節省時間,但隨之而來的測試資料覆蓋與錯亂問題,更會造成測試的不穩定。整合測試通常涉及共享資源(資料庫、檔案系統、網路服務),並行執行容易產生:

  • 資料競爭:多個測試同時修改相同的測試資料
  • 資源衝突:埠號衝突、資料庫連線競爭
  • 狀態污染:一個測試的副作用影響到其他測試

單元測試的並行考量:一般的單元測試,除非測試方法多到誇張的數量,不然還是盡可能不使用並行執行。原因包括:

  • 除錯困難:並行執行時,測試失敗的錯誤訊息可能會交錯,增加除錯難度
  • 靜態狀態問題:如果程式碼中有靜態變數或全域狀態,並行執行容易暴露這些問題
  • 效益有限:單元測試通常執行很快,並行帶來的時間節省可能不如穩定性重要

實務建議:我現在的策略是優先保證測試的穩定性和可靠性,時間節省是次要考量。如果真的需要並行執行,會先確保:

  • 測試間完全獨立,沒有共享狀態
  • 有完善的資源隔離機制
  • 並行度設定保守(通常不超過 CPU 核心數)

測試工具選擇的實務考量

工具選擇的決策框架

經過這麼多年的經驗,我發現選擇測試工具不只是技術問題,更是策略問題。我現在會從幾個角度來評估:

學習成本:團隊需要多少時間來熟悉這個工具?是否有足夠的文件和社群支援?

整合能力:工具能否與現有的技術棧良好整合?是否支援我們使用的 CI/CD 平台?

長期維護:工具的維護狀況如何?背後有穩定的組織支持嗎?授權條件是否合理?

擴展性:當專案變大變複雜時,工具能否跟上需求?

成本效益:投入的學習和導入成本,相對於帶來的效益是否合理?

實際專案中的工具組合

在不同類型的專案中,我會選擇不同的工具組合:

小型專案或原型開發:簡單的工具組合就夠了,xUnit + 內建的 Assert,重點是快速驗證概念。

中型企業應用:xUnit + AwesomeAssertions + NSubstitute + AutoFixture,這個組合能應付大部分的測試需求。

大型分散式系統:除了上面的工具,還需要加上 Testcontainers、.NET Aspire Test Projects,以及各種效能測試工具。

遺留系統維護:可能需要更多的 Mock 工具和適配器,來處理難以測試的舊程式碼。

重點是不要為了用新工具而用新工具,要根據實際需求來選擇。

工具導入的漸進策略

我學到一個重要教訓:不要一次導入太多新工具。每個工具都有學習曲線,如果同時導入多個工具,團隊會感到負擔很重。

我現在的做法是階段性導入:

  1. 第一階段:建立基本的測試文化,使用最簡單的工具組合。
  2. 第二階段:等團隊熟悉基本測試後,導入更進階的工具。
  3. 第三階段:根據專案的特殊需求,導入專門的工具。

每個階段都要給團隊足夠的時間來適應,並且要有明確的成功指標。


第三章:AI 時代的開發與測試模式轉變的想法

AI 對測試程式碼產生的影響

AI 產生測試的優勢與限制

GitHub Copilot 和 ChatGPT、Claude、Gemini 等 AI 工具讓寫測試程式碼變得容易很多。只要給個提示,AI 就能產生看起來很完整的測試程式碼。語法正確、結構清楚、甚至還會用適當的 Mock 和 Assert。

但我發現了幾個問題:

AI 產生的測試往往缺乏業務邏輯的深度理解。它可能會測試所有的程式路徑,但不一定測試所有的業務情境。比如說,一個訂單處理的邏輯,AI 可能會測試正常流程和例外處理,但可能會遺漏一些邊界情況:庫存不足時的處理、優惠券過期的情況、會員等級的特殊規則等等。

測試的意圖表達不夠清楚。AI 產生的測試通常會有很制式的命名:Test_Method_Should_Return_Expected_Result,但這樣的命名並不能清楚表達測試的業務意圖。一個好的測試名稱應該像是 當庫存不足時_下訂單_應該拋出庫存不足異常

對測試策略的思考不足。AI 可能會為每個 public 方法都產生測試,但不會思考哪些測試是真正有價值的。結果就是產生了一堆低價值的測試,反而增加了維護負擔。

測試品質判斷的人工智慧盲點

AI 很會寫語法正確的程式碼,但對於測試品質的判斷就不那麼可靠了。我遇過一些例子:

過度 Mock 的問題:AI 傾向於 Mock 所有的依賴,包括一些可能不需要 Mock 的簡單物件。結果是測試變得很複雜,但實際測試價值不高。

測試資料的不合理性:AI 產生的測試資料可能在語法上正確,但在業務邏輯上不合理。比如產生一個負數的年齡、或是一個未來日期的出生日期。

測試覆蓋的盲點:AI 容易產生能讓覆蓋率報告好看的測試,但可能遺漏了真正重要的業務路徑。

我現在的做法是:讓 AI 幫我產生測試的骨架,但一定要自己檢查和調整測試的邏輯和資料。

在 AI 輔助開發中,測試理解的重要性

為什麼理解測試原理比會寫測試程式碼更重要

在 AI 可以快速產生測試程式碼的時代,我越來越覺得理解測試原理比會寫測試程式碼更重要。

測試策略的規劃:什麼時候需要單元測試、什麼時候需要整合測試、如何平衡測試的範圍和執行時間,這些都需要人來判斷。AI 可能會建議你為每個方法都寫單元測試,但實際上某些簡單的 DTO 轉換可能不需要測試。

業務邏輯的理解:測試不只是程式碼的驗證,更是業務需求的體現。一個了解業務邏輯的開發者能寫出真正有價值的測試,而 AI 只能根據程式碼的結構來產生測試。

測試維護的判斷:當需求變更時,哪些測試需要修改、哪些測試可以保留、哪些測試應該刪除,這些判斷需要對系統有深度理解的人來做。

AI 時代測試工程師的核心競爭力

我覺得在 AI 時代,測試工程師(或者說有測試技能的開發者)的核心競爭力在於:

測試思維:能夠從使用者的角度思考系統的行為,設計出有意義的測試情境。

系統性思考:能夠從整個系統的角度來規劃測試策略,平衡不同層級測試的比重。

品質判斷:能夠判斷測試的價值,識別出高價值的測試和低價值的測試。

問題診斷:當測試失敗時,能夠快速定位問題的根因,判斷是程式碼問題還是測試問題。

這些能力都需要經驗的累積和思考的訓練,不是 AI 能夠輕易取代的。

如何善用 AI 而不被 AI 誤導

我現在使用 AI 輔助測試的策略是:

用 AI 來產生測試程式碼的骨架:讓 AI 幫我建立基本的測試結構、Mock 設定、Assert 語法等等。

自己思考測試的業務邏輯:測試什麼情境、用什麼資料、期望什麼結果,這些都要自己決定。

用 AI 來改善測試程式碼的品質:讓 AI 幫我檢查測試的命名、重構重複的程式碼、優化測試的可讀性。

保持對 AI 建議的批判性思考:不要照單全收 AI 的建議,要根據專案的實際情況來判斷。

人機協作在測試領域的實踐

AI 適合處理的測試工作

根據我的經驗,AI 在以下幾個方面特別有用:

重複性的程式碼產生:建立相似的測試結構、產生大量的測試資料、寫重複的 Mock 設定等等。

語法和格式的優化:改善測試程式碼的可讀性、統一命名規範、重構重複的程式碼。

測試程式碼的除錯:幫助找出測試程式碼中的語法錯誤、邏輯問題等等。

文件和註解的產生:為複雜的測試邏輯產生說明文件、為測試方法產生清楚的註解。

人類應該專注的測試工作領域

而人類應該專注在:

測試策略的規劃:決定測試的範圍、層級、優先順序等等。

業務邏輯的驗證:設計能真正驗證業務需求的測試情境。

測試品質的評估:判斷測試的價值、識別測試的盲點、評估測試的維護成本。

問題的診斷和解決:當測試失敗時,分析問題的根因並找出解決方案。

團隊協作和知識傳承:推動測試文化、訓練團隊成員、建立測試的最佳實踐。

但不管技術如何發展,我相信人類的判斷力和創造力在測試領域仍然是不可取代的。


第四章:持續精進與未來方向

持續學習的方向建議

經過這 30 天的整理和反思,我覺得未來值得關注的測試技術方向有幾個:

雲原生測試技術:隨著容器化和微服務的普及,Testcontainers、.NET Aspire 這類工具會變得更重要。學會在分散式環境中設計和執行測試,是未來的必備技能。

AI 輔助測試:不是要取代人類,而是要學會如何有效地與 AI 協作。理解 AI 的優勢和限制,建立良好的人機協作流程。

效能和可靠性測試:隨著系統規模的增大,效能測試、混沌工程、可靠性測試這些領域會變得更重要。

測試策略和架構:技術會不斷變化,但測試的原理和策略相對穩定。投資時間理解測試策略、測試架構設計,會有長期的回報。

給讀者的實用建議

如果你想開始或改善測試實踐,我的建議是:

立即可行的第一步

  1. 從新功能開始:不要嘗試為整個舊系統補測試,從下一個新功能開始要求自己寫測試。

  2. 選擇簡單的工具組合:xUnit + 基本的 Assert 就夠了,不要一開始就使用太多工具。

  3. 專注在核心業務邏輯:為最重要的業務邏輯寫測試,不要為了覆蓋率而測試 getter/setter。

漸進式的技能發展

  1. 先學會寫好的單元測試:理解 3A 原則、學會合理的 Mock、寫清楚的測試名稱。

  2. 再學習整合測試:當單元測試變成習慣後,開始學習整合測試的技巧。

  3. 最後學習進階工具:AutoFixture、Testcontainers 這些工具,等你有了基礎再學習。

在工作中推廣測試文化

  1. 以身作則:自己先開始寫測試,用實際行動證明測試的價值。

  2. 分享成功案例:當測試幫助解決問題時,在團隊中分享這些經驗。

  3. 循序漸進:不要期望一夜之間改變整個團隊,給大家足夠的時間適應。


結語:測試修練永不停歇

寫完這 30 天的測試修練總結,我最大的感悟是:測試不只是技術問題,更是思維問題。

在這個 AI 工具越來越強大的時代,我們不需要擔心被取代,而是要學會如何更好地利用這些工具。AI 可以幫我們寫程式碼,但不能幫我們思考;AI 可以產生測試,但不能替我們判斷測試的價值。

測試的核心價值在於幫我們建立對程式碼的信心,讓我們敢於重構、敢於改變。這個價值不會因為技術的演進而消失,反而會變得更加重要。

回到前言中提到的那個老派工程師的身份認同,經過這 30 天的整理和反思,我發現所謂的「老派」其實是一種堅持:堅持品質、堅持原則、堅持在快速變化的技術浪潮中保持理性思考。不管 AI 如何發展,這些基本功永遠不會過時。

我希望這 30 天的分享能對大家有所幫助。測試修練是一個持續的過程,沒有終點,只有不斷的改善和學習。

但對於某些人來說,也許這些過往被認為很難學起來、很難上手的測試技術,在往後 AI 技術逐漸發展成熟後,大概也沒多少人會再去重視並且去學怎麼寫測試了。

藉由這 30 天的修練,除了將我過去應用在工作上經驗寫下來並且分享給大家之外,也是讓這些測試技術可以留下來做個記錄,因為假以時日,我也可能因為 AI 的使用而逐漸忘記該怎麼寫測試,希望到了那個時候我還可以回過頭來看看這些記錄,讓我能夠重拾記憶,喚起我過去的測試經驗。

範例程式碼


感謝大家跟我一起完成了這 30 天的測試修練之旅。如果這個系列對你有幫助,歡迎分享給其他需要的開發者。讓我們一起建立更好的軟體品質文化!


上一篇
Day 30 - TUnit 進階應用:執行控制與測試品質和 ASP.NET Core 整合測試實戰
系列文
重啟挑戰:老派軟體工程師的測試修練31
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言