No one sets out to write a slow test, and yet it happens. A test matcher that burns through a full Capybara timeout. An innocent-looking factory that cascades into dozens of records. One test at a time, your suite gets slower. By the time you notice, CI takes hours and nobody wants to touch it.
Having tests is not enough. We need fast tests. If tests are slow, developers won’t run them locally. When only CI runs the full suite, feedback loops stretch from seconds to hours.
A linter for test performance
Without visibility, performance will always be an afterthought. We have no shortage of performance monitoring tools for production, but very few for tests.
I built Test Budget to fill that gap. It reads your test run timings and reports when they exceed a configured budget. It doesn’t change how your tests run. It just tells you when they’re too slow (before it gets worse).
You can set budgets at two levels:
- Suite-level: your entire test suite should run in under 10 minutes, for example.
- Per-test-type: model tests should run in under 1.5 seconds, system tests under 10 seconds, and so on.
Estimating and enforcing your first budget
You don’t have to pick numbers from thin air. Test Budget can generate a starter config from your existing test results. First, run your tests with RSpec’s JSON formatter to produce a timings file:
bundle exec rspec --format json --out tmp/test_timings.json
Then generate a starter config from those results:
bundle exec test_budget init tmp/test_timings.json
It derives budgets from your actual data:
- The total suite budget is set 10% above your current total, giving you headroom to keep adding tests without immediately blowing the budget.
- The per-test-type budgets are based on the 99th percentile. Your slowest 1% of tests are the ones you need to fix. The rest already pass.
These are starting points. The generated config is a plain YAML file you can tune to match your team’s standards.
Then run the audit after your tests:
bundle exec test_budget audit
Test budget: 1 violation(s) found
1) spec/system/signup_spec.rb -- creates account (11.20s) exceeds system limit (6.00s)
The audit tells you exactly which tests to look at. From there, you can make them faster. The README has a list of strategies to use in these cases.
Allowlisting is temporary by design
Like RuboCop’s generated todo, Test Budget has an allowlist. Instead of bumping the budget to accommodate a slow test, you allowlist it with a reason. This keeps the budget honest for everything else and makes it clear which tests need attention.
allowlist:
- test_case: "spec/services/invoice_pdf_spec.rb -- generates PDF with line items"
reason: "PDF generation is inherently slow, tracked in #1234"
expires_on: "2026-04-01"
Allowlist entries have an expiration date. When they expire, the test starts failing the audit again. This creates friction on purpose. It keeps entries from quietly overstaying their welcome and encourages you to fix the underlying issue instead of sweeping it under the rug.
You can’t afford slow tests
The right budget depends on your team and project, but I’d encourage you to be aggressive. Way too many teams are comfortable with 10+ minute test suites. That is an awful lot of time to wait for feedback. Fast tests are one of the best things you can do for developer productivity.
The goal isn’t zero violations on day one. It’s to stop overspending and make test performance visible. Start with what you have, then work on improving it.
Give Test Budget a try:
bundle add test_budget
bundle exec rspec --format json --out tmp/test_timings.json
bundle exec test_budget init tmp/test_timings.json
bundle exec test_budget audit