# SQT Endterm Study Plan (Resit 2024-2025 Based)

## Exam Overview

| Attribute | Detail |
|-----------|--------|
| **Format** | WebLab exam, 3 hours |
| **Pass Criteria** | >= 5.8 points (57.5/100 minimum to pass) |
| **Question Types** | ~84% programming, ~16% theory MC |
| **Andy Features** | "Only tests" and "Analyse coverage" only |
| **Documentation** | Java, JUnit, AssertJ, Mockito, Selenium, jqwik available |
| **IntelliJ** | Possible but not guaranteed; always copy/save to WebLab |
| **Grading Weights** | Coverage 5%, Mutation 5%, Meta 85-90%, Code checks 0-10% |
| **Zero-point Rules** | Code must compile + all tests must pass |
| **Penalties** | Applied silently during exam (cannot see them from Andy) |

**Topics covered:** All 11 lectures (Chapters 1-10 of Aniche's book + Guest lectures on Automatic Test Generation). JavaScript front-end testing is NOT on the exam. TDD is theory-only (MC questions).

---

## Tier 1: Very High weight programming assignments -- study these first

| # | Topic | Resit Weight | Lectures | Key Concepts | Done |
|---|-------|-------------|----------|--------------|------|
| 1 | Domain/Specification Testing | 20.0 pts | L2 | Equivalence partitioning, boundary analysis, category-partition method, meta tests | [ ] |
| 2 | Selenium/WebDriver | 20.0 pts | L9 | WebDriver setup, element locating, Page Objects, test infrastructure | [ ] |
| 3 | SQL Testing | 18.0 pts | L9 | SQL queries, JOINs, subqueries, WITH clauses, COALESCE, LEFT OUTER JOIN, category-partition on SQL | [ ] |
| 4 | Mocking | 15.0 pts | L6 | Mockito stubbing (when/thenReturn), verification (verify/never), ArgumentCaptor, spy, mock vs stub, code check penalties | [ ] |
| 5 | Property-Based Testing | 10.0 pts | L5 | jqwik @Property, @ForAll, @IntRange, Arbitraries API, property types (round-tripping, invariants, idempotence), provider methods, things to avoid | [ ] |
| 6 | Mutation Testing | 5.0 pts | L3 | Mutants, killing mutants, PIT report, equivalent mutants, mutation operators | [ ] |

---

## Tier 2: Theory MC + medium-weight programming -- study these next

| # | Topic | Resit Weight | Lectures | Key Concepts | Done |
|---|-------|-------------|----------|--------------|------|
| 7 | MC/DC Coverage | 4.0 pts | L3 | Modified Condition/Decision Coverage, N+1 test cases, independent influence, truth tables, condition/decision subsumption | [ ] |
| 8 | Branch Coverage | 3.0 pts | L3 | Branch (decision) coverage, minimum tests, CFG, path vs branch coverage | [ ] |
| 9 | Design by Contract | 1.0 pts | L4 | Preconditions, postconditions, class invariants, assert vs if/throw, defensive vs cooperative, LSP | [ ] |
| 10 | Invariant in Subclass | 1.0 pts | L4 | Subclass invariants must be stronger or equal, Liskov Substitution Principle, precondition/postcondition strength in overrides | [ ] |
| 11 | Test-Driven Development | 1.0 pts | L8 | Red-green-refactor cycle, TDD steps, when to use TDD, pragmatic vs strict TDD | [ ] |
| 12 | Test Code Quality | 1.0 pts | L10 | Test smells (unclear assertions, bad handling of resources, fixtures too general, sensitive assertions), DAMP vs DRY, naming, structure (AAA/GWT) | [ ] |
| 13 | Testing Principles | 1.0 pts | L1 | Exhaustive testing impossible, defect clustering, pesticide paradox, verification vs validation, testing pyramid | [ ] |

---

## Tier 3: Lower priority -- review if time permits

| # | Topic | Lectures | Key Concepts | Done |
|---|-------|----------|--------------|------|
| 14 | Boundary Testing | L2 | On-point vs off-point, floating-point precision, equality conditions | [ ] |
| 15 | Test Smells | L10 | Flaky tests, long methods, test code duplication, obscure tests | [ ] |
| 16 | Design for Testability | L7 | Dependency injection, dependency inversion, hexagonal architecture, controllability, observability, ports and adapters | [ ] |
| 17 | JavaScript Front-End Testing | L11 | Jest (NOT on exam -- skip) | [x] |
| 18 | Auto Test Generation | L12 | Fuzzing, search-based generation (review briefly for theory) | [ ] |

---

## Resit 2024-2025 Questions

| Q# | Pts | Topic | Type | Lectures | Done |
|----|-----|-------|------|----------|------|
| 1 | 1.0 | Testing Principles | MC | L1 | [ ] |
| 2 | 1.0 | Design by Contract | MC | L4 | [ ] |
| 3 | 1.0 | Invariant in Subclass | MC | L4 | [ ] |
| 4 | 1.0 | Test-Driven Development | MC | L8 | [ ] |
| 5 | 1.0 | Test Code Quality | MC | L10 | [ ] |
| 6 | 3.0 | Branch coverage: processOrder | MC | L3 | [ ] |
| 7 | 4.0 | MC/DC | MC | L3 | [ ] |
| 8 | 20.0 | Domain testing: Count Connected Components | Programming | L2 | [ ] |
| 9 | 15.0 | Mocking: CTF Point Awarder | Programming | L6 | [ ] |
| 10 | 10.0 | Property-based testing: Prime Factorisation | Programming | L5 | [ ] |
| 11 | 20.0 | Selenium: Hangman | Programming | L9 | [ ] |
| 12 | 18.0 | SQL: Queue Requests | Programming | L9 | [ ] |
| 13 | 5.0 | Mutation testing: squaredDistance | Collection | L3 | [ ] |

---

## Exam Question Prediction Table

### Programming Questions

| Topic | Predicted Question Type | What to Prepare |
|-------|------------------------|-----------------|
| **Domain Testing** | Write test cases for a method using specification-based testing. Augment with structural testing. | Identify partitions from specs, test boundary values, use @ParameterizedTest or multiple @Test, include null/empty edge cases, think about meta-test mutations |
| **Selenium** | Write WebDriver tests for a web application. Test user interactions, form submissions, error messages. | CSE1110.createDriver(), By.id/By.tagName, sendKeys/click, isDisplayed(), text assertions, helper methods for repeated actions |
| **SQL** | Write SQL queries to test a database method. Test edge cases with different data configurations. | SQL syntax: SELECT, JOIN, GROUP BY, CASE WHEN, COALESCE, WITH clauses, test data setup via Dao.save(), verify return values |
| **Mocking** | Create test doubles for dependencies. Test interactions and behaviors using Mockito. | mock(), when/thenReturn, verify(), verify(never), ArgumentCaptor, never mock data classes or class under test |
| **Property-Based Testing** | Write jqwik properties (NOT example-based tests) for a method. Use constraints and generators. | @Property (NOT @Test), @ForAll, @IntRange, Arbitraries.integers()/strings()/booleans(), property assertions, provider methods if needed |
| **Mutation Testing** | Analyze PIT report. Identify surviving mutants. Write tests to kill them. | Understand mutation types (CONDITIONALS_BOUNDARY, INCREMENTS, NEGATE_CONDITIONALS, etc.), read coverage report, write targeted tests |

### Theory MC Questions

| Topic | Predicted Question Type | What to Review |
|-------|------------------------|-----------------|
| **Testing Principles** | Scenario-based MC: which principle applies? (defect clustering, pesticide paradox, verification vs validation) | All 7 principles with examples |
| **Design by Contract** | True/False: pre/post conditions, invariants, assert vs if/throw, public vs non-public methods | Preconditions on public methods, invariant rules, LSP |
| **Invariant in Subclass** | Select valid subclass invariants given superclass invariant | Subclass invariant = superclass invariant AND additional constraints |
| **TDD** | Scenario MC: what is the next step in TDD cycle? | Red-green-refactor steps |
| **Test Code Quality** | Identify test smells in code snippets | Unclear assertions, fixtures too general, sensitive assertions, bad resource handling |
| **MC/DC** | Given expression, select minimum test set for MC/DC | N+1 test cases, independent influence method |
| **Branch Coverage** | Given method, count minimum tests for full branch coverage | Count all if-true and if-false branches |

---

## How to Solve Each Pattern

### P1 -- Domain/Specification Testing (20 pts)

**How to recognize:** "Use specification-based testing to create a suitable test suite" or "derive test cases from the specification"

**Steps:**
1. **Read the specification carefully** -- understand inputs, outputs, and all constraints
2. **Identify partitions** for each input:
   - Null vs non-null
   - Empty vs non-empty
   - Valid range vs invalid range
   - Single vs multiple values
3. **Identify boundary values** for numeric inputs:
   - On-point (value in the condition) and off-point (just beyond)
   - For `> n`: on = n, off = n+1
   - For `>= n`: on = n, off = n-1
   - For `== n`: on = n, off = n-1 and n+1
4. **Analyze input interactions** -- how do multiple inputs affect each other?
5. **Devise test cases** -- pick one representative from each partition combination
6. **Automate** -- write @Test or @ParameterizedTest methods
7. **Augment with structural testing** -- check coverage, add tests for uncovered branches
8. **Think about meta tests** -- what bugs could Andy inject? (null checks, boundary off-by-one, wrong comparisons)

**Code example from Resit (ConnectedComponents):**
```java
@Test
void nullGrid() {
    int[][] grid = null;
    assertThat(ConnectedComponents.countComponents(grid)).isEqualTo(0);
}

@Test
void emptyGrid() {
    int[][] grid = {};
    assertThat(ConnectedComponents.countComponents(grid)).isEqualTo(0);
}

@Test
void allOneComponent() {
    int[][] grid = {{1, 1}, {1, 1}};
    assertThat(ConnectedComponents.countComponents(grid)).isEqualTo(1);
}

@Test
void eachCellIsAComponent() {
    int[][] grid = {{1, 2, 1}, {2, 1, 2}};
    assertThat(ConnectedComponents.countComponents(grid)).isEqualTo(6);
}
```

**Key strategy:** Include diverse grid configurations -- single cell, all same, alternating, multiple values, vertical/horizontal dividers, diagonal splits. Cover null, empty, and single-element edge cases.

---

### P2 -- Selenium/WebDriver Testing (20 pts)

**How to recognize:** "Write Selenium tests to check whether the web application works correctly" or "automated browser tests"

**Steps:**
1. **Create the WebDriver** using `CSE1110.createDriver(url)` -- DO NOT use `driver.get()`
2. **Locate all elements** you need in @BeforeEach using `driver.findElement(By.id(...))` or other By strategies
3. **Write helper methods** for repeated actions (initGame, guess, assertGameState, etc.)
4. **Write test methods** covering:
   - Normal happy path
   - Error messages and validation
   - State transitions (screens showing/hiding)
   - Game win/lose conditions
   - Edge cases (duplicate input, wrong input format)
5. **Verify all meta test scenarios** -- think about what bugs Andy might inject

**Code example from Resit (Hangman):**
```java
class HangmanTests {
    private WebDriver driver;
    private WebElement wordField;
    private WebElement gameStartButton;
    private WebElement letterField;
    private WebElement letterSubmitButton;
    private WebElement currentWord;
    private WebElement message;

    private void initGame(String hidden) {
        wordField.sendKeys(hidden);
        gameStartButton.click();
    }

    private void guess(String letter) {
        letterField.sendKeys(letter);
        letterSubmitButton.click();
    }

    @BeforeEach
    public void initDriver() {
        driver = CSE1110.createDriver("https://weblab.tudelft.nl/docs/cse1110/...");
        wordField = driver.findElement(By.id("word-input"));
        gameStartButton = driver.findElement(By.id("wordSubmitForm")).findElement(By.tagName("button"));
        letterField = driver.findElement(By.id("letter-input"));
        letterSubmitButton = driver.findElement(By.id("letterSubmitForm")).findElement(By.tagName("button"));
        currentWord = driver.findElement(By.id("display-word"));
        message = driver.findElement(By.id("message"));
    }

    @Test
    public void wrongGuessNotIncreasing() {
        initGame("Tree");
        guess("a");
        String word = currentWord.getText().replaceAll(" ", "");
        assertThat(word).isEqualTo("____");
    }

    @Test
    public void duplicateLetters() {
        initGame("Tree");
        guess("t");
        guess("T");
        assertThat(message.getText()).isEqualTo("You already guessed this letter!");
    }

    @Test
    public void WinMessageNotDisplayed() {
        initGame("tree");
        guess("t"); guess("r"); guess("e");
        assertThat(message.getText()).isEqualTo("You won!");
    }

    @AfterEach
    public void close() {
        driver.close();
    }
}
```

**Key tips:**
- Use `CSE1110.createDriver()` not `driver.get()`
- Find elements by ID first; if not available, use tag names, CSS selectors, or XPath
- Write helper methods to reduce duplication (DAMP principle)
- Test ALL meta scenarios: wrong guesses, duplicate letters, win/lose conditions, input validation, screen transitions
- Use `isDisplayed()` for screen visibility tests
- Use `replaceAll(" ", "")` when checking displayed words with spaces

---

### P3 -- SQL Testing (18 pts)

**How to recognize:** "Use SQL testing to create a suitable test suite" or "test the SQL query method"

**Steps:**
1. **Understand the database schema** -- tables, columns, relationships (provided in the assignment)
2. **Understand the method under test** -- what query does it execute? What does it return?
3. **Identify test scenarios** using category-partition method:
   - Different status values (PENDING, APPROVED, IN_PROGRESS)
   - Single vs multiple records per category
   - Edge cases (no records, all same status, ties)
   - Boundary conditions (equal overflow values)
4. **Set up test data** using `dao.save(new Record(...))` before each test
5. **Call the method under test** and assert the result
6. **Cover all partitions** -- premium vs non-premium customers, available vs busy, etc.

**Code example from Resit (Queue Requests):**
```java
@Test
void taBusy() {
    taDao.save(new TA(1, "R&L", "BUSY", 3));
    requestDao.save(new Request(1, "R&L", "PENDING", 3));
    CourseAndOverflow result = requestDao.getBusiestCourseFromLab(3);
    assertThat(result).isEqualTo(new CourseAndOverflow("R&L", 1));
}

@Test
void considerMultipleTAs() {
    taDao.save(new TA(1, "SQT", "AVAILABLE", 1));
    taDao.save(new TA(2, "SQT", "AVAILABLE", 1));
    taDao.save(new TA(3, "SQT", "AVAILABLE", 1));
    requestDao.save(new Request(3, "SQT", "PENDING", 1));
    requestDao.save(new Request(4, "SQT", "PENDING", 1));
    requestDao.save(new Request(5, "SQT", "PENDING", 1));
    requestDao.save(new Request(6, "SQT", "PENDING", 1));
    requestDao.save(new Request(7, "SQT", "PENDING", 1));
    CourseAndOverflow result = requestDao.getBusiestCourseFromLab(1);
    assertThat(result).isEqualTo(new CourseAndOverflow("SQT", 2));
}

@Test
void considersTAsFromCourseWithFewTAs() {
    taDao.save(new TA(1, "SQT", "AVAILABLE", 1));
    taDao.save(new TA(2, "SQT", "AVAILABLE", 1));
    taDao.save(new TA(3, "CO", "AVAILABLE", 1));
    requestDao.save(new Request(1, "SQT", "PENDING", 1));
    requestDao.save(new Request(2, "SQT", "PENDING", 1));
    requestDao.save(new Request(3, "CO", "PENDING", 1));
    requestDao.save(new Request(4, "CO", "PENDING", 1));
    CourseAndOverflow result = requestDao.getBusiestCourseFromLab(1);
    assertThat(result).isEqualTo(new CourseAndOverflow("CO", 1));
}
```

**Key tips:**
- Only count `PENDING` requests and `AVAILABLE` TAs
- Overflow = pending requests - available TAs
- Return the course with the BIGGEST overflow
- Test: no requests, all busy, ties between courses, multiple TAs, different lab IDs, mixed statuses (only PENDING counts)
- Ensure unique IDs for each record
- Extend the template class (e.g., `RequestDaoIntegrationTemplate`)

---

### P4 -- Mocking with Mockito (15 pts)

**How to recognize:** "Create a suitable test suite using test doubles" or "use Mockito"

**Steps:**
1. **Identify dependencies** of the class under test (interfaces/classes it depends on)
2. **Create mocks in @BeforeEach** using `mock(ClassName.class)`
3. **Stub return values** using `when(mock.method(args)).thenReturn(value)`
4. **Set up the class under test** with the mocks via constructor injection
5. **Call the method under test** with specific inputs
6. **Verify interactions** using `verify(mock).method(args)` and `verify(mock, never()).method(args)`
7. **Use ArgumentCaptor** when you need to capture and inspect passed arguments
8. **Assert on results** -- don't just verify interactions, also check outcomes

**Code example from Resit (CTF Point Awarder):**
```java
class PointAwarderCTFTest {
    private PlayerRepository playerRepository;
    private ChallengeRepository challengeRepository;
    private FlagRepository flagRepository;
    private FraudAlertSystem fraudAlertSystem;
    private PointAwarderCTF pointAwarderCTF;

    @BeforeEach
    public void setUp() {
        playerRepository = mock(PlayerRepository.class);
        challengeRepository = mock(ChallengeRepository.class);
        flagRepository = mock(FlagRepository.class);
        fraudAlertSystem = mock(FraudAlertSystem.class);
        pointAwarderCTF = new PointAwarderCTF(playerRepository, challengeRepository,
                                              flagRepository, fraudAlertSystem);
    }

    @Test
    public void testFlagCorrect() {
        when(playerRepository.getGroupNumber(4)).thenReturn(1);
        Flag flag = new Flag("flag", "funny phrase", 1);
        when(flagRepository.getFlagForGroup(1, 1)).thenReturn(flag);
        when(flagRepository.getFlagsForChallenge(1)).thenReturn(List.of(flag));
        when(challengeRepository.getPoints(1)).thenReturn(5);

        pointAwarderCTF.awardPoints("flag", 4, 1);

        verify(playerRepository).awardPoints(1, 5);
        verify(fraudAlertSystem, never()).sendFraudAlert(any());
    }

    @Test
    public void testFraudAlert() {
        when(playerRepository.getGroupNumber(1)).thenReturn(2);
        Flag flagGroup2 = new Flag("flag", "funny phrase", 2);
        Flag flagGroup3 = new Flag("flaGGG", "funny phrase", 3);
        when(flagRepository.getFlagForGroup(2, 5)).thenReturn(flagGroup2);
        when(flagRepository.getFlagsForChallenge(5)).thenReturn(List.of(flagGroup2, flagGroup3));

        pointAwarderCTF.awardPoints("flaGGG", 1, 5);

        ArgumentCaptor<FraudAlert> fraudAlertCaptor = ArgumentCaptor.forClass(FraudAlert.class);
        verify(fraudAlertSystem).sendFraudAlert(fraudAlertCaptor.capture());
        FraudAlert fraudAlert = fraudAlertCaptor.getValue();
        assertThat(fraudAlert.playerId).isEqualTo(1);
        assertThat(fraudAlert.submittingGroupNumber).isEqualTo(2);
        assertThat(fraudAlert.flagGroupNumber).isEqualTo(3);

        verify(playerRepository, never()).awardPoints(eq(2), anyInt());
    }
}
```

**Key tips:**
- Mock ALL dependency interfaces, never mock the class under test
- Never mock data classes (Flag, FraudAlert) -- use real objects
- Never use spies unless explicitly required (penalty)
- Stub ALL methods that will be called during the test
- Verify both positive and negative interactions (verify + verify(never))
- Use ArgumentCaptor to inspect objects passed to mock methods
- Cover all code paths: correct flag, fraud alert, invalid flag
- Use `any()`, `anyInt()`, `eq()` matchers as needed

---

### P5 -- Property-Based Testing with jqwik (10 pts)

**How to recognize:** "Use property-based testing (with jqwik)" and restrictions like "no @Test, no filter, no if"

**Steps:**
1. **Use @Property (NOT @Test)** -- this is required by jqwik
2. **Use @ForAll with constraints** like @IntRange, @StringLength, @Bool, etc.
3. **Identify the property** to test (round-tripping, invariant, idempotence, hard-to-compute-easy-to-verify, etc.)
4. **Write the assertion** based on the property
5. **Use Arbitraries API** for complex data generation when needed
6. **Use provider methods (@Provide)** only when constraints are too complex for direct constraints

**Code example from Resit (Prime Factorisation):**
```java
class PrimeFactorisationTests {
    @Property
    void factorsArePrimeNumbers(@ForAll @IntRange(min=2, max=1000) int n) {
        List<Integer> factors = PrimeFactorisation.primeFactors(n);
        assertThat(primesUnder1000).containsAll(factors);
    }

    @Property
    void productOfFactorsEqualsOriginal(@ForAll @IntRange(min=2, max=1000) int n) {
        List<Integer> factors = PrimeFactorisation.primeFactors(n);
        int product = 1;
        for (int factor : factors) {
            product *= factor;
        }
        assertThat(product).isEqualTo(n);
    }

    @Property
    void throwsExceptionWhenLessThan2(@ForAll @IntRange(min=-1000, max=1) int n) {
        assertThatThrownBy(() -> PrimeFactorisation.primeFactors(n))
            .isInstanceOf(IllegalArgumentException.class);
    }

    @Property
    void throwsExceptionWhenHigherThan1000(@ForAll @IntRange(min=1001) int n) {
        assertThatThrownBy(() -> PrimeFactorisation.primeFactors(n))
            .isInstanceOf(IllegalArgumentException.class);
    }

    private final List<Integer> primesUnder1000 = List.of(
            2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
            73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151,
            // ... continue with all primes up to 1000
    );
}
```

**Key tips:**
- NEVER use @Test, @Example, Arbitraries.of(), Arbitrary.sample(), filter(), if statements, Assume.that(), Math.random(), or Arbitraries.randomValue()
- Use @IntRange, @StringLength, @Bool for simple constraints
- Use Arbitraries.integers().between(), Arbitraries.strings().alpha() for generators
- Use Combinators.combine() for combining multiple arbitrary values
- For exception testing: @Property + assertThatThrownBy()
- For collections: @ForAll List<@IntRange(...) Integer> generates a random list
- Don't repeat production code in the test (no re-implementing the algorithm)
- Don't use existing implementations as oracles (no differential testing)
- Don't generate small unrepresentative subsets or filter too aggressively
- Properties to consider: correctness, round-tripping, idempotence, invariants

---

### P6 -- MC/DC Coverage (4 pts)

**How to recognize:** "Select test cases to achieve MC/DC" or "What is the minimum set of tests"

**Steps (without truth tables):**
1. **Count the conditions** (N) in the expression
2. **You need N+1 test cases** minimum
3. **For each condition**, find a pair of tests where:
   - That condition flips (true/false)
   - All other conditions stay the SAME
   - The overall decision changes
4. **Pick existing test cases** when possible to minimize the set

**Example from Resit: `A && B || C || !D` = `((A && B) || C) || !D`**
- 4 conditions -> need 5 test cases

```
For A independently: fix B=true, C=false, D=true
  T1: A=TRUE, B=true, C=false, D=true -> outcome: TRUE
  T2: A=FALSE, B=true, C=false, D=true -> outcome: FALSE

For B independently: fix A=true, C=false, D=true
  T1 (already): A=TRUE, B=true, C=false, D=true -> TRUE
  T3: A=TRUE, B=FALSE, C=false, D=true -> FALSE

For C independently: fix A=false or B=false, D=true
  T2 (already): A=FALSE, B=true, C=false, D=true -> FALSE
  T4: A=FALSE, B=true, C=TRUE, D=true -> TRUE

For D independently: fix A=false or B=false, C=false
  T2 (already): A=FALSE, B=true, C=false, D=true -> FALSE
  T5: A=FALSE, B=true, C=false, D=FALSE -> TRUE
```

**Key tips:**
- Read operator precedence: && binds tighter than ||
- For each condition, you need to "fix" the other conditions so the chosen condition CAN independently change the outcome
- Pick test cases that are in the answer options
- You need exactly N+1 tests for N conditions

---

### P7 -- Branch Coverage (3 pts)

**How to recognize:** "What is the minimum number of tests to achieve 100% branch coverage?"

**Steps:**
1. **Count all branches** in the method (each if/else/for/while/switch creates branches)
2. **Remember:** each `if` has two branches (taken and not-taken), even if there's no explicit else
3. **One test can cover multiple independent branches** if they appear on different paths
4. **Find the minimum set** of test cases that hits all branches

**Example from Resit (processOrder):**
```java
public static String processOrder(double orderAmount, boolean isMember,
                                   boolean hasCoupon, boolean isBlacklisted) {
    if (isBlacklisted) { return "Order rejected: blacklisted user"; }  // 2 branches
    if (isMember) {           // 2 branches
        discount += 0.10;
        if (hasCoupon) { discount += 0.05; }  // 2 branches
    } else {
        if (hasCoupon) { discount += 0.04; }  // 2 branches
    }
    if (finalAmount < 20) { return "Order rejected: minimum amount not met"; }  // 2 branches
    else if (finalAmount > 500) { return "Order requires manual review"; }  // 3 branches (if, else-if, else)
    return "Order accepted...";
}
```
Answer: 5 test cases needed to cover all branches.

**Key tips:**
- Count each if/else-if/else as separate branches
- A for loop body that may or may not execute counts as 2 branches
- One test can cover multiple branches if the paths are independent
- Don't forget implicit else branches (even when there's no explicit else keyword)

