How do you run a single unittest test method from the command line with `python -m unittest`?
answer
- The runner takes arguments
- Name the thing you want to run
- Dots, not slashes
- module, then class, then method
basics
~10 sPass its dotted path as an argument: python -m unittest package.module.ClassName.test_method. Shorten the path to run a whole module or one TestCase, and a file path such as tests/test_csv_import.py works too.
solid answer
~50 s`python -m unittest` takes a list of test names, each a **dotted import path** resolved left to right: module, then the `unittest.TestCase` subclass, then the method. So `payroll.tests.test_csv_import.CsvImportTests.test_rejects_negative_hours` runs exactly one method, dropping the last segment runs the whole class, and dropping two runs the whole module. You can pass several names in one command. A file path like `tests/test_csv_import.py` is also accepted and converted to the dotted name for you, which is handy with shell completion. With **no** arguments at all the command is equivalent to `python -m unittest discover`. Because the name is an import path and not a filesystem path, it is resolved against `sys.path` — run the command from the project root, or the module simply will not be found. Add `-v` for per-test output and `-k` to filter by name.
code
console · 3 linespython -m unittest -v payroll.tests.test_csv_import.CsvImportTests.test_rejects_negative_hours
python -m unittest payroll.tests.test_csv_import.CsvImportTests
python -m unittest payroll.tests.test_csv_importgo deeper
Memorise the shape python -m unittest module.Class.method and that chopping segments off the right widens what runs. Be ready to say the argument is a dotted import path, not a file path.
Explain the resolution mechanics: the loader imports the longest importable prefix and then walks attributes. Know that python -m puts the current directory on sys.path, which is why the same command works from the project root and fails elsewhere.
Show you use this to narrow a failing run fast — pass two module names together to reproduce an interaction, add -v to read the exact ids back, and use -k and -f to bisect. Explain the exit status contract that CI depends on.
Own the convention: one documented entry point for local runs and CI so a failure reproduces identically in both. Be ready to argue why the python -m form is preferable to running test files as scripts, and what that buys a team standardising a repository layout.
`python -m unittest` is a thin command-line front end over three objects: a `unittest.TestLoader` turns names into tests, a `unittest.TestSuite` holds them, and a `unittest.TextTestRunner` runs them and prints the dots. Everything you type on the command line only decides what the loader is asked to load. ### Names are import paths, not file paths Each positional argument is a dotted name. The loader imports the longest importable prefix and then walks the rest with attribute access. Given `payroll.tests.test_csv_import.CsvImportTests.test_rejects_negative_hours`, it imports `payroll.tests.test_csv_import`, fetches the `CsvImportTests` class from it, then fetches the `test_rejects_negative_hours` method and wraps it in a one-test suite. The separator is a plain dot at every level; there is no special punctuation between the module part and the class part, which is why the module has to be importable as spelled. That gives you three granularities from one syntax: - `python -m unittest payroll.tests.test_csv_import` — every test in the module. - `python -m unittest payroll.tests.test_csv_import.CsvImportTests` — every test in that class. - `python -m unittest payroll.tests.test_csv_import.CsvImportTests.test_rejects_negative_hours` — one method. Several names can be given at once, and they are run in the order listed, which is the cheapest way to reproduce an interaction between two specific modules. ### The file-path convenience form `python -m unittest tests/test_csv_import.py` is accepted: the runner strips the `.py` and turns the separators into dots, producing `tests.test_csv_import`. It exists so shell filename completion is usable; it is **not** an escape hatch from importability. If the resulting dotted name cannot be imported from the current `sys.path`, it fails exactly as the dotted form would. ### Why `ModuleNotFoundError` shows up even though the file exists This is the single most common stumble. `python -m` puts the current working directory at the front of `sys.path`, so the dotted name is resolved relative to *where you ran the command*, not to where the file lives. Running `python -m unittest test_csv_import` from inside `tests/` works; running `python -m unittest tests.test_csv_import` from the project root works; running the latter from inside `tests/` does not. Note that running a *named* module this way goes through the ordinary import machinery, so a directory without an `__init__.py` is still importable as a namespace package — that is different from recursive discovery, which does require `__init__.py` on every directory it walks into. ### Flags worth knowing at the same time - `-v` prints one line per test with its full dotted id, which is also the fastest way to learn the exact name to paste back into the command. - `-k` filters by name and may be repeated; a test runs if it matches any pattern. The pattern is matched case-sensitively against the test's full `module.Class.method` name. If the pattern contains no `*` it is wrapped in wildcards and behaves as a substring match, so `-k negative` works; if it *does* contain a `*`, it is used as written, so `-k 'CsvImportTests.test_head*'` matches nothing and you need a leading `*`. - `-f` stops at the first failure or error, `-b` buffers stdout and stderr so passing tests stay quiet, and `--locals` adds local variables to tracebacks. - The process exit status is non-zero when anything failed, which is what CI keys off. ### Running several names at once Because the names are positional arguments, `python -m unittest tests.test_rate_cache tests.test_csv_import` loads both and runs them in the order given. That is the cheapest reproduction available when one test only misbehaves after another has run, and it needs no configuration file, no marker and no plugin. The same list accepts a mix of granularities, so a whole module can be paired with a single method from another. ### The in-file alternative The familiar `if __name__ == "__main__": unittest.main()` block at the bottom of a test module lets you run `python tests/test_csv_import.py` directly. `unittest.main()` parses the same arguments, so `python tests/test_csv_import.py -v -k negative` behaves like the module form. The difference is the import context: run as a script, the module is imported as `__main__` and its own package-relative imports may resolve differently. Preferring `python -m unittest ...` keeps one import story for local runs and CI alike. ### What to say in an interview Name the dotted path, show the three granularities, and mention that the name is resolved through the import system — that last point is what separates someone who has memorised a command from someone who has debugged a failing test run.
- What exactly does the `-k` option match against?The test's full dotted name, `module.ClassName.method_name`, matched case-sensitively with `fnmatch` semantics. A pattern containing no `*` is automatically wrapped in wildcards, so `-k negative` is a substring match. A pattern that already contains a `*` is used verbatim, so `-k 'CsvImportTests.test_head*'` matches nothing without a leading `*`. The option may be repeated, and a test runs if it matches any of the patterns.
- The file is right there, so why does the dotted path fail with ModuleNotFoundError?Because the argument is an import path, not a filesystem path. `python -m` places the current working directory at the front of `sys.path`, so `tests.test_csv_import` only resolves when you run the command from the project root. From inside `tests/` you would name it `test_csv_import` instead. Nothing about the file's existence on disk is consulted; only what the import system can reach.
- What does `python -m unittest` do when you give it no test names at all?It is equivalent to `python -m unittest discover`: it starts discovery in the current directory with the default `test*.py` pattern. That is why a bare invocation in the wrong directory reports `NO TESTS RAN` rather than an error — it found nothing to load, which is not the same as failing to load something.
The dotted name is a postal address read left to right: country, street, house. Chop the last part off and you address the whole street.
saying these in an interview costs you the question
- Thinking the argument is a file path, so writing slashes and a .py suffix everywhere
- Using a colon or double-colon between the module and the class name
- Believing you must edit the file or add a marker to run one test
- Assuming -k selects a single test exactly rather than matching a pattern
- Running from the wrong directory and concluding the test file is broken
- Thinking a bare `python -m unittest` runs nothing instead of starting discovery