跳到主要內容

Web Day 16 測試入門:pytest 與 TestClient

Web Day 16 測試入門:pytest 與 TestClient

執行需求:CPU 可跑。今天是「Web 系統實戰:用 FastAPI 打造能上線的後端」系列的第十六篇,我們正式進入品質區塊。在前十五天,你已經能用 FastAPI 寫出 REST 端點、用 SQLModel 操作 SQLite、用 PyJWT 做基本的身份驗證;接下來七天要做的事是「讓這套程式在每次修改之後還能保持正確」。第一個工具是 pytest 與 TestClient,這也是入門自動化測試最友善的一對組合。

引言

很多人寫完 API 就不寫測試,理由多半是「測試很花時間」、「我手動測過了」、「等有空再寫」。這些理由在前幾天的小型範例裡或許撐得過去,可一旦專案開始擴張、改動加速、前後端分離,「靠手動點」就會立刻破功。今天要做的就是把「能不能跑」變成「有沒有一條指令就能驗證所有端點」:我們用 pytest 8.4 做為測試框架,用 FastAPI 內建的 TestClient 做為呼叫端點的工具,這條組合在 CPU 上就能跑,不需要任何外部服務。

這篇文章會做四件事:第一,解釋測試在後端系統裡扮演什麼角色、單元測試與整合測試差別在哪;第二,介紹 TestClient 怎麼把 ASGI 應用包起來;第三,給出一個能直接用 pytest 執行的範例專案;第四,整理常見踩雷與命名建議。今天的所有程式碼都接續 Web Day 4 到 Web Day 15 的同一個專案結構,如果你跳著讀也沒關係,文末有最小可執行的版本。

為什麼要寫測試:從「會跑」到「敢改」

寫後端最大的恐懼不是寫新功能,而是「改舊功能」。當你把 JWT 驗證邏輯從寫死改成依賴注入時,最怕的是原本能跑的登入流程突然壞掉。沒有測試時,你只能靠「開瀏覽器手動點」、「請同事幫忙看」這些方式驗證;有了測試之後,你可以一個指令把所有端點跑過一輪,五秒內就知道這次改動有沒有打到不該打的東西。

測試在後端通常分成三層:

  1. 單元測試(unit test):只測一個函式或一個類別的行為,不碰 I/O、不碰資料庫、不打網路。最便宜、最快、最容易寫。
  2. 整合測試(integration test):把幾個元件接在一起測,例如「HTTP 端點 → 服務層 → SQLite 資料庫」。比單元測試慢一點,但能抓到元件之間的接縫問題。
  3. 端對端測試(end-to-end test):啟動整個服務,用真正的 HTTP 客戶端去打。最接近真實狀況,但也最貴、最脆弱。

這七天我們會專注在前兩層:單元測試會用 pytest 的純函式測試,整合測試會用 FastAPI 的 TestClient 在記憶體裡跑起整個 ASGI 應用,不需要真的佔用一個 port、不需要真的啟動 uvicorn。今天先打穩地基,Day 17 會用 fixture 把測試資料與資料庫管理起來,Day 18 會把同步/非同步情境處理好。

TestClient 是什麼:把 ASGI 應用塞進記憶體

FastAPI 是一個 ASGI 應用框架,意思是它的「應用」本身(app 物件)是一個符合 ASGI 規範的可呼叫物件,只要能呼叫它,就能模擬整個 HTTP 生命週期。TestClient 是 starlette.testclient 提供的工具,底層用 httpx 0.28 做為驅動引擎,把請求丟進 ASGI 應用、把回應接回來,整個過程都在同一個 Python 行程裡完成,不會打真的網路。

這樣做有兩個好處:第一,速度比啟動 uvicorn 再用 curl 測快上數十倍;第二,可以在 CI 裡同時跑數十個測試而不會搶 port。下面是一個簡化的示意:

# app/main.py
# 接續 Web Day 4 的最小 FastAPI 服務,加上一個會做計算的端點
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI(title="Web Day 16 範例", version="0.16.0")


class Item(BaseModel):
    name: str
    price: int


@app.get("/")
def root():
    return {"message": "hello from web day 16"}


@app.get("/health")
def health():
    return {"status": "ok"}


@app.post("/items")
def create_item(item: Item):
    if item.price <= 0:
        raise HTTPException(status_code=422, detail="price 必須大於 0")
    return {"name": item.name, "price": item.price}

這支程式定義了三個端點:/、/health 與 /items。/items 用 Pydantic v2 的 BaseModel 接收 JSON 主體,並在價格小於等於零時丟出 HTTPException。這個 HTTPException 會被 FastAPI 自動轉成 422 狀態碼與 JSON 錯誤主體,這是我們稍後要在測試裡驗證的行為之一。

