Adventures in Unit Testing Persuasion
A couple weeks ago a friend asked me to help him put together a compelling story for his engineering management on why they should make unit testing (and Test Driven Development) an expectation for their engineering teams. We spent some time talking about the virtues of unit testing, the benefits of TDD, and the potential resistance he was bound to face. In my friend's case, his management was interested in what unit testing and TDD could do for them, although, neither of us were sure of their definition of "unit testing."
Once my friend felt comfortable with his pros and cons, he set up a time to meet with his manager, and presented to him why they should move in this direction. When he told me about how it went, I asked if I could blog about it solely because he ran into some of the resistance that I had predicted. I thought this would be good information to share.
In his presentation, my friend proposed the use of Visual Studio Team System's unit testing framework (the team already uses VSTS and Team Foundation Server), a third party mock object framework, and the practice of TDD. Here are some of the reactions, and my rebuttals to each:
All this sounds good but now we have to right more code the manager says. We already can’t meet our timelines so how’s this going to work.
True, you will have to write more code. In fact, the general rule of thumb a 1.25:1 or 1.5:1 ratio - that is, expect to write between 1.25 and 1.5 lines of unit test code for every 1 line of implementation code. That's pretty significant. The trade off is of course that you recoup the time spent writing the unit test code by not having to deal with preventable bugs and regressions later. I have yet to hear anyone that has done unit testing well complain about this.
Most of the cost related to fixing a bug is in having the developer reproduce the bug, find the source of the bug and ensure the fix doesn't break anything else. The actual time spent writing the code to fix the bug is typically the smallest part of the bug investment. Spending some time up front to write the unit tests, and less time tediously tracking down to root of a bug sounds like a good investment to me. Think of it as more time up front, and less time dealing with bugs.
This Mock object and setup looks like a lot of work.
There are a number of mock Frameworks out there (I use Rhino Mocks) to make the use of mock objects easy. Good tools are the key here. Having a Mock Framework that can spin up an instance of an object based on an interface or class definition is essential. The right tools make the job a lot easier, and give you less code to maintain.
Why couldn’t I just write a script to populate some stuff in the database and get that information and then clean it up when I'm done.
In some cases you will do this - integration tests. When you need to test how your boundary code - your data access code - integrates with another system - your database - you will want this kind of unit test. In all other cases you don't want to rely on the complete system working in order to test a part of it.
The key to good unit testing is to test the smallest unit possible to verify the expected functionality. By testing small units you can easily identify the point of failure when something isn't working (there will be a failure at some point, I guarantee it). By relying on a connection to a database, and the database being in the correct state, and the correct permissions being applied, you are creating a dependency chain that makes it difficult to quickly and easily identify the point of failure.
Using mock objects to represent the points of integration outside the class being tested enables you to narrow the scope of failure to a single class - the class under test. The mock objects enable you to define a predictable state - you can define what a dependency will do for you in a know state (happy path, failure, boundary, etc.).
If I need to mock my integration points why would I just go an mock other method calls.
I see a light bulb coming on. This is exactly what you want to do. Anytime the code you are testing is going outside its class boundary, you should use a mock instance of the other class (dependency).
This is analogous to testing engine parts in isolation before testing the engine completely. You want to ensure that each part works independently before assembling the engine, only to find out something is causing a mechanical failure. For example, you would want to use a “mock” fuel system to test a fuel injector. You don’t need to hook the fuel injector to the real fuel system to ensure the fuel injector works. In fact, you probably wouldn’t want to for safety reasons.
Obviously I am a proponent of good unit testing. I love that this team is interested in writing better code, and they are willing to put forth the effort to learn how to write good unit tests. While I am also a staunch believer in TDD, I encourage them (and anyone in a similar position) to start by learning to write good unit tests - whether you write them first or last is less important than getting good at writing them. Once you are proficient in good unit testing, start looking at TDD.
Technorati tags: Unit Testing, TDD, Mock, Agile
Comments
Anonymous
June 27, 2007
The comment has been removedAnonymous
June 29, 2007
This a great post, one well worth keeping to refer to when making the TDD pitch over and over to different stakeholders. I think the point you make about the time to find, repeat, and diagnose a bug vs. the time taken to code the fix is important and often overlooked. Often the business is accepting of the bugfix cycle in general and would rather be 'in control' of which bugs get addressed and when, and mistakenly see the upfront TDD as ultimately the same bugfix mentality, just frontloaded rather than after the fact. As you point out this is in fact not the case. Accepting a standard bugfix cycle is not just a shifting of when time is spent but is rather far, far more time-expensive.Anonymous
September 17, 2007
I'm trying to evangelize the unit testing lifestyle but as far as I've been able to discover, the VSTS unit testing features only deal with managed code. Am I missing something?