iT邦幫忙

2026 iThome 鐵人賽

DAY 10
2
Software Development

從脆弱腳本到可信任測試平台:自動化測試架構30天系列 第 10

Day 10|測試框架的分層設計:打造清晰可讀的 pytest 專案結構

  • 分享至 

  • xImage
  •  

「好的專案結構就像整理得當的工具箱:開發者不需要看文件,也能在 3 秒內找到測試資料、Page Object 或工具函式放在哪裡。」

大家好,我是 Jane,一個每天跟 Python、pytest 和自動化測試架構打交道的自動化測試工程師(SDET)。

在過去幾天裡,我們從「單一腳本」聊到「測試框架核心(Day 6)」、「測試案例粒度(Day 7)」、「 Clean Code(Day 8)」以及「Page/Component Object 實戰(Day 9)」。

有了這些技術塊之後,下一個最常讓 QA 頭痛的問題就是:

  • 「我該怎麼把這些檔案擺放乾淨?conftest.py 該寫什麼?測試資料要放在哪?pages 資料夾該怎麼層級化?」

如果沒有規劃好專案結構,隨著測試案例累積到 50 個以上,你的專案很快就會出現 .py 檔案亂放、import 路徑超長且容易循環引用的災難。

今天這篇文章,我們就來手把手建立一個可擴充、結構清晰、符合 Python/pytest 生態系的最佳實踐(Best Practices)專案結構

一、 測試框架的分層設計 Blueprint

一個能維護三年的 pytest 自動化測試專案,通常具備以下 8 大層級

┌────────────────────────────────────────────────────────────────┐
│ 1. Tests Layer (測試案例層) - tests/                            │
│    - 僅包含 test_*.py,專注於語意化步驟與 pytest assert 斷言     │
├────────────────────────────────────────────────────────────────┤
│ 2. Business Flow / Service Layer (業務流程層 - 選填)            │
│    - 組合多個 Page/Component 操作的複雜商業情境                  │
├────────────────────────────────────────────────────────────────┤
│ 3. Page & Component Object Layer (頁面與元件物件層) - pages/    │
│    - 封裝 DOM Locator 與介面操作 (Click, Fill, Get)             │
├────────────────────────────────────────────────────────────────┤
│ 4. Test Data Layer (測試資料層) - test_data/ fixtures/          │
│    - JSON/YAML 靜態資料或 Factory 產生的動態資料                 │
├────────────────────────────────────────────────────────────────┤
│ 5. Fixtures & Lifecycle Layer (生命週期管理) - conftest.py      │
│    - pytest 的 Fixture、Driver 初始化、環境變數載入              │
├────────────────────────────────────────────────────────────────┤
│ 6. Configuration Layer (設定檔) - config.py / .env / pytest.ini │
│    - 不同環境的 Base URL、Timeout、帳號密碼等配置                │
├────────────────────────────────────────────────────────────────┤
│ 7. Utilities & Helpers Layer (工具層) - utils/                 │
│    - DB 連線、API Client、DB 清理、特製 Log/Report 工具         │
└────────────────────────────────────────────────────────────────┘

二、 落地實戰:完整的 pytest 專案目錄樹

以下是推薦的標準 Python/pytest 自動化測試專案結構:

Plaintext

my_autotest_framework/
│
├── .env.example                # 環境變數範本 (不可 commit 敏感資訊)
├── pytest.ini                  # pytest 核心設定 (如: addopts, markers, testpaths)
├── requirements.txt            # 套件依賴需求 (playwright, pytest, python-dotenv 等)
│
├── config/                     # 環境組態管理
│   ├── __init__.py
│   └── settings.py             # 讀取 .env 的動態設定類別
│
├── pages/                      # Page & Component Object 層
│   ├── __init__.py
│   ├── base_page.py            # 所有 Page 的基底類別 (提供通用等待與操作)
│   ├── components/             # 可複用的 Component Objects
│   │   ├── __init__.py
│   │   ├── navbar_component.py
│   │   └── table_component.py
│   └── dashboard_page.py       # 具體頁面 Object
│
├── test_data/                  # 靜態測試資料庫
│   └── user_data.json
│
├── utils/                      # 通用工具庫
│   ├── __init__.py
│   ├── api_client.py           # 快速透過 API 建資料的 Helper
│   └── db_helper.py            # 資料庫查詢與清理工具
│
├── tests/                      # 測試案例層 (pytest 執行的核心)
│   ├── conftest.py             # 全域/區域 pytest Fixtures (核心靈魂!)
│   ├── auth/                   # 依模組/功能分資料夾
│   │   └── test_login.py
│   └── dashboard/
│       └── test_user_search.py

