Alright, let’s talk about actually running your tests. You’ve written these beautiful, intricate test cases—monuments to your foresight and paranoia. Now what? You don’t just stare at them admiringly; you set them loose and see what breaks. Python’s unittest framework gives you a few ways to do this, and understanding the nuances is the difference between a smooth workflow and banging your head on your desk wondering why it can’t find your tests.

The Basic, Explicit Command

The most straightforward way is to run a test file directly, just like any other Python script. If your file is called test_calculator.py and it contains a TestCase class like TestCalculator, you can just do:

python test_calculator.py

This works because unittest has a clever little trick up its sleeve: when you execute a script containing unittest.TestCase classes, it triggers the framework’s built-in command-line entry point. It’s simple, it’s direct, and it’s perfectly fine for when you’re focused on a single file. But we can do better.

Using python -m unittest Explicitly

The more “correct” way, and the one that scales, is to use the python -m unittest command. To run that same single file and every test in it, you’d use:

python -m unittest test_calculator.py

“Why would I do that?” you might ask. It seems more verbose. The magic comes when you want to run something less than the entire file. The -m unittest approach unlocks targeting. Want to run just a single test case class within that file?

python -m unittest test_calculator.TestCalculator

Even better, want to run just one specific test method because it’s the one you’re currently fixing?

python -m unittest test_calculator.TestCalculator.test_addition

This granularity is a lifesaver. You don’t want to wait for your entire 2000-test suite to run when you’re debugging one weird edge case in a single function. This command structure is your precision scalpel.

The Real Power: Test Discovery

Now, let’s say your project isn’t a single file. It’s a proper package with structure. You’ve got tests in a tests/ directory, maybe even nested in subfolders that mirror your source code (tests/unit/, tests/integration/). You are not going to type out every file path manually. This is where unittest’s test discovery comes in, and it’s both brilliant and, frankly, a bit obtuse in its defaults.

The command is deceptively simple:

python -m unittest discover

This one command will go off and find all your tests. But how does it find them? This is the critical part it often forgets to explain clearly. By default, discover:

  1. Starts looking in the current directory (.).
  2. Looks for any file or directory named test*.py or *test.py. So test_models.py, api_tests.py, and a directory named tests/ all match the pattern.
  3. Inside those files, it looks for any class that inherits from unittest.TestCase and grabs all the methods whose names start with test.

This pattern-driven approach is why you should religiously name your test files test_<something>.py and your test methods test_<some_behavior>. It’s not just a convention; it’s a requirement for the automation to work.

When Discovery Goes Sideways (and How to Fix It)

Here’s the part that trips everyone up. You run python -m unittest discover from your project’s root and get a depressing Ran 0 tests in 0.000s. Panic sets in. “They were right there! I swear!” Calm down. It’s probably one of these issues:

  • You’re in the wrong directory. The discovery start directory matters. If your tests/ folder is a sibling to your source code, you need to be in the parent directory. Use the -s flag to specify the start directory explicitly.

    python -m unittest discover -s ./tests
    
  • Your import paths are broken. This is the big one. Your test files are in tests/test_models.py and they try to from myapp import models. But myapp isn’t on the Python path! So you get an ImportError before the test can even be discovered and run. The solution is to use the -t flag to specify the top-level directory, which gets added to the Python path.

    python -m unittest discover -s ./tests -t ./
    

    This command says: “Start discovery in ./tests, but treat ./ (the project root) as the top-level, so imports work.” This is arguably the most important command-line flag for real-world projects.

  • You’re using a different pattern. If you’re a heretic who uses a different naming pattern (not recommended, but you do you), you can override it with the -p flag.

    python -m unittest discover -p "*_check.py"
    

So my recommended, bomb-proof command for running a full test suite from a project root with a tests/ directory is almost always:

python -m unittest discover -s tests -t . -v

Ah, the -v! I almost forgot. Always use the verbose flag (-v). It tells you which tests are running as they run, so you aren’t left staring at a blank screen wondering if it’s working or if it’s frozen. The output is just so much more humane.