Avoid decreasing code coverage by tests of types

Rule ND1103 Code Smells Regression

Runs only in diff mode (baseline defined) and when coverage data is imported. Warns when a type's covered lines decreased since baseline (for refactored types, accounting for lines of code added).

This can mean tests were removed, but more often that changes haven't been properly covered by tests.

To visualize changes, right-click a matched type and select:

- Compare older and newer versions of source file

- or Compare older and newer versions decompiled with Reflector

How to fix

Write more unit-tests covering the changes. Hard-to-test code signals a design needing refactoring.

Estimated Debt: linear from 15 minutes to 3 hours with the number of lines no longer covered. Severity varies from High to Critical likewise: fix these issues now, before such code gets released in production.

CQLinq source

This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.

// <Name>Avoid decreasing code coverage by tests of types</Name>
// <Id>ND1103:AvoidDecreasingCodeCoverageByTestsOfTypes</Id>
warnif count > 0 
from t in JustMyCode.Types where
  t.IsPresentInBothBuilds() && t.CoverageDataAvailable && t.OlderVersion().CoverageDataAvailable
  && t.NbLinesOfCode.HasValue && t.OlderVersion().NbLinesOfCode.HasValue  
let locDiff = (int)t.NbLinesOfCode.Value - (int)t.OlderVersion().NbLinesOfCode.Value
where locDiff >= 0
let uncoveredLoc  = (int)t.NbLinesOfCodeNotCovered.Value - ((int)t.OlderVersion().NbLinesOfCodeNotCovered.Value + locDiff)
where uncoveredLoc > 0

orderby uncoveredLoc descending

select new { 
   t,
   OldCoveragePercent = t.OlderVersion().PercentageCoverage,
   NewCoveragePercent = t.PercentageCoverage,
   OldLoc = t.OlderVersion().NbLinesOfCode,
   NewLoc = t.NbLinesOfCode,
   uncoveredLoc,

   Debt = uncoveredLoc.Linear(1, 15,  100, 3*60).ToMinutes().ToDebt(),

   // The annual interest varies linearly from interest for severity High for one line of code that is not covered by tests anymore
   // to interest for severity Critical for 50 lines of code that are not covered by tests anymore
   AnnualInterest = uncoveredLoc.Linear(1,    Severity.High.AnnualInterestThreshold().Value.TotalMinutes, 
                                        50, 2*Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes).ToMinutes().ToAnnualInterest()
   

}

//<Expl>
// The *{0}* used to be {1}% covered by tests in the baseline and is
// now {2}% covered, with {5} new statement{5?:s} not being covered.
// Write additional unit tests to cover changes.
// Identifying *hard-to-test* code often indicates *poor design*
// that needs refactoring.
//</Expl>

//<Desc>
// Runs only in *diff mode* (baseline defined) and when coverage data is
// imported. Warns when a type's covered lines decreased since baseline (for
// refactored types, accounting for lines of code added).
//
// This can mean tests were removed, but more often that changes haven't been
// properly covered by tests.
//
// To visualize changes, right-click a matched type and select:
//
// - Compare older and newer versions of source file
//
// - or Compare older and newer versions decompiled with Reflector
//</Desc>

//<HowToFix>
// Write more unit-tests covering the changes. *Hard-to-test code* signals a
// design needing refactoring.
//
// Estimated Debt: linear from 15 minutes to 3 hours with the number of lines
// no longer covered. Severity varies from **High** to **Critical** likewise:
// fix these issues **now**, before such code gets released in production.
//</HowToFix>

Code Smells Regression: all 9 items