php · level 3 · unit 7 · lesson 4
- meet it
- guess first
- your turn
- fix it
- nailed it
Tap a green word if you get lost.
meet it
Test the edges, where bugs love to hide.
Code usually works for the obvious input. Bugs hide at the edges: empty strings, zero, negative numbers, the exact limit and one past it. Every deserves its own test. So does bad input that your code should refuse, which means checking that it throws.
An assert_throws helper runs a closure and passes only if the right exception escapes. InvalidArgumentException::class gives a class's name as a string. Keep every test and re-run them all after each change, and a is caught before your users find it.
<?php
function assert_throws(string $name, string $class, callable $action): void {
try {
$action();
echo "FAIL $name: nothing was thrown\n";
} catch (Throwable $e) {
echo $e instanceof $class ? "PASS $name\n" : "FAIL $name: got " . get_class($e) . "\n";
}
}
function quantity(int $n): int {
if ($n < 1 || $n > 10) { throw new InvalidArgumentException("Choose 1 to 10, not $n."); }
return $n;
}
assert_throws("refuses 0", InvalidArgumentException::class, fn() => quantity(0));
assert_throws("refuses 11", InvalidArgumentException::class, fn() => quantity(11)); Since PHP 8, throw also works inside an arrow function, as in fn() => throw new OutOfStock().
guess first
Three tests of throwing. What will this show?
Guess before you run it. Wrong guesses are how you learn, and nothing is marked.
<?php
function assert_throws(string $name, string $class, callable $action): void {
try {
$action();
echo "FAIL $name: nothing was thrown\n";
} catch (Throwable $e) {
echo $e instanceof $class ? "PASS $name\n" : "FAIL $name: got " . get_class($e) . "\n";
}
}
class OutOfStock extends RuntimeException {}
assert_throws("sold out", RuntimeException::class, fn() => throw new OutOfStock("none left"));
assert_throws("bad amount", InvalidArgumentException::class, fn() => throw new OutOfStock("none left"));
assert_throws("no problem", OutOfStock::class, fn() => "fine");
your turn
Your turn. Add an edge case to the test run.
Add a test that should pass: a name, the value you expect, then the code that should give it, like count([]) for an empty basket.
fix it
The 9 o'clock edge case fails. Fix the shop, not the test.
- Noon and 5pm pass, but exactly 9 fails. The fake clock makes the test repeatable, so the bug is in
shopOpen. - The shop opens at 9, so 9 itself should count. Look at the comparison on line 8.
- On line 8, change
> 9to>= 9, then run the tests again.
nailed it
you tested the edges like a professional. :D
- Edge cases sit at the limits: empty, zero, exactly the maximum.
assert_throwspasses only when the right exception escapes.- Re-run every test after each change to catch regressions.
new words in your collection
- edge case
- regression
This lesson isn’t saved. A free account remembers every lesson you finish.