A classical problem with
unit tests is the need for calling non-public methods of the code to test.
Indeed, we generally prefer isolating unit tests code in dedicated tests
assemblies. And non-public code cannot be called from outer assemblies.
We can distinguish
between internal and private methods. Concerning internal code elements, you can harness
the System.Runtime.CompilerServices.InternalsVisibleToAttribute
to make internal types/methods/fields visible from unit tests. This is clean
and I hope this tips is now widely adopted.
Concerning private methods we are stuck with
reflection to invoke them from unit tests. This is unfortunate, both because it
requires more work on tests and because we lose strong-typing verification
while compiling tests.
The idea I would like to
propose here is to use some NDepend capabilities to be able to test private methods trough strong typed tests. The
astute is to use a dedicated attribute to transform a private method into an internal
method for which, NDepend warns if the method is called from outside its class. You can use your own
attribute to do so, or the predefined attribute NDepend.CQL.IsOnlyUsedByTestAttribute that you can find in the
redistributable NDepend.CQL.dll. Then
you need the following CQL rule
to be warned if there are some methods tagged that could not be private. Here
the CQL condition CouldBePrivate can
be understood as not used from outside its
class:
Normal
0
21
false
false
false
FR
X-NONE
X-NONE
MicrosoftInternetExplorer4
// <Name>Method tagged with IsOnlyUsedByTestAttribute must not be used from outside
its class</Name>
WARN IF Count > 0 IN SELECT METHODS WHERE
HasAttribute "NDepend.CQL.IsOnlyUsedByTestAttribute" AND
!CouldBePrivate
To make this work
properly, don’t forget to not reference unit tests assemblies from your NDepend
project. Else NDepend could not consider such method as CoulBePrivate.
Also you might already have
a rule to care for optimal encapsulation,
meaning you want to make sure that all methods not used from outside their
classes are declared private. In such case don’t forget to tweak this rule
slightly to avoid being informed of method tagged with IsOnlyUsedByTestAttribute.
Normal
0
21
false
false
false
FR
X-NONE
X-NONE
MicrosoftInternetExplorer4
// <Name>Methods that could be declared as 'private' in C#, 'Private' in
VB.NET</Name>
WARN IF Count > 0 IN SELECT METHODS WHERE
CouldBePrivate AND
!HasAttribute "NDepend.CQL.IsOnlyUsedByTestAttribute "
We apply this trick on our own code and I find it clean because:
- It works: no more reflection, no more weak typing, no more dilemma.
- It is not intrusive: as mentioned the
attribute can be your own attribute, not related to NDepend. In the future, you might be able to use
another way than NDepend to harness information carried by such attribute.
- It is documentation: the attribute tells the developer reading the code that the tagged method is private and it is called from automatic tests. This is an extra information that can be precious to avoid breaking some test code unexpectedly.
- It is active documentation: without the attribute, NDepend will tell you that you need it since the method satisfies the CQL condition CouldBePrivate and with the rule mentioned above, you want to avoid such method.
- It classifies all occurrences of private methods called by unit tests: you just need to ask for methods tagged with the concerned attribute.
I
intentionally didn’t mention so far the hot debate about if tests should call or
not private methods.
Personally I try to avoid this practice as much as I can, both because 100% of
the feature of a class should be testable from the outside and because private
things are supposed to be implementation details, highly subject to change. But it does happen sometime
that testing directly private methods help writing easier tests. In such cases
I estimate being pragmatic is allowed. It is so counter-productive to spend 30 minutes writing
a single unit test that recovers all the messy states needed to cover a tricky
scenario, generally implemented though one or two lines of code in a if clause of a private method.
Posted
Sun, May 10 2009 11:05 AM
by
Patrick Smacchia