Testing What the Agent Wrote
Week 10 · From a sorting method to Project Pulse
Week 10 · From a sorting method to Project Pulse
Code review: static
Read the code. Reason about what it would do.
Testing: dynamic
Run the code. Watch what it does.
public static int[] sort(int[] input) {
int[] a = input.clone();
for (int i = 0; i < a.length - 1; i++) {
int min = i;
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[min]) {
min = j;
}
}
int tmp = a[i];
a[i] = a[min];
a[min] = tmp;
}
return a;
}class SelectionSortTest {
@Test
void sortsAnUnsortedArray() {
int[] result = SelectionSort.sort(new int[] {5, 2, 9, 1});
assertThat(result).containsExactly(1, 2, 5, 9);
}
@Test
void keepsDuplicates() {
int[] result = SelectionSort.sort(new int[] {3, 1, 3, 1});
assertThat(result).containsExactly(1, 1, 3, 3);
}
@Test
void sortsAnEmptyArray() {
assertThat(SelectionSort.sort(new int[] {})).isEmpty();
}
}A test is inputs plus an oracle.
@Test
void resultIsSorted() {
int[] result = SelectionSort.sort(new int[] {5, 2, 9, 1});
assertThat(result).isSorted();
}Which broken sort passes this test?
return new int[0]; is sorted. Green.assertThat(result).containsExactly(1, 2, 5, 9);
assertThat(result).hasSize(4);
assertThat(result).isSorted();
assertThat(result).contains(5).doesNotContain(7);
assertThat(evaluation.getWeek()).isEqualTo("2023-W31");
assertThat(catchThrowable(() -> SelectionSort.sort(null)))
.isInstanceOf(NullPointerException.class);@Test marks a test; @BeforeEach gives every test fresh setup.@ParameterizedTest runs one test body over many rows of input.assertThat checks the results, as in Project Pulse.public PeerEvaluation addPeerEvaluation(PeerEvaluation newPeerEvaluation) {
// Make sure the submission week is one of the course section's active weeks and is still open
requireOpenSubmissionWindow(...);
// ... the evaluator is on a team, and the evaluator and evaluatee are on the same team ...
// Make sure this is not a duplicate evaluation
if (this.evaluationRepository.findByWeekAndEvaluatorIdAndEvaluateeId(
week, evaluatorId, evaluateeId).isPresent()) {
throw new PeerEvaluationIllegalArgumentException(
"You have already submitted an evaluation for ... in this week.");
}
return this.evaluationRepository.save(newPeerEvaluation);
}
Shortened from EvaluationService on Project Pulse main.
@ExtendWith(MockitoExtension.class)
class EvaluationServiceTest {
@Mock
PeerEvaluationRepository evaluationRepository;
@Mock
StudentRepository studentRepository;
@Mock
Clock clock; // Mock the clock bean
@InjectMocks
EvaluationService evaluationService;@Mock: a fake whose answers the test controls.@InjectMocks: the real class under test, handed the fakes.given(mock.call()).willReturn(value): "when asked this, answer that".@Test
void testAddPeerEvaluationDuplicatedEvaluation() {
// Given
PeerEvaluation newPeerEvaluation = new PeerEvaluation("2023-W31", john, eric, ...);
given(this.evaluationRepository.findByWeekAndEvaluatorIdAndEvaluateeId("2023-W31", 3, 4))
.willReturn(Optional.of(newPeerEvaluation));
// When
Throwable throwable = catchThrowable(
() -> this.evaluationService.addPeerEvaluation(newPeerEvaluation));
// Then
assertThat(throwable)
.isInstanceOf(PeerEvaluationIllegalArgumentException.class)
.hasMessage("You have already submitted an evaluation for "
+ newPeerEvaluation.getEvaluatee().getFirstName() + " in this week.");
}
Shortened from EvaluationServiceTest (the fixed clock setup is left out).
The clock is a dependency too. A test that reads today's date passes this week and fails some other week.
letterGrade(score): 90–100 A · 80–89 B · 70–79 C · 60–69 D · 0–59 F · anything else invalid
A partition is a group of inputs the code should treat the same way. One test per group.
95 · 85 · 75 · 65 · 30 · -5 · 120Boundaries: both sides of every edge
-1, 0 · 59, 60 · 69, 70 · 79, 80 · 89, 90 · 100, 101
Corners: the odd shapes
Empty · one element · all equal · null · huge
A QA engineer walks into a bar. Orders a beer. Orders 0 beers. Orders 99999999999 beers. Orders a lizard. Orders -1 beers. The first real customer asks where the bathroom is. The bar bursts into flames.
@ParameterizedTest
@CsvSource({ "0, F", "59, F", "60, D", "69, D", "70, C", "79, C",
"80, B", "89, B", "90, A", "100, A" })
void letterGradeFollowsTheScale(int score, String grade) {
assertThat(Grades.letterGrade(score)).isEqualTo(grade);
}
@ParameterizedTest
@ValueSource(ints = { -1, 101 })
void letterGradeRejectsScoresOutsideZeroToHundred(int score) {
assertThat(catchThrowable(() -> Grades.letterGrade(score)))
.isInstanceOf(IllegalArgumentException.class);
}@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("dev")
@Tag("integration")
@Transactional
public abstract class AbstractIntegrationTest {
@ServiceConnection
static final MySQLContainer<?> mysql = SharedContainers.MYSQL;
Shortened from Project Pulse's AbstractIntegrationTest.
Trace every test back to a use case. A flow with no test was never checked.
@Test
void testStudentJohnGenerateOwnWeeklyPeerEvaluationSummary() throws Exception {
this.mockMvc.perform(get(this.baseUrl + "/evaluations/students/4/week/2023-W31")
.header(HttpHeaders.AUTHORIZATION, this.studentJohnToken))
.andExpect(jsonPath("$.code").value(StatusCode.SUCCESS))
.andExpect(jsonPath("$.data.privateComments").isEmpty()); // privateComments MUST be empty
}
@Test
void testStudentJohnGenerateAnotherStudentsWeeklyPeerEvaluationSummary() throws Exception {
this.mockMvc.perform(get(this.baseUrl + "/evaluations/students/5/week/2023-W31")
.header(HttpHeaders.AUTHORIZATION, this.studentJohnToken))
.andExpect(jsonPath("$.code").value(StatusCode.FORBIDDEN))
.andExpect(jsonPath("$.message").value("No permission."));
}
Shortened from EvaluationIntegrationTest.
The use case says students see only their own evaluations. List every request that should be forbidden.
testEvaluatorJohnUpdatesAnotherEvaluatorsEvaluationtestStudentJohnGetDetailedWeeklyPeerEvaluationsOfSingleStudenttestStudentJohnGenerateWeeklyPeerEvaluationForSectiontestStudentJohnCannotAdoptAnotherEvaluatorsRatingsThroughCreateCoverage shows what ran, not what was checked.
What it is
The share of lines (or branches) your tests executed.
What it is for
Finding code no test touches. Never a score to chase.
// Given: 2026-W38 is the last active week; Sep 23 is in the week after
Section section = section(1, MONDAY, WEDNESDAY);
section.setActiveWeeks(List.of("2026-W37", PREVIOUS_WEEK));
given(sectionService.findReminderEligibleSectionsForWeek(PREVIOUS_WEEK))
.willReturn(List.of(section));
// When
scheduler(LocalDate.of(2026, 9, 23), true).sendWeeklyReminders();
// Then
verify(emailService, times(1)).sendReminderEmail(eq("s1@abc.edu"), anyString(), anyString());I mocked the database, the mail server, and the clock. The test passed. So did the bug.
it('sends no reminder when the instructor cancels (5a)', () => {
openAndChoosePeerEvaluation(owingStatus)
cy.get('[data-cy=remind-cancel]').click()
cy.get('[data-cy=remind-dialog]').should('not.be.visible')
cy.get('@reminders.all').should('have.length', 0)
})
From frontend/cypress/e2e/reminders.spec.cy.js.
| Layer | Checks | Project Pulse |
|---|---|---|
| Unit | One class, dependencies mocked; milliseconds | about 125 |
| Integration | Real Spring, real MySQL, HTTP; seconds | about 320 |
| End to end | The browser and the whole stack; slowest | 19 Cypress, not in CI |
Find where the rule lives. Test it at that layer.
Every bug fix comes with a test that fails before the fix and passes after.
on:
pull_request:
branches:
- main
...
- name: Build and test with Maven
working-directory: ./backend
run: mvn --batch-mode package
From Project Pulse's .github/workflows/maven-build.yml.
Delegate
The candidate list, boilerplate, parameterized cases, running the suite.
Keep
The oracle, which tests are in scope, and every change to a failing test.
When an agent writes the code, the tests are the part you read.