三、 各關鍵層級的 Python 程式碼示範

我們來看這個架構中的幾個關鍵檔案該怎麼寫:

1. 設定檔層 (config/settings.py)

利用 python-dotenvpydantic-settings 統一管理環境組態:

Python

# config/settings.py
import os
from dotenv import load_dotenv

# 載入 .env 檔案
load_dotenv()

class Settings:
    BASE_URL: str = os.getenv("BASE_URL", "https://staging.example.com")
    TIMEOUT: int = int(os.getenv("TIMEOUT", "10000")) # 毫秒
    ADMIN_USER: str = os.getenv("ADMIN_USER", "admin_test")
    ADMIN_PASS: str = os.getenv("ADMIN_PASS", "secure_password")

settings = Settings()

2. 基底頁面層 (pages/base_page.py)

提供所有 Page 物件共用的基礎能力:

Python

# pages/base_page.py
from playwright.sync_api import Page
from config.settings import settings

class BasePage:
    def __init__(self, page: Page):
        self.page = page

    def navigate_to(self, path: str = ""):
        full_url = f"{settings.BASE_URL}{path}"
        self.page.goto(full_url)

    def get_title(self) -> str:
        return self.page.title()

3. pytest 生命週期與 Fixture 層 (tests/conftest.py)

conftest.py 是 pytest 的大腦,負責提供 Page 物件、初始化頁面、處理失敗截圖與 Report Hooks:

Python

# tests/conftest.py
import pytest
from playwright.sync_api import Page
from pages.dashboard_page import DashboardPage
from config.settings import settings

@pytest.fixture(scope="function")
def dashboard_page(page: Page) -> DashboardPage:
    """自動注入 Playwright page 並初始化 DashboardPage 的 Fixture"""
    dashboard = DashboardPage(page)
    dashboard.navigate_to("/dashboard")
    return dashboard

# 當測試失敗時自動截圖附加到 pytest 報告中
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()

    if report.when == "call" and report.failed:
        page: Page = item.funcargs.get("page")
        if page:
            # 截圖並儲存
            page.screenshot(path=f"reports/screenshots/failure_{item.name}.png")

4. 測試案例層 (tests/dashboard/test_user_search.py)

乾淨、極簡、語意明確的 pytest 測試:

Python

# tests/dashboard/test_user_search.py
from playwright.sync_api import expect
from pages.dashboard_page import DashboardPage

def test_admin_should_see_results_when_searching_valid_user(dashboard_page: DashboardPage):
    """
    測試意圖: 管理員在 Dashboard 搜尋合法使用者時,應該列出該使用者資料
    """
    # Act: 透過元件進行搜尋操作
    dashboard_page.user_table.search("Jane")

    # Assert: 斷言留在 Test Case 層,語意極度清晰
    rows = dashboard_page.user_table.get_rows()
    expect(rows).to_have_count(1)

四、 第一線 QA 的專案維護 3 大原則

  1. tests/ 資料夾內部禁止出現硬編碼(No Hardcoding)

    測試案例裡不可以出現 https://... 網址、帳號密碼或 .btn-primary 等定位器。網址走 config,定位器走 pages,資料走 test_data

  2. 善用 pytest 的 conftest.py 作用域(Scope)

    • 需要跨測試共用的高成本操作(如:API 登入取得 Token),使用 scope="session"
    • 需要每個案例乾淨隔離的操作(如:打開全新頁面),使用 scope="function"(預設)。
  3. 保持依賴單向流動,防止 Circular Import(循環引用)

    依賴方向永遠應該是:

    Tests > Pages / Business Flow > Components / BasePage > Config / Utils

    千萬不要在 pages/ 裡面去引用 tests/ 裡面的 Fixture 或函式!

結語與第二部分總結

恭喜你!到今天 Day 10 為止,我們正式完成了一個具備良好架構、高可讀性且符合 Clean Code 原則的測試框架基礎

我們從第二部分的 Day 6 到 Day 10,依序解鎖了:

  • Day 6:腳本與框架的差異(7 大要素)
  • Day 7:測試案例粒度與 AAA 模式
  • Day 8:測試程式碼品質與 Bad Smells 重構
  • Day 9:Page & Component Object 實戰拆解
  • Day 10:pytest 專案分層結構落地

從明天開始,我們將邁入整個系列文章最精彩、也最接地氣的《第三部分:處理最容易讓測試變脆弱的問題》。


上一篇
Day 9|Page Object到底該怎麼用?從濫用反思到 Component Object 實戰
下一篇
Day 11|為什麼UI自動化測試這麼容易壞?剖析 Web 測試脆弱的 8 大根源
系列文
從脆弱腳本到可信任測試平台:自動化測試架構30天12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言