Every now and then I’ll be throwing some “Notes” out there. These are things that I’ve come across before, solved before, and then somehow forgotten about. Inevitably, the problem happens again someday, and I spend far too long searching the web for the solution that actually worked last time.
In the interests of helping my fellow programmers, and future me, I’ll just put these out there as part of my permanent record and maybe it’ll turn up in your search someday. Maybe that’s why you’re reading this right now. Maybe it’s why I’m reading this in the future. In that case; You’re welcome, future me.
Collecting code coverage in Visual Studio is pretty easy, as long as you have access to the Enterprise Edition. Fortunately, I do. I know not everyone does, and I think it’s ridiculous that Microsoft made code coverage an Enterprise feature. This is something we want to encourage the youngest of interns to absorb, not something reserved for senior Greybeards like me.
Anyway, inevitably, whenever I start analyzing coverage, I always run into the same problem. The test assemblies themselves have been counted in my totals. Since the tests all get run, the test coverage for those assemblies is nearly 100%, and it skews my overall results, making it look like my team’s coverage is better than it really is. What I want to do is exclude the test assemblies from the coverage numbers, but the ExcludeFromCodeCoverageAttribute can’t be applied to entire assemblies, and I don’t feel like adding it to every individual test class, so I fire up a web search and end up at the Microsoft documentation for .runsettings files. I follow the advice in the documentation, create a .runsettings file with nothing but a ModulePath Exclude section for and the next time I gather coverage, I see a bunch of assemblies that aren’t even mine. I’m talking about Nunit, JSON.net, etc.
The problem is that as soon as you specify modules to exclude, everything else gets included by default. You need to specify an Include section as well, but the syntax can be a little weird because it’s a RegEx, and now we have two problems. I try to write a simple RegEx to match only the files in my project, but the problem is that the files that aren’t mine are located in folders named after my projects, so my “magic words” are still part of their path, and they therefore get caught up in the results. I need a RegEx that will look for my root namespace as part of the filename, but not as part of the complete file path.
Enough already. What’s the answer?
Okay, here are the magic words. Assuming my root namespace, perhaps the name of the client, is “Foo”, my runsettings file will look like this:
<?xml version="1.0" encoding="utf-8"?> <RunSettings> <RunConfiguration> <MaxCpuCount>1</MaxCpuCount> <ResultsDirectory>.\TestResults</ResultsDirectory> <TargetPlatform>x86</TargetPlatform> <TargetFrameworkVersion>Framework45</TargetFrameworkVersion> </RunConfiguration> <DataCollectionRunSettings> <DataCollectors> <DataCollector friendlyName="Code Coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=18.104.22.168, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <Configuration> <CodeCoverage> <ModulePaths> <Include> <!-- Include only our own (Foo) assemblies --> <ModulePath>.*\\Foo\.[^\\]*\.dll$</ModulePath> </Include> <Exclude> <!--Exclude the test assemblies themselves --> <ModulePath>.*Tests\.dll</ModulePath> </Exclude> </ModulePaths> </CodeCoverage> </Configuration> </DataCollector> </DataCollectors> </DataCollectionRunSettings> </RunSettings>
It’s that middle part there that’s the important bit, especially the RegEx in the Include section. It’s looking for the word “Foo” followed by any number of characters that aren’t a backslash (because I’m on Windows), followed by “.dll”. Users of other operating systems may have to adjust the direction of the slashes accordingly… I haven’t looked yet
Then, I have a more regular-looking Exclude section to ignore any file that ends in “Tests.dll”, which is the convention I happen to be following (e.g. Foo.UnitTests.dll”, “Foo.IntegrationTests.dll”, etc.)
Hopefully this helps someone. If you’re a RegEx ninja, please don’t kick sand in my face. If you’d like to share a better way of expressing this, I’m open to suggestions, of course.