Why Your .NET Code Coverage Badge is 'Unknown' in GitLab (And How to Fix It)


In a recent post, I shared how to set up a CI/CD pipeline for a .NET Aspire project on GitLab. The pipeline includes unit tests, security scanning, and secret detection, and if any of those fail, the pipeline would fail. Great, but what about code coverage for the unit tests? The pipeline included code coverage commands, but the coverage was not visible in the GitLab interface. Let's fix that.

(blog post en français ici)

Badge on Gitlab showing coverage unknown

The Problem

One thing I initially thought was that the regex used to extract the coverage was incorrect. The regex used in the pipeline was:

coverage: '/Total\s*\|\s*(\d+(?:\.\d+)?)%/'

That regex came directly from the GitLab documentation, so I thought it should work correctly. However, coverage still wasn't visible in the GitLab interface.

So with the help of GitHub Copilot, we wrote a few commands to validate:

  • That the coverage.cobertura.xml was in a consistent location (instead of being in a folder with a GUID name)
  • That the coverage.cobertura.xml file was in a valid format
  • What exactly the regex was looking for

Everything checked out fine, so why was the coverage not visible?

The Solution

It turns out that the coverage command with the regex expression is scanning the console output and not the coverage.cobertura.xml file. Aha! One solution was to install dotnet-tools to changing where the the test results was persisted; to the console instead of the XML file, but I preferred keeping the .NET environment unchanged.

The solution I ended up implementing was executing a grep command to extract the coverage from the coverage.cobertura.xml file and then echoing it to the console. Here's what it looks like:

- COVERAGE=$(grep -o 'line-rate="[0-9.]*"' TestResults/coverage.cobertura.xml | head -1 | grep -o '[0-9.]*' | awk '{printf "%.1f", $1*100}')
- echo "Total | ${COVERAGE}%"

Results

And now when the pipeline runs, the coverage is visible in the GitLab pipeline!



And the badge is updated to show the coverage percentage.

Coverage badge showing percentage


Complete Configuration

Here's the complete test job configuration. Of course, the full .gitlab-ci.yml file is available in the GitLab repository.

test:
  stage: test
  image: mcr.microsoft.com/dotnet/sdk:9.0
  <<: *dotnet_cache
  dependencies:
    - build
  script:
    - dotnet test $SOLUTION_FILE --configuration Release --logger "junit;LogFilePath=$CI_PROJECT_DIR/TestResults/test-results.xml" --logger "console;verbosity=detailed" --collect:"XPlat Code Coverage" --results-directory $CI_PROJECT_DIR/TestResults
    - find TestResults -name "coverage.cobertura.xml" -exec cp {} TestResults/coverage.cobertura.xml \;
    - COVERAGE=$(grep -o 'line-rate="[0-9.]*"' TestResults/coverage.cobertura.xml | head -1 | grep -o '[0-9.]*' | awk '{printf "%.1f", $1*100}')
    - echo "Total | ${COVERAGE}%"
  artifacts:
    when: always
    reports:
      junit: "TestResults/test-results.xml"
      coverage_report:
        coverage_format: cobertura
        path: "TestResults/coverage.cobertura.xml"
    paths:
      - TestResults/
    expire_in: 1 week
  coverage: '/Total\s*\|\s*(\d+(?:\.\d+)?)%/'

Conclusion

I hope this helps others save time when setting up code coverage for their .NET projects on GitLab. The key insight is that GitLab's coverage regex works on console output, not on the files (XML or other formats).

If you have any questions or suggestions, feel free to reach out!


~frank



Reading Notes #657

a rocky path ends at the shore of a lake
This week's collection of interesting articles and resources covers AI development, DevOps practices, and open source tools. From GitHub Copilot customization to local AI deployments and containerization best practices, here are the highlights worth your attention.

AI

DevOps

  • Local Deploy with Bicep (Sam Cogan) - A perfect short story, I'll explain why the hell bicep can now deploy locally and how to do it

