在這篇文章中,我將逐步介紹在 ReadmeGenie 中實施單元測試、處理複雜的配置挑戰以及引入強大的程式碼覆蓋率的過程。從最初的測試設計到設定預提交掛鉤,這個過程涉及程式碼品質、可靠性和開發人員工作流程的一系列改進。
首先,我選擇unittest作為編寫和執行測試的主要框架。 Python 內建的unittest 提供了一種用於定義測試案例的結構化方法,並且它與mock 的整合使其非常適合測試複雜的配置和API 呼叫。
我建立了一個專用的測試運行程式(tests/test_runner.py),用於自動發現和執行tests/目錄中的所有測試檔案:
# tests/test_runner.py import unittest if __name__ == "__main__": loader = unittest.TestLoader() suite = loader.discover(start_dir="tests", pattern="test_*.py") runner = unittest.TextTestRunner(verbosity=2) runner.run(suite)
此設定可確保執行 python tests/test_runner.py 將自動載入並執行所有測試文件,從而輕鬆驗證專案的整體功能。
ReadmeGenie 專案需要對多個元件進行全面測試:
每個測試檔案都根據其測試的模組命名(例如,test_parse_arg.py 用於參數解析,test_model.py 用於模型函數),確保結構清晰、可維護。
設定 test_loadConfig.py 是這個專案中最具挑戰性的部分。最初,我遇到了與環境變數和檔案路徑檢查相關的持續問題。由於 load_config() 旨在處理各種配置來源(例如環境變數、.env 檔案、JSON 和 TOML 檔案),因此測試需要大量模擬才能準確模擬這些環境。
涉及的主要問題:
環境變數衝突:現有環境變數有時會幹擾模擬值。使用 @patch.dict("os.environ", {}, clear=True),我清除了測試範圍內的環境變數以確保結果一致。
檔案路徑檢查:由於 load_config() 檢查檔案是否存在,我使用 os.path.exists 來模擬設定檔存在或不存在的場景。
模擬 open 和 toml.load:這些需要精確的模擬來處理遺失、空白或已填入設定檔的情況。在 toml.load 上使用帶有 patch 的 mock_open,我有效地模擬了每種情況。
解決這些問題後,test_loadConfig.py 現在涵蓋了三個主要場景:
這是 test_loadConfig.py 的最終版本:
# tests/test_runner.py import unittest if __name__ == "__main__": loader = unittest.TestLoader() suite = loader.discover(start_dir="tests", pattern="test_*.py") runner = unittest.TextTestRunner(verbosity=2) runner.run(suite)
測試到位後,我們專注於使用coverage.py 測量和提高覆蓋率。透過設定 80% 的閾值,我們的目標是確保程式碼的所有關鍵部分都經過測試。
我在 pyproject.toml 中使用以下設定配置了coverage.py:
import unittest from unittest.mock import mock_open, patch from loadConfig import load_config class TestLoadConfig(unittest.TestCase): @patch.dict("os.environ", {}, clear=True) @patch("loadConfig.os.getenv", side_effect=lambda key, default=None: default) @patch("loadConfig.os.path.exists", return_value=False) @patch("builtins.open", new_callable=mock_open, read_data="{}") @patch("loadConfig.toml.load", return_value={}) def test_load_config_empty_file(self, mock_toml_load, mock_open_file, mock_exists, mock_getenv): config = load_config() self.assertEqual(config, {}) @patch.dict("os.environ", {}, clear=True) @patch("loadConfig.os.getenv", side_effect=lambda key, default=None: default) @patch("loadConfig.os.path.exists", return_value=True) @patch("builtins.open", new_callable=mock_open, read_data='{"api_key": "test_key"}') @patch("loadConfig.toml.load", return_value={"api_key": "test_key"}) def test_load_config_with_valid_data(self, mock_toml_load, mock_open_file, mock_exists, mock_getenv): config = load_config() self.assertEqual(config.get("api_key"), "test_key") @patch.dict("os.environ", {}, clear=True) @patch("loadConfig.os.getenv", side_effect=lambda key, default=None: default) @patch("loadConfig.os.path.exists", return_value=False) @patch("builtins.open", side_effect=FileNotFoundError) @patch("loadConfig.toml.load", return_value={}) def test_load_config_file_not_found(self, mock_toml_load, mock_open_file, mock_exists, mock_getenv): config = load_config() self.assertEqual(config, {})
此配置包括分支覆蓋率、突出顯示缺失的行,並強制執行最低 75% 的覆蓋率閾值。
為了將其整合到開發工作流程中,我添加了一個預提交掛鉤,以確保在每次提交時檢查程式碼覆蓋率。如果覆蓋率低於 75%,提交將被阻止,提示開發人員在繼續之前提高覆蓋率:
[tool.coverage.run] source = [""] branch = true omit = ["tests/*"] [tool.coverage.report] show_missing = true fail_under = 75
我們最近的報告報告顯示:
- repo: local hooks: - id: check-coverage name: Check Coverage entry: bash -c "coverage run --source=. -m unittest discover -s tests && coverage report -m --fail-under=75" language: system
雖然某些領域的覆蓋率很高(例如 loadConfig.py 為 100%),但 models/model.py 和 readme_genie.py 仍有改進的機會。專注於未經測試的分支和邊緣情況對於實現我們 85% 或更高整體覆蓋率的目標至關重要。
這個專案教會了我很多關於單元測試、模擬和程式碼覆蓋率的知識。設定 test_loadConfig.py 是一次特別有價值的經歷,促使我探索更深層的設定模擬。用於覆蓋的預提交掛鉤增加了一層品質保證,強制執行一致的測試標準。
展望未來,我的目標是透過添加邊緣情況和提高分支覆蓋率來進一步完善這些測試。這不僅會讓ReadmeGenie更加強大,也為未來的發展打下堅實的基礎。
以上是在 ReadmeGenie 中實作單元測試的詳細內容。更多資訊請關注PHP中文網其他相關文章!