自動化測試是將人對軟體的測試行為轉化為由機器執行的實踐,能夠大幅提升測試效率和一致性。

什麼是自動化測試?#

自動化測試的本質:先寫一段程式碼,然後去測試另一段程式碼。實現自動化測試本身屬於開發工作,需要投入時間和精力。

自動化測試的價值#

優勢說明
替代重複性工作讓測試工程師專注於用例設計和新功能測試
提升回歸效率非常適合敏捷開發的頻繁回歸需求
利用無人值守時間夜間執行測試,白天分析結果
實現特殊測試7×24 穩定性測試、高並行壓力測試
保證一致性避免人為遺漏或疏忽

自動化測試的局限#

業界玩笑話:「開發手一抖,自動化測試忙一宿」——自動化測試的維護成本是實施前必須考慮的關鍵因素。

局限說明
不能取代手工測試只能替代高頻率、機械化的步驟
維護成本高被測系統變化時需要更新用例
開發工作量大執行次數 ≥ 5 次才能收回成本
依賴用例品質不穩定的用例比沒有更糟糕

適合自動化測試的場景#

適合自動化的條件檢查清單:
├── ☑ 需求穩定,不頻繁變更
├── ☑ 研發周期長,需要頻繁回歸
├── ☑ 需要跨平台重複執行
├── ☑ 手工測試無法實現(如高並行)
├── ☑ 開發過程規範,具備可測試性
└── ☑ 團隊具備編程能力

ROI 分析#

自動化測試 ROI = (手工測試成本 × 執行次數 - 自動化開發成本 - 維護成本) / 自動化投入

經驗法則:
- 執行次數 ≥ 5 次,自動化才划算
- 20% 的精力覆蓋 80% 的回歸測試

單元測試#

單元測試是對軟體中最小可測試單元(通常是函式或類)進行隔離測試。

單元測試的價值#

電視機生產的類比:
├── 電子元器件 ─────────── 單元(函式/類)
├── 功能電路板 ─────────── 模組
└── 完整電視機 ─────────── 系統

先測試元器件,再組裝,比組裝後逐級排查問題效率高得多。

單元測試用例的組成#

單元測試用例是「輸入資料」和「預期輸出」的集合。但這兩者都比想像中複雜得多。

輸入資料包括:

  • 被測函式的輸入參數
  • 函式內部讀取的全局/靜態變數
  • 函式內部讀取的成員變數
  • 函式內部呼叫子函式獲得/改寫的資料

預期輸出包括:

  • 被測函式的回傳值
  • 被測函式的輸出參數
  • 被測函式改寫的成員變數/全局變數
  • 被測函式進行的文件/資料庫/訊息佇列更新

驅動程式碼、樁程式碼和 Mock 程式碼#

┌─────────────────────────────────────────────────┐
│                   測試程式碼                       │
│  ┌──────────────────────────────────────────┐  │
│  │           驅動程式碼 (Driver)               │  │
│  │    - 呼叫被測函式                         │  │
│  │    - 準備測試資料                         │  │
│  │    - 驗證結果                             │  │
│  └──────────────────────────────────────────┘  │
│                      ↓                          │
│  ┌──────────────────────────────────────────┐  │
│  │           被測函式                        │  │
│  └──────────────────────────────────────────┘  │
│                      ↓                          │
│  ┌──────────────────────────────────────────┐  │
│  │       樁程式碼 (Stub) / Mock 程式碼           │  │
│  │    - 代替被測函式呼叫的真實程式碼            │  │
│  │    - 控制執行路徑                         │  │
│  └──────────────────────────────────────────┘  │
└─────────────────────────────────────────────────┘

Stub vs Mock#

特性Stub(樁程式碼)Mock(模擬程式碼)
目的控制被測函式的執行路徑驗證交互行為
關注點回傳值呼叫方式、參數、次數、順序
Assert 位置驅動程式碼中Mock 函式中
// Stub 示例:控制回傳值
@Test
void testWithStub() {
    // Stub:讓 userService.getUser() 回傳固定值
    when(userService.getUser(anyLong())).thenReturn(testUser);

    // 執行被測方法
    String result = orderService.createOrder(userId);

    // 在驅動程式碼中驗證結果
    assertEquals("SUCCESS", result);
}

// Mock 示例:驗證交互
@Test
void testWithMock() {
    orderService.createOrder(userId);

    // 驗證 Mock 是否被正確呼叫
    verify(emailService, times(1)).sendConfirmation(any());
    verify(inventoryService).reduceStock(productId, 1);
}

整合測試#

整合測試關注軟體模組之間的介面呼叫和資料傳遞。

整合測試 vs 單元測試#

