rut

A Python unittest runner with test impact analysis, dependency ordering, and incremental testing.

Why Test Order Matters: Dependency-Aware Testing

When a test fails, you want to know immediately. But if your test runner executes tests in arbitrary order, you might wait minutes before seeing the failure that matters.

Comparison of alphabetical vs dependency order - showing much earlier failure detection

The problem with random test order

Most test runners discover tests alphabetically or by file modification time. This means:

tests/test_account.py      # runs first (alphabetically)
tests/test_currency.py     # runs second
tests/test_report.py       # runs third
tests/test_transaction.py  # runs fourth
tests/test_utils.py        # runs last

But what if test_utils.py tests the foundational code that everything else depends on? A bug in utils.py will cause cascading failures across all tests — but you won’t see the root cause until the very end.

Import graph to the rescue

Your code has a natural dependency structure. If currency.py imports from utils.py, then utils is more foundational. Tests for foundational modules should run first.

Finance module dependency graph showing import relationships

rut analyzes your imports to build a dependency graph and topological sorts it: utils → currency → account → transaction → report. A bug in utils.py fails fast — you see it in seconds, not minutes.

How rut does it

rut analyzes your import graph at test discovery time:

$ rut --dry-run
Test order (by import dependencies):
  tests/test_utils.py::TestUtils
  tests/test_currency.py::TestCurrency
  tests/test_account.py::TestAccount
  tests/test_transaction.py::TestTransaction
  tests/test_report.py::TestReport

No configuration needed. It reads your actual imports and sorts accordingly.

Easier debugging

There’s another benefit: when a foundational module breaks, you don’t have to dig through 300 failing tests to find the root cause.

A bug in utils.py causes cascading failures. With alphabetical order:

FAIL test_account.py::test_balance_in_eur - TypeError: unsupported operand type
FAIL test_currency.py::test_convert_usd_to_eur - ImportError: cannot import name
FAIL test_report.py::test_net_worth - TypeError: unsupported operand type
FAIL test_transaction.py::test_cross_currency - TypeError: unsupported operand type
... more failures ...
FAIL test_utils.py::test_round_amount - NameError: name 'round' is not defined

Which failure matters? You have to guess.

With dependency order and -x (stop on first failure):

$ rut -x
FAIL test_utils.py::test_round_amount - NameError: name 'round' is not defined
Stopping at first failure.

The first failure points directly to the root cause. No need to wade through cascading failures to find the one that matters.

Limitations

  • Test isolation: This assumes tests are independent. If test order affects results, you have flaky tests.
  • See how the import graph works for details on what is and isn’t tracked (dynamic imports, circular dependencies, etc.).