Testing your code
Writing down what should be true, so a change that breaks it says so — the habit that separates working code from code that worked once.
How do you know it works?
Running a program and reading the output proves that one run worked. It does not prove the function is right — only that the values you happened to try gave the answers you happened to expect.
A test writes the expectation down so the machine can check it, on every run, without you. In Python the smallest test is one line: assert followed by something that must be true.
Three lines that say what the function means
def add(a, b):
return a + b
assert add(2, 3) == 5
assert add(-1, 1) == 0
assert add(0, 0) == 0
print("All tests passed.")A failed assert is information
When the condition is false, Python stops at that line with an AssertionError. That is the point: a test that can fail is what tells you when a later change breaks something that used to work.
So the tests worth writing first are the awkward ones. Empty input, zero, one item, a negative number, a word with a space in it — the cases you would not bother trying by hand are exactly where the bugs live.
The habit matters more than the tool
Real projects use a test runner — pytest is the common one — which finds every test, runs them together, and prints a report. Underneath, most of its tests are the same idea: call the function, assert what should come back.
Write the test while the function is fresh, next to the line that defines it. A test written a week later tests what you remember, not what you decided.
Prove the initials
The initials function only returns the first letter. Fix it so it returns the first letter of every word, then prove it with three asserts: a two-word name, a single word, and a three-word name.
A test names a behaviour
The name of a test is the sentence a failure prints. test_discount_of_zero_leaves_price_alone tells you what broke before you open the file; test_1 tells you nothing. Renaming tests is cheap, and it turns a red run into a diagnosis.
Keep one claim per test where you can. Two asserts in one function are fine when they describe the same behaviour, but a test that checks three unrelated things fails as a unit, and the first failure hides the others.
Tests are documentation that runs
A test is an example of the function being used, with the expected answer written down. Unlike a comment, it cannot drift silently: change the function and the example either still holds or fails. That is why a well-tested module is easier to read than a heavily commented one.
It also gives you permission to change the implementation. When the behaviour is pinned by tests, a rewrite is a calculation rather than a gamble: run the suite, and the parts that still behave the same are still correct.
None of this requires a framework. A file of asserts you run before you commit is already the habit; pytest only makes the report nicer.
Do not test the language, though. assert 2 + 2 == 4 checks Python, not your program, and it will never fail for a reason you can fix. Test the decisions your code makes: the branch, the calculation, the default.
Do not test private details either, such as the exact wording of an error you may reword next month. Test the promise the function makes to its caller. A test coupled to the implementation breaks when you improve the code, which teaches the wrong lesson about changing it.
A test you cannot make fail is not testing anything. Change an expected value once and watch it break — if it does not, the test is not running.
A test is a claim you can run
An assert is a sentence about one call: given these inputs, this is the answer. Keep each claim small enough that a failure names the behaviour, not just the line. Three asserts that check three cases are more useful than one long assert that checks everything at once.
Test the function, not the printing. A function that returns its answer can be checked directly; one that prints can only be checked by reading the output. That is one more reason return is the habit to prefer.
A suite is a list of small claims
A test file collects these claims, often as small functions with one claim each. Naming them test_something is what lets a runner such as pytest find them without being told where to look.
Run the whole file after every change. One failing test names one behaviour; a suite of them turns “I think it still works” into a fact you can check in a second.
Three testing mistakes: comparing with is where == is meant, which asks whether two things are the same object rather than the same value; testing only the happy path, where bugs are least likely; and writing a test that depends on today’s date or a random number, so it passes this morning and fails tomorrow.
Try these next
- Add an assert for an empty string and decide what
initials("")should return. - Change one expected value on purpose, run the file, and read the AssertionError.
- Write two asserts for the same function with different capitalisation.
Prove the discount
The function applies a percentage discount, but it subtracts the percentage instead of the money. Fix it so 10 percent off 200 gives 180, then prove it with asserts for a normal price, a zero discount and a full discount.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What happens when an assert condition is false?