接著寫第一個 pytest 測試檔。在 Web Day 2 的專案結構下,測試檔放在 tests/ 目錄裡,檔名以 test_ 開頭,這是 pytest 預設的發現規則:

# tests/test_main.py
# 第一個 pytest 測試檔:用 TestClient 打三個端點
from fastapi.testclient import TestClient

from app.main import app

client = TestClient(app)


def test_root_returns_hello():
    response = client.get("/")
    assert response.status_code == 200
    body = response.json()
    assert body == {"message": "hello from web day 16"}


def test_health_returns_ok():
    response = client.get("/health")
    assert response.status_code == 200
    assert response.json() == {"status": "ok"}


def test_create_item_success():
    payload = {"name": "notebook", "price": 120}
    response = client.post("/items", json=payload)
    assert response.status_code == 200
    assert response.json() == {"name": "notebook", "price": 120}


def test_create_item_rejects_non_positive_price():
    payload = {"name": "broken", "price": 0}
    response = client.post("/items", json=payload)
    assert response.status_code == 422
    detail = response.json()["detail"]
    assert any("price" in str(item).lower() for item in detail)

這四個測試涵蓋了我們最常需要驗證的四件事:正確的回應、JSON 結構、寫入路徑、以及錯誤處理。client = TestClient(app) 會把整個 ASGI 應用包起來,接下來的 client.get 與 client.post 就像寫 requests 一樣簡單,但背後是直接把事件送進 app,完全不經過 TCP。

pytest 的核心斷言只有 assert 一個關鍵字。當 assert 後面的表達式為 False 時,pytest 會把該行的程式碼片段擷取起來當作錯誤訊息,這也是為什麼 pytest 的失敗訊息通常比其他框架更易讀。response.json() 回傳的是一般的 Python 字典,所以你可以直接用 == 比對,也可以用 in 檢查部分內容。

完整實作:安裝、執行、調整

在這個系列裡,環境管理統一用 uv(Web Day 2 介紹過)。我們沿用同樣的 pyproject.toml,把 pytest 加進來:

# pyproject.toml(節錄)
[project]
name = "web-system"
version = "0.16.0"
requires-python = ">=3.13"
dependencies = [
    "fastapi==0.116",
    "uvicorn[standard]==0.35",
    "pydantic==2.11",
]