---

### P8 -- Mutation Testing (5 pts)

**How to recognize:** "Create mutants that survive/are killed" or "analyze mutation coverage report"

**Steps:**
1. **Read the PIT report** (via "Analyse coverage" in Andy)
2. **Identify surviving mutants** -- these indicate weak tests
3. **Identify the mutation type** (CONDITIONALS_BOUNDARY, INCREMENTS, NEGATE_CONDITIONALS, etc.)
4. **Write a test** that specifically targets the mutated code path
5. **For "create mutants" questions:** inject common mutations (change < to <=, change + to -, negate conditions)

**Key mutation operators to know:**
- CONDITIONALS_BOUNDARY: changes < to <=, > to >=, etc.
- INCREMENTS: changes ++ to --, or +1 to -1
- NEGATE_CONDITIONALS: flips boolean expressions
- MATH: changes arithmetic operators (+ to -, etc.)
- VOID_METHOD_CALLS: removes method calls

**Key tips:**
- A mutant is "killed" if your test suite fails on it (good!)
- A mutant "survives" if your tests pass on it (bad!)
- Equivalent mutants cannot be killed (semantically identical to original)
- Use the coverage report to find surviving mutants

---

### P9 -- Design by Contract (1 pt)

**How to recognize:** MC questions about preconditions, postconditions, invariants, assert vs if/throw

