Skip to content

Testing and CI/CD Best Practices

This guide covers best practices for testing and continuous integration with Bazel.

Key Principles

  1. Test Isolation

    • Each test should be hermetic
    • Tests should not depend on external services
    • Test data should be versioned with code
  2. Test Organization

    • Group related tests
    • Use appropriate test sizes
    • Balance test granularity
  3. CI/CD Integration

    • Leverage remote caching
    • Use build stamping
    • Configure test sharding

Test Structure

1. Test Sizes

Organize tests by size:

python
# Small tests - unit tests, no external deps
go_test(
    name = "util_test",
    size = "small",
    srcs = ["util_test.go"],
    deps = [":util"],
)

# Medium tests - integration tests, local deps
go_test(
    name = "service_test",
    size = "medium",
    srcs = ["service_test.go"],
    deps = [
        ":service",
        "//testing/testdata",
    ],
)

# Large tests - e2e tests, external deps
go_test(
    name = "e2e_test",
    size = "large",
    srcs = ["e2e_test.go"],
    tags = ["e2e"],
    deps = ["//..."],
)

2. Test Data

Manage test data properly:

python
# Package test data
filegroup(
    name = "testdata",
    srcs = glob(["testdata/**"]),
    visibility = ["//visibility:public"],
)

# Use in tests
rust_test(
    name = "parser_test",
    srcs = ["parser_test.rs"],
    data = [":testdata"],
)

3. Test Tags

Use tags for test categorization:

python
# Integration test requiring database
go_test(
    name = "db_test",
    size = "medium",
    srcs = ["db_test.go"],
    tags = [
        "requires-db",
        "integration",
    ],
)

# End-to-end test
go_test(
    name = "e2e_test",
    size = "large",
    srcs = ["e2e_test.go"],
    tags = [
        "e2e",
        "manual",
    ],
)

CI Configuration

1. Basic CI Setup

yaml
# .github/workflows/bazel.yml
name: Bazel Build and Test
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Mount bazel cache
        uses: actions/cache@v3
        with:
          path: "~/.cache/bazel"
          key: bazel-${{ runner.os }}-${{ hashFiles('MODULE.bazel') }}
      
      - name: Run tests
        run: |
          bazel test //... \
            --config=ci \
            --test_output=errors

2. Test Sharding

Configure test sharding for large test suites:

bash
# .bazelrc
build:ci --test_sharding_strategy=explicit
build:ci --test_env=SPLIT_SIZE=10

# Run with sharding
bazel test //... --test_sharding_strategy=explicit --runs_per_test=10

3. Remote Caching

Set up remote caching for CI:

bash
# .bazelrc
build:ci --remote_cache=grpc://remote.cache
build:ci --remote_upload_local_results
build:ci --remote_download_minimal

Common Patterns

1. Test Helpers

Create reusable test infrastructure:

python
# testing/testutil/BUILD.bazel
go_library(
    name = "testutil",
    srcs = ["testutil.go"],
    visibility = ["//visibility:public"],
    deps = [
        "@com_github_stretchr_testify//assert",
        "//testing/fixtures",
    ],
)

2. Mock Generation

Generate mocks consistently:

python
# Generate mocks for interfaces
go_mockgen(
    name = "mock_service",
    interfaces = ["Service"],
    library = "//internal/service",
    package = "mock_service",
)

3. Integration Tests

Handle integration test dependencies:

python
# testing/integration/BUILD.bazel
container_image(
    name = "test_db",
    base = "@postgres_base//image",
    env = {
        "POSTGRES_DB": "test",
        "POSTGRES_PASSWORD": "test",
    },
)

go_test(
    name = "integration_test",
    srcs = ["integration_test.go"],
    deps = [":test_db"],
    tags = ["requires-db"],
)

Best Practices

  1. Test Organization

    • Use appropriate test sizes
    • Group related tests together
    • Maintain test data alongside tests
  2. CI Configuration

    • Use build stamping for versioning
    • Configure test sharding
    • Set up remote caching
  3. Test Dependencies

    • Make tests hermetic
    • Version test data
    • Use mock generation
  4. Performance

    • Configure test sharding
    • Use remote caching
    • Run tests in parallel

Common Issues

1. Flaky Tests

Handle flaky tests:

python
# Mark known flaky tests
go_test(
    name = "flaky_test",
    srcs = ["flaky_test.go"],
    flaky = True,
    # Consider fixing instead of marking flaky
)

2. Resource Constraints

Handle resource-intensive tests:

python
# Mark tests needing resources
go_test(
    name = "memory_test",
    srcs = ["memory_test.go"],
    tags = ["requires-ram-8g"],
)

3. External Dependencies

Handle tests with external dependencies:

python
# Skip tests requiring external services
go_test(
    name = "external_test",
    srcs = ["external_test.go"],
    tags = [
        "external",
        "manual",
    ],
)

Next Steps

  1. Learn about Performance Best Practices
  2. Study Container Images
  3. Explore Cross Platform Builds

For more information about optimizing builds, see Performance Best Practices.

Released under the MIT License.