惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Apple Machine Learning Research
Apple Machine Learning Research
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
V
V2EX
量子位
Last Week in AI
Last Week in AI
Jina AI
Jina AI
博客园 - 【当耐特】
爱范儿
爱范儿
宝玉的分享
宝玉的分享
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hugging Face - Blog
Hugging Face - Blog
博客园 - 三生石上(FineUI控件)
有赞技术团队
有赞技术团队
小众软件
小众软件
IT之家
IT之家
博客园_首页
博客园 - 聂微东
S
SegmentFault 最新的问题
阮一峰的网络日志
阮一峰的网络日志
博客园 - 叶小钗

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Why We Built TestSmith: The Test Coverage Problem Nobody ...
Oscar Rieken · 2026-05-24 · via DEV Community

Every team I've worked on has had the same conversation at some point. Someone opens the coverage report, sees a sea of red, and asks: "How do we get this up?" The answer is always some version of "we need to write more tests," followed by a long silence, because everyone knows what that actually means — hours of boilerplate, test file setup, mock wiring, and fixture scaffolding before you've written a single meaningful assertion.

That's the problem TestSmith was built to solve.

The Real Bottleneck Isn't Willingness

Developers generally want to write tests. The resistance isn't laziness — it's the setup cost. For every new module you want to test, you have to:

  • Create the test file in the right location with the right naming convention
  • Import the module under test
  • Import the test framework and any mock libraries
  • Set up fixtures for external dependencies
  • Write the boilerplate class or function structure that the framework expects
  • Then, finally, write the actual test logic

For a well-understood module with clear inputs and outputs, steps 1 through 5 can easily take longer than step 6. You're doing janitorial work before you can do the meaningful work. And if you're adding coverage to a large existing codebase — the kind of coverage catch-up project every team eventually faces — you're doing that setup dozens or hundreds of times.

Why Python First

We wrote the first version of TestSmith in Python for the most straightforward of reasons: our immediate problem was a Python codebase.

But Python also happened to be a good fit for the tool itself. Python's AST module is excellent — ast.parse() gives you a full parse tree in a few lines, and walking it to extract class names, function signatures, and import statements is straightforward. For a tool that needs to understand source code structure without actually running it, static AST analysis is exactly right, and Python's standard library makes it easy.

import ast

tree = ast.parse(source_code)
for node in ast.walk(tree):
    if isinstance(node, ast.FunctionDef):
        if not node.name.startswith('_'):  # skip private
            public_functions.append(node.name)

Enter fullscreen mode Exit fullscreen mode

The other reason was speed of iteration. We were solving our own problem — we needed the tool to work on Python projects, and we were Python developers. Building it in Python meant we could use it on itself from day one, which is a useful forcing function for catching rough edges.

What the Tool Actually Does

The core idea is simple: given a source file, generate the test scaffold that you'd write by hand.

For a Python service like this:

# src/services/payment.py

class PaymentService:
    def __init__(self, stripe_client, db):
        self.stripe = stripe_client
        self.db = db

    def process_payment(self, order_id: str, amount: int) -> dict:
        ...

    def refund(self, payment_id: str) -> bool:
        ...

Enter fullscreen mode Exit fullscreen mode

TestSmith generates:

# tests/services/test_payment.py

import pytest
from unittest.mock import MagicMock, patch
from src.services.payment import PaymentService


@pytest.fixture
def stripe_client():
    return MagicMock()


@pytest.fixture
def db():
    return MagicMock()


@pytest.fixture
def payment_service(stripe_client, db):
    return PaymentService(stripe_client=stripe_client, db=db)


class TestPaymentService:
    def test_process_payment(self, payment_service):
        # TODO: implement
        pass

    def test_refund(self, payment_service):
        # TODO: implement
        pass

Enter fullscreen mode Exit fullscreen mode

It's not a complete test. It's the scaffold — the file is in the right place, the imports are correct, the fixtures for the constructor dependencies are wired up, and the test methods exist. The developer fills in the assertion logic. The janitorial work is already done.

The tool also handles things that are easy to get wrong: where test files should live relative to source files (which varies by framework and project convention), how to name fixtures based on constructor parameters, which mock library to use, and how to structure the test class if the source is class-based vs. the test functions if it's function-based.

The Gap Analysis Problem

Coverage reports tell you what's untested, but they don't prioritise it. A file with three simple utility functions and a file with a complex payment processing pipeline both show up as "uncovered." Knowing which one to tackle first requires reading the code.

TestSmith added a coverage gap command that went a step further: it computed a coupling score for each untested module based on how many other modules imported it. A module imported by ten others is higher priority than one imported by none — because a bug in the heavily-imported module has a wider blast radius.

$ testsmith gaps

Coverage gaps (by coupling score):

  src/services/payment.py        coupling: 8   functions: 5   ← fix this first
  src/utils/currency.py          coupling: 6   functions: 3
  src/models/order.py            coupling: 4   functions: 7
  src/scripts/backfill.py        coupling: 0   functions: 12

Enter fullscreen mode Exit fullscreen mode

This gave teams a principled answer to "where do we start?" rather than requiring someone to manually audit the codebase.

What v1 Didn't Do Well

The tool worked. Teams used it and got value from it. But two things became clear over time.

Distribution was painful. pip install testsmith sounds simple, but in practice it meant managing Python versions, virtual environments, and dependency conflicts — especially in CI. A testing tool that requires its own setup to work in CI is fighting against itself.

One language wasn't enough. Once word got around that the tool existed, the first question from every team was "does it work for TypeScript?" or "can it do Java?" The Python-only design wasn't a deliberate choice — it was an artifact of solving our own immediate problem. But the architecture didn't make adding languages easy. Every language-specific piece of logic was woven through the core code rather than isolated.

Those two problems drove the v2 rewrite in Go: a single static binary that drops into any environment, and a plugin architecture where each language is an isolated driver.

But that's the next post.

TestSmith is open source at github.com/orieken/testsmith. The v1 Python package is archived at archive/v1/ for reference.