August 29, 2017
Putting .NET Unit Tests In The Same Project As Source Code (?)Something's been bugging for me for quite some time. About 15 years, in fact.
The convention in .NET development has been to have separate source and test projects.
While this provides a clean separation when it comes to builds, that comes at a price. Any class or method I want to reference in a test has to be public.
So either we make a bunch of stuff public so we can test it, or we test through a teeny tiny public "keyhole", which leads to the code being tested often being many layers removed from its unit tests. This is not good for debuggability.
In Java, we don't really do this. There would typlically be one Eclipse or IntelliJ project with a folder for the source code and a folder for the test code. Classes that are only internally visible inside that project can be unit tested easily.
And I've been wondering why we don't also do this in .NET? What if I were to move the tests into a subfolder of the source project?
Then I could make the class under test internal, minimising visibility is that class doesn't need to be exposed in an API.
Ah, but - I hear you ask - what happens when we build the project? Do we really want to include all the public test classes and have a reference to the unit testing framework?
No problem. We can edit the .csproj file to only include the tests and associated DLL references in a debug build.
A next step might be to create a Visual Studio project template that's already set up with the Test subfolder and DLL reference for unit testing, with the build configurations tweaked appropriately.
Easy as peas. Now why weren't we doing this all along?
So this has turned out to be a little more controversial than I thought.
Firstly, thanks to @vgaltes for pointing me to this resource that says you can actually mark "friendly" assemblies that can see another assemblies internal features. I didn't know that.
That would be a clean way of achieving the goal of being able to unit test classes and methods without making them public.
Another school of thought is that you shouldn't have tests for internal details, and should only test through public APIs. This theoretically makes internal refactoring easier. Been there, done that, and got the "I Love My Debugger" t-shirt.
In practice, I've found that testing through a public "keyhole" can lead to unit tests that are often far-removed from the code they're testing. And to ease refactoring, I'd prefer better and more focused unit tests that are loosely coupled to the interfaces of the objects they're testing.
That's not to say, of course, that you shouldn't have API tests. I highly recommend them; partly because they're useful for figuring out what your API design needs (a client code perspective), partly because they can serve as a "user manual" for client developers, and partly for testing backwards compatibility by running old tests against new implementations.
Now it may be, of course, that your API is extensive and that many of the classes, interfaces and methods in your project are public anyway.
But the designs I tend to end up with often favour public interfaces implemented by non-public classes. The tests need to get a hold of the implementations.
Of course, you can skin this cat in many different ways, and a lot will depend on your build & config needs, your testing approach and your architecture.
The fact remains, though, that we put source and tests in the same projects in other languages without giving it a second thought. On larger solutions, I often find it cumbersome to have 2x projects for everything. Take a look at this Refactoring Golf solution, for example. Tied myself in knots with that one.
No doubt this debate will continue.
Posted 2 years, 11 months ago on August 29, 2017