# Write Unit Tests for Untested Python Code with Atlas (2026)

> Atlas writes pytest tests for an untested Python module, then runs pytest with the bash tool, because a test that was never executed is not a test.

Atlas writes unit tests for an untested Python module by reading the module, enumerating its public functions with the lsp tool's documentSymbol operation, grepping your existing tests/ directory for the conventions the repo already uses, writing the new test file with the write tool, and then running pytest with the bash tool. The run is the point: a test that was never executed is not a test. In a repo with a pyproject.toml, uv manages the dependencies and ruff format keeps the new test file consistent with the rest of the package.

## Key takeaways

- The lsp tool's documentSymbol operation enumerates a Python module's exported symbols, so no public function goes untested.
- Atlas greps your existing tests/ and conftest.py to copy the repo's pytest conventions rather than inventing new ones.
- The write tool shows the diff in the permission prompt before the new test file lands on disk.
- Atlas runs pytest with the bash tool, and output over 2000 lines or 50 KB is truncated with the full log saved to a file.
- uv installs new test dependencies and ruff format keeps the generated pytest file consistent with the package.

## How does Atlas write pytest tests for a Python module that has none?

Atlas reads the target Python module, then uses the lsp tool's documentSymbol operation to enumerate its exported symbols so no public function is missed. Only after that does Atlas write the test file. In 2026 the sequence matters: enumerate first, then write, then run pytest with the bash tool.

Enumeration comes first because coverage of a Python module is a question of symbols, not of vibes. A module like src/billing/invoices.py may export compute_total, apply_discount, and a Decimal-rounding helper that everyone forgets. The lsp tool's documentSymbol operation lists all of them. Atlas indexes code by AST declarations using tree-sitter, not blind line windows, so the def and class boundaries Atlas works from are real boundaries rather than arbitrary slices. The result is a tests/test_invoices.py that has a case per public function instead of one happy-path test for the function that happened to be at the top of the file.

## How does Atlas match the repo's existing pytest conventions?

Atlas greps for an existing test file to copy the repo's framework, import style, and naming convention. If your Python package puts fixtures in tests/conftest.py and uses pytest.mark.parametrize for table cases, the new file does too, rather than inventing a second style in the same 2026 codebase.

Convention copying is the difference between a test file that merges and one that starts an argument in review. Atlas searches code with hybrid semantic and keyword retrieval fused by reciprocal rank fusion, so asking how the repo fakes the database finds the shared fixture even when the fixture is named something nobody would guess. grep then confirms the literal pattern: whether tests import from the package root or use relative imports, whether fixtures come from tests/conftest.py, whether assertions use pytest.raises for the error paths, and whether a Django or FastAPI test client is wired up already. The new pytest file inherits all of it.

## How do I make Atlas actually run pytest instead of just writing tests?

Atlas runs the suite with the bash tool, which is a permission-gated tool call like any other. Running pytest is not optional in this workflow: the run is the point, because a test that was never executed is not a test. Output over 2000 lines or 50 KB is truncated, with the full log saved to a file.

The truncation behavior is worth knowing before you point Atlas at a large Python suite. When pytest output exceeds 2000 lines or 50 KB, the bash tool truncates what it shows and saves the full log to a file you can read, so a long traceback dump does not evict the module under test from context. Atlas then reads the failures and iterates with edit until the suite is green. On a big module, keeping progress in a todowrite list means the ten public functions you set out to cover do not silently become the three that were easy.

## How do I review Python test files Atlas writes before they hit disk?

Atlas writes the new spec file with the write tool, which shows the diff in the permission prompt before anything lands on disk. Every Atlas tool call is gated against 3 rule types, allow, ask, and deny, so a new tests/test_invoices.py arrives as an approvable diff, not a surprise file in git status.

