“We will add tests later” is the most expensive sentence in software development. It sounds reasonable in the moment – the deadline is tight, the feature is urgent, and writing tests feels like overhead when you already know the code works because you just manually tested it. But “later” never comes, and the cost of skipping tests compounds silently until it erupts as a production outage, a two-week bug hunt, or a team that is afraid to touch its own codebase.
This is not a moral argument about engineering best practices. It is a financial argument. Automated testing has a measurable return on investment, and skipping it has measurable costs that increase exponentially over time. Understanding these costs in concrete terms helps engineering leaders and business stakeholders make informed decisions about where to invest their development budget.
The Compound Interest of Technical Debt
Every untested line of code is a form of debt that accrues interest. The principal is the time you saved by not writing the test. The interest is every minute of additional time spent on that code in the future because the test does not exist.
Consider a simple example. A developer writes a function that calculates shipping costs based on weight, dimensions, destination zone, and service level. Without tests, this takes 2 hours. With a comprehensive test suite covering standard cases, edge cases (zero weight, oversized packages, international zones), and boundary conditions, it takes 3.5 hours. The “savings” from skipping tests: 1.5 hours.
Now track the interest payments:
Week 2: Another developer modifies the function to add a new service level. Without tests, they spend 45 minutes manually testing that existing calculations still work. With tests, they run the suite in 10 seconds and know immediately.
Month 3: A bug is reported – shipping costs are wrong for packages over 70 pounds. The developer investigating has never seen this code. Without tests, they spend 4 hours understanding the logic, reproducing the bug, fixing it, and manually testing all scenarios. With tests, they write a failing test that reproduces the bug (20 minutes), fix the code until the test passes (30 minutes), and have confidence that nothing else broke.
Month 8: The company switches shipping carriers and the rate structure changes. Without tests, refactoring the calculation function is terrifying – the developer does not know which inputs produce which outputs and cannot verify that the refactored version matches the old behavior for all cases. They spend 16 hours on what should be a 3-hour change. With tests, they update the expected values, refactor the code, and run the suite. Done in 3 hours.
Year 2: The original developer has left the company. No one fully understands the shipping calculation code. Without tests, it becomes a “do not touch” module that the team routes around. With tests, the test suite serves as executable documentation that shows exactly what the code is supposed to do.
Total interest paid on skipping that 1.5 hours of test writing: 20+ hours in the first year alone, plus ongoing fear-driven development overhead for the life of the codebase. The ROI of that 1.5 hour test investment is roughly 13x in the first year.
Quantifying Bug Costs by Detection Stage
The cost of a bug increases by an order of magnitude at each stage of the development lifecycle. This is not theoretical – it has been consistently measured across industries.
During development (caught by a unit test): Cost is near zero. The developer gets immediate feedback, fixes the bug before committing, and moves on. Total time: 5 to 30 minutes.
During code review (caught by a reviewer): Cost is low but real. The reviewer spots the issue, writes a comment, the developer context-switches back to the code, understands the feedback, makes the fix, and re-submits. Total time for both people: 1 to 3 hours.
During QA/staging (caught by manual testing): Moderate cost. A QA engineer encounters the bug, files a ticket with reproduction steps, a developer is assigned, context-switches to the code (likely days after writing it), reproduces the bug, fixes it, re-deploys to staging, and QA re-tests. Total time across all parties: 4 to 12 hours.
In production (caught by a user): High cost. A user encounters the bug, contacts support, a support agent triages and escalates, an engineer investigates (potentially requiring log analysis, database queries, and customer communication), develops a fix under pressure, deploys a hotfix, and the team conducts a post-mortem. Total time across all parties: 8 to 40 hours. Plus the intangible cost of damaged user trust.
In production, undetected for weeks (caught during an audit or by a different bug’s investigation): Very high cost. The bug has been silently corrupting data, miscalculating values, or creating inconsistencies that now require retroactive correction. We worked with a company whose untested invoicing module had been applying a rounding error to every invoice for four months. The financial reconciliation took three weeks and the customer communication took another two.
Automated testing is essentially a mechanism for catching bugs at the cheapest possible stage. A unit test suite that runs in 30 seconds on every commit catches the majority of defects at the “near zero” cost level, preventing them from ever reaching the expensive stages.
The Velocity Paradox: Why Skipping Tests Slows You Down
The most counterintuitive cost of skipping tests is reduced development velocity. Teams skip tests to ship faster, but within three to six months, they are shipping slower than teams that test.
The mechanism is straightforward: without tests, every code change carries unknown risk. Developers compensate by moving more carefully, spending more time on manual verification, avoiding refactoring (because refactoring untested code is reckless), and building features in isolation rather than modifying shared code (because shared code changes might break things you cannot see).
A study by Microsoft Research found that teams with comprehensive test suites made changes 40 percent faster than teams without tests, even accounting for the time spent writing tests. The difference was almost entirely in reduced debugging time and increased confidence during refactoring.
We see this pattern consistently in our client work. Teams that come to us with untested codebases describe the same symptoms: feature development that used to take two weeks now takes four; deployments that used to happen daily now happen weekly (or less) because each deployment requires a full manual regression pass; senior developers spending 30 to 50 percent of their time debugging rather than building.
The inflection point typically hits at 12 to 18 months after the codebase reaches meaningful complexity (roughly 20,000 to 50,000 lines of code). Before that point, the codebase is small enough that developers can hold it in their heads and manually verify changes. After that point, no one can reason about the full system, and the absence of tests becomes a daily tax on every engineer.
See also: The AI Technology Stack: Models, Frameworks, and Infrastructure Guide
What to Test and What to Skip
Not all tests deliver equal value, and testing everything is neither practical nor necessary. A strategic testing approach maximizes ROI by focusing effort where it matters most.
Always test: business logic. Any calculation, rule evaluation, or decision-making code should have thorough unit tests. This includes pricing calculations, eligibility determinations, permission checks, state machine transitions, and data transformations. These are the functions where bugs have financial or operational consequences, and they are the easiest to test because they are typically pure functions with clear inputs and outputs.
Always test: API contracts. Every API endpoint should have integration tests that verify the request/response contract: correct status codes, response shapes, error handling, authentication enforcement, and authorization rules. These tests catch breaking changes before they reach consumers and serve as living documentation of your API behavior.
Always test: data integrity operations. Any code that creates, updates, or deletes data in your database should have tests verifying that the correct records are affected and that related data remains consistent. Database migration scripts should be tested against realistic data volumes.
Test strategically: UI interactions. End-to-end tests for critical user paths (signup, checkout, core workflows) are valuable but expensive to write and maintain. Cover the five to ten most important user journeys with end-to-end tests and rely on unit tests for component-level behavior.
Skip or defer: trivial glue code. Simple CRUD endpoints that do nothing but pass data between the API layer and the database, with no business logic, provide low ROI for unit testing. Cover these with integration tests instead.
Skip: implementation details. Do not test that a specific private function was called with specific arguments. Test behavior and outcomes. If the internals of a module change but the external behavior stays the same, your tests should still pass. Tests that break on internal refactoring waste time and discourage improvement.
Building a Testing Culture When You Are Starting From Zero
If your codebase currently has no tests, the path forward is not to stop everything and write tests for the entire codebase. That approach fails because it is boring, takes too long, and does not deliver visible value quickly enough to sustain momentum.
Instead, use the test-on-touch rule: every time a developer modifies a file, they add tests for the code they are changing. Every new feature is developed with tests. Every bug fix includes a regression test that would have caught the bug. Over six months, your test coverage organically grows to cover the most active parts of the codebase – which are, by definition, the parts where tests are most valuable.
Set up continuous integration immediately. A CI pipeline that runs your test suite on every pull request is the enforcement mechanism that makes testing a habit rather than an aspiration. When a PR cannot be merged until tests pass, writing tests becomes a natural part of the development workflow rather than an optional extra.
Start with a fast feedback loop. Unit tests that run in under 30 seconds give developers immediate feedback. If your test suite takes 10 minutes to run, developers will stop running it locally and only discover failures after pushing – which defeats the purpose. Invest in test performance: use in-memory databases for testing, mock external services, and parallelize test execution.
Celebrate coverage milestones. When the team hits 30 percent coverage, then 50 percent, then 70 percent, acknowledge the progress. Testing is an investment whose returns are felt gradually, and visible metrics help the team see the value accumulating.
The mathematics of automated testing are clear: a modest upfront investment in test writing eliminates exponentially growing costs in debugging, manual verification, and fear-driven development. Teams that test ship faster, deploy with confidence, and spend their engineering hours building features rather than fighting fires. The cost of skipping tests is not zero – it is just deferred, with interest.
If your codebase lacks test coverage and it is slowing your team down, talk to The Proper Motion Company. We help teams introduce automated testing strategically, building coverage where it delivers the most value and establishing the CI practices that make testing sustainable.