The Problem: Long Time Test Execution in CI/CD
Test suites have grown in size and complexity. What used to be a quick validation step has become a bottleneck that can significantly impact development velocity. CI/CD pipelines that take a long time to complete are delaying feedback and deployments, creating frustration for developers and slowing down the entire development process. Here is the reality many teams face:
- Large test suites with hundreds or thousands of tests
- Long-running integration tests that interact with databases, APIs, or external services
- CI/CD pipelines that take 20+ minutes to complete, delaying feedback and deployments
The Solution: Distributed Test Execution
The solution is to distribute test execution across multiple machines, running different test groups in parallel. This approach can reduce test execution time by 70-90%.
Results may vary
Key Benefits
- Significantly faster feedback loops and shorter deployment cycles - From 20 minutes to 4 minutes in our example
- Scalable - Easy to adjust number of runners based on test suite size and available resources
Implementation: The parTestGroup SBT Task
The core of this solution is a custom SBT task that divides tests into groups and allows running specific groups independently.
The SBT Task Implementation
Usage Examples
Example 1: Split tests into 3 groups and run the first group
Example 2: Split tests into 3 groups and run the third group
Complete CI/CD Implementation
Multi-Machine Workflow Structure
Performance Results
In our demonstration project with 70 test suites - each containing a simulated long-running test - the results are as follows:
Single-machine execution takes ~20 minutes
Parallel execution completes in ~4 minutes
Screenshot: Single-machine execution

Screenshot: Parallel execution

Critical Optimization: Pre-compilation and Build Artifact Sharing
The Problem with Naive Distribution
When each machine independently compiles the entire codebase, you're essentially paying for the same compilation work multiple times across different runners. This not only increases your total billing time but also wastes valuable computing resources. Furthermore, each machine must download and resolve all dependencies from scratch, creating redundant network traffic and extending setup times. The result is a distributed system that performs more work than necessary, negating the potential benefits of parallelization.
Compile Once, Distribute Everywhere
Our implementation uses a two-phase approach:
The compilation job:
- Restores cache
- Runs incremental (re)compilation
- Uploads build artifacts using GitHub Actions cache
Each test group job:
- Ensures compilation completes first
- Downloads cached compilation results
- Runs only tests - no compilation overhead
Understanding Amdahl's Law and Test Optimization Limits
What is Amdahl's Law?
Amdahl's Law is a fundamental principle in parallel computing that describes the theoretical speedup achievable when parallelizing a computation. The law states that the overall speedup is limited by the portion of the computation that cannot be parallelized.
Key Formula:
Where:
Pis the proportion of the computation that can be parallelizedNis the number of processors/machines(1 - P)is the sequential portion that cannot be parallelized
Implications for Test Distribution
When distributing test execution, Amdahl's Law reveals some limitations:
Example Scenario:
- You have a test suite with 100 tests
- 99 tests take 1 minute each (parallelizable)
- 1 test takes 15 minutes (sequential bottleneck)
Sequential Execution: 99 + 15 = 114 minutes
Parallel Execution (10 machines):
- 99 tests distributed across 10 machines: ~10 minutes
- 1 long test still takes 15 minutes (cannot be parallelized)
- Total time: 15 minutes (limited by the 15-minute test)
The Reality Check: Even with infinite parallelization, you cannot achieve execution time below 15 minutes because that's the duration of your longest sequential test.
If you have a problem with a long-running individual test, you should look for optimization opportunities in:
- The source code of the test itself - Are there inefficient test setup/teardown operations?
- The code being tested - Is the application code performing unnecessary operations, making slow API calls, or using inefficient algorithms?
- Consider test isolation - can long tests be broken into smaller units?
Beyond SBT: Considering Bazel for Advanced Optimization
This article focuses on SBT and provides a simple, practical solution that should be applicable to every project using SBT. While our approach delivers significant performance improvements through distributed test execution, some projects may require further optimizations — particularly for build parallelization and advanced incremental compilation. In such cases, tools like Bazel can provide additional benefits.
If you're interested in exploring how Bazel can help overcome monorepo challenges and achieve even more efficient builds, we recommend reading our article: Overcoming monorepo challenges with Bazel for your projects The article covers real-world examples of how Bazel transforms large codebases into manageable, efficient ecosystems.
Conclusion
By implementing the parTestGroup task and leveraging test distribution, teams can achieve significant performance improvements — reducing test execution time from 20 minutes to just 4 minutes in our example, representing a 80% reduction in feedback time.
Whether you're dealing with hundreds of unit tests or complex integration test suites, this pattern can help you move faster and more efficiently, ultimately improving your team's development velocity and deployment confidence.
Links and Resources
Full Working Example: You can find a complete, working implementation of this approach on GitHub