方面單元測試整合測試
範圍單個函式/類多個模組協作
依賴處理使用 Stub/Mock使用真實依賴
目標驗證邏輯正確性驗證介面協作

抽樁(Un-stub)#

單元測試階段:函式 A 呼叫 Stub 函式 B
        ↓
整合測試階段:將 Stub 函式 B 替換為真實函式 B(抽樁)

API 自動化測試#

API 測試的三大步驟:

// 1. 準備測試資料
UserDTO testUser = new UserDTO("test@example.com", "password123");

// 2. 發起 API 呼叫
Response response = given()
    .contentType(ContentType.JSON)
    .body(testUser)
    .when()
    .post("/api/users");

// 3. 驗證回傳結果
response.then()
    .statusCode(201)
    .body("id", notNullValue())
    .body("email", equalTo("test@example.com"));

API 測試框架選擇#

框架語言特點
REST AssuredJava流式 API,易於使用
Postman/NewmanJS界面友好,支持 CI/CD
pytest + requestsPython靈活,社區活躍
KarateJavaDSL 風格,無需編碼

GUI 自動化測試#

Selenium 工作原理#

┌──────────────────────────────────────────────────────┐
│                    測試程式碼 (Client)                  │
│         driver.findElement(By.id("login"))           │
└──────────────────────────────────────────────────────┘
                          │
                    HTTP Request
                    (WebDriver 協定)
                          ↓
┌──────────────────────────────────────────────────────┐
│              Remote Server (WebDriver)               │
│         解析請求,呼叫瀏覽器原生 WebDriver             │
└──────────────────────────────────────────────────────┘
                          │
                    原生 API 呼叫
                          ↓
┌──────────────────────────────────────────────────────┐
│                     瀏覽器                           │
│              執行頁面操作,回傳結果                    │
└──────────────────────────────────────────────────────┘

頁面物件模型(Page Object Model)#

頁面物件模型將頁面元素和操作封裝成獨立的類,使測試程式碼更易維護。

// 頁面物件類
public class LoginPage {
    private WebDriver driver;

    @FindBy(id = "username")
    private WebElement usernameInput;

    @FindBy(id = "password")
    private WebElement passwordInput;

    @FindBy(id = "login-btn")
    private WebElement loginButton;

    public void login(String username, String password) {
        usernameInput.sendKeys(username);
        passwordInput.sendKeys(password);
        loginButton.click();
    }
}

// 測試類
public class LoginTest {
    @Test
    void testLogin() {
        LoginPage loginPage = new LoginPage(driver);
        loginPage.login("user@example.com", "password");
        // 驗證登錄成功
    }
}

測試資料管理#

創建方式#

方式說明適用場景
API 呼叫通過產品 API 創建資料資料準確性要求高
資料庫操作直接操作資料庫API 不支持,需要大量資料
綜合方式API + 資料庫創建特定狀態的資料

創建時機#

時機說明優缺點
On-the-fly測試執行時實時創建資料隔離好,但效率低
Out-of-box預先創建好「開箱即用」效率高,但可能被污染

最佳實踐:穩定資料(如商品類目)用 Out-of-box,一次性資料(如訂單)用 On-the-fly。

單元測試最佳實踐#

FIRST 原則#

原則說明
Fast快速執行,秒級反饋
Independent用例之間相互獨立
Repeatable任何環境下結果一致
Self-validating自動判斷通過/失敗
Timely與生產程式碼同時編寫

測試程式碼組織(AAA 模式)#

@Test
void shouldCalculateTotalPrice() {
    // Arrange(準備)
    ShoppingCart cart = new ShoppingCart();
    cart.add(new Product("Apple", 10.0), 2);
    cart.add(new Product("Banana", 5.0), 3);

    // Act(執行)
    double total = cart.calculateTotal();

    // Assert(驗證)
    assertEquals(35.0, total, 0.001);
}

實施單元測試的挑戰#

常見挑戰及解決方案
挑戰解決方案
緊密耦合的程式碼難以隔離重構程式碼,引入依賴注入
程式碼可測試性差設計時考慮可測試性
無法模擬系統底層函式使用 PowerMock 等工具
覆蓋率越往後越難提高設定合理目標,不追求 100%

自動化測試框架選型#

單元測試框架#

語言框架特點
JavaJUnit 5, TestNG註解驅動,生態豐富
Pythonpytest, unittestpytest 更靈活
JavaScriptJest, MochaJest 內建 Mock
Gotesting + testify內建支持

Mock 框架#

語言框架
JavaMockito, PowerMock
Pythonunittest.mock, pytest-mock
JavaScriptJest (內建), Sinon.js

程式碼覆蓋率工具#

語言工具
JavaJaCoCo
JavaScriptIstanbul, nyc
Pythoncoverage.py
Gogo test -cover