{"slug": "why-we-rewrote-our-python-cli-in-go-and-what-we-gained", "title": "Why We Rewrote Our Python CLI in Go (and What We Gained)", "summary": "The article explains why the developers of TestSmith rewrote their CLI tool from Python to Go, citing distribution friction as the primary motivation. By moving to Go, they achieved a single static binary with no runtime dependencies, simplified cross-platform builds, and leveraged native concurrency for parallel test generation. The rewrite also improved the command interface with proper subcommands and introduced a plugin architecture for easier language support.", "body_md": "TestSmith v1 was a Python CLI. It worked. Users could pip install testsmith\n, point it at a source file, and get a test scaffold back. But every team that tried to wire it into CI hit the same wall: Python environments.\nThe problem wasn't Python itself — it was distribution. A static analysis tool that requires a matching Python version, a virtual environment, and a pinned dependency tree is a hard sell for a step that runs on every push. We were shipping a tool, not a library. Tools should be frictionless.\nWe rewrote TestSmith v2 in Go. The goal was a single static binary with no runtime dependencies — something you could drop into any CI runner, any Docker image, any developer's PATH, and it would just work.\nGo was the right choice for three reasons:\nSingle binary. go build\nproduces one self-contained executable. No pip, no venv, no requirements.txt\n. Users download a binary or brew install\nit and they're done.\nCross-platform with one build. The v1 CI matrix was a headache — different Python versions across Ubuntu, macOS, and Windows, with slightly different behavior on each. Go's cross-compilation gave us linux/amd64\n, darwin/amd64\n, darwin/arm64\n, and windows/amd64\nfrom a single build step.\nNative concurrency. Test generation is embarrassingly parallel — each file is independent. Go's goroutines and channels made the fan-out generation and the debounced file watcher straightforward to implement without pulling in async libraries.\nThe command surface was cleaned up in the same pass. v1 used flags for everything:\ntestsmith <file> # generate\ntestsmith --all # generate all\ntestsmith --graph # dependency graph\ntestsmith --prune # prune stale fixtures\ntestsmith --watch # watch mode\nv2 uses proper subcommands:\ntestsmith generate <file>\ntestsmith generate --all\ntestsmith graph\ntestsmith prune\ntestsmith watch\nThis made shell completion, help text, and per-command flags much cleaner. Cobra's built-in completion generator gives us bash, zsh, fish, and PowerShell completion for free.\nv1 was monolithic Python — one codebase with hardcoded branches for each language it supported. Adding a new language meant editing multiple core files.\nv2 uses a LanguageDriver\ninterface. Each language (Go, Python, TypeScript, Java, C#) is a separate package that implements the interface. The core generation pipeline never knows which language it's dealing with — it just calls through the interface:\ntype LanguageDriver interface {\nDetectProject(dir string) (*ProjectContext, error)\nAnalyzeFile(path string, ctx *ProjectContext) (*SourceAnalysis, error)\nDeriveTestPath(sourcePath string, ctx *ProjectContext) (string, error)\nGenerateTestFile(analysis *SourceAnalysis, opts GenerateOpts) (*GeneratedFile, error)\n// ... and more\n}\nAdding a new language is now a matter of creating a new package and registering it — no changes to the pipeline.\nThe v1 Python package didn't disappear. It lives in archive/v1/\nand continues to receive bug fixes during the transition period. Teams already using v1 in production don't need to migrate immediately. The v2 binary is a clean break for new users; v1 stays stable for existing ones.\nYes, unambiguously. The CI story went from \"install Python, set up venv, pin deps\" to \"download one binary.\" The Windows test matrix went from flaky (Python path issues) to clean. And the plugin architecture means we can add a Ruby or Rust driver without touching the core generation logic.\nThe rewrite took longer than a feature would have. But distribution friction is a silent project killer — nobody files a bug for \"this was annoying to set up,\" they just stop using the tool.\nTestSmith is an open-source CLI for generating test scaffolds across Go, Python, TypeScript, Java, and C#. The source is at github.com/orieken/testsmith.", "url": "https://wpnews.pro/news/why-we-rewrote-our-python-cli-in-go-and-what-we-gained", "canonical_source": "https://dev.to/orieken/why-we-rewrote-our-python-cli-in-go-and-what-we-gained-21bg", "published_at": "2026-05-23 16:23:38+00:00", "updated_at": "2026-05-23 16:31:04.551090+00:00", "lang": "en", "topics": ["developer-tools", "open-source", "enterprise-software"], "entities": ["TestSmith", "Python", "Go", "CI", "Docker"], "alternates": {"html": "https://wpnews.pro/news/why-we-rewrote-our-python-cli-in-go-and-what-we-gained", "markdown": "https://wpnews.pro/news/why-we-rewrote-our-python-cli-in-go-and-what-we-gained.md", "text": "https://wpnews.pro/news/why-we-rewrote-our-python-cli-in-go-and-what-we-gained.txt", "jsonld": "https://wpnews.pro/news/why-we-rewrote-our-python-cli-in-go-and-what-we-gained.jsonld"}}