Why .NET Developers Don’t Unit Test

Short answer: It’s Microsoft’s fault.

Long answer:

I am going to describe the journey of Well Intention Programmer (WIP).  He started writing software code in the mid-90s using this book:

image

and when .NET came out he slid over to C#

image

Along the way, he has written several highly-regarded programs that always exceed expectations and came in under-budget in the the 1st release but struggled in subsequent releases.  So the WIP decides to learn about how to improve his code quality to help him with the old code and to deliver more timely 2.x releases.  He picks up theses books:

imageimage

And  learns that he shouldn’t change a single line of code without covering unit tests.  Eager to try unit testing out, he opens up some legacy code and finds this:

public List<String> GetRegions()
{
    List<String> regions = new List<string>();
    String connectionString = @"Server=.;Database=Northwind;Trusted_Connection=True;";

    using(SqlConnection connection = new SqlConnection(connectionString))
    {
        String commandText = "Select * from Region";
        using (SqlCommand command = new SqlCommand(commandText, connection))
        {
            connection.Open();
            SqlDataReader reader = command.ExecuteReader();
            while (reader.Read())
            {
                regions.Add(reader["RegionDescription"].ToString());
            }
        }
    }

    return regions;
}

So the 1st thing the WIP might want to refactor is the hard-coded connection string in the function.  Microsoft has been telling the WIP for years that this string belongs in the .config file.  And it really does – not so much because you are going to swap out your DBMS (never happens) but because you should have environment-specific settings (that you don’t have on your local workstation).

To protect himself from the change, the WIP right clicks on the method name in VS2010

image

and he gets this out of the box test snippet like so:

[TestClass()]
public class NorthinwindFactoryTests
{
    [TestMethod()]
    public void GetRegionsTest()
    {
        NorthinwindFactory target = new NorthinwindFactory(); // TODO: Initialize to an appropriate value
        List<string> expected = null; // TODO: Initialize to an appropriate value
        List<string> actual;
        actual = target.GetRegions();
        Assert.AreEqual(expected, actual);
        Assert.Inconclusive("Verify the correctness of this test method.");
    }
}

He then alters it like so, using the Microsoft snippet to guide him.

[TestMethod()]
public void GetRegionsTest()
{
    NorthinwindFactory target = new NorthinwindFactory();
    Int32 expected = 4;
    Int32 actual = target.GetRegions().Count;
    Assert.AreEqual(expected, actual);
}

When he runs it, the test passes.

He then adds a reference to System.Configuration

image

The WIP comments out the initial line and then add a functionally equivalent line using a .config file:

//String connectionString = @"Server=.;Database=Northwind;Trusted_Connection=True;";
String connectionString = ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString;

He re-runs the unit test and it still passes.  Felling good, he checks the code back into source.  Unfortunately, when the build goes to TEST/QA, it breaks because they have a special .config they use and that build server doesn’t have that database installed.  After a couple of hours of wrangling, WIP gets the correct .config into all environments and includes Northwind as part of the build.

WIP then reads that unit tests shouldn’t have external dependencies like databases and .config files.  To unit test right, he reads, he should use a Mocking Framework and he heard that MOQ is the best out there (editor comment: he’s right).  So WIP downloads MOQ and tries to mock his ConfigurationManager:

image

After a couple of hours of researching, he decides that the ConfigurationManager cannot be mocked.  Unwilling to give up on mocking, he fires up Rhino Mocks.  Alas:

image

Still unwilling to give up, he hits F12 on the Configuration Manager to see if it uses an interface that he can mock.  Perhaps an IConfigurationManager?  Nope.

image

so now the WIP has spent several of hours and he is nowhere with Unit Tests.

So forgetting ConfigurationManager, he decided to mock SqlConnection.  It starts out well enough:

image

and then

image

So that is good.  he can create a mock connection like so but he can’t use it without casting.

image

image

So he just can’t set the connection string.  This violation of the Law Of Demeter makes it really hard.  All he wanted to do is set a String value called ConnectionString and instead he has to learn about the internal workings of the DbConnection class.

So the WIP decided that he needs to re-write the entire method and use Entity Framework.  After all, EF is the new new data access technology and it must be mockable.  So the WIP goes out to this great blog post and is disappointed to learn that EF is even harder to mock than ADO.NET classes.

The WIP then turns his attention to his UI and tried to unit test/mock the visual data controls in ASP.NET.  Already half-expecting the answer, the WIP is not surprised

image

WIP probably gives up.

So whose fault is it?

The books?  Low. The point of the books are to teach the language, not good coding practices.  And basic books that don’t have a UI to demonstrate a concept (versus using unit tests) would take attention away from the primary focus – learning the language.  The most successful books have real working, albeit simple, UI projects.

The developer?  Low. WIP is hard-working, want to do the right thing, and are going to try unit testing.  He also has deadlines.

Unit Tests Advertising? Low.  True that unit tests have generally been forced upon dev teams by people better paid, with cooler job titles if not smarter than them.  Also, these smarter people also likely introduce different, convoluted techniques of hundreds of lines long or that use parts of the .NET language spec that only Phds understand to work around the fact that lots of the .NET APIs are untestable.

The projects?  Medium.  Many .NET projects do not have any business logic – they are simply CRUD UIs on top of a database.  Unit tests can’t tell you if the Sql string returns the expected result.  And in lots of projects, that is all it has.  Also, in lots of .NET projects, any logic that could be tested is intermingled with the untestable code.  In the example above, the assumption tha GetRegions() is testable is false.

The API?  Absolutely. Every major class in Microsoft’s data stack are un-mockable and therefore un-unit testable.   Would it really kill MSFT to have an interface for these classes because perhaps, just perhaps, the classes won’t be only used in the way that they were thought of 15-20 years ago?  I get that Microsoft has been dragging code and outdated APIs though the different versions of .NET and it has to be frustrating to them that they can’t introduce changes easily.  However, until Microsoft starts making the classes that everyone uses Mockable, unit testing will not take off.  And it is not just ADO.NET, WinForms, WebForms, System.IO, etc… all are un-mockable.

So some people might ask.  Why not use VS2012 MSFT mocks?  Well, that is a clown show.  It flies in the face of iterative development – every new object, regenerate your mocks.  Also, the syntax pales in comparison to MOQ.  It is a compensation for poor class design that has been dragged forward – interest on technical debt if you will…  I think Microsoft should change their APIs, not build tools to get around their API’s failings.

Advertisements

3 Responses to Why .NET Developers Don’t Unit Test

  1. Mark says:

    I feel your pain. In roughly the same position, with roughly the same outcomes. I really, really want to take my coding to the next level with TDD, but getting it to work in this environment, which I like a lot, keeps stumping me. Well said.

  2. Michael says:

    I’ve always found it hard to justify unit tests on purely methods that connect to a database to retrieve data/update date. A good place for the WIP in your example to start would be to start testing the business logic. The tests will eventually force him to mock the database connections, however, now he’ll realize he can mock them through a layer of abstraction like the repository pattern. That way, when he makes that move to EF, or some other ORM, he only has to change one module and won’t break any of his tests. The tests have forced him to invert his dependencies, and separate the business logic which needs to be tested from the CRUD operations, which, in my opinion, really don’t (or at least saved for the end-to-end testing).

  3. pregunton says:

    Do Unit Test with another user for SQL Server (Trusted Connection) ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: