A program that seems to work is not the same as a program that does work. Every programmer writes bugs, and the good ones find them quickly because they have a method. In this course you write tests with assert and with a table of cases, use the unittest module, choose the edge cases that find most bugs, and test with random inputs against a trusted answer. You learn a loop for finding a bug: reproduce it, locate it, explain it, fix one thing, and test again. A small text library with four hidden bugs is the project at the end. The practice runs your code on real cases and tells you which results are wrong.
5 lessons, about 2 h 30 min5 simulations10 problems10 practice questionsPython in every lesson
A test runs your code with an input and compares the answer with one that you worked out from the rules, not from the code. assert says that something must be True, and stops with an AssertionError and an optional message when it is not, which is useful for checking input and for writing quick tests. A table of tests holds the inputs and the expected answers, and a loop runs them all and counts the failures, so one run shows all the problems. Choose the cases with care: one in each branch, both sides of every edge, the smallest and the biggest input, and the empty case. A failing test names the input, the expected answer and the answer that was given, and tests let you change code without fear.
The unittest module is Python's standard tool for tests. A test class inherits from unittest.TestCase, each method whose name starts with test is a test, and the assert methods such as assertEqual, assertAlmostEqual, assertTrue and assertRaises check the results and give a clear report when they fail. setUp runs before each test, so that every test starts from a fresh state. unittest.main runs all the tests and prints a dot for a pass, an F for a failure and an E for an error, then a summary. A test should check one thing and have a name that says what it checks. Use assertAlmostEqual for decimal numbers, and assertRaises with a with block for an expected error.
Most bugs live at the edges: the empty input, a single item, repeats, items that are all equal, the ends of a range, and strange values such as zero and negative numbers. A function that works for the usual cases can fail on all of them. List the edge cases before you test, and test each. When a trusted answer exists, such as Python's own sorted, test with many random inputs from a fixed seed and compare the two, which finds bugs that nobody thought of and returns the first failing input. Properties that must always hold, such as the result being in order, having the same length and the same items, can be checked with assert for every random input. Assert inside a function can also check the thinking of the code.
Finding a bug is a loop and not a guess. First reproduce it: make it fail every time, with the smallest input. Then locate it, by reading the traceback from the bottom, by printing the values inside the code, or by checking the result of each stage of a pipeline until you find the first stage that is wrong. Then explain why, in a sentence, before you change anything. Fix one thing, and test again, with the failing case and all the old tests, and keep the new test so that the bug cannot return. If it still fails, look again. The common causes are off-by-one errors, a wrong condition at an edge, a variable that is not what you think, and a changed list that you also used elsewhere. Explaining the code aloud often shows the bug.
In this project we fix a small text library that has four hidden bugs. The library has four functions that count the words of a text, give the average length of a word, find the most common word with the first in the alphabet for a tie, and test for a palindrome. A table of thirteen tests finds the bugs: splitting on a single space gives empty words, the average divides by zero for an empty text, a greater-or-equal in the tie rule lets the wrong word win, and the palindrome test does not ignore capitals and punctuation. We run the tests, read the failures, explain each bug in a sentence, fix one thing at a time, run all the tests again, and extend the library with tests of our own.
Log in to join the discussion.
Loading…