Why Testing Matters
By the end of this lesson
Explain what tests actually buy you, and use that to judge whether a given test is worth writing.
Tests exist so you can change code without checking everything by hand.
That is the justification, and holding onto it is useful, because it gives you a way to judge any individual test: does this one make me more willing to change the code? Tests written for other reasons — to hit a coverage number, to satisfy a rule — tend to be the ones that get deleted.
The problem they solve
Imagine a pricing calculation used in eleven places. You need to change how discounts combine. Without tests, you make the change and then face a choice: manually exercise eleven scenarios, or hope.
In practice, people hope. Or, more often, they avoid the change altogether and add a special case alongside the old logic. Do that a few times and you have code nobody wants to touch — not because it is complicated, but because nobody can predict what a change will break.
[Fact]
public void ApplyDiscount_WithTenPercent_ReducesPriceCorrectly()
{
// Arrange
var calculator = new PriceCalculator();
// Act
decimal result = calculator.ApplyDiscount(200m, 10);
// Assert
Assert.Equal(180m, result);
}- The name states the scenario and the expected outcome, so a failure is informative before you read any code.
- Arrange, act, assert: set up the situation, perform one action, check one outcome.
- This test now runs in milliseconds, every time anyone builds, forever.
What makes a test worth having
- It tests behaviour, not implementation — it still passes after a refactor that preserves behaviour
- It fails for exactly one reason, so a failure points at a cause
- Its name describes the scenario, so you can diagnose from the test run output alone
- It runs fast enough that nobody is tempted to skip it
- It does not depend on other tests, or on the order they run in
The shape matters more than the labels. Many small fast tests catch logic mistakes immediately. A smaller number of integration tests catch wiring mistakes that unit tests structurally cannot see. A handful of end-to-end tests confirm the critical paths work as a whole.
Inverting this — mostly slow end-to-end tests — produces a suite that takes twenty minutes, fails intermittently, and gets ignored. At that point it costs more than it returns.
Summary
- Tests exist so code can be changed safely — judge each test against that
- The real cost of no tests is improvements that never get attempted
- Test behaviour rather than implementation, or tests punish refactoring
- Favour many fast tests and few slow ones; a slow suite gets ignored
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Think about it
Think about it
A test checks that a service calls the repository's Save method exactly once. You refactor the service to batch several saves into one call. The behaviour a user sees is unchanged, but the test fails. Was it a good test?
Show solution
No. It asserted an implementation detail — how the work was carried out — rather than the outcome. Since the observable behaviour did not change, a failure here is a false alarm.
A better test would assert the outcome: after the operation, the expected records exist with the expected values. That holds regardless of how many calls it took internally, so it survives the refactor and still catches genuine breakage.
Knowledge check
Nothing is recorded and there is no score. The explanation appears either way.
Saved in this browser only.