Knowing the runner's options makes the edit-test loop fast, locally and in CI.
The command-line runner is python -m unittest:
python -m unittestwith no arguments runs discovery: it findstest*.pyfiles in the current directory and importable subpackages, and runs every TestCase in them.python -m unittest discover -s tests -p "test_*.py"sets the start directory and file pattern explicitly.- Run a subset by dotted name:
python -m unittest tests.test_api,tests.test_api.TestLogin, ortests.test_api.TestLogin.test_bad_password. -k patternselects tests whose names contain the substring (or match a wildcard pattern).
Useful flags:
-vverbose: one line per test with its name and result.-f/--failfast: stop at the first failure.-b/--buffer: hide stdout/stderr of passing tests.--durations N(3.12+): report the N slowest tests.
For discovery, test directories must be importable: a package with __init__.py or a namespace package reachable from the start directory. The exit status is non-zero when anything fails, which is what CI relies on.
pytest can run the same tests unchanged (pytest tests/), so starting with unittest doesn't lock you in.
Example
mkdir -p demo/tests && cd demo
touch tests/__init__.py
cat > tests/test_math.py <<'EOF'
import unittest
class TestMath(unittest.TestCase):
def test_add(self): self.assertEqual(1 + 1, 2)
def test_mul(self): self.assertEqual(2 * 3, 6)
EOF
python -m unittest -v # discovery from the current directory
# test_add (tests.test_math.TestMath.test_add) ... ok
# test_mul (tests.test_math.TestMath.test_mul) ... ok
# Ran 2 tests in 0.000s
# OK
python -m unittest -k mul tests.test_math # only test_mul
python -m unittest --durations 3 # slowest tests (3.12+)
echo "exit status: $?" # 0 when everything passedRemember
- python -m unittest discovers test*.py files automatically
- Run subsets by dotted name or -k pattern
- -v, -f, -b and --durations tune the output
Watch out for
- A tests folder without __init__.py that discovery can't import, so zero tests run.