Chapter Roadmap (1 of 3)
- 7.1 What does a test actually show? Behaviour and test evidence
- Executable availability examples; observe result and state together.
- 7.2 What a test looks like: Arrange–Act–Assert
- Assertions that can be wrong; fresh, small setup.
- 7.3 Red, green, refactor: one cycle in detail
- One recorded cycle, and why the first failure must be the intended one.
- 7.4 Edges, clocks and a small lending flow
- Boundary values, a controlled clock, a bounded lending flow.
Chapter Roadmap (2 of 3)
- 7.5 Test doubles: dummies, stubs, fakes, spies and mocks
- Name the role a substitute plays, then add Mockito for one interaction.
- 7.6 What testing tells you about design
- 7.7 Tests that look good but are weak or brittle
- Coverage is not correctness; a mutation experiment; a mutant that survives.
- 7.8 Preparing the Milestone 2 evidence
Chapter Roadmap (3 of 3)
- 7.9 Keeping implementation and expected behaviour apart, with AI separate implementation from expected behaviour.
- 7.10 Testing habits that mislead
- 7.11 Try it yourself: a test-first change and Milestone 2 evidence
- Steps 1 to 9; steps 8 and 9 on your team’s project.
- 7.12 Questions to think with, and midterm practice
- A five-task practice set for Chapters 2 to 7.
- What to take away, and what comes next
- Chapter 8: construction and compatibility patterns.
Learning Outcomes (1 of 2)
By the end of this chapter, you should be able to:
- Translate acceptance examples into focused tests with meaningful expected outcomes.
- Demonstrate red-green-refactor and explain why the first failure is the intended one.
- Control data, time and collaborators to make tests repeatable.
Milestone 2 Checklist: Use It From the Start (1 of 2)
Milestone 2 falls in this week. By your demonstration you need:
- A working MVP flow that someone else can run from written instructions.
- Acceptance examples mapped to named tests.
- One red-green-refactor sequence recorded from your own code.
- A short rationale for your object-oriented and SOLID choices.
- Start with items 1 and 3.
Milestone 2 Checklist (2 of 2)
- A justified test double or controlled clock.
- A build result for the revision you demonstrate.
- Real review feedback and a short retrospective with a next action.
- A statement of what the MVP does not do.
- Section 7.8 explains each item.
- The checklist is preparation. The module leader’s approved Milestone 2 requirements remain authoritative.
Where You Start and the Tools
- You start with: Chapter 6’s Java 17 application, explicit collaborator construction, student and staff policies, local notification outputs. The earlier executable checks remain available.
- New tools: Maven, JUnit Jupiter and Mockito, with an explicitly versioned configuration in the companion.
- Core reading 7.1 to 7.8. Section 7.5 introduces hand-written doubles before Mockito.
- Lab: steps 1 to 6, 8 and 9. Steps 1 to 6 on the book’s project; **steps 8 and 9 on your team’s project.
Three Plausible, Wrong Implementations
- One passes because the test only checks “no exception”.
- Another rejects every enquiry and still passes the overlap example.
- A third accepts adjacent dates incorrectly because it treats the earlier reservation’s end as inclusive.
- Each looks plausible when its tests ask incomplete questions.
What Testing Is For
- Not to accumulate green indicators.
- To create evidence about specific behaviour, reveal misunderstandings and support changes in design.
- This chapter turns the availability examples of Chapters 3 and 4 into executable tests, demonstrates a complete red-green-refactor cycle, and uses controlled collaborators.
AQ Examples (1 of 2)
| Example | Setup and action | Required observation |
|---|---|---|
| AQ-01 | Empty schedule; enquire about CAM1 for a valid interval | Available; records and notifications unchanged |
| AQ-02 | CAM1 reserved [20 Oct, 22 Oct); enquire [21 Oct, 23 Oct) | Unavailable; no side effects |
| AQ-03 | Same booking; enquire [22 Oct, 23 Oct) | Available because the end is exclusive |
The API Decision
- The selected Java API is
isAvailable(itemId, start, endExclusive), returning a boolean for a valid interval. - Blank or null item identifier:
IllegalArgumentException. - Equal or reversed dates:
IllegalArgumentException. - Null dates:
NullPointerExceptionthroughLoanPeriod. - These choices settle the earlier open API question for this teaching version.
What Availability Means
- No conflicting reservation for the item and interval.
- It does not mean a particular borrower is eligible.
- The query accepts any positive interval, including more than seven days; student limits apply when a reservation is requested.
- It does not know whether an item is physically on loan or present in a real catalogue.
Observe Result and State Together
- The overlap example checks: a false result, unchanged reservations, unchanged recorded notifications.
- An implementation that calls
reserveand undoes it would also send a notice: the unchanged-notices check exposes it. - One that adds a temporary record directly and removes it again leaves nothing for a sequential test to see.
- We reject that design by reading it, not by testing it.
Choose Observations by the Promise
- For an immutable snapshot, compare the before and after lists: they expose replacement, deletion or reordering. Checking only the count misses changes to contents.
- Inspecting a private list through reflection binds the test to representation when a public snapshot already exposes the behaviour.
- A separate example confirms that an enquiry does not consume the next reservation identifier.
The Expected Answer Must Be Independent
- It must come from an agreed example or rule.
- Calculating it by calling the same method under test is circular.
- Copying the implementation’s overlap expression into a test reproduces its mistake.
- Concrete dates with an independently understood boundary are a better starting point.
Introduce a Runner Without Losing Earlier Evidence
- JUnit provides annotations, assertions and reporting. Maven compiles and runs it through the configured Surefire plugin.
mvn --batch-mode verify- The existing
scripts/check.shnow delegates to that command. - A
LegacyRegressionTestinvokes the four oldermainmethods from four JUnit tests. A class merely placed in a test directory would not run itsmain.
Counts Are Not Interchangeable
- The wrapper reports four JUnit cases.
- Their internal output still reports: 34 Chapter 2 checks, five Chapter 3 example groups, 44 Chapter 5 checks, 28 Chapter 6 checks.
- Do not merge those counts as if they were identical units.
- The useful question: which behaviours execute, and what failures reach the build?
The First Availability Test
boolean available = coordinator.isAvailable(
"CAM1", start.plusDays(1), start.plusDays(3));
assertAll(
() -> assertFalse(available, "An overlapping item must be unavailable"),
() -> assertEquals(recordsBefore, coordinator.reservations()),
() -> assertEquals(noticesBefore, inbox.received()));- Arranged: a two-day CAM1 reservation, with snapshots of records and notices.
How Many Assertions?
- Several assertions can belong together when they describe one outcome.
- Splitting this test into three methods with identical setup may obscure the relationship between a query result and its non-mutation promise.
- One giant method for every query, loan and notification scenario is the opposite problem: a failure is hard to locate.
Exceptional Outcomes
- Use
assertThrowsfor a specified exceptional outcome, and inspect state afterwards when preservation matters. - A catch block that prints an error and continues does not establish that the right exception occurred.
- Wrapping a test in a broad catch can turn a failure into a false success.
Keep Setup Independent and Small
- Each scenario constructs fresh mutable collaborators unless sharing is part of the behaviour.
- A static coordinator reused across tests makes results depend on which test ran first: the same test may pass alone and fail in the suite.
- Prefer the smallest setup that makes the scenario meaningful, using real simple domain objects.
Green: Implement the Required Decision
- Construct a
LoanPeriod, inspect existing reservations, return false if the same item has an overlapping period, otherwise true. - It does not call
reserve, allocate an identifier or invoke the sender. - The same test then passes: useful evidence for that example, not permission to stop thinking.
An Always-False Method Passes Too
- An implementation that always returned false would also satisfy the first overlap test.
- More examples are needed to distinguish overlap rejection from universal rejection:
- empty schedule,
- adjacency,
- different item,
- invalid input,
- identifier preservation,
- eligibility distinction.
Refactor: Share Conflict Detection
- The green query duplicates conflict scanning already in
reserve. - Extract a private
hasConflict(itemId, requestedPeriod)used by both. reservekeeps its validation and eligibility order and still stores exactly one record.- The query uses the same conflict meaning without acquiring reservation’s mutation or borrower-limit behaviour.
After the Extraction
- Rerun the TDD test and the legacy bridge.
- Observed: five JUnit cases (the new test and four legacy wrappers) pass.
- The extraction is justified because both callers need the same meaning of a conflict, not because duplicated text is forbidden.
- If one caller later needs stock or maintenance status, do not force that meaning into the helper.
Preserve a Traceable Sequence
- The companion includes red, green and refactored coordinator snapshots with their commands and results.
- In a team repository, separate commits or a recorded diff sequence give equivalent evidence.
- Keep the actual failing assertion and correction, not just a screenshot saying tests passed.
- A wrong implementation supplied after the fact shows test sensitivity. It does not reconstruct a test-first history that never happened.
Test the Edges That Carry Meaning
- Borrowing: a student with two records may add a third; one with three may not. Seven days allowed, eight not. Staff: five records, fourteen days.
- Overlap: an enquiry beginning at the exclusive end is available; equal start and end is invalid; a different item does not conflict because dates match.
- Each example distinguishes a plausible error in the rule.
Control Time Where Behaviour Depends on Today
- Availability needs no clock in this version.
- The new loan checkout does: checkout is allowed only on a date within the confirmed reservation’s half-open period.
LoanServicereceives a JavaClockand usesLocalDate.now(clock).- Tests use
Clock.fixed, or a small controlled clock when the scenario must advance.
Why the Zone Matters
- A fixed UTC instant does not define a local date without a zone.
- One test uses 19 October 22:30 UTC with Asia/Baghdad and expects the local checkout date 20 October.
- A deliberate example of zone-sensitive conversion, not reliance on the machine’s default zone.
- Do not wait until midnight or call
Thread.sleep: change the supplied clock.
A Bounded Lending Flow
- The MVP increment adds a small
LoanServicelinked to one coordinator in the same process. - It records checkout and return against a confirmed reservation.
- A
Loanis a Java record with validated identifiers, a checkout date and an optional return date. Null means one documented thing: the loan is active. - Records give concise value-oriented data, but validation still has to be written.
The Loan Record
public record Loan(String reservationId, String borrowerId, String itemId,
LocalDate borrowedOn, LocalDate returnedOn) {
public Loan {
if (reservationId == null || reservationId.isBlank()
|| borrowerId == null || borrowerId.isBlank()
|| itemId == null || itemId.isBlank()) {
throw new IllegalArgumentException("Loan identifiers must not be blank.");
}
Objects.requireNonNull(borrowedOn, "borrowedOn");
if (returnedOn != null && returnedOn.isBefore(borrowedOn)) {
throw new IllegalArgumentException("Return cannot precede checkout.");
}
}
public boolean active() { return returnedOn == null; }
}LoanService.checkOut
LocalDate today = LocalDate.now(clock);
if (today.isBefore(reservation.period().start())
|| !today.isBefore(reservation.period().endExclusive())) {
throw new IllegalStateException("Checkout must be within the reservation period.");
}
boolean itemAlreadyOut = loans.values().stream()
.anyMatch(loan -> loan.active() && loan.itemId().equals(reservation.itemId()));
if (itemAlreadyOut) throw new IllegalStateException("Item is already on loan.");LoanService.returnLoan
Loan current = loans.get(reservationId);
if (current == null || !current.active()) {
throw new IllegalStateException("No active loan for this reservation.");
}
Loan returned = new Loan(current.reservationId(), current.borrowerId(),
current.itemId(), current.borrowedOn(), LocalDate.now(clock));
loans.put(reservationId, returned);- The service replaces an active loan value with a returned value instead of mutating earlier snapshots.
Do Not Invent Lifecycle Rules Silently
- Returning a loan does not delete or cancel its reservation.
- The reservation-count rule still counts every stored reservation; availability stays based on those records.
- An early return therefore does not free a reserved interval or restore a borrower’s allowance.
- An explicit, tested inherited limitation. Changing it would disguise a feature change as testing work.
Test Doubles (1 of 2)
| Role | Purpose | Example in this project |
|---|---|---|
| Dummy | Supplies an argument the scenario should not use | A sender passed to an immediately rejected request |
| Stub | Produces a controlled answer or failure | A sender that throws the expected notification exception |
| Fake | A working simplified substitute | An in-memory output standing in for external delivery |
Test Doubles (2 of 2)
| Role | Purpose | Example in this project |
|---|---|---|
| Spy | Records calls for later inspection | The recorder, when a test examines received reservations |
| Mock | Supports verification of expected interactions | A Mockito sender checked for one call or no calls |
- The vocabulary follows Meszaros, summarised by Fowler. Roles overlap; the class name alone does not determine the role.
Keep the Subject Real
- The coordinator is the subject under test in notification scenarios.
- Mocking its
reservemethod would replace the behaviour we want to examine. - Keep the real coordinator, rules and small immutable values. Replace the output boundary that would otherwise complicate observation.
- A hand-written recorder is often clearer than a framework: its list makes the effect visible.
Mockito for a Specific Interaction
NotificationSender sender = mock(NotificationSender.class);
ReservationCoordinator c = ReservationApplication.create(sender);
c.reserve("B1", "CAM1", day, 2);
ArgumentCaptor<Reservation> captured = ArgumentCaptor.forClass(Reservation.class);
verify(sender).send(captured.capture());
Reservation sent = captured.getValue();
assertAll(() -> assertEquals("R1", sent.id()),
() -> assertEquals("B1", sent.borrowerId()),
() -> assertEquals("CAM1", sent.itemId()));Two More Mockito Tests
// no notification after a rejection
assertEquals("Rejected: borrower limit or duration.", c.reserve("B1", "CAM1", day, 8));
assertTrue(c.reservations().isEmpty());
verifyNoInteractions(sender);
// expected failure: record retained, exactly one attempt
doThrow(new NotificationException("Expected test failure"))
.when(sender).send(any(Reservation.class));
/* ... reserve ... */
verify(sender, times(1)).send(any(Reservation.class));Mockito Setup Notes
- The supplied setup uses its interface-only proxy mock maker: mocks are Java dynamic proxies, and no class or static mocking is needed.
- On newer JDKs, Mockito may still attach its ByteBuddy agent and the JVM prints “A Java agent has been loaded dynamically”. The tests still pass.
- Passing
-XX:+EnableDynamicAgentLoadingto the test JVM removed the message. The JVM says dynamic loading will be disallowed by default in a future release: check Mockito’s documentation for your JDK.
A Preview: Doubles for an Outside Data Source
BorrowerDirectory knownStudent = id -> Optional.of(new Borrower(id, BorrowerCategory.STUDENT));
BorrowerDirectory nobody = id -> Optional.empty();
BorrowerDirectory offline = id -> { throw new DirectoryException("provider offline"); };- The contract has one method, so a lambda is enough. Chapter 8 builds the real types.
- Compiled and run against Chapter 8’s types, they print
STUDENT,falseandfailure: provider offline.
What Each Directory Double Answers
knownStudent: a stub with a fixed answer: tests what the service does with a borrower it can trust.nobody: a stub saying “absent or inactive”: tests the ordinary rejection.offline: a throwing stub: tests that an outage is reported as an outage, not as “not found”.- None needs a framework. None proves that a real provider behaves this way.
Match the Double to the Question
- A recorder when you want to inspect accumulated notices.
- A throwing stub when you need a predictable output failure.
- An interaction-verifying mock when the contractual call itself is the observation.
- A fixed clock supplies controlled time; it does not prove the real system clock is correct.
- Neither recorder nor mock proves that email is delivered. Chapter 13 covers real adapters.
Difficult Setup Can Reveal a Design Problem
- Chapter 6’s constructor injection lets tests choose a recorder, failing sender and policy mapping without altering the coordinator.
LoanServicereceives a clock for the same reason.- A difficult test may expose hidden global configuration, mixed responsibilities or unclear results. It may also expose a poorly chosen test: is the scenario trying to inspect a private detail?
The Query’s Clear Contract
- The availability query creates no records and has no notification side effects: easy to test result and state separately.
- Implementing it by calling
reservewould entangle eligibility, identifiers and notifications. - Test difficulty would then be useful feedback about the proposed design.
Use Real Simple Collaborators
LoanPeriodis cheap, deterministic and central to the rule. Mocking it to say “overlaps” would prevent the tests from exercising real period semantics.- An ordinary list or map should usually remain real: replacing it with a mock leads to assertions about
add,getand ordering, not preserved records. - A substitute should remove an obstacle or expose a meaningful boundary, not erase the behaviour under examination.
Preserve Evidence During Migration
- Moving to JUnit does not require rewriting every old assertion at once.
- The bridge makes earlier runners visible to the new build; new tests are focused methods with readable names.
- The bridge is transitional: four coarse results give less precise reporting. Later migration can improve diagnosis without changing expected behaviour.
- Do not discard earlier checks because their syntax predates the framework.
The Result
- One failure, zero errors, nonzero build exit. The assertion expects
trueand receivesfalse. - A behavioural failure in the adjacency example, not a missing tool or compile issue.
- Restore the original and rerun: the test passes.
- It supports one claim: this test detects this fault. It is not a mutation score.
- Never change the correct expected value to accommodate the mutant.
Which Test Caught Which Fault (1 of 4)
| Fault | Caught by |
|---|---|
isAvailable always returns true | AvailabilityTddTest overlapping enquiry |
isAvailable always returns false | AvailabilityTest empty-schedule, adjacency, eligibility examples |
| Conflict check ignores the item identifier | adjacency/different-item example and the Chapter 2 bridge |
Inclusive end boundary in LoanPeriod | adjacency test and both older bridges |
Which Test Caught Which Fault (2 of 4)
| Fault | Caught by |
|---|---|
| Checkout allowed on the exclusive end day | checkoutOutsideHalfOpenPeriodIsRejected |
| Checkout allowed before the start | the same test |
| One reservation checked out twice | aReservationCannotBeCheckedOutTwiceEvenAfterReturn |
| Item-already-on-loan check removed | unreturnedItemBlocksTheNextAdjacentReservationUntilReturned |
Which Test Caught Which Fault (3 of 4)
| Fault | Caught by |
|---|---|
| Return of an inactive loan allowed | checkoutAndReturnUseTheSuppliedClock |
| Real system clock used instead of the supplied one | three LoanServiceTest methods |
| Clock zone ignored | clockZoneDeterminesTheCheckoutDate |
| Return date recorded as the checkout date | checkoutAndReturnUseTheSuppliedClock and the backward-clock test |
The Mutant That Survives
- An enquiry that adds a temporary record and removes it again.
- With identifiers derived from the list size, it leaves no trace: records, notices and next identifier end exactly as before.
- In a sequential program, no test can tell it from the correct one. Only a concurrent observer could.
- Response: reject the design by reading it; do not claim a test can catch it. The identifier example is still worth keeping.
Do Not Let Tests Reproduce the Same Misunderstanding
- An AI-generated implementation and AI-generated tests may agree because both assume inclusive end dates. Agreement is not independent evidence.
- Check expected values against the human-reviewed convention and concrete examples.
- Avoid expected values derived from the result: an identifier equal to the identifier in the same returned object is no independent fact. For the first reservation, an explicit R1 is meaningful.
Avoid Unnecessary Implementation Coupling
- A test asserting the query calls private
hasConflictexactly once fails under a different correct structure. - The public result and state contract should survive refactoring.
- Exact text assertions are fine when text is part of the existing contract, but wording changes then require deliberate expectation updates.
The Demonstration Journey
Available before reservation: true
Confirmed: R1 for B1 on CAM1 [2026-10-20, 2026-10-22).
Available after reservation: false
Loan active after checkout: true
Loan active after return: false
Accepted-reservation notices: 1- Fixed clock; one coordinator and loan service. Checkout and return send no new notices.
What the Demonstration Is and Is Not
- A concrete example of a bounded MVP increment. Your assessed domain and scope may differ.
- No remote delivery, database or hosted execution.
- Another person should be able to reproduce it and explain what remains outside its claim.
- A hosted CI run is separate evidence from the local Maven runs.
Make the Retrospective Useful
- Connect an observation to an action.
- Example (record it only if it happened): availability tests silently assumed the machine’s current date, so all time dependencies become explicit.
- Avoid pre-filled success narratives: “we collaborated well” explains nothing.
- Distinguish a proposed improvement from one already tested.
Ground Rules for the Activity (1 of 2)
| For this activity | |
|---|---|
| Your goal | Separate a proposed implementation from the evidence that would accept or reject it |
| You start with | AQ-01 to AQ-05 as reviewed, and Chapter 6’s coordinator |
| What is in bounds | Evaluate the candidate implementation and its tests; do not treat either as proof of the other |
The Ready-Made Example
Author-written fictional AI output.
An enquiry can call
reserveand remove the temporary record. Treat the end date as inclusive so adjacent bookings conflict. A test is sufficient if the method returns without throwing. Since the implementation and generated tests agree, coverage will prove correctness. For time-sensitive loan tests, use the current date and wait until tomorrow to test the next state.
Model Critique (1 of 2)
| Claim | Verdict | Independent check or correction |
|---|---|---|
| Query by reserve-and-remove | Reject | reserve sends a notice and can be refused by limits; adding and removing directly leaves no trace sequential tests can see, so reject by reading |
| Inclusive end means adjacency conflicts | Reject | The agreed interval is half-open; [20,22) and [22,23) do not overlap |
| No exception is sufficient | Reject | An always-true or always-false method can return normally and be wrong |
Model Critique (2 of 2)
| Claim | Verdict | Independent check or correction |
|---|---|---|
| Implementation and tests mutually prove correctness | Reject | Both may share the same mistaken convention |
| Coverage proves correctness | Reject | Execution does not validate expected values or sensitivity to defects |
| Use the real date and wait for tomorrow | Reject | Supply a fixed or controlled clock and an explicit zone |
- The wrong-boundary experiment is a concrete verification step: no need to ask another AI whether the mutant is wrong.
Mistakes 1 to 3
| Mistake | Consequence | Better action |
|---|---|---|
| Count a dependency-download error as TDD red | Behaviour was never examined | Reach the intended assertion failure |
| Write expected values by calling the implementation | Reproduces the same mistake | Use independently reviewed examples |
| Mock the object under test | Replaces the behaviour being investigated | Keep the subject real |
Mistakes 4 to 6
| Mistake | Consequence | Better action |
|---|---|---|
| Use real time for a deterministic date rule | Results change with date or zone | Inject an explicit clock |
| Treat every recorder as the same double role | Hides why it is used | Describe its role in the particular test |
| Verify private call counts | Breaks harmless refactoring | Observe public behaviour or meaningful boundary interactions |
What You Start With, and the Demonstration
- You start with: Chapter 6’s version, the reviewed query criteria, the new loan-flow rules and the companion’s Maven/JUnit setup.
- Demonstration: run the labelled red stage, inspect the precise failure, apply the green implementation, extract conflict detection, rerun the new test and the legacy bridge.
- Guided practice: convert one policy boundary table into parameterised examples and discuss which incorrect result each row distinguishes. Compare a hand-written recorder with the Mockito interaction test.
Independent Work: Steps 7 to 9
- Rehearse the MVP demonstration from documented instructions, obtain actual review feedback and write a short retrospective with a next action.
- On your team’s project, take one backlog behaviour and develop it test-first. Record the actual red failure, the green change and the refactoring. Do not reconstruct a sequence afterwards.
- On your team’s project, work through the Milestone 2 checklist: rehearse your own demo, get review feedback, write a retrospective. Record which items are complete, which are gaps and who will close each one.
Expected Output and Completion
- Output: runnable source; focused tests; TDD and mutation evidence; double rationale; actual build result; demonstration notes; review and retrospective notes; explicit limitations; the step 8 and 9 records.
- Complete when: the failure is behavioural and understood; query results and state effects match the examples; time-sensitive tests do not depend on the actual day; the whole regression suite executes; the demo includes reservation, checkout and return.
- If you want more: sketch how completed reservations should affect counts and early-return availability. Do not silently implement it.
What the Build Establishes
- 35 JUnit invocations: one TDD test, eight additional availability invocations, ten policy rows, nine loan invocations, three Mockito tests, four legacy wrappers.
- The expected output ends
Tests run: 35, Failures: 0, Errors: 0, Skipped: 0andBUILD SUCCESS. - An execution inventory, not a claim of exhaustive correctness or a quality score.
Versions Pinned for This Chapter
| Tool | Tested selection | Role |
|---|---|---|
| Java | Java 17 language/API target | Application and test execution |
| Maven | 3.9.11 | Build and dependency resolution |
| Compiler Plugin | 3.14.1 | Java compilation |
| Surefire Plugin | 3.5.4 | JUnit Platform execution |
| JUnit Jupiter | 5.13.4 | Assertions, discovery, parameterised cases |
| Mockito | 5.20.0 | Interface mock, stubbing, verification |
Acceptance Mapping (1 of 2)
| Requirement or risk | Executable evidence |
|---|---|
| AQ-01 empty schedule available | emptyScheduleIsAvailableWithoutAnySideEffect |
| AQ-02 overlap unavailable | AvailabilityTddTest overlapping enquiry |
| AQ-03 adjacent periods allowed | adjacentPeriodAndDifferentItemAreAvailable |
| AQ-04 different item | the same test checks MIC1 separately |
| AQ-05 equal or reversed interval | parameterised invalidPeriodFailsWithoutChangingState |
Acceptance Mapping (2 of 2)
| Requirement or risk | Executable evidence |
|---|---|
| Enquiry does not allocate an ID | queryDoesNotAllocateAnIdentifier |
| Availability differs from eligibility | two tests: count limit and a thirty-day enquiry |
| Checkout window excludes its end | two invocations of checkoutOutsideHalfOpenPeriodIsRejected |
| Local date depends on clock zone | clockZoneDeterminesTheCheckoutDate |
| Backward time cannot predate checkout | backwardClockCannotRecordReturnBeforeCheckout |
Suggested Evidence Packet (1 of 2)
| Document | Useful content |
|---|---|
| Scope note | Selected query and loan rules; synthetic inputs; lifecycle limitations |
| TDD record | Actual red message, same test at green, refactoring diff, regression result |
| Acceptance mapping | Named tests tied to concrete examples |
| Double rationale | Which collaborator is replaced, why, and what observation it enables |
Suggested Evidence Packet (2 of 2)
| Document | Useful content |
|---|---|
| Mutation record | Isolated faulty comparison, selected failure and restored pass |
| Build result | Executed revision, tool versions, actual report |
| Demonstration | Reproducible steps from empty state through reserve, checkout, return |
| Review and retrospective | Actual observations, corrective action, a next-iteration experiment |
- A copied sample table is not evidence that a team ran the tests or conducted a review.
Concept Questions
- Why is “the method did not throw” insufficient evidence for an availability query?
- What distinguishes the red stage of the worked cycle from a missing-dependency failure?
- When does the notification recorder act as a spy, and why can a framework mock serve a similar purpose?
- Why do both the instant and zone matter when testing a local-date checkout rule?
Diagnosis Questions
- A test calculates its expected availability by calling the same overlap helper used by production code. Both treat adjacency as overlap. Explain why the green result is weak and replace it with an independent example.
- A loan test passes alone but fails after another test, and both use a static shared loan service. Diagnose the likely cause and propose a setup strategy that still allows intentional multi-step scenarios.
Guidance: Questions 1 and 2
- A valid query must distinguish available from conflicting intervals and preserve state. Always-true returns normally but fails AQ-02; always-false fails AQ-01 and AQ-03.
- The worked red run discovered and executed the named test, reached the assertion and reported
falseexpected buttrueactual. A dependency-resolution failure never establishes application behaviour.
Guidance: Questions 3 and 4
- When the test examines its received records, the recorder provides observed interaction history: a spy. A Mockito mock can record and verify too. The classification depends on the test’s purpose and observation.
- One instant gives different dates in different zones. The test must state both when the rule concerns a local date. The Baghdad example fixes the interpretation.
Guidance: Questions 5 and 6
- The test derives its expectation from the same faulty rule, so the two agree without independent validation. Book
[2026-10-20, 2026-10-22), enquire[2026-10-22, 2026-10-23), explicitly expect true, and run the mutant to show this rejects the inclusive comparison. - Earlier tests may have inserted or returned loans that alter later tests’ starting state. Use a fresh coordinator, sender and loan service per independent scenario. A multi-step lifecycle test may share its own service while advancing a controlled clock.
Transfer Challenge: Room Booking
A room-booking service permits availability enquiry and check-in only during the booked interval.
- Design three acceptance examples, one clock-controlled test and one collaborator substitute.
- Explain which expectations transfer from equipment lending and which rules need stakeholder clarification, such as cleaning time or late check-in.
Transfer Guidance
- Examples: an empty schedule is available; an overlapping booking makes an enquiry unavailable without adding a booking; an enquiry starting at the previous exclusive end is available if no turnaround rule applies.
- Check-in: fixed clock at the booking start: accept; at the exclusive end: reject if that is the agreed policy. A recorder observes one check-in notice.
- Clarify cleaning intervals, early arrival, late check-in, no-show expiry, who authorises entry.
- Represent the time zone and choose an appropriate time type. Do not copy
LocalDatemerely because the earlier project used whole days.
Midterm Practice Set: What It Covers
- The midterm covers Chapters 2 to 7 (Weeks 1 to 6).
- According to the module descriptor, it asks you to:
- model a scenario with UML class diagrams,
- identify SOLID violations and smells in supplied code,
- refactor short snippets by introducing interfaces and dependency injection,
- write unit tests with test doubles for a small component.
- Do not add Chapter 8’s patterns. Whether Mockito is allowed in the exam is the module leader’s decision; hand-written doubles work anywhere.
Task 1: UML
Draw a class diagram. Include multiplicities, one composition, one interface realisation and one dependency, and write down one rule your diagram cannot express.
- A library lends laptops. Each laptop has an asset tag and a model.
- A loan links one borrower to one laptop and records a start date and a due date.
- A borrower and a laptop can each appear in many loans over time.
- A loan can be renewed any number of times; a renewal records a new due date and has no meaning without its loan.
- When a loan is overdue, a notice is delivered through a channel: email today, other channels later.
Task 1: Reading the Model
- A borrower and a laptop each link to any number of loans over time:
0..*at the loan side. - A renewal cannot exist without its loan: composition.
- The channel is an interface because other channels are expected;
Loandepends on the interface, not onEmailChannel. - A rule such as “a laptop is on at most one active loan” cannot be expressed by multiplicity: write it as a note or invariant.
- Penalise inheritance for “has”, a solid line for realisation, a composition diamond between
BorrowerandLoan.
Task 2: Diagnose This Class
public class OverdueReminder {
public boolean remind(String borrowerEmail, LocalDate dueDate, String itemName) {
if (LocalDate.now().isAfter(dueDate)) {
MailClient client = new MailClient("smtp.campus.example", 25);
client.send(borrowerEmail, "Overdue: " + itemName,
"Please return " + itemName + ".");
System.out.println("Reminder sent to " + borrowerEmail);
return true;
}
return false;
}
}MailClientopens a real network connection: assume you cannot call it from a test.
Task 2: A Strong Diagnosis
- Three jobs mixed: deciding overdue, building and sending the message, reporting to the console (single responsibility).
- Reads the system clock directly: the result depends on the date the test runs.
- Constructs
MailClientwith a hard-coded host and port: a concrete provider it cannot swap (dependency inversion), and untestable without a mail server. - The boolean does not distinguish “not overdue” from “sent”.
- Connect each defect to a consequence: “the overdue path cannot run in a test; here it throws
IllegalStateExceptionfromMailClient”.
Task 3: Refactor It
public interface ReminderSender {
void send(String to, String subject, String body);
}
public final class OverdueReminder {
private final Clock clock;
private final ReminderSender sender;
public OverdueReminder(Clock clock, ReminderSender sender) { /* null checks */ }
public boolean remind(String borrowerEmail, LocalDate dueDate, String itemName) {
if (!LocalDate.now(clock).isAfter(dueDate)) return false;
sender.send(borrowerEmail, "Overdue: " + itemName, "Please return " + itemName + ".");
return true;
}
}Task 3: What Was Preserved
- A
MailReminderSenderadapts the suppliedMailClientand keeps the console message, so behaviour is preserved. - The composition root would call
new OverdueReminder(Clock.systemDefaultZone(), new MailReminderSender(new MailClient("smtp.campus.example", 25))). - The constructor is a deliberate API change; the observable behaviour of
remindis preserved. - No credit for adding an interface to
MailClient(you do not own it) or for a factory nobody needs.
Task 4: Tests With a Double
@Test void noReminderOnTheDueDateItself() {
assertFalse(reminderOn("2026-10-20T23:00:00Z").remind("[email protected]", DUE, "CAM1"));
assertTrue(sender.calls.isEmpty());
}
@Test void exactlyOneReminderTheDayAfterTheDueDate() {
assertTrue(reminderOn("2026-10-21T00:00:00Z").remind("[email protected]", DUE, "CAM1"));
assertEquals(List.of("[email protected]|Overdue: CAM1|Please return CAM1."), sender.calls);
}- A hand-written spy records calls; the clock is fixed; expected values come from the scenario.
- Also test an earlier date. State which role your double plays.
Task 5: Trace a Rejected Reservation
- Trace, in order: argument validation, borrower count, policy decision, conflict checking.
- A rejection occurs before storage and notification.
- Choose observations that show it: the rejection outcome, unchanged records, no sender interaction.
- Identify the role of the injected sender.
- A good answer connects design, behaviour and evidence rather than listing acronyms.
Summary
- Meaningful tests distinguish intended behaviour from plausible mistakes.
- The worked cycle makes one missing rule fail visibly, implements it and protects the following structural change.
- Controlled collaborators and clocks make outcomes reproducible; carefully chosen assertions keep tests useful during refactoring.
Sources and Further Reading
- JUnit Team: JUnit 5 User Guide, 5.13.4.
- Apache Maven: Surefire with the JUnit Platform; Maven 3.9.11; Compiler Plugin.
- Fowler: Test Driven Development; Test Double (2006).
- Oracle: Java SE 17
Clock; Record Classes. - Mockito Team: API documentation; Mockito 5.20.0.
- Martin: Agile Software Development (2002). Beck: Test-Driven Development: By Example (2002).
- Full references and links are in the chapter text.