[project.optional-dependencies]
dev = [
    "pytest==8.4",
    "httpx==0.28",
    "pytest-asyncio==0.24",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra -q"

pytest-asyncio 在 Day 18 處理非同步端點時會用到,今天可以不安裝。執行 uv sync 把環境建好:

# 命令列
uv sync --extra dev
# 執行測試
uv run pytest
# 預期輸出(實際數字依測試個數而定):
# tests/test_main.py ....                                  [100%]
# 4 passed in 0.12s

第一次看到綠色 4 passed 是品質區塊的「破蛋」時刻。從此之後,每次你修改 app/main.py,只要 uv run pytest 跑過去沒紅燈,就代表這次改動沒有打到基本盤。下面這個例子展示怎麼用 pytest.mark.parametrize 把同一段斷言套到多組輸入上,這是 pytest 最好用、CPython 內建 unittest 沒有的功能:

# tests/test_items.py
# 用 parametrize 把同一個端點的多組輸入展開成多個測試
import pytest
from fastapi.testclient import TestClient

from app.main import app

client = TestClient(app)


@pytest.mark.parametrize(
    ("payload", "expected_status"),
    [
        ({"name": "pen", "price": 10}, 200),
        ({"name": "book", "price": 250}, 200),
        ({"name": "sticker", "price": 5}, 200),
        ({"name": "broken", "price": 0}, 422),
        ({"name": "broken", "price": -3}, 422),
    ],
)
def test_create_item_payloads(payload, expected_status):
    response = client.post("/items", json=payload)
    assert response.status_code == expected_status


@pytest.mark.parametrize("missing_field", ["name", "price"])
def test_create_item_missing_field(missing_field):
    payload = {"name": "x", "price": 1}
    payload.pop(missing_field)
    response = client.post("/items", json=payload)
    assert response.status_code == 422

第一個測試用五組輸入驗證「價格大於 0 才接受」的規則,第二個用兩個 case 驗證「欄位缺失會被 Pydantic 拒絕」。parametrize 的好處是「一個函式、多個案例」,失敗訊息會自動帶上參數值(例如 PASSED[10-200]),等於免費幫你寫測試報告。

另一個常用工具是 pytest.raises。當你預期某段程式會丟出特定例外時,用它可以把「會丟」這件事寫成斷言:

# tests/test_exceptions.py
# 用 pytest.raises 驗證某個函式會丟出例外
import pytest

from app.main import create_item  # 若你把邏輯抽成純函式


class DummyItem:
    pass


def test_create_item_rejects_non_positive_price():
    bad = DummyItem()
    bad.name = "x"
    bad.price = 0
    with pytest.raises(ValueError):
        create_item(bad)

這個範例只是示意「純函式也可以用 pytest.raises」,目前 create_item 是端點函式、會丟 HTTPException,實務上你會把純邏輯抽到 service 層再用 raises 包。今天先看到這個工具,Day 17 會大量用到。

另一個常見的情境是「端點依賴了外部服務」。Web Day 5 介紹過 FastAPI 的 Depends,當端點簽名裡出現 db: Session = Depends(get_db) 之類的參數時,TestClient 會照常解析依賴(因為它就在同一個行程裡)。如果你想驗證「某個依賴真的被呼叫」,可以暫時用 app.dependency_overrides 換成假實作:

# tests/test_overrides.py
# 用 dependency_overrides 換掉真實依賴
from fastapi.testclient import TestClient

from app.main import app
from app.deps import get_current_user  # 假設是認證依賴


def fake_current_user():
    return {"id": 1, "role": "admin"}


def test_protected_endpoint_runs_with_fake_user():
    app.dependency_overrides[get_current_user] = fake_current_user
    try:
        client = TestClient(app)
        response = client.get("/admin/dashboard")
        assert response.status_code == 200
    finally:
        app.dependency_overrides.clear()

dependency_overrides 是 FastAPI 為測試量身打造的機制:你可以把任何 Depends 換成假實作,跑完測試後記得 clear(),避免影響下一個測試。這個技巧在 Day 17 與 Day 40 都會反覆出現,今天先知道「有這招」就夠了。

常見錯誤與踩雷

第一個踩雷是「TestClient 沒辦法跨行程呼叫」。它跟瀏覽器、requests 不一樣,不會真的送出 HTTP 封包,而是直接把 ASGI 事件送進應用。這表示它不能測試「啟動後過五秒才收到的請求」、「跨行程的背景任務」(Day 19 會處理);但反過來說,它非常安全,可以在 CI 裡開大量平行測試。

第二個踩雷是「測試改動真的資料庫」。當你的端點寫入了 SQLite 或 PostgreSQL(Day 6-10 介紹過),TestClient 預設會用同一個 engine,也就是說你在測試裡新增的資料會殘留到下次測試。這個問題在 Day 17 會用 fixture 與臨時資料庫徹底解決,今天的範例刻意避開資料庫,先專心把 HTTP 邊界測好。

第三個是「assert 之後的表達式被當成一般的布林判斷」。如果寫成 assert response.status_code == 200 卻忘記加 ==,寫成 assert response.status_code, 200(兩個值用逗號隔開),pytest 會把第一個值當條件、第二個值被丟掉,於是「status_code 為 0 或 None」這個測試會誤判通過。寫完之後用 uv run pytest -x 先跑一次,失敗時立刻停,比一次跑完再回頭看哪裡紅燈省事很多。

第四個是「匯入順序錯誤,導致 app 在測試載入時就跑了一次啟動流程」。如果你的 app/main.py 裡有「建立資料表」的程式碼(很多教學會這樣寫),那麼 from app.main import app 會把那段程式也跑一次。解法是把啟動流程放進 lifespan 或啟動腳本,不要在模組層執行副作用。

效能與實務提醒

TestClient 的速度很快,但「每個測試都重新打造一個 client」是個常見的反模式。TestClient(app) 本身不貴,但當你開始在 client 層注入資料庫 session、外部依賴時(Day 17 會做),每次重新打造就會重開連線。比較好的做法是把 client 放進 fixture,讓 pytest 在整個模組共用同一個 client,並在需要時用另一個 fixture 重置狀態。

另一個提醒是「測試名稱要能讀」。pytest 發現測試時,預設用 檔名::函式名 組成識別碼,例如 tests/test_items.py::test_create_item_rejects_non_positive_price。好的測試函式名應該是「在失敗時一眼看出為什麼失敗」的句子,例如 test_create_item_returns_422_when_price_is_zero,而不是 test_items_1。

最後,pytest 有兩個常見旗標值得記住:-x 第一次失敗就停、-k 名稱 只跑名字裡有指定字串的測試(例:pytest -k price 只跑跟價格有關的測試)。開發階段用 -x、CI 階段把所有測試跑完,是這個系列後續幾篇都會用到的節奏。

測試目錄怎麼擺:tests/、conftest.py 與模組化

Web Day 2 介紹過的專案結構,會把測試程式碼放在與應用程式碼平行的 tests/ 目錄。一個簡單的測試目錄通常長這樣:

# 命令列:顯示測試目錄結構
tree tests
# 預期輸出:
# tests/
# ├── __init__.py
# ├── conftest.py          # 共用 fixture(Day 17 詳細介紹)
# ├── test_main.py         # 對應 app/main.py 的測試
# ├── test_items.py        # 對應 item 相關端點
# ├── test_overrides.py    # 依賴替換相關測試
# └── test_exceptions.py   # 例外處理測試

__init__.py 讓 tests/ 成為正式的 Python 套件,這樣測試檔之間可以用絕對匯入(from app.main import app)而不用 sys.path 小技巧。conftest.py 是 pytest 自動載入的「共享空間」,所有放在這裡的 fixture 都會被同目錄與子目錄的測試看到,Day 17 會大量利用這個特性。今天先建立空檔案佔位,後面填入 fixture。

模組化的好處是「改一個端點,只跑那個測試」可以用 pytest tests/test_items.py 直接完成;CI 階段則會用 pytest tests 把整個目錄跑過一輪。這也是為什麼我們推薦一開始就把測試目錄切乾淨,不要把所有測試塞進一個檔案。檔案太大時 pytest 的失敗訊息會很難讀,找對應的測試函式也很花時間。

寫測試的工程紀律:三條簡單守則

第一條:每個測試只驗一件事。一個函式裡同時斷言 status code、JSON 結構、副作用,失敗時你只會知道「有東西壞了」,不會知道是哪裡壞了。把斷言拆成幾個獨立測試,失敗訊息會清楚告訴你哪個情境出問題。

第二條:測試之間不互相依賴。pytest 預設按檔名字母順序跑,這意味著如果測試 A 改動了共用狀態、測試 B 又讀了那個狀態,順序一換就壞掉。正確的做法是「每個測試自己建立自己需要的狀態」,Day 17 的 fixture 正是用來落實這條守則的。

第三條:測試名稱要描述情境,不是描述函式。test_user_can_login_with_valid_credentials 比 test_login 好太多,前者在失敗時讓你知道「哪個情境壞了」,後者只有「有東西壞了」的訊息。pytest 的識別碼會把函式名展開到錯誤訊息裡,這是免費的測試報告。

什麼時候該寫、什麼時候先緩緩

寫測試不是越多越好,而是要在「改這個地方可能壞掉」「這個地方壞掉會很痛」的地方先寫。常見的優先順序是:核心商業邏輯(金額計算、衝突檢查)、認證授權(Web Day 11-13)、對外的契約端點(HTTP API 的回應形狀)、最後才是工具函式與內部 helper。

有些地方反而不適合寫測試:純展示用的 demo、一次性的資料遷移腳本、美術給視覺效果的 CSS 動畫。後者用截圖比對(視覺回歸)會更合適;前兩者通常只在特定時間點跑一次,寫成正式測試的成本遠高於偶爾手動檢查。

另一個判斷方式是「這個地方會不會被改」。如果某段程式碼一年只會被改一次,那寫測試的投資報酬率不高;但如果每兩週就會被改一次、而且每次改動都會擔心打破既有行為,趕快把測試補上。今天的範例都圍繞在「會被常常改」的端點,這也是這個系列後續八篇的寫法:每一篇都先把測試寫起來,再迭代實作。

最後一個提醒:寫測試的當下,你會開始懷疑自己設計的端點是否合理。一個寫起來卡卡的測試通常意味著端點的輸入或輸出定義得不乾淨;與其讓測試遷就設計,不如回頭調整設計。今天的三個端點都很簡單,但你已經可以看到哪些適合直接寫測試、哪些需要先重構。

小結

今天我們把測試的第一塊地基打下來了。pytest 8.4 是測試框架,assert 是唯一的斷言關鍵字;FastAPI 的 TestClient 把 ASGI 應用包在記憶體裡,用 httpx 0.28 當底層驅動;parametrize 與 raises 是兩個立即能省時間的工具。我們給出了一個最小可執行的範例專案,uv run pytest 一行指令就能跑完四到八個測試。今天還沒有引入 fixture,所以測試之間共用同一份狀態;明天我們會用 fixture 把這個問題處理掉,並把測試資料庫也納入管理。

結語

測試是品質的起點,但單元測試不是品質的全部。今天我們用 TestClient 驗證了 HTTP 邊界,但資料庫、認證、外部 API 這些「依賴」目前還是寫死或省略的。明天,我們會用 pytest 的 fixture 機制把測試資料、測試客戶端、測試資料庫全部管起來:同樣的範例專案,加上 fixture 之後,每個 case 都能在乾淨的環境裡跑、不會互相污染。

延伸資源

  • pytest 官方文件(8.4,2025):https://docs.pytest.org/en/stable/
  • FastAPI Testing 官方教學(0.116,2025-07):https://fastapi.tiangolo.com/tutorial/testing/
  • httpx 官方文件(0.28,2025):https://www.python-httpx.org/
  • Starlette TestClient 說明(0.41,2025):https://www.starlette.io/testclient/
  • Test-Driven Development with Python(Harry Percival,2025 仍持續更新):https://www.obeythetestinggoat.com/

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...