[{"data":1,"prerenderedAt":1105},["ShallowReactive",2],{"navigation_docs":3,"-python-python-unit-testing-unittest-pytest":352,"-python-python-unit-testing-unittest-pytest-surround":1100},[4,54,83,116,125,161,179,200],{"title":5,"path":6,"stem":7,"children":8},"Ai","/ai","ai",[9,12,42,46,50],{"title":10,"path":6,"stem":11},"AI 技術探索","ai/index",{"title":13,"path":14,"stem":15,"children":16,"page":41},"Rag","/ai/rag","ai/RAG",[17,21,25,29,33,37],{"title":18,"path":19,"stem":20},"BM25 演算法深入解析：從 TF-IDF 到現代搜尋引擎的核心技術","/ai/rag/bm25-deep-dive-from-tf-idf-to-rag","ai/RAG/bm25-deep-dive-from-tf-idf-to-rag",{"title":22,"path":23,"stem":24},"Dense Retrieval（向量檢索）深入解析","/ai/rag/dense-retrieval-deep-dive","ai/RAG/dense-retrieval-deep-dive",{"title":26,"path":27,"stem":28},"RAG（Retrieval-Augmented Generation）原理與實作入門","/ai/rag/rag-intro-principles-and-implementation","ai/RAG/rag-intro-principles-and-implementation",{"title":30,"path":31,"stem":32},"RAG Retrieval Strategy 深入解析","/ai/rag/rag-retrieval-strategy-deep-dive","ai/RAG/rag-retrieval-strategy-deep-dive",{"title":34,"path":35,"stem":36},"Reciprocal Rank Fusion（RRF）教學：打造更穩定的檢索融合策略","/ai/rag/rrf-reciprocal-rank-fusion-guide","ai/RAG/rrf-reciprocal-rank-fusion-guide",{"title":38,"path":39,"stem":40},"TF-IDF（Term Frequency–Inverse Document Frequency）深入解析","/ai/rag/tf-idf-deep-dive","ai/RAG/tf-idf-deep-dive",false,{"title":43,"path":44,"stem":45},"Docus AI 功能完整實作指南","/ai/docus-ai-implementation","ai/docus-ai-implementation",{"title":47,"path":48,"stem":49},"llms.txt 是什麼？在 Nuxt 中控制 AI 與搜尋引擎爬蟲的新方式","/ai/llms-txt","ai/llms-txt",{"title":51,"path":52,"stem":53},"MCP（Model Context Protocol）是什麼？用「AI 的 USB-C」理解模型如何安全連接工具與資料","/ai/mcp_concept","ai/mcp_concept",{"title":55,"path":56,"stem":57,"children":58,"page":41},"Data Analysis","/data-analysis","data-analysis",[59,63,67,71,75,79],{"title":60,"path":61,"stem":62},"Exploratory Data Analysis（EDA）資料探索分析入門","/data-analysis/exploratory-data-analysis-eda","data-analysis/exploratory-data-analysis-eda",{"title":64,"path":65,"stem":66},"PR-AUC 深入解析：不平衡分類問題的重要評估指標","/data-analysis/pr-auc-for-imbalanced-classification","data-analysis/pr-auc-for-imbalanced-classification",{"title":68,"path":69,"stem":70},"ROC-AUC 深入解析：理解分類模型的判斷能力","/data-analysis/roc-auc-complete-guide","data-analysis/roc-auc-complete-guide",{"title":72,"path":73,"stem":74},"SHAP（SHapley Additive exPlanations）模型解釋方法深入解析","/data-analysis/shap-model-interpretation-complete-guide","data-analysis/shap-model-interpretation-complete-guide",{"title":76,"path":77,"stem":78},"Social Network Analysis（SNA）深入解析","/data-analysis/social-network-analysis-sna","data-analysis/social-network-analysis-sna",{"title":80,"path":81,"stem":82},"Time-based Split：為什麼時間序列資料不能使用 Random Split？","/data-analysis/time-based-split-vs-random-split","data-analysis/time-based-split-vs-random-split",{"title":84,"path":85,"stem":86,"children":87,"page":41},"MachineLearning","/machine_learning","machine_learning",[88,92,96,100,104,108,112],{"title":89,"path":90,"stem":91},"Confusion Matrix（混淆矩陣）完整解析","/machine_learning/confusion-matrix","machine_learning/confusion-matrix",{"title":93,"path":94,"stem":95},"Decision Tree 模型完整教學：從原理到 Python 實作","/machine_learning/decision-tree-complete-guide","machine_learning/decision-tree-complete-guide",{"title":97,"path":98,"stem":99},"為什麼 Decision Tree 在 Tabular Data 上常常勝過 Deep Learning？","/machine_learning/decision-tree-vs-deep-learning-on-tabular-data","machine_learning/decision-tree-vs-deep-learning-on-tabular-data",{"title":101,"path":102,"stem":103},"LightGBM 模型入門：理解高效能的 Gradient Boosting 演算法","/machine_learning/lightgbm-intro-for-tabular-data","machine_learning/lightgbm-intro-for-tabular-data",{"title":105,"path":106,"stem":107},"Logistic Regression（Logit 回歸）完整教學","/machine_learning/logistic-regression-complete-guide","machine_learning/logistic-regression-complete-guide",{"title":109,"path":110,"stem":111},"為什麼模型訓練需要 Train / Validation / Test Dataset？","/machine_learning/train-validation-test-dataset","machine_learning/train-validation-test-dataset",{"title":113,"path":114,"stem":115},"XGBoost 原理與實務教學","/machine_learning/xgboost-complete-guide","machine_learning/xgboost-complete-guide",{"title":117,"path":118,"stem":119,"children":120,"page":41},"Network","/network","network",[121],{"title":122,"path":123,"stem":124},"DNS 設定入門：CNAME vs A Record 完整解析","/network/dns-cname-vs-a-record-guide","network/dns-cname-vs-a-record-guide",{"title":126,"path":127,"stem":128,"children":129},"Nuxt","/nuxt","nuxt",[130,133,137,141,145,149,153,157],{"title":131,"path":127,"stem":132},"Nuxt 實戰指南","nuxt/index",{"title":134,"path":135,"stem":136},"Abstraction Layer 與 Adapter 的差別，一次搞懂設計角色","/nuxt/adapter_abstraction_layer","nuxt/adapter_abstraction_layer",{"title":138,"path":139,"stem":140},"Nuxt 4 全面理解 — 專案檔案結構解析","/nuxt/directory_structure","nuxt/directory_structure",{"title":142,"path":143,"stem":144},"Nuxt 前端 build 流程完整解析","/nuxt/frontend_build","nuxt/frontend_build",{"title":146,"path":147,"stem":148},"Nitro 是什麼？從 Nuxt 專案到 Server 與 Edge 的關鍵引擎","/nuxt/nitro","nuxt/nitro",{"title":150,"path":151,"stem":152},"深入理解 Nuxt 的 .nuxt 與 .output：為什麼部署時一定要分清楚？","/nuxt/nuxt_and_output","nuxt/nuxt_and_output",{"title":154,"path":155,"stem":156},"Nuxt 中 dev、build、preview 的差異一次搞懂","/nuxt/nuxt_dev_build_preview","nuxt/nuxt_dev_build_preview",{"title":158,"path":159,"stem":160},"Nuxt Image vs \u003Cimg>：為什麼 Nuxt 3 專案幾乎都該用 \u003CNuxtImg>？","/nuxt/nuxt_image","nuxt/nuxt_image",{"title":162,"path":163,"stem":164,"children":165},"Python 程式開發","/python","python/index",[166,167,171,175],{"title":162,"path":163,"stem":164},{"title":168,"path":169,"stem":170},"如何在 FastAPI 中調用記憶體 Buffer 回傳圖片 (附實例)","/python/memory_buffer","python/memory_buffer",{"title":172,"path":173,"stem":174},"Python 單元測試教學：如何為你的程式撰寫 Test","/python/python-unit-testing-unittest-pytest","python/python-unit-testing-unittest-pytest",{"title":176,"path":177,"stem":178},"如何使用 uv 取代 pip：改善 Python 專案的開發流程","/python/uv","python/uv",{"title":180,"path":181,"stem":182,"children":183,"page":41},"Statistic","/statistic","statistic",[184,188,192,196],{"title":185,"path":186,"stem":187},"卡方檢定（Chi-Square Test）入門教學","/statistic/chi-square-test-intro","statistic/chi-square-test-intro",{"title":189,"path":190,"stem":191},"常見抽樣方法（Sampling Methods）教學：Stratified、Quota、Convenience、Systematic、Simple Random","/statistic/common-sampling-methods-guide","statistic/common-sampling-methods-guide",{"title":193,"path":194,"stem":195},"Cramér’s V 指標介紹：如何衡量兩個類別變數之間的關聯","/statistic/cramers-v-for-categorical-association","statistic/cramers-v-for-categorical-association",{"title":197,"path":198,"stem":199},"Mann–Whitney U 與 Kolmogorov–Smirnov（KS）檢定：非參數統計檢定的入門指南","/statistic/mann-whitney-u-and-ks-test-intro","statistic/mann-whitney-u-and-ks-test-intro",{"title":201,"path":202,"stem":203,"children":204},"WebDev","/web_dev","web_dev",[205,208,234,248,282,312,334],{"title":206,"path":202,"stem":207},"Web 前端開發","web_dev/index",{"title":209,"path":210,"stem":211,"children":212},"瀏覽器與渲染","/web_dev/browser","web_dev/browser/index",[213,214,218,222,226,230],{"title":209,"path":210,"stem":211},{"title":215,"path":216,"stem":217},"瀏覽器儲存空間完整解析：Cookie、localStorage、IndexedDB 到 Cache Storage","/web_dev/browser/browser-storage-comprehensive-guide","web_dev/browser/browser-storage-comprehensive-guide",{"title":219,"path":220,"stem":221},"DOM (Document Object Model) 深入解析","/web_dev/browser/dom","web_dev/browser/dom",{"title":223,"path":224,"stem":225},"Service Worker Request Flow 深入解析","/web_dev/browser/service-worker-request-flow","web_dev/browser/service-worker-request-flow",{"title":227,"path":228,"stem":229},"CSR、SSR 與 SSG 是什麼？前端渲染策略完整比較","/web_dev/browser/ssr_csr_ssg","web_dev/browser/ssr_csr_ssg",{"title":231,"path":232,"stem":233},"Virtual DOM 深入解析：為什麼它能優化前端效能？","/web_dev/browser/virtual_dom","web_dev/browser/virtual_dom",{"title":235,"path":236,"stem":237,"children":238},"圖形技術","/web_dev/graphics","web_dev/graphics/index",[239,240,244],{"title":235,"path":236,"stem":237},{"title":241,"path":242,"stem":243},"為什麼 Three.js 專案幾乎都選擇 CSR？從 SSR 問題談起","/web_dev/graphics/threejs_csr","web_dev/graphics/threejs_csr",{"title":245,"path":246,"stem":247},"WebGL 是什麼？為什麼前端 3D 幾乎都靠它？","/web_dev/graphics/webgl","web_dev/graphics/webGL",{"title":249,"path":250,"stem":251,"children":252},"架構與配置","/web_dev/infrastructure","web_dev/infrastructure/index",[253,254,258,262,266,270,274,278],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257},"Zeabur + K3s + Nuxt 部署架構完整解析","/web_dev/infrastructure/k3s-zeabur","web_dev/infrastructure/K3s-zeabur",{"title":259,"path":260,"stem":261},"ECS vs Docker vs Kubernetes：從部署堆疊理解三層架構","/web_dev/infrastructure/ecs-docker-kubernetes-stack","web_dev/infrastructure/ecs-docker-kubernetes-stack",{"title":263,"path":264,"stem":265},"ECS vs VPS 是什麼？從部署網站的角度一次搞懂差別","/web_dev/infrastructure/ecs-vs-vps","web_dev/infrastructure/ecs-vs-vps",{"title":267,"path":268,"stem":269},"Zeabur 內網服務連線完整入門指南","/web_dev/infrastructure/k3s-internal-networking","web_dev/infrastructure/k3s-internal-networking",{"title":271,"path":272,"stem":273},"使用 Microservices 架構設計系統的優勢解析","/web_dev/infrastructure/microservices-architecture-advantages","web_dev/infrastructure/microservices-architecture-advantages",{"title":275,"path":276,"stem":277},"Nginx 入門教學：從反向代理到與 Kubernetes 的架構比較","/web_dev/infrastructure/nginx-intro","web_dev/infrastructure/nginx-intro",{"title":279,"path":280,"stem":281},"YAML 配置是什麼？為什麼現代開發都在用它","/web_dev/infrastructure/yaml-configuration","web_dev/infrastructure/yaml-configuration",{"title":283,"path":284,"stem":285,"children":286},"網絡與通訊","/web_dev/network","web_dev/network/index",[287,288,292,296,300,304,308],{"title":283,"path":284,"stem":285},{"title":289,"path":290,"stem":291},"HTTP Request 結構完整解析：從 Request Line 到 Header 一次看懂","/web_dev/network/http-request-structure","web_dev/network/http-request-structure",{"title":293,"path":294,"stem":295},"OSI 模型（Open Systems Interconnection Model）完整解析","/web_dev/network/osi-model","web_dev/network/osi-model",{"title":297,"path":298,"stem":299},"RESTful API 設計原則與實務解析","/web_dev/network/restful-api","web_dev/network/restful-api",{"title":301,"path":302,"stem":303},"RESTful API 五大設計原則深入解析","/web_dev/network/restful-api-principles","web_dev/network/restful-api-principles",{"title":305,"path":306,"stem":307},"Server-Sent Events (SSE) 深入解析與實作教學","/web_dev/network/sse-introduction","web_dev/network/sse-introduction",{"title":309,"path":310,"stem":311},"WebSocket 入門教學：從概念到 Node.js 實作","/web_dev/network/websocket-introduction","web_dev/network/websocket-introduction",{"title":313,"path":314,"stem":315,"children":316},"認證與安全","/web_dev/security","web_dev/security/index",[317,318,322,326,330],{"title":313,"path":314,"stem":315},{"title":319,"path":320,"stem":321},"使用 Cookie 與 Session 建立使用者驗證（完整新手教學）","/web_dev/security/cookie-session-authentication","web_dev/security/cookie-session-authentication",{"title":323,"path":324,"stem":325},"JWT 驗證機制完整解析：從登入流程到實務應用","/web_dev/security/jwt-authentication","web_dev/security/jwt-authentication",{"title":327,"path":328,"stem":329},"Secret Key 簽名是什麼？從零理解資料簽名的本質","/web_dev/security/secret-key-signing","web_dev/security/secret-key-signing",{"title":331,"path":332,"stem":333},"SSH（Secure Shell）是什麼？從遠端登入到安全通道的核心概念","/web_dev/security/ssh","web_dev/security/ssh",{"title":335,"path":336,"stem":337,"children":338},"SEO 與規範","/web_dev/seo","web_dev/seo/index",[339,340,344,348],{"title":335,"path":336,"stem":337},{"title":341,"path":342,"stem":343},"Canonical URL 是什麼？用生活化方式搞懂前端 SEO 的基本保命符","/web_dev/seo/canonical_link","web_dev/seo/canonical_link",{"title":345,"path":346,"stem":347},"robots.txt 是什麼？SEO 的第一道守門員","/web_dev/seo/robot_txt","web_dev/seo/robot_txt",{"title":349,"path":350,"stem":351},"Nuxt SEO 中的 Meta 與 SEO 工具是如何運作的？","/web_dev/seo/seo","web_dev/seo/seo",{"id":353,"title":172,"body":354,"description":1090,"extension":1091,"links":1092,"meta":1093,"navigation":494,"path":173,"seo":1098,"stem":174,"__hash__":1099},"docs/python/python-unit-testing-unittest-pytest.md",{"type":355,"value":356,"toc":1079},"minimark",[357,362,366,374,381,384,388,396,437,440,443,445,449,456,463,610,613,639,648,650,653,656,672,675,683,686,688,691,694,815,818,821,833,836,838,842,852,857,860,876,881,977,987,990,999,1006,1008,1011,1018,1024,1027,1029,1033,1039,1042,1056,1059,1061,1064,1072,1075],[358,359,361],"h2",{"id":360},"為什麼在-python-專案中需要撰寫測試","為什麼在 Python 專案中需要撰寫測試？",[363,364,365],"p",{},"當專案逐漸變大時，程式碼的修改往往會帶來意想不到的錯誤。例如你修改了一個函式，可能會影響到其他依賴該函式的模組。如果每次修改都只能靠人工測試，不但效率低，也很容易遺漏問題。",[363,367,368,369,373],{},"因此在軟體開發中，",[370,371,372],"strong",{},"自動化測試（automated testing）"," 是非常重要的一環。透過撰寫測試程式，我們可以讓電腦自動檢查程式是否符合預期行為。",[363,375,376,377,380],{},"在 Python 中，最常見的測試方式是 ",[370,378,379],{},"單元測試（unit test）","。單元測試的目標是針對「最小功能單位」進行驗證，例如一個函式或一個類別的方法。只要每個單元都能正確運作，整體系統的可靠度也會大幅提升。",[382,383],"hr",{},[358,385,387],{"id":386},"一個最簡單的-python-函式","一個最簡單的 Python 函式",[363,389,390,391,395],{},"在撰寫測試之前，我們先建立一個非常簡單的函式。例如在 ",[392,393,394],"code",{},"math_utils.py"," 中建立一個加法函式：",[397,398,403],"pre",{"className":399,"code":400,"language":401,"meta":402,"style":402},"language-python shiki shiki-themes github-dark","def add(a, b):\n    return a + b\n","python","",[392,404,405,422],{"__ignoreMap":402},[406,407,410,414,418],"span",{"class":408,"line":409},"line",1,[406,411,413],{"class":412},"snl16","def",[406,415,417],{"class":416},"svObZ"," add",[406,419,421],{"class":420},"s95oV","(a, b):\n",[406,423,425,428,431,434],{"class":408,"line":424},2,[406,426,427],{"class":412},"    return",[406,429,430],{"class":420}," a ",[406,432,433],{"class":412},"+",[406,435,436],{"class":420}," b\n",[363,438,439],{},"這個函式的功能很單純：接收兩個數字並回傳相加結果。",[363,441,442],{},"接下來我們的目標，就是寫一個測試程式來確認這個函式是否正常運作。",[382,444],{},[358,446,448],{"id":447},"使用-python-內建的-unittest-撰寫測試","使用 Python 內建的 unittest 撰寫測試",[363,450,451,452,455],{},"Python 標準函式庫提供了一個測試框架 ",[370,453,454],{},"unittest","，它的概念和許多其他語言的測試框架（例如 Java 的 JUnit）相似。",[363,457,458,459,462],{},"首先建立一個測試檔案 ",[392,460,461],{},"test_math_utils.py","：",[397,464,466],{"className":399,"code":465,"language":401,"meta":402,"style":402},"import unittest\nfrom math_utils import add\n\n\nclass TestMathUtils(unittest.TestCase):\n    def test_add(self):\n        result = add(2, 3)\n        self.assertEqual(result, 5)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n",[392,467,468,476,489,496,501,524,536,561,575,580,585,604],{"__ignoreMap":402},[406,469,470,473],{"class":408,"line":409},[406,471,472],{"class":412},"import",[406,474,475],{"class":420}," unittest\n",[406,477,478,481,484,486],{"class":408,"line":424},[406,479,480],{"class":412},"from",[406,482,483],{"class":420}," math_utils ",[406,485,472],{"class":412},[406,487,488],{"class":420}," add\n",[406,490,492],{"class":408,"line":491},3,[406,493,495],{"emptyLinePlaceholder":494},true,"\n",[406,497,499],{"class":408,"line":498},4,[406,500,495],{"emptyLinePlaceholder":494},[406,502,504,507,510,513,515,518,521],{"class":408,"line":503},5,[406,505,506],{"class":412},"class",[406,508,509],{"class":416}," TestMathUtils",[406,511,512],{"class":420},"(",[406,514,454],{"class":416},[406,516,517],{"class":420},".",[406,519,520],{"class":416},"TestCase",[406,522,523],{"class":420},"):\n",[406,525,527,530,533],{"class":408,"line":526},6,[406,528,529],{"class":412},"    def",[406,531,532],{"class":416}," test_add",[406,534,535],{"class":420},"(self):\n",[406,537,539,542,545,548,552,555,558],{"class":408,"line":538},7,[406,540,541],{"class":420},"        result ",[406,543,544],{"class":412},"=",[406,546,547],{"class":420}," add(",[406,549,551],{"class":550},"sDLfK","2",[406,553,554],{"class":420},", ",[406,556,557],{"class":550},"3",[406,559,560],{"class":420},")\n",[406,562,564,567,570,573],{"class":408,"line":563},8,[406,565,566],{"class":550},"        self",[406,568,569],{"class":420},".assertEqual(result, ",[406,571,572],{"class":550},"5",[406,574,560],{"class":420},[406,576,578],{"class":408,"line":577},9,[406,579,495],{"emptyLinePlaceholder":494},[406,581,583],{"class":408,"line":582},10,[406,584,495],{"emptyLinePlaceholder":494},[406,586,588,591,594,597,601],{"class":408,"line":587},11,[406,589,590],{"class":412},"if",[406,592,593],{"class":550}," __name__",[406,595,596],{"class":412}," ==",[406,598,600],{"class":599},"sU2Wk"," \"__main__\"",[406,602,603],{"class":420},":\n",[406,605,607],{"class":408,"line":606},12,[406,608,609],{"class":420},"    unittest.main()\n",[363,611,612],{},"這段程式包含幾個重要概念：",[614,615,616,626,633],"ol",{},[617,618,619,622,623],"li",{},[392,620,621],{},"TestMathUtils"," 是一個測試類別，必須繼承 ",[392,624,625],{},"unittest.TestCase",[617,627,628,629,632],{},"所有測試函式名稱必須以 ",[392,630,631],{},"test_"," 開頭",[617,634,635,638],{},[392,636,637],{},"assertEqual"," 用來檢查結果是否符合預期",[363,640,641,642,644,645,647],{},"當執行這個檔案時，",[392,643,454],{}," 會自動尋找所有 ",[392,646,631],{}," 開頭的函式並逐一執行。",[382,649],{},[358,651,652],{"id":652},"如何執行測試",[363,654,655],{},"在 terminal 中，你可以使用以下指令執行測試：",[397,657,661],{"className":658,"code":659,"language":660,"meta":402,"style":402},"language-bash shiki shiki-themes github-dark","python -m unittest\n","bash",[392,662,663],{"__ignoreMap":402},[406,664,665,667,670],{"class":408,"line":409},[406,666,401],{"class":416},[406,668,669],{"class":550}," -m",[406,671,475],{"class":599},[363,673,674],{},"執行後可能會看到類似以下輸出：",[397,676,681],{"className":677,"code":679,"language":680,"meta":402},[678],"language-text",".\n----------------------------------------------------------------------\nRan 1 test in 0.001s\n\nOK\n","text",[392,682,679],{"__ignoreMap":402},[363,684,685],{},"這表示測試成功通過。如果測試失敗，Python 會顯示錯誤訊息，幫助你快速找到問題。",[382,687],{},[358,689,690],{"id":690},"撰寫更多測試案例",[363,692,693],{},"在實務開發中，我們通常會測試不同的輸入情境。例如：",[397,695,697],{"className":399,"code":696,"language":401,"meta":402,"style":402},"class TestMathUtils(unittest.TestCase):\n    def test_add_positive(self):\n        self.assertEqual(add(2, 3), 5)\n\n    def test_add_negative(self):\n        self.assertEqual(add(-1, -2), -3)\n\n    def test_add_zero(self):\n        self.assertEqual(add(5, 0), 5)\n",[392,698,699,715,724,744,748,757,783,787,796],{"__ignoreMap":402},[406,700,701,703,705,707,709,711,713],{"class":408,"line":409},[406,702,506],{"class":412},[406,704,509],{"class":416},[406,706,512],{"class":420},[406,708,454],{"class":416},[406,710,517],{"class":420},[406,712,520],{"class":416},[406,714,523],{"class":420},[406,716,717,719,722],{"class":408,"line":424},[406,718,529],{"class":412},[406,720,721],{"class":416}," test_add_positive",[406,723,535],{"class":420},[406,725,726,728,731,733,735,737,740,742],{"class":408,"line":491},[406,727,566],{"class":550},[406,729,730],{"class":420},".assertEqual(add(",[406,732,551],{"class":550},[406,734,554],{"class":420},[406,736,557],{"class":550},[406,738,739],{"class":420},"), ",[406,741,572],{"class":550},[406,743,560],{"class":420},[406,745,746],{"class":408,"line":498},[406,747,495],{"emptyLinePlaceholder":494},[406,749,750,752,755],{"class":408,"line":503},[406,751,529],{"class":412},[406,753,754],{"class":416}," test_add_negative",[406,756,535],{"class":420},[406,758,759,761,763,766,769,771,773,775,777,779,781],{"class":408,"line":526},[406,760,566],{"class":550},[406,762,730],{"class":420},[406,764,765],{"class":412},"-",[406,767,768],{"class":550},"1",[406,770,554],{"class":420},[406,772,765],{"class":412},[406,774,551],{"class":550},[406,776,739],{"class":420},[406,778,765],{"class":412},[406,780,557],{"class":550},[406,782,560],{"class":420},[406,784,785],{"class":408,"line":538},[406,786,495],{"emptyLinePlaceholder":494},[406,788,789,791,794],{"class":408,"line":563},[406,790,529],{"class":412},[406,792,793],{"class":416}," test_add_zero",[406,795,535],{"class":420},[406,797,798,800,802,804,806,809,811,813],{"class":408,"line":577},[406,799,566],{"class":550},[406,801,730],{"class":420},[406,803,572],{"class":550},[406,805,554],{"class":420},[406,807,808],{"class":550},"0",[406,810,739],{"class":420},[406,812,572],{"class":550},[406,814,560],{"class":420},[363,816,817],{},"這樣做的目的是確保函式在不同情境下都能正常運作。",[363,819,820],{},"良好的測試通常會包含：",[822,823,824,827,830],"ul",{},[617,825,826],{},"正常輸入",[617,828,829],{},"邊界條件（boundary cases）",[617,831,832],{},"錯誤輸入",[363,834,835],{},"這可以讓程式在未來修改時仍保持穩定。",[382,837],{},[358,839,841],{"id":840},"使用-pytest更簡潔的-python-測試框架","使用 pytest：更簡潔的 Python 測試框架",[363,843,844,845,847,848,851],{},"除了 ",[392,846,454],{}," 之外，Python 社群更常使用 ",[370,849,850],{},"pytest","。",[363,853,854,856],{},[392,855,850],{}," 的優點是語法更簡潔，不需要建立測試類別。",[363,858,859],{},"首先安裝 pytest：",[397,861,863],{"className":658,"code":862,"language":660,"meta":402,"style":402},"pip install pytest\n",[392,864,865],{"__ignoreMap":402},[406,866,867,870,873],{"class":408,"line":409},[406,868,869],{"class":416},"pip",[406,871,872],{"class":599}," install",[406,874,875],{"class":599}," pytest\n",[363,877,878,879,462],{},"接著建立測試檔案 ",[392,880,461],{},[397,882,884],{"className":399,"code":883,"language":401,"meta":402,"style":402},"from math_utils import add\n\n\ndef test_add():\n    assert add(2, 3) == 5\n\n\ndef test_add_negative():\n    assert add(-1, -2) == -3\n",[392,885,886,896,900,904,913,935,939,943,951],{"__ignoreMap":402},[406,887,888,890,892,894],{"class":408,"line":409},[406,889,480],{"class":412},[406,891,483],{"class":420},[406,893,472],{"class":412},[406,895,488],{"class":420},[406,897,898],{"class":408,"line":424},[406,899,495],{"emptyLinePlaceholder":494},[406,901,902],{"class":408,"line":491},[406,903,495],{"emptyLinePlaceholder":494},[406,905,906,908,910],{"class":408,"line":498},[406,907,413],{"class":412},[406,909,532],{"class":416},[406,911,912],{"class":420},"():\n",[406,914,915,918,920,922,924,926,929,932],{"class":408,"line":503},[406,916,917],{"class":412},"    assert",[406,919,547],{"class":420},[406,921,551],{"class":550},[406,923,554],{"class":420},[406,925,557],{"class":550},[406,927,928],{"class":420},") ",[406,930,931],{"class":412},"==",[406,933,934],{"class":550}," 5\n",[406,936,937],{"class":408,"line":526},[406,938,495],{"emptyLinePlaceholder":494},[406,940,941],{"class":408,"line":538},[406,942,495],{"emptyLinePlaceholder":494},[406,944,945,947,949],{"class":408,"line":563},[406,946,413],{"class":412},[406,948,754],{"class":416},[406,950,912],{"class":420},[406,952,953,955,957,959,961,963,965,967,969,971,974],{"class":408,"line":577},[406,954,917],{"class":412},[406,956,547],{"class":420},[406,958,765],{"class":412},[406,960,768],{"class":550},[406,962,554],{"class":420},[406,964,765],{"class":412},[406,966,551],{"class":550},[406,968,928],{"class":420},[406,970,931],{"class":412},[406,972,973],{"class":412}," -",[406,975,976],{"class":550},"3\n",[363,978,979,980,982,983,986],{},"可以看到語法比 ",[392,981,454],{}," 簡單許多，只需要使用 ",[392,984,985],{},"assert"," 即可。",[363,988,989],{},"執行測試：",[397,991,993],{"className":658,"code":992,"language":660,"meta":402,"style":402},"pytest\n",[392,994,995],{"__ignoreMap":402},[406,996,997],{"class":408,"line":409},[406,998,992],{"class":416},[363,1000,1001,1002,1005],{},"pytest 會自動搜尋所有 ",[392,1003,1004],{},"test_*.py"," 檔案並執行測試。",[382,1007],{},[358,1009,1010],{"id":1010},"測試檔案的常見專案結構",[363,1012,1013,1014,1017],{},"在實際專案中，測試通常會放在 ",[392,1015,1016],{},"tests"," 資料夾中，例如：",[397,1019,1022],{"className":1020,"code":1021,"language":680,"meta":402},[678],"project/\n│\n├─ src/\n│  └─ math_utils.py\n│\n├─ tests/\n│  └─ test_math_utils.py\n│\n└─ requirements.txt\n",[392,1023,1021],{"__ignoreMap":402},[363,1025,1026],{},"這樣可以讓程式碼與測試分開管理，也方便 CI/CD 系統自動執行測試。",[382,1028],{},[358,1030,1032],{"id":1031},"什麼是-test-driven-developmenttdd","什麼是 Test Driven Development（TDD）？",[363,1034,1035,1036,851],{},"在某些開發流程中，工程師會先寫測試，再寫實際程式碼。這種方式稱為 ",[370,1037,1038],{},"TDD（Test Driven Development）",[363,1040,1041],{},"TDD 的基本流程通常是：",[614,1043,1044,1047,1050,1053],{},[617,1045,1046],{},"先寫測試（測試會失敗）",[617,1048,1049],{},"撰寫程式讓測試通過",[617,1051,1052],{},"重構程式碼",[617,1054,1055],{},"重複這個循環",[363,1057,1058],{},"這種方式可以確保程式設計是由需求驅動，並且每個功能都有測試覆蓋。",[382,1060],{},[358,1062,1063],{"id":1063},"結論",[363,1065,1066,1067,1069,1070,851],{},"在 Python 專案中撰寫測試，是確保程式品質的重要方法。透過單元測試，我們可以在修改程式時快速確認是否破壞既有功能。Python 提供了內建的 ",[392,1068,454],{}," 框架，同時社群也廣泛使用語法更簡潔的 ",[392,1071,850],{},[363,1073,1074],{},"建立良好的測試習慣，不僅能降低 bug 發生的機率，也能讓專案在長期維護與擴展時更加穩定。當專案規模逐漸增加時，自動化測試往往會成為維持程式品質的關鍵工具。",[1076,1077,1078],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":402,"searchDepth":424,"depth":424,"links":1080},[1081,1082,1083,1084,1085,1086,1087,1088,1089],{"id":360,"depth":424,"text":361},{"id":386,"depth":424,"text":387},{"id":447,"depth":424,"text":448},{"id":652,"depth":424,"text":652},{"id":690,"depth":424,"text":690},{"id":840,"depth":424,"text":841},{"id":1010,"depth":424,"text":1010},{"id":1031,"depth":424,"text":1032},{"id":1063,"depth":424,"text":1063},"介紹 Python 中常見的單元測試方法，包含 unittest 與 pytest 的基本概念、撰寫方式與實際範例，幫助你建立可靠的程式品質。","md",null,{"tags":1094,"category":401,"date":1097},[401,1095,454,850,1096],"testing","software_engineering","2026-03-14",{"title":172,"description":1090},"97s5XxzwDr4hN2TP4lgYqkQq7VRoxV2oxidPSZXucFQ",[1101,1103],{"title":168,"path":169,"stem":170,"description":1102,"children":-1},"學習如何使用 io.BytesIO 在 FastAPI 中即時產生並回傳 Matplotlib 圖表，避免實體檔案帶來的效能瓶頸。",{"title":176,"path":177,"stem":178,"description":1104,"children":-1},"介紹 uv 及其基礎指令 (init, add, sync)，幫助初學者理解如何利用 uv 進行高效且穩定的 Python 環境與套件管理。",1776690843679]