AI-Assisted Coding
Back to Daily Workflow with AI

Build 3 — Add Tests to Untyped Code

Take undocumented untested code; AI helps you both type it and test it. Coverage + types in one push.

115 minutesProject submission
🎯 Free Guest Mode: You are learning for free. Sign in to save your completion progress and quiz answers.

Assignment Instructions

🎯 Hands-on Portfolio Deliverable: Retrofit an untyped, untested Python codebase with full type annotations and comprehensive pytest fixtures using AI assistance, transforming legacy code into maintainable production software.


The brief

Use your own untyped Python codebase or download the untyped starter project. Add tests and types in tandem. The starter README explains setup; your own project may use a different layout.

Why both at once

Adding types reveals what tests should cover (type signatures define contracts). Adding tests reveals what types ARE (the actual behavior). They reinforce each other.

Starting point

A codebase with:

  • Functions without type hints.
  • Few or no tests.
  • Behavior knowable only by reading code.

Choose a manageable scope. The supplied starter is intentionally small; for your own codebase, focus on a manageable set of public functions.

Step 1: Audit (10 min)

Install the project dependencies plus pyright, pytest, and pytest-cov in a virtual environment. Run pyright src/ (or your project's source path). If tests exist, run pytest --cov=src --cov-report=term; if no tests or coverage data exist, record “no baseline tests; coverage not measured.” Record the actual baseline rather than assuming a particular error count.

Step 2: Plan (10 min)

Discuss with AI:

"I have this untyped codebase [paste/share files]. I want to add type hints AND tests. What order should I work in? Start with public APIs or internal functions?"

AI suggests starting with public APIs (most-used; types matter most). Then tests for each.

Order:

  1. Top-level functions (public API).
  2. Add types + minimal docstrings.
  3. Add tests for each.
  4. Move to internal functions.
  5. Iterate.

Step 3: Type the public API (15 min)

Pick one public function. Look at it; understand what it does.

"Add type hints to this function. Don't change behavior. If types are unclear from code, ask me."

AI adds:

def fetch_user(user_id: int) -> dict[str, Any] | None:
    ...

If you don't know what it returns, AI asks. You answer. AI updates types.

Step 4: Test the function (15 min)

"Generate pytest tests for fetch_user. Based on the type signature and the implementation, test happy path, missing user (returns None), and any other behavior you observe."

AI generates tests. Run them; they pass against current code.

Now you have:

  • A typed signature (contract).
  • Tests verifying behavior.
  • Documentation (implicit in types + tests).

Step 5: Repeat for each public function

Same pattern. Type. Test. Verify.

Step 6: Identify the gaps

After typing public APIs, you'll notice:

  • Internal functions still untyped.
  • Some return types are Any (didn't know better).
  • Edge cases not tested.

Iterate:

  • Refine Any to specific types.
  • Add tests for edge cases AI mentioned.
  • Add types to internal functions.

Step 7: Type-check + test

pyright src/  # errors should drop
pytest --cov  # coverage should climb

Target after this build: pyright basic mode passes; coverage > 70%.

For strict mode + 90% coverage: takes more iteration.

Step 8: Document findings

# Retrofit Reflection

## Codebase stats
Before:
- Type errors (pyright basic): 247
- Test coverage: 12%

After:
- Type errors: 0
- Test coverage: 73%

## What AI helped with most
- Generating tests from function signatures.
- Inferring types from usage patterns.

## What I had to do manually
- Confirming ambiguous return types.
- Edge cases AI didn't think of.

## Surprises
- Found 2 actual bugs while adding types (functions returned wrong types).
- Found 3 untested branches.

Deliverables

  • Codebase with type hints on public APIs.
  • Tests covering happy path + key edge cases.
  • Pyright passes basic mode.
  • Coverage > 70%.
  • Reflection document.

What you'll learn

  • Types reveal contracts; tests verify them; they reinforce.
  • AI accelerates both — read the function; types + tests follow.
  • Untyped code often has hidden bugs that types reveal.
  • AI-suggested tests miss edges — add them yourself.
  • Incremental approach works (don't try perfect; aim for better).

Stretch

  • Pyright strict mode passes.
  • Coverage > 90% on critical modules.
  • Add tests for any bugs found during the retrofit.

📊 100-Point Evaluation Rubric

Evaluation DimensionWeightTarget Standard (Pass ≥ 80%)
1. Type Annotation Completeness25 ptsPublic APIs and core internal functions annotated with modern Python type hints (`int
2. Test Suite & Coverage25 ptsPytest test suite reaches > 70% line coverage, covering normal execution flows, null checks, and error responses.
3. Edge Cases & Bug Discovery25 ptsIdentified and tested meaningful edge cases or contracts; add regression tests for bugs if found. If none are found, document the checks performed.
4. Documentation & Retrofit Reflection25 ptsDocumented before/after audit metrics (pyright error count, coverage %) and analysis of AI prompt patterns for test/type generation in RETROFIT.md.

📤 Submission Instructions & Guidelines

  1. Deliverable Formats:
    • Option A (Recommended): Upload a single .zip archive containing your typed Python files, pytest test suite, and RETROFIT.md.
    • Option B: Upload your RETROFIT.md and paste your GitHub repository link in the Submission Notes box below.
  2. Pre-Submission Verification:
    • Run pyright src/ and ensure 0 type errors in basic mode.
    • Run pytest --cov=src and verify coverage exceeds 70%.
  3. Submit Your Work:
    • Use the file dropzone below to upload your .zip file.
    • Click Submit Assignment to complete this build.
Checking your access…