S SmartDocs
Serie: Angelia java 149 líneas · Actualizado 2026-09-27

GuestListTest.java

Angelia/Java/Week_6_7/code/05_junit_tdd/GuestListTest.java

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.Arrays;

import static org.junit.jupiter.api.Assertions.*;

/**
 * GuestList 的 JUnit 5 黑箱測試。
 *
 * 重點:
 *   - 變數宣告成 GuestList(介面),而不是某個實作類別:測的是「契約」,不是實作細節。
 *   - 每個 @Test 方法都是獨立的:JUnit 會為每個測試建立一個「新的」測試物件,
 *     所以 @BeforeEach 裡建立的名單不會在測試之間互相干擾。
 *   - 同一組測試對兩種實作都跑一次(見 LazyTest / StrictTest 兩個子類別)。
 *
 * 在 IntelliJ 中:把游標放在紅色的 @Test 上按 Alt+Enter → Add JUnit5 to classpath,
 * 然後按類別名旁邊的綠色播放鍵即可執行。
 */
abstract class GuestListTest {

    /** 由子類別決定要測哪一種實作(工廠方法)。 */
    abstract GuestList create();

    private GuestList gl;

    @BeforeEach
    void setUp() {
        gl = create();                        // 每個測試開始前都拿到一份全新的空名單
    }

    @Test
    @DisplayName("新名單是空的")
    void emptyListHasNobody() {
        assertFalse(gl.isComing("Fabian"));
        assertEquals(0, gl.count());
        assertEquals(0, gl.coming().length);
    }

    @Test
    @DisplayName("add 之後 isComing 為 true")
    void addThenIsComing() {
        gl.add("Fabian");
        assertTrue(gl.isComing("Fabian"));
        assertFalse(gl.isComing("Nicholas"));
        assertEquals(1, gl.count());
    }

    @Test
    @DisplayName("集合語意:同一個人 add 兩次等於 add 一次")
    void addingTwiceIsIdempotent() {
        gl.add("Fabian");
        gl.add("Fabian");
        assertTrue(gl.isComing("Fabian"));
        assertEquals(1, gl.count(), "重複加入不應被重複計算");
        gl.remove("Fabian");
        assertFalse(gl.isComing("Fabian"), "移除一次之後必須完全消失");
        assertEquals(0, gl.count());
    }

    @Test
    @DisplayName("remove 只影響被移除的人")
    void removeAffectsOnlyThatGuest() {
        gl.add("Fabian");
        gl.add("Nicholas");
        gl.remove("Fabian");
        assertFalse(gl.isComing("Fabian"));
        assertTrue(gl.isComing("Nicholas"));
        assertEquals(1, gl.count());
    }

    @Test
    @DisplayName("從空名單或不存在的人 remove 不會出錯")
    void removeFromEmptyOrMissing() {
        gl.remove("Fabian");                  // 空名單
        assertFalse(gl.isComing("Fabian"));
        gl.add("Ann");
        gl.remove("Bob");                     // 不存在的人
        assertTrue(gl.isComing("Ann"));
        assertEquals(1, gl.count());
    }

    @Test
    @DisplayName("移除後可以再加回來")
    void reAddAfterRemove() {
        gl.add("Fabian");
        gl.remove("Fabian");
        gl.add("Fabian");
        assertTrue(gl.isComing("Fabian"));
        assertEquals(1, gl.count());
    }

    @Test
    @DisplayName("加入很多人:觸發陣列倍增也要正確")
    void manyGuestsTriggerResize() {
        int i = 0;
        while (i < 1000) {
            gl.add("guest" + i);
            i = i + 1;
        }
        assertEquals(1000, gl.count());
        assertTrue(gl.isComing("guest0"));
        assertTrue(gl.isComing("guest999"));
        assertFalse(gl.isComing("guest1000"));
    }

    @Test
    @DisplayName("coming() 回傳的是防禦性複製,改它不影響名單")
    void comingReturnsDefensiveCopy() {
        gl.add("Fabian");
        gl.add("Nicholas");
        String[] snapshot = gl.coming();
        assertEquals(2, snapshot.length);
        String[] sortedSnapshot = snapshot.clone();
        Arrays.sort(sortedSnapshot);
        assertArrayEquals(new String[]{"Fabian", "Nicholas"}, sortedSnapshot);

        snapshot[0] = "Intruder";             // 惡意(或不小心)改動回傳的陣列
        assertFalse(gl.isComing("Intruder"), "封裝:外部改動不得影響內部狀態");
        assertEquals(2, gl.count());
    }

    @Test
    @DisplayName("assertThrows 示範:null 名字不合法(本實作選擇拋出例外)")
    void nullNameIsRejected() {
        // 這個測試示範 assertThrows 的用法;GuestList 介面本身沒有規定 null 的行為,
        // 所以嚴格來說它是「白箱」測試——只對我們自己的實作成立。
        IllegalArgumentException e =
                assertThrows(IllegalArgumentException.class, () -> gl.add(null));
        assertNotNull(e.getMessage());
    }
}

/** 對「懶惰」實作跑整套測試。 */
class LazyGuestListTest extends GuestListTest {
    @Override
    GuestList create() {
        return new GuestArrayList();
    }
}

/** 對「嚴格」實作跑同一套測試。 */
class StrictGuestListTest extends GuestListTest {
    @Override
    GuestList create() {
        return new GuestArrayUniqueList();
    }
}

Artículos relacionados