Java TDD FAQ
範圍 — Java 的測試驅動開發:red-green-refactor 循環、JUnit 5、AAA 結構、mock,以及什麼才值得測。 另見:
java_spring.md— Spring 的測試切片;java_exception.md— 對失敗做斷言。
總覽
**測試驅動開發(TDD)**是先寫一個會失敗的測試,再寫讓它通過的程式碼。測試是拿來 驅動設計的,不只是驗證設計。
1) Red-Green-Refactor 循環
text
┌─────────────────────────────────────┐
│ │
▼ │
RED → write a small failing test │
│ │
▼ │
GREEN → write the minimum code to pass │
│ │
▼ │
REFACTOR → clean up code + tests, ───────┘
keep everything green
- Red(紅燈):測試失敗(證明它真的在測東西)。
- Green(綠燈):用最簡單的方式讓它通過 —— 就算先寫死也行,之後再一般化。
- Refactor(重構):在通過的測試這張安全網底下改善結構。
- 每個循環盡量短(幾分鐘)。綠燈時就 commit。
好處:覆蓋率是寫出來就有的、回饋更快、設計更好(可測試就是低耦合)、測試本身 是活的文件、重構有信心。
2) JUnit 5 基礎
java
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
Calculator calc;
@BeforeEach // runs before each test
void setUp() { calc = new Calculator(); }
@Test
void addsTwoNumbers() {
assertEquals(5, calc.add(2, 3));
}
@Test
void throwsOnDivideByZero() {
assertThrows(ArithmeticException.class, () -> calc.divide(1, 0));
}
@ParameterizedTest
@ValueSource(ints = {2, 4, 6})
void detectsEven(int n) {
assertTrue(calc.isEven(n));
}
}
常見的註解與斷言:
| 註解 | 意義 |
|---|---|
@Test |
標記一個測試方法 |
@BeforeEach / @AfterEach |
每個測試前/後各跑一次 |
@BeforeAll / @AfterAll |
只跑一次(static) |
@DisplayName |
給測試一個人看得懂的名字 |
@ParameterizedTest |
用多組輸入跑同一個測試 |
@Disabled |
跳過某個測試 |
主要斷言:assertEquals、assertTrue/assertFalse、assertNull、
assertThrows、assertAll(成組)、assertTimeout。
3) 測試結構:AAA(Arrange-Act-Assert)
java
@Test
void appliesDiscount() {
// Arrange — set up inputs & state
Cart cart = new Cart();
cart.add(new Item("book", 100));
// Act — invoke the behavior under test
double total = cart.totalWith(new PercentDiscount(10));
// Assert — verify the outcome
assertEquals(90.0, total);
}
一個測試只斷言一件邏輯上的行為。測試名稱要說行為
(throwsWhenCartEmpty),不要說實作。
4) Mock
用 test double 把受測單元和慢的、外部的協作者(資料庫、網路、時間)隔開。 以 Mockito 為例:
java
@Test
void sendsEmailOnSignup() {
EmailService email = mock(EmailService.class); // create double
when(email.isUp()).thenReturn(true); // stub a return
SignupService service = new SignupService(email); // inject the mock
service.register("a@b.com");
verify(email).send(eq("a@b.com"), anyString()); // verify interaction
}
名詞:
- Stub:回傳事先安排好的答案。
- Mock:stub 再加上記錄與驗證互動。
- Fake:輕量但真的能跑的實作(例如記憶體版的 repository)。
該 mock 的是你自己掌握邊界的協作者;不要去 mock 值物件,也不要 mock 受測類別本身。
5) 什麼該測
- 該測:商業邏輯、邊界案例、界線值(0、1、最大值、空、null)、 錯誤路徑,以及每一個分支與判斷。
- 不必糾結:無腦的 getter/setter、框架自己的程式碼,或是 mock 到最後 測試只是把實作再抄一遍。
- FIRST 原則:Fast(快)、Independent(獨立)、Repeatable(可重複)、 Self-validating(自我驗證)、Timely(及時)。
- 目標是:一個測試只會因為一個理由失敗,而且讀起來就像行為的規格書。