Open Source

  • Introducing OpenCLI (Patrik Svensson) - A standard that describes CLI so both humans and agents can understand how it works. Love it!

~frank


How to Have GitLab CI/CD for a .NET Aspire Project

Getting a complete CI/CD pipeline for your .NET Aspire solution doesn't have to be complicated. I've created a template that gives you everything you need to get started in minutes.

(blog post en français ici)

Watch the Video


Part 1: The Ready-to-Use Template

I've built a .NET Aspire template that comes with everything configured and ready to go. Here's what you get:

What's Included

  • A classic .NET Aspire Starter project (API and frontend)
  • Unit tests using xUnit (easily adaptable to other testing frameworks)
  • Complete .gitlab-ci.yml pipeline configuration
  • Security scanning and secret detection
  • All documentation you need

What the Pipeline Does

The pipeline runs two main jobs automatically:

  1. Build: Compiles your code
  2. Test: Runs all unit tests, scans for vulnerabilities, and checks for accidentally committed secrets (API keys, passwords, etc.)

You can see all test results directly in GitLab's interface, making it easy to track your project's health.

How to Get Started

It's simple:

  1. Clone the template repository: cloud5mins/aspire-template
  2. Replace the sample project with your own .NET Aspire code
  3. Push to your GitLab repository
  4. Watch your CI/CD pipeline run automatically

That's it! You immediately get automated builds, testing, and security scanning.

Pro Tip: The best time to set up CI/CD is when you're just starting your project because everything is still simple.


Part 2: Building the Template with GitLab Duo

Now let me share my experience creating this template using GitLab's AI assistant, GitLab Duo.

Starting Simple, Growing Smart

I didn't build this complex pipeline all at once. I started with something very basic and used GitLab Duo to gradually add features. The AI helped me:

  • Add secret detection when I asked: "How can I scan for accidentally committed secrets?"
  • Fix test execution issues when my unit tests weren't running properly
  • Optimize the pipeline structure for better performance
screen capture in VSCode using GitLab Duo to change the default location for the job SAST

Working with GitLab in VS Code

While you can edit .gitlab-ci.yml files directly in GitLab's web interface, I prefer VS Code. Here's my setup:

  1. Install the official GitLab extension from the VS Code marketplace

Once you've signed in, this extension gives you:

  • Direct access to GitLab issues and work items
  • AI-powered chat with GitLab Duo

GitLab Duo in Action

GitLab Duo became my pair programming partner. Here's how I used it:

Understanding Code: I could type /explain and ask Duo to explain what any part of my pipeline configuration does by highlighting that section.

screen capture in VSCode using GitLab Duo to explain part of the code

Solving Problems: When my solution didn't compile, I described the issue to Duo and got specific suggestions. For example, it helped me realize some projects weren't in .NET 9 because dotnet build required the Aspire workload. I could either keep my project in .NET 8 and add a before_script instruction to install the workload or upgrade to .NET 9; I picked the latest.

Adding Features: I started with just build and test, then incrementally asked Duo to help me add security scanning, secret detection, and better error handling.

Adding Context: Using /include to add the project file or the .gitlab-ci.yml file while asking questions helped Duo understand the context better.

Learn More with the Docs: During my journey, I knew Duo wasn't just making things up as it was referencing the documentation. I could continue my learning there and read more examples of how before_script is used in different contexts.

The AI-Assisted Development Experience

What impressed me most was how GitLab Duo helped me learn while building. Instead of just copying configurations from documentation, each conversation taught me something new about GitLab CI/CD best practices.

Conclusion

I think this template can be useful for anyone starting a .NET Aspire project. Ready to try it? Clone the template at cloud5mins/aspire-template and start building with confidence.

Whether you're new to .NET Aspire or CI/CD, this template gives you a good foundation. And if you want to customize it further, GitLab Duo is there to help you understand and modify the configuration.

If you think we should add more features or improve the template, feel free to open an issue in the repository. Your feedback is always welcome!

[Screen capture of the Aspire template project on GitLab




Thanks to ‪David Fowler‬ for his feedback!