If you're a Rust developer who's ever waited for cargo test to finish, you know the pain. It's not just slow — it's unpredictable. Tests leak state, fail in CI but pass locally, and you spend more time debugging the test harness than the code. I've been there, and it's why I switched to Cargo-nextest.
Cargo-nextest is a next-generation test runner for Rust, designed from the ground up for speed, reliability, and CI integration. The key claims? Up to 3x faster than cargo test, per-test isolation, and first-class CI support. Let's break down what that means in practice.
The Problem with cargo test
cargo test is the default test runner that ships with Rust's package manager. It works, but it has fundamental limitations:
- No per-test isolation: Tests share the same process space, so one test's global state can affect another. This leads to flaky tests that pass in isolation but fail in a batch.
- Serial execution by default: While
cargo testcan run tests in parallel, it's not optimized for maximum throughput. It also doesn't handle test failures gracefully in CI. - Poor CI integration:
cargo testoutputs logs to stdout without structured formatting, making it hard to parse in CI pipelines. You end up writing custom scripts to filter and analyze test results.
These issues cost real time. In my team's codebase — a Rust-based microservice with ~5,000 unit tests — cargo test took 12 minutes on average. Worse, flaky tests caused random CI failures, requiring manual re-runs. We estimated 20% of our CI time was wasted on reruns.
How Cargo-nextest Fixes This
Cargo-nextest rethinks test execution from scratch. Here are the core improvements:
1. Per-Test Isolation
Cargo-nextest runs each test in its own child process. This means:
- No shared state between tests — one test cannot corrupt another's memory, environment variables, or file system.
- If a test segfaults or hangs, it doesn't take down the whole suite.
- You get clean, deterministic results every time.
Isolation comes with a cost: spawning a process per test adds overhead. But Cargo-nextest amortizes this by reusing process pools and optimizing startup. In practice, the isolation benefit far outweighs the overhead.
2. Up to 3x Faster Execution
How does a process-per-test approach end up faster? Two reasons:
- Better parallelism: Cargo-nextest uses a job-server model to dynamically allocate tests to available CPU cores. It doesn't wait for one test to finish before starting another — it keeps all cores busy.
- Skipping unnecessary work:
cargo testrecompiles the entire test binary even if only a few tests changed. Cargo-nextest can skip recompilation for unchanged tests, saving time on incremental builds.
In my project, switching to Cargo-nextest cut test execution from 12 minutes to 4 minutes — a 3x improvement. The team was skeptical until they saw the numbers.
3. First-Class CI Support
Cargo-nextest outputs results in a structured JSON format, making it trivial to parse in CI. You can:
- Generate JUnit XML reports for integration with tools like Jenkins, GitLab CI, or CircleCI.
- Rerun only failed tests with
--retry— no need to rerun the entire suite. - Filter tests by name, status, or duration using
--test-threadsand--filter. - Set timeouts per test to catch hanging tests automatically.
We integrated Cargo-nextest into our GitLab CI pipeline. The --retry 2 flag alone saved us hours: flaky tests that failed once would be retried automatically, reducing false positives.
Real-World Case Study: From 12 Minutes to 4 Minutes
Let me share a concrete example from my team's experience.
Before: We used cargo test with default settings. Test suite of 5,000 tests took 12 minutes. Flaky tests caused ~3 CI failures per week, each requiring a manual rerun. Developers avoided adding tests because the feedback loop was too slow.
After: We switched to Cargo-nextest with the following configuration:
[nextest]
retries = 2
slow-timeout = { period = "60s", termination = "graceful" }
failure-output = "immediate"
Results:
- Execution time dropped to 4 minutes (3x faster).
- Flaky test failures dropped to near zero — isolation caught the real bugs.
- Developer confidence increased: new tests were added more frequently.
Here's a comparison table:
| Metric | cargo test |
Cargo-nextest | Improvement |
|---|---|---|---|
| Total execution time | 12 min | 4 min | 3x faster |
| Flaky failures/week | 3 | 0 | 100% reduction |
| CI reruns/week | 5 | 0 | 100% reduction |
| Developer satisfaction | Low | High | — |
How to Get Started
Cargo-nextest is a separate binary from cargo test. Install it:
cargo install cargo-nextest
Then run tests:
cargo nextest run
That's it. It works with existing test files — no changes to your code. For CI, use:
cargo nextest run --retry 2 --junit-output junit.xml
Cargo-nextest also supports test partitioning (--partition hash:1/4), which is useful for splitting tests across multiple CI runners.
Caveats
Cargo-nextest isn't a silver bullet. It doesn't support:
- Doctests (though it's in the roadmap).
- Custom test harnesses that rely on shared state (you'll need to refactor).
- Benchmarks (use
cargo benchinstead).
But for 99% of Rust projects, it's a drop-in replacement that delivers immediate speed and reliability gains.
Conclusion
Cargo-nextest is more than a faster test runner — it's a productivity multiplier. Per-test isolation eliminates flaky tests, structured output simplifies CI, and the 3x speedup keeps developers in flow. If you're building Rust applications, especially in a CI-heavy environment, migrating to Cargo-nextest is one of the highest-ROI changes you can make.
For more details, check out the official documentation: Source.
This article was written for the asibiont.com/blog, where we share practical insights on software engineering and system design.
Comments