1.2.6 Test Suites
test_suite gives an existing set of tests one stable Bazel label. That is useful when a team wants to say "run the smoke set" or "run the presubmit set" without repeating a long target list on every command line. A suite is a selection layer, not a new runner: Bazel expands it into underlying test targets and executes those targets through their existing runners1,2.
The knobs from 1.2.3 Test Tags, 1.2.5 Test Sharding, and 1.2.7 Test Output & Debugging still apply at the member-test level. A suite changes how you name and collect tests, not how one test target behaves when Bazel finally runs it.
Prefer a named set over an ad-hoc pattern
0.2.3 Target Patterns already gave you broad selectors such as //... and //pkg/.... A test_suite is different: it is a real target in the build graph, declared once in BUILD.bazel, reviewed like any other target, and reused by name.
test_suite(
name = "ci_tests",
tests = [
":smoke_tests",
"//integration:db_test",
],
)
That matters because suites can point at individual tests, nest other suites, and expand across package boundaries1. In the test-suites verification snippet, the root package defines only suite targets:
$ bazel query "kind(test, //:*)"
//:ci_tests
//:smoke_tests
But asking Bazel for the runnable tests behind the top-level suite expands it into the leaf tests, including tests from other packages:
$ bazel query "tests(//:ci_tests)"
//integration:api_test
//integration:db_test
//unit:fast_test
The suite label is the handle humans type, but the work still happens on the concrete member test targets Bazel expands underneath it1,3.
Compare: //slow:e2e_test has tags = ["manual"]. bazel test //... skips it, but //:ci_tests explicitly lists it in a test_suite. Which command runs the test, and what changes if the suite itself is tagged manual?
Reveal
bazel test //:ci_tests runs it because the suite names the target explicitly. The manual tag removes tests from wildcard expansion while leaving named tests reachable. That is the same idea as 1.2.3 Test Tags: manual changes discovery while the target remains runnable.
If the suite target itself is tagged manual, wildcard patterns skip the suite too. A direct bazel test //:ci_tests still means "run this named selection." That distinction is why suites are useful for CI: they can intentionally include tests that broad patterns avoid.
Suites Group Targets While Sharding Splits One
This is where test_suite and 1.2.5 Test Sharding diverge. Sharding takes one test target and runs it in multiple shard invocations. A suite does the opposite kind of composition: it gives several already-separate test targets one shared label.
So if the problem is "this single test target is too large", reach for sharding. If the problem is "people need one memorable name for these three test targets", reach for a suite.
Suites fit naturally into bazel test
The bazel test command accepts both test-rule targets and test_suite targets as test inputs2. That makes suite labels a good fit for human-facing commands such as:
bazel test //:smoke_tests
bazel test //:ci_tests --build_tests_only
--build_tests_only is a useful companion here because Bazel then limits top-level build work to the unfiltered concrete test targets and test_suite targets that survived the active size, timeout, tag, and language filters. Their production dependencies still build as needed2. Suites combine with the filters from 1.2.2 Test Sizes & Timeouts and 1.2.3 Test Tags rather than replacing them.
Use test_suite when the set is stable and human-chosen: smoke_tests, presubmit_tests, team_a_regression. Once the set needs to be derived from a diff, ownership graph, or monorepo-wide change analysis, the center of gravity moves toward query-driven selection in 5.2.7 Query in CI Pipelines and 6.5.1 Affected-Target Service Contract instead of maintaining ever-larger hand-written collector labels4,5.
One advanced edge case: --expand_test_suites
When a test_suite is named on the command line, Bazel normally expands it much like a wildcard test pattern. In platform-aware builds, that means incompatible member tests are skipped under --expand_test_suites. Using --noexpand_test_suites changes the behavior: the suite target itself remains incompatible instead6.
Use a test_suite when people need one stable label for several existing tests. It groups and names real test targets. It does not introduce a separate execution model. Execution behavior stays on the tests inside it.
Check your understanding · 2 questions
1.True or false: test_suite behavior.
Choose True or False for each sentence
2.Which query expands a suite into the concrete tests it contains?
Select one answer
Footnotes
-
The Bazel Query Reference —
tests()expandstest_suitetargets into individual tests, applies tag/size filtering, and may return tests from other packages referenced directly or indirectly by suites ↩1 ↩2 ↩3 -
Commands and Options —
bazel testaccepts test-rule andtest_suitetargets, and--build_tests_onlykeeps only the unfiltered test targets needed for execution ↩1 ↩2 ↩3 -
Query guide — practical
tests(//foo:smoke_tests)examples, pluskind(...)andattr(...)queries for inspecting suite expansion ↩ -
Designing a language Agnostic CI using Bazel queries — uses
tests()andkind()to derive the CI test stage from the Bazel graph ↩ -
How Bazel built its CI system on top of Buildkite — expands wildcard test requests into concrete target lists with
bazel query, then shards that list across CI jobs ↩ -
Platforms —
--expand_test_suitesskips incompatible member tests, while--noexpand_test_suitesleaves the suite target itself incompatible ↩