Writing unit tests can easily become a false sense of security; “all my tests pass, so surely my application must work.” But, how do you know for sure that you’ve tested everything? Using TestDriven.NET, I can run all my tests with code coverage reporting through its integration with NCover, by right-clicking on the solution folder which holds all my test projects and selecting ‘Test With - Coverage’:
This runs all my tests…
and opens up NCover Explorer with the coverage report:
This is interesting; from the results we see here, it looks as if my test coverage is far from perfect. Only 58% for my backend code? Yikes! But if we drill into the assembly, we get a different story:
Looking at this, I know that the Properties namespace is just some Visual Studio generated code – I don’t care about testing that. Additionally, the Model namespace has 55% coverage – which again sounds just about right, because most of the code in this namespace is code auto-generated by the Linq to Sql designer anyways. So if I exclude those two namespaces from the results (hit Del), the overall coverage for my backend assembly suddenly jumps to 100%.
The moral here is that we usually need more knowledge about the code base than simply looking at the code coverage results can give us in order to really determine what parts of the code are thoroughly tested and which parts are not. To some extent, we can use our own knowledge about the code to draw conclusions about the results, like I did above. However, relying on our intuition about which parts of the code are ‘important’ can be a bit dangerous – what do we really know, anyway? After all, programmers are notorious for underestimating.
Augmenting Test Coverage with Static Code Analysis
What we need, is more tangible evidence about the state of our codebase, and performing some form of static code analysis can give us just that. Visual Studio has some static code analysis functionality built in, but for this post I’ll be using the much more advanced tool NDepend for my examples.
A popular static code analysis metric you may have heard about, is cyclomatic complecity. It tells us something about the complexity of the method by analysing the number of execution branches that exists within it: Long methods with lots of conditional statements will have a high complexity, a sign that the method is hard to understand and may be susceptible to bugs – and more importantly a sign that such a method needs to be tested thoroughly (if not refactored into something more manageable, that is). There are lots of other metrics as well – if you want to learn more about static code analysis I recommend you check out the Metrics Definitions page over at the NDepend site, or read this great introductory article by Scott Hanselman.
One of the coolest features of NDepend, is that we can import the test coverage report from NCover 2.x (or Visual Studio), by going to the Analysis page in Project Properties:
This gives us the power to combine what we know about the source code through the static code analysis, and augment it with the test coverage analysis.
NDepend has a feature called CQL, Code Query Language, which allows us to write queries using SQL-alike syntax in order to find out things about our code. After importing the code coverage results, we can now for example query the code base for any method that is fairly complex and which does not have 100% test coverage:
SELECT METHODS WHERE PercentageCoverage < 100 AND CyclomaticComplexity > 10
This is a naive query though - a better one is the default “Complex methods should be 100% covered by tests” constraint that ships with NDepend, which looks at a set of relevant metrics:
WARN IF Count > 0 IN SELECT TOP 10 METHODS WHERE
( NbILInstructions > 200 OR
ILCyclomaticComplexity > 50 OR
ILNestingDepth > 4 OR
NbParameters > 5 OR
NbVariables > 8 OR
NbOverloads > 6 ) AND
PercentageCoverage < 100
There are lots of ways we can combine the metrics to come up with interesting results – we could for instance look at which types and methods are the most used in our application (using metrics like method rank and afferent coupling), and ensure that we cover these with lots of tests. Patrick Smacchia has a great post where he goes more in depth on how to take advantage of the combination of static code and code coverage analysis.
Running these queries on my code base, I find very few methods that seem to need more thorough testing. How confident should that make me that my code is well tested and does not contain any major bugs?
100% Coverage, a False Sense of Security?
There are lots of ways to calculate test coverage, and the naive statement coverage method that most tools use by default has several disadvantages – most notably that code often has many decision points and loops, and simply measuring that all statements have been executed can easily yield false negatives. Branch coverage on the other hand, measures which of the possible execution paths through the code have been exercised. Take the following method, for instance:
public string Silly(bool someCondition)
{
object val = null;
if(someCondition) val = new object();
return val.ToString();
}
With the following test, statement coverage will report 100% for this method:
[Fact]
public void Silly_returns_a_string_when_condition_true()
{
Assert.NotNull(Silly(true));
}
But if someone calls Silly(false), then clearly it will blow up! Branch coverage, however, would report < 100% for this method, because the branch on which someCondition = false was never executed by the tests. Both NCover and NDepend support branch coverage metrics, so we could for instance run the following query to find all methods like the above:
SELECT METHODS WHERE PercentageBranchCoverage < 100 AND PercentageCoverage == 100
If you want to know more about code coverage analysis and the different ways of calculating it, then have a look at Steve Cornett’s paper.
Knowledge is Power
In the end, the more you know about your code the better equipped you are to make sure it will do what it’s supposed to. Using tools like NDepend for static code analysis and NCover for code coverage analysis can be of great help in not only gaining more insight about your code, but also guiding you to the parts of the code which are most important to the application as a whole.