chore: add test coverage reporting for golang tests

What does this MR do and why?

This MR adds test coverage reporting for our Go tests by integrating gotestsum for test execution and gocover-cobertura for generating GitLab-compatible coverage reports.

The pipeline configuration follows GitLab's official documentation: https://docs.gitlab.com/ci/testing/code_coverage/#configure-coverage-reporting

The existing make test target now supports an optional COVERAGE parameter. When set to true, it generates detailed coverage reports. This allows developers to skip coverage generation during local development for faster test iterations while ensuring CI pipelines capture full coverage data.

Test coverage will help us quickly identify which parts of the codebase lack associated tests, improving code quality and review efficiency.


References

Please include cross links to any resources that are relevant to this MR. This will give reviewers and future readers helpful context to give an efficient review of the changes introduced.


How to set up and validate locally

  1. Install the dependencies:
make deps
  1. Run the tests with coverage for any environment:
COVERAGE=true ./env.sh postgres make test
  1. View the coverage report in your browser:
open coverage/coverage.html

The artifacts from the job can also be verified by going to the JOB link and browsing the artifacts here: https://gitlab.com/gitlab-org/cells/topology-service/-/jobs/11772057846/artifacts/browse/coverage/


Alternative

While common-ci-tasks provides a go-unittests.yml template, I decided against using it due to our specific requirements.

The Challenge: We need to run unit tests across three different environments (null, spanner, postgres), each requiring different service configurations. This creates integration challenges with the template.

Evaluated Approaches

Option 1: Matrix with All Services

go_unittests:
  services:
    - spanner
    - postgres
  parallel:
    matrix:
      - TEST_ENV: [null, spanner, postgres]
  • Starts unnecessary services for each environment (e.g., spanner service running for postgres tests)
  • Wastes CI resources significantly

Option 2: Four Separate Jobs with Template

go_unittests:  # Base job from template
  rules:
    - when: never  # Must manually disable

.test:
  extends: go_unittests
  <<: *default-rules  # Re-enable for child jobs

test with-null:
  extends: .test
  
test with-spanner:
  extends: .test
  services: [spanner]
  
test with-postgres:
  extends: .test
  services: [postgres]
  • Requires disabling the base template job (workaround)
  • Adds complexity with nested extends
  • Less maintainable configuration

The reason why I decided to duplicate the configuration is because:

Local Development Friendly: Developers can run make test for fast iteration, or COVERAGE=true make test to see coverage locally before pushing

Resource Efficient: Each test job only starts the services it actually needs

Simple and Explicit: Three clearly-defined test jobs with their specific service requirements

Maintains Consistency: Uses the same coverage tools as the template (gotestsum, gocover-cobertura)

Single Source of Truth: Coverage logic lives in the Makefile, used identically in CI and locally

Better Fit: Our ./env.sh wrapper pattern doesn't align with the template's design (though we could update the template to support it if needed)


Edited by Tarun Khandelwal

Merge request reports

Loading