Instructor Notes
This is a placeholder file. Please add content here.
What is unit testing?
Organizing code to enable unit testing
Instructor Note
Points worth drawing out in discussion:
-
momentum_magnitude()is worth extracting even though it is a single line — it has a name, a clear contract, and can be tested with exact Pythagorean triples -
is_z_candidate()makes the boundary conditions explicit and testable; students should notice the question mark in the comment about whether the upper bound is inclusive or exclusive, and recognise this as a specification decision that needs to be made and documented -
analyse_candidates()now takes all its inputs as parameters and returns all its outputs as values — it can be tested without any files or output capture. -
process_candidates()still exists but is now just a thin I/O wrapper; the principle is not to eliminate I/O but to push it to the boundary.- we _don’t have a unit test for it, and that’s intentional: it connects units together, so becomes an effective integration test.
- this is exactly what we described in the first episode: Unit tests tell you which component is broken; integration tests tell you that the components work together. If your integration test fails but all your unit tests pass, the bug is almost certainly in the way the units are connected, which is a much smaller place to look.
- we haven’t completely eliminated the global energy scale: we might still need to get this from a global variable, but our code no longer depends on it.
- This is mostly good software design practice, but thinking about it terms of testing can help make design decisions.
Instructor Note
Important to highlight that to have a feasible exercise to demonstrate the concepts, or examples are somewhat contrived. Nevertheless, we want to highlight what practices and habits to adopt now rather than after writing thousands of lines of code. Equally, be honest and note that they may have to work with legacy code in research, but these techniques can help to mitigate problems.
Instructor Note
The tension in part 3 of the challenge is deliberate - this is a hard problem, and we acknowledge it.
The two snippets together make a useful point to draw out explicitly at the end of the exercise: process_candidates() and estimate_mass_resolution() have different kinds of testability problem, and the fix in each case is different. But both fixes follow the same underlying principle — make all dependencies explicit in the function signature. Global state, file handles, and random number generators are all the same kind of problem: hidden inputs that the function’s caller cannot see or control. A function whose entire input is visible in its signature is a function you can reason about, test, and trust.
Unit testing with assert()
Integrating tests into a build system
Introducing GoogleTest
Floating point comparisonsTesting with floating point numbers
Testing exceptional behaviour
Testing stateful classes
Instructor Note
Point out that this challenge requires careful reading of the Doxygen
— specifically that underflow increments n_underflow() and
n_entries() but does not affect bin_counts().
Students who miss the n_entries() assertion are leaving
part of the contract untested. We are deliberately testing only
underflow here, not overflow. The overflow branch will be left uncovered
and discovered in episode 10. Do not draw attention to this omission —
let the coverage report make the discovery.
Instructor Note
The challenge asks specifically about underflow rather than overflow.
This is deliberate — using overflow here would cover the overflow branch
of fill() and remove the gap we need for episode 10.
The follow-up question about mean() and
n_entries() has no single correct answer but should prompt
students to articulate that n_entries() counts all fills
while mean() uses only in-range values — a subtle but
important aspect of the contract.
Test fixtures
Instructor Note
The fixture fills underflow but deliberately omits overflow. This
leaves the overflow branch of fill() uncovered, which
students will discover in episode 10. Do not draw attention to the
omission — present the fixture as a natural choice of test data.
Instructor Note
OverflowCount asserting zero is a legitimate and useful
test — it confirms the underflow fill did not accidentally increment the
overflow counter. It also means a test exists that touches
n_overflow() without covering the overflow branch of
fill(), which is exactly the situation the coverage report will
illuminate. later
Code coverage
Instructor Note
Show the fill() function in the HTML report. The line if (x >= x_max_) will appear green — because the line executed — but with incomplete branch coverage, because only the false branch was taken. The lines inside the if block will appear red. This illustrates the difference between line coverage and branch coverage. The if line is green because it ran. But the true branch — the path where x >= x_max_ — was never taken. Line coverage alone would tell us this line is fine. Branch coverage tells us we only tested half of it.
Instructor Note
Closing point is that coverage gives us an answer, albeit a limited, specific one, to the question of whether we have tested everything. 100% coverage is not the same as a complete test suite. In particular, neither answer the question of whether we have covered the input parameter (or numerical) space of what we are testing