New Types and Methods should be tested
Runs only in diff mode (baseline defined) and when coverage data is imported, on types and methods added since baseline.
Covered code is maintainable and non-error-prone. Since stopping development to test a poorly covered code base is impracticable, each time a method or type gets added, write associated unit-tests. Maintainability increases, refactoring becomes driven by testability, and the overall design improves.
Severity is High: these issues reflect an actual trend of not writing tests for new code.
Covered code is maintainable and non-error-prone. Since stopping development to test a poorly covered code base is impracticable, each time a method or type gets added, write associated unit-tests. Maintainability increases, refactoring becomes driven by testability, and the overall design improves.
Severity is High: these issues reflect an actual trend of not writing tests for new code.
How to fix
Write unit-tests covering most methods and types added since the baseline.
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>New Types and Methods should be tested</Name>
// <Id>ND1601:NewMethodsShouldBeTested</Id>
warnif count > 0
// Gather typesMoved, types whose parent namespaces changed since baseline
let typesRemoved = context.CompareContext.OlderCodeBase.Application.Types
.Where(t => t.WasRemoved()).Select(t => t.Name).ToHashSetEx()
let typesMoved = Application.Types
.Where(t => t.WasAdded() && typesRemoved.Contains(t.Name)).ToHashSetEx()
// Gather methodsMoved keys: a method removed from a type of the baseline and added, with the
// same name (signature embedded), lines of code and IL size, to another type is relocated
// code (a class split into helpers): its coverage debt pre-dates the baseline, it is not new.
let methodsRemovedKeys = context.CompareContext.OlderCodeBase.Application.Methods
.Where(m => m.WasRemoved() && m.NbILInstructions >= 10)
.Select(m => m.Name + "|" + m.NbLinesOfCode.ToString() + "|" + m.NbILInstructions.ToString()).ToHashSetEx()
let newMethodsUntested = (from m in JustMyCode.Methods where
m.NbLinesOfCode > 0 &&
m.PercentageCoverage < 30 &&
m.WasAdded() &&
// Don't match methods considered as new because its parameters changed
(!m.ParentType.IsPresentInBothBuilds() ||
!m.ParentType.OlderVersion().MethodsAndConstructors.Any(m1 => m1.WasRemoved() && m1.SimpleNameEquals(m.SimpleName))) &&
// Don't match methods in types moved, nor methods relocated to another type
!typesMoved.Contains(m.ParentType) &&
!methodsRemovedKeys.Contains(m.Name + "|" + m.NbLinesOfCode.ToString() + "|" + m.NbILInstructions.ToString())
select new {
elem = (ICodeContainer)m,
// Simplistic Debt estimation, because the effort to write tests for a method not 100% tested
// is already estimated properly with the rule "Code should be tested".
debt = m.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(),
}).ToArray() // enumerate once
// newMethodsUntestedInNewTypeTested is all new methods not 100% covered with parent type more than 0% covered
let newMethodsUntestedInNewTypeTested= from x in newMethodsUntested where
x.elem.AsMethod.ParentType.PercentageCoverage > 0 ||
!x.elem.AsMethod.ParentType.WasAdded()
select x
// newTypesUntested contains all type 0% covered whose methods are in methodsUntested
let newTypesUntested = newMethodsUntested
.Where(x => x.elem.AsMethod.ParentType.PercentageCoverage == 0 &&
x.elem.AsMethod.ParentType.WasAdded())
.ToLookup(x => x.elem.AsMethod.ParentType)
.Select(@group =>
new {
elem = (ICodeContainer)@group.Key,
debt = @group.Sum(m => m.debt) })
//from x in newMethodsUntested // use this clause to list all methods instead of types & methods
from x in newMethodsUntestedInNewTypeTested.Concat(newTypesUntested)
orderby x.elem.NbLinesOfCode descending,
x.elem.NbLinesOfCodeNotCovered ,
x.elem.PercentageCoverage
select new {
x.elem,
x.elem.PercentageCoverage,
x.elem.NbLinesOfCode,
x.elem.NbLinesOfCodeNotCovered,
CyclomaticComplexity = x.elem.IsMethod ? x.elem.AsMethod.CyclomaticComplexity : x.elem.AsType.CyclomaticComplexity,
Debt = x.debt,
Severity = Severity.High
}
//<Expl>The new *{0}* is {1}% covered by tests.
// It is important to direct the testing endeavor primarily towards newly developed code.</Expl>
//<Desc>
// Runs only in *diff mode* (baseline defined) and when coverage data is
// imported, on types and methods added since baseline.
//
// Covered code is *maintainable* and *non-error-prone*. Since stopping
// development to test a poorly covered code base is impracticable, each time
// a method or type gets added, write associated unit-tests. Maintainability
// increases, refactoring becomes driven by testability, and the overall
// design improves.
//
// Severity is **High**: these issues reflect an actual trend of not writing
// tests for new code.
//</Desc>
//<HowToFix>
// Write unit-tests covering most methods and types added since the baseline.
//</HowToFix>
Open in the interactive Rules Explorer · All default rules
Code Coverage: all 15 items
- Code should be tested ND1600
- New Types and Methods should be tested ND1601
- Methods refactored should be tested ND1602
- Assemblies and Namespaces should be tested ND1603
- Types almost 100% tested should be 100% tested ND1604
- Namespaces almost 100% tested should be 100% tested ND1605
- Types that used to be 100% covered by tests should still be 100% covered ND1606
- Types tagged with FullCoveredAttribute should be 100% covered ND1607
- Types 100% covered should be tagged with FullCoveredAttribute ND1608
- Methods should have a low C.R.A.P score ND1609
- Complex Methods should be 100% tested ND1610
- Test methods should assert something ND1611
- Test Methods
- Methods directly called by test Methods
- Methods directly and indirectly called by test Methods