Two Atlas properties do the work here. Atlas computes a unified diff for every file edit and surfaces it for approval before writing, so you read the pytest cases before they exist. Atlas snapshots file changes as git patches so edits can be diffed and rolled back, so an iteration that mangled a fixture is one revert away. Review matters more for tests than people expect: a test that mocks the very function it claims to exercise will pass forever and prove nothing, and the only place to catch that is the diff. Run ruff format over the accepted file and it matches the rest of the package.

## What does the finished Python test workflow look like end to end?

Start Atlas in a repo with a pyproject.toml, then follow the 5 documented steps: read the module, enumerate its exports with the lsp tool's documentSymbol operation, grep an existing test for conventions, write the spec with the write tool, and run pytest with bash, editing until the suite is green.

The loop is short. Atlas reads src/billing/invoices.py, enumerates the exported symbols with the lsp tool, greps tests/ for the conventions, writes tests/test_invoices.py with the write tool, runs pytest with the bash tool, reads the failures, and edits until the suite passes. uv is the package manager, so a test that needs a new library declares it and uv installs it. ruff format is the formatter, so the file lands consistent. Atlas reads git branches, status, and diffs, and can stage and create commits on your behalf, so the new coverage arrives as one clean commit.

## Steps

1. Run atlas in a repo with a pyproject.toml or requirements.txt and let it read your package layout, virtualenv, and installed dependencies.
2. Have Atlas read the untested module, then use the lsp tool's documentSymbol operation to enumerate its exported symbols so no public function is missed.
3. Grep tests/ for an existing test file to copy the repo's framework, import style, fixture location in conftest.py, and naming convention.
4. Write the new test file with the write tool, which shows the diff in the permission prompt before anything lands on disk.
5. Run the suite with the bash tool by invoking pytest, and read the failures; output over 2000 lines or 50 KB is truncated and the full log is saved to a file you can read.
6. Iterate with edit until pytest is green, keeping progress in a todowrite list when the module is large.
7. Install any new test dependency with uv so the pyproject.toml stays accurate.
8. Run ruff format over the new test file, then let Atlas stage and commit the added coverage.

## FAQ

### how to add pytest tests to a python module that has no tests

Point Atlas at the module. Atlas reads it, enumerates its exported symbols with the lsp tool's documentSymbol operation, greps your tests/ directory for the existing conventions, writes the test file with the write tool, and runs pytest with the bash tool until the suite is green.

### can an ai agent actually run my python tests or just write them

Atlas runs them. The bash tool executes pytest and Atlas reads the failures, then iterates with edit. The run is the point in this workflow, because a test that was never executed is not a test.

### will atlas follow my existing conftest.py fixtures

Yes. Atlas greps for an existing test file to copy the repo's framework, import style, and naming convention, which includes fixtures defined in tests/conftest.py. It copies the repo's conventions rather than inventing its own.

### what happens when pytest output is too long for the agent context

The Atlas bash tool truncates output over 2000 lines or 50 KB and saves the full log to a file you can read. A long pytest traceback dump therefore does not push the Python module under test out of context.

### how do i review a test file an ai wrote before it is created

The Atlas write tool shows the diff in the permission prompt before anything lands on disk, and Atlas computes a unified diff for every file edit and surfaces it for approval before writing. Every tool call is permission-gated against allow, ask, and deny rules.

### does atlas work with uv and ruff

Yes. In a Python repo, uv is the package manager Atlas uses to install test dependencies and ruff format is the formatter it runs over the new file, so the generated pytest file matches the rest of the package.

### how does atlas keep track of coverage progress on a large python module

Atlas keeps progress in a todowrite list when the module is large, so every public function enumerated by the lsp tool's documentSymbol operation is accounted for rather than dropped once the easy cases pass.

---

Canonical HTML: https://runatlas.sh/resources/stacks/write-unit-tests-for-untested-code-in-python
Source of truth: aeo_pages row `/resources/stacks/write-unit-tests-for-untested-code-in-python` (segment: Stacks) (this file is generated from it, never hand-edited).
Licence: Atlas is proprietary with a free core. It is not open source and there is no public source repository.