**Key rules to remember:**
- **Preconditions:** Client's responsibility. Public methods should use if/throw (not assert). Non-public methods can use assert.
- **Postconditions:** Supplier's responsibility. Should always hold after method execution.
- **Class Invariants:** Must hold before AND after each public method. May temporarily not hold during method execution.
- **Overriding methods:** Weaken preconditions (accept more inputs), strengthen postconditions (deliver more). This is LSP.
- **Subclass invariants:** Must be stronger than or equal to superclass invariant.
- **Assertions can be disabled** (-ea flag in IntelliJ/Maven enables them by default for tests).
- **Input validation != preconditions:** Input validation is for user input (always checked), preconditions are for internal API contracts.

---

### P10 -- Invariant in Subclass (1 pt)

**How to recognize:** MC question: "Given superclass invariant, which are valid subclass invariants?"

**Rule:** Subclass invariant = superclass invariant AND (possibly) additional constraints

**Example:** Superclass: `points >= 0`
- Valid subclass invariants: `points >= 0`, `points > 0`, `points > 2`, `points >= 0 && health > 0`, `points >= 0 && points <= 100`
- Invalid: `points <= 0`, `points > -2`, `health >= 0` (doesn't include superclass constraint)

**Key tips:**
- The subclass invariant must be STRONGER than or EQUAL to the superclass invariant
- Stronger = more restrictive = fewer satisfying values
- You can ADD new constraints (AND additional conditions)
- You can RESTRICT existing constraints (change >= to >)
- You CANNOT weaken existing constraints (change > to >=)

---

### P11 -- TDD Theory (1 pt)

**How to recognize:** MC question about the TDD cycle

**TDD cycle (red-green-refactor):**
1. Write a failing test (RED)
2. Write the MINIMUM production code to make the test pass (GREEN)
3. Refactor both test and production code (REFACTOR)
4. Repeat: add next test, go back to step 1

**Key rules:**
- Write ONLY as much production code as needed to pass the current test
- Do NOT write more tests before writing production code
- Do NOT implement the complete algorithm before writing tests
- Test failure at step 1 just means the functionality is not yet implemented (not a spec problem)
- Refactoring comes AFTER the test passes, not before

---

### P12 -- Test Code Quality (1 pt)

**How to recognize:** MC question: "Which test smells are present?" or code analysis

**Common test smells:**
- **Unclear assertions:** `assertEquals(true, success)` instead of `assertTrue(success)`
- **Bad handling of complex/external resources:** Optimistically calling DB/REST APIs expecting them to work
- **Fixtures that are too general:** @BeforeEach sets up too much data that most tests don't need
- **Sensitive assertions:** Overly-specific assertions tightly coupled to implementation (e.g., toString() comparison)
- **Flaky tests:** Non-deterministic tests (Thread.sleep, network calls, time-dependent)
- **Test code duplication:** Same setup/assertions repeated across many tests
- **Long methods:** Tests that do too much (setup + act + assert + more setup + more assertions)
- **Obscure tests:** Hard to understand what the test is checking

**DAMP vs DRY:** Tests should be DAMP (Descriptive And Meaningful Phrases), not DRY (Don't Repeat Yourself). Duplication in tests is better than abstracted-away meaning.

---

### P13 -- Testing Principles (1 pt)

**How to recognize:** MC question: scenario-based, "which principle applies?"

**Seven testing principles:**
1. **Exhaustive testing is impossible** -- you can never test all inputs
2. **Early testing** -- testing should start early in the lifecycle
3. **Defect clustering** -- bugs happen more in some places than others (prioritize testing)
4. **Pesticide paradox** -- repeated tests find fewer bugs over time (vary your tests)
5. **Testing is context-dependent** -- different systems need different testing approaches
6. **Absence-of-defects fallacy** -- finding and fixing bugs doesn't help if the system builds the wrong product
7. **The mythic man-month** -- adding resources to a late project makes it later (about scheduling, not testing)

**Key distinction:** Verification = "building the product right" (does it match specs?). Validation = "building the right product" (does it meet user needs?).

---

## Confidence Assessment

### Strong -- can solve with minimal hints
- [ ] Domain/Specification Testing (P1)
- [ ] Selenium/WebDriver Testing (P2)
- [ ] SQL Testing (P3)
- [ ] Mocking with Mockito (P4)
- [ ] Property-Based Testing (P5)
- [ ] MC/DC Coverage (P6)
- [ ] Branch Coverage (P7)
- [ ] Mutation Testing (P8)
- [ ] Design by Contract (P9)
- [ ] Invariant in Subclass (P10)
- [ ] TDD Theory (P11)
- [ ] Test Code Quality (P12)
- [ ] Testing Principles (P13)

### Needs review -- require practice or have conceptual gaps
- [ ] Boundary Testing (on/off-points, floating-point)
- [ ] Test Smells identification
- [ ] Design for Testability (DI, DIP, hexagonal)
- [ ] Auto Test Generation concepts
- [ ] JavaScript Front-End Testing (NOT on exam -- skip)

### Not attempted -- review if time permits
- [ ] All topics marked above

---

## Exam Strategy

1. **Start with the programming assignments** (65 points total) -- these are the biggest weight
2. **Do the MC questions quickly** (16 points) -- they're fast points
3. **Within programming, start with easier ones** -- if stuck, move on and come back
4. **Write SOME tests** even if not perfect -- partial credit is better than zero
5. **Fix failing tests immediately** -- don't submit broken code
6. **Use Andy's "Analyse coverage"** to check what's missing
7. **Don't over-optimize** -- "some tests" beats "no tests"
8. **For PBT:** remember the constraints strictly (no @Test, no filter, no if, no sample)
9. **For Selenium:** always use CSE1110.createDriver(), never driver.get()
10. **For Mocking:** mock dependencies, never data classes or the class under test
