Academy

Testing

The Cost of Not Testing

Every program you write makes assumptions. This function always receives a positive number. That string is never empty. The list always has at least one item. These assumptions are invisible until they're wrong — and when they're wrong, something breaks, often in a way that's hard to trace back to the original assumption.

Testing is the discipline of making assumptions explicit and verifiable. A test is a piece of code that calls your code with known inputs and asserts that the outputs match what you expect. If they don't, the test fails immediately, loudly, and with a clear message.

This lesson uses Python's built-in unittest module — available everywhere, no installation needed.

Note: In a real development environment, you'd run tests from the command line with python -m unittest or using pytest, which discovers test files automatically. In this sandbox, we run the test suite directly by calling unittest.main() — the output is identical.

Your First Test

The structure:

  • Test cases are classes that inherit from unittest.TestCase
  • Every test is a method that starts with test_ — the naming convention is how the runner finds them
  • self.assertEqual(actual, expected) checks that two values are equal. If not, the test fails with a clear message.
  • unittest.main(argv=[""], exit=False, verbosity=2) runs all tests and reports the results

verbosity=2 shows each test's name and result individually — useful for seeing exactly what ran.

The Assert Methods

unittest gives you specific assertion methods for different comparisons — each produces a more informative failure message than a raw assert:

assertAlmostEqual is essential for floating-point comparisons — 0.1 + 0.2 is not exactly 0.3 in floating-point arithmetic, but it's close enough.

Testing for Exceptions

A function that should raise an exception under certain conditions needs to be tested for that too:

with self.assertRaises(ValueError): passes if the code inside raises a ValueError — and fails if it doesn't raise one (or raises a different exception). Testing that errors are raised correctly is just as important as testing that correct input produces correct output.

setUp and tearDown

When multiple tests share the same setup code, setUp runs before each test method — and tearDown runs after each one, regardless of whether the test passed or failed:

Each test method starts with a fresh cart containing notebook and pen. Tests are completely independent — one test's actions don't affect another's. This isolation is fundamental: a failing test should point to exactly one problem, not a cascade of side effects.

What Happens When a Test Fails

The failure message tells you exactly: which test failed, what was expected, and what was actually received. This is the feedback loop that makes testing valuable — not just "something is wrong" but "this specific expectation was not met."

The failing test has also revealed a real limitation: the function doesn't handle mixed case. You now know what to fix.

Test-Driven Development (TDD)

TDD inverts the usual order of work:

  1. Write a test for behaviour that doesn't exist yet — it fails
  2. Write the minimum code to make the test pass
  3. Refactor the code without breaking the tests

The tests define the specification. The function satisfies it. When requirements change — "passwords must also contain a special character" — you add a test first, watch it fail, then update the function. The existing tests confirm you haven't broken anything that was working.

This discipline changes how you think about code. Tests are not a chore added after writing — they are the design.

What You Have Learned

Testing turns invisible assumptions into explicit, verifiable statements — and makes every change to your code safe.

The key ideas:

  • Tests are code that calls your code and asserts expected behaviour
  • unittest.TestCase subclasses group related tests; methods starting with test_ are discovered automatically
  • Core assertions: assertEqual, assertTrue, assertFalse, assertIn, assertRaises, assertAlmostEqual
  • setUp / tearDown run before/after each test — use them to ensure test isolation
  • When a test fails, it reports exactly what was expected and what was received
  • TDD: write the test first, then the code — tests define the specification
  • Tests give you confidence to refactor and extend code without fear

You've Completed the Advanced Series

Thirty lessons across three tiers. From print("Hello, World!") to decorators, closures, async programming, NumPy arrays, Pandas DataFrames, and test-driven development.

You now have a complete, working knowledge of Python as it is used in the real world — in web development, data science, automation, scientific computing, and software engineering.

What comes next is practice, projects, and going deeper into the domains that interest you most. The language is now a tool you control. What you build with it is entirely up to you.