Speeding Up Android Unit Tests with Test Sharding on GitLab CI
Article Summary
Fadel Trivandi Dipantara from Bukalapak faced a brutal reality: 3,000+ test classes taking 2.5 hours to run. His team couldn't ship fast enough, and traditional solutions kept failing.
Bukalapak's Android codebase spans 18,000+ Kotlin classes across 200+ modules. The engineering team tried multiple approaches to speed up their CI pipeline: testing only changed modules (broke dependent code), testing affected modules (caused timeouts), and parallelizing by module type (uneven distribution). Each solution created new problems.
Key Takeaways
- Test sharding by module count cut feature changes by 30%, lib changes by 50%
- Dynamic node allocation based on test count prevents wasted runner resources
- Module level sharding preserves accurate Jacoco coverage reporting
- Ruby scripts generate affected modules using ripgrep for fast dependency tracking
- GitLab dynamic child pipelines decide runner count based on actual test volume
By distributing modules across nodes based on test count rather than module type, Bukalapak achieved 30 to 50% faster CI runs while maintaining accurate code coverage.
About This Article
Bukalapak's test pipeline had uneven job distribution. Fast unit tests finished in 30 minutes while UI tests took 1 hour, which meant the entire pipeline had to wait. On top of that, empty test jobs still used 5 minutes of runner time just for global setup scripts.
Bukalapak implemented test sharding with module-level distribution. Ruby scripts count tests per module and assign them evenly across nodes, which prevents any single node from becoming a bottleneck. The approach also preserves Jacoco coverage accuracy.
The implementation improved speed by 30% for feature module changes and 50% for library module changes. Instead of using a fixed number of nodes, the system now allocates only the runners it actually needs based on test volume.