I recently analyzed NUnit
v2.4.8 with NDepend.
first impression is that developers behind NUnit know their job and did
excellent work. This positive feedback comes from many details, the
fact that you can
seamlessly open the VisualStudio solution and compile everything
immediately, the amount of documentation, the high number of releases,
of tests, the support for every version of .NET (and also non-MS ones),
In terms of Lines of Code,
NUnit is relatively small with 12 559 LoC (not including tests assemblies).
To be compared to 70 681 LoC for NDepend v2.11,
42 092 LoC for Paint.NET v3.22,
36 143 LoC for NHibernate v2.0,
29 791 LoC for Castle v2.0.3,
474 388 LoC for Resharper v4.1,
and around 2M LoC for the entire .NET Fx 3.5 (including WPF, WCF…).
Test Coverage of NUnit
In terms of code
coverage, NCover says
47% but some tests didn’t pass while running with TestDriven.NETereHere . Here is a
NDepend snapshot of methods more than 90% covered (rectangles in blue represent methods that are at least 90% covered by tests). Without surprise, UI code is poorly
Assemblies of NUnit: How to compile them 3 times faster?
While looking at
assemblies, an immediate remark comes to me: is there really a need for 19
assemblies for these 13 068 Lines of Code?
I wrote once about the benefits of having less but bigger assemblies.
One of the reason was the compilation time and indeed, the 13K LoC of NUnit +
4K Loc of test projects takes 32 seconds to compile (total of 29 projects). In
comparison the 70K LoC of NDepend are compiled in 11 seconds (total of 11 projects).
So how does such
a gap can be explained? 2 reasons, first the high number of
projects/assemblies and second, the use of the Copy Local project
The VisualStudio Project Reference + Copy Local true option is evil! If you don’t believe me, go and check by yourself: because of this option the assembly
nunit.core.dll is duplicated 21 times
while compiling the NUnit solution with VisualStudio! It means that it has to
be copied/loaded/parsed 21 times by the C# compiler that apparently doesn’t come
with a cache/hashCode optimization for such a case.
So I did the experiment of
replacing projects references by assembly references. I also parametrized VisualStudio to output all assemblies in a
single folder (menu Project Properties
> Build > Output path). The
compilation times went from 32 seconds to 12 seconds!
The second reason that
explains slow compilation is the high number of project (19 project + 10 test
projects). Clearly, if assemblies were merged into: few test assemblies, one GUI
assembly, one console assembly and one framework assembly, the compilation duration
could be decreased from 12 seconds to something like 3 seconds, a x10 factor compared to the
initial 32 seconds. Then a tool such as NDepend
could be used to guarantee that the structure of the code remain
And indeed, the code of NUnit is pretty well layered. This is the
result of the initial decision to create plenty of assemblies. This can
be shown by
looking at NUnit namespaces through a dependency matrix, there are only
This is a great news compare
to the monolithic code structure of NHibernate for example, where all of the 62
namespaces literally depend on all others 62 namespaces (as described here).
Another thing to notice
is that this high number of assemblies forces NUnit consumer projects to reference
several assemblies and also to deploy all of them, without really knowing if
one is missing or not. It would be nice to just be able to reference nunit.framework.dll and be able to just
copy/paste it at whim.
Assemblies of NUnit: Insider view
By contacting Charlie Poole,
the lead developer of NUnit, to have its opinion before publishing the
current post, I had the surprise that Charlie spontaneously confesses
that reducing the number of assemblies is indeed part of their upcoming plans.
This also come with some justification for each assembly, here are Charlie’s remarks:
assemblies, nunit.exe and nunit-console.exe do nothing but call the
main of the two runner dlls. This is a feature requested by folks who
want to run NUnit out of their own exe.
- The uikit assembly was made separate so that it could be used in alternative guis.
mock and framework assemblies are both referenced by users. But not
many users use our mock framework, so they need to be able to reference
the one they want to use instead.
- NUnit uses remoting, with
some parts loaded in the test domain and others only in the primary
domain. This impacts the minimum number of assemblies you want to have.
Our split is currently between util and core, with core.interfaces
pointing the way to a future split.
- NUnit has multiple runners,
some we write, some other people write. So engine that runs tests has
to be separate for those users.
NUnit Code Quality
Through the prism of classic metrics, the code quality is pretty neat: only 62 methods
on 3 233 slightly exceed classic thresholds (the report tells 4 388 methods because it includes tier code methods):
WARN IF Count > 0 IN SELECT METHODS WHERE
!NameIs "InitializeComponent()" AND
// Metrics' definitions
( NbLinesOfCode > 30 OR // http://www.ndepend.com/Metrics.aspx#NbLinesOfCode
NbILInstructions > 200 OR // http://www.ndepend.com/Metrics.aspx#NbILInstructions
CyclomaticComplexity > 20 OR // http://www.ndepend.com/Metrics.aspx#CC
ILCyclomaticComplexity > 50 OR // http://www.ndepend.com/Metrics.aspx#ILCC
ILNestingDepth > 4 OR // http://www.ndepend.com/Metrics.aspx#ILNestingDepth
NbParameters > 5 OR // http://www.ndepend.com/Metrics.aspx#NbParameters
NbVariables > 8 ) // http://www.ndepend.com/Metrics.aspx#NbVariables
NUnit Code Maintenance and Evolution
let’s also mention that the NUnit team is serious about maintenance and
evolution: more than half of the code base has been refactored between
v2.4.1 and v2.4.8. To read this metric view know that:
- Each rectangle represents a method
- The surface of a rectangle is proportional to the number of Lines of Code of the method
- Rectangles in blue are those matched by the CQL query below. In this context rectangles in blue are methods that have been refactored or added between
v2.4.1 and v2.4.8.