This week I met with a prospect, who, low and behold, is a developer. And we jumped into the code. That means that I made him choose a piece of code he wanted to test, and we wrote a unit test for it, using Isolator.
Sounds easy. It took an hour and a half.
This post is about the steps we went through when starting to unit test. It is an example for everyone brave enough to learn how to start unit testing, on his existing code-base. We’ll start with a pre-requisite: have a test framework in place. We had to install the MS unit testing tools as part of VS, because it wasn’t installed.
1. Pick what to test.
This took about 15 minutes. I emphasize that I want to test logic, meaning ifs/switches and such. Start from there, and build tests around that piece of code. As the first test, I want to test a simple if statement. What we chose is an IF surrounded by a for-each loop.
2. Pick a scenario.
Once you selected a simple enough code to test, it’s easy to pick a scenario. If not, go back to step 1, and select simple code that you can think of a scenario for.
3. Write an empty test, naming it after the scenario you are about to test.
This is crucial, especially when pairing. And if not. If at least one of you is inexperienced in writing tests it’s easy to go off on a tangent, and naming helps focus. We use this pattern for naming tests:
METHODNAME_WHEN_EXPECTEDRESULT
Where WHEN is the scenario’s initial state, which drives it. and EXPECTEDRESULT is what we are going to assert. We usually have one test class per tested class, so there’s no need to use the class’ name. Make it readable, so everyone who reads it understand what you are testing. Get someone to review the name, or pair with someone. Did I say this is crucial?
4. Write the test’s body without any faking code. It MUST assert something.
When we wrote the test, we just invoked the method. If it runs, it’s ok, since the test didn’t crash, right?
Wrong!
Write an assertion. Even a simple one, like IsNotNull. A failing test that fails because of the assertion. Of course initially the test crashed before getting to the assertion, but after adding some faking code, it didn’t and we didn’t know if the test really passed. So add an assertion.
5. Run the test.
At this point, I knew it would fail, but I wanted feedback. It crashed somewhere in the database access in EntLib (MS Enterprise libraries). Which led us immediately to the next point:
6. Think. Decide what you want to fake. Then think again. Be sure.
I could fake database calls in EntLib. But do I really want to do it? Why not fake the DAL (Data access layer) objects? They encapsulate more calls, and I’ll write less faking code. What helped here is stepping through the code with the debugger, seeing all the method calls, and who’s calling who. Isolate where it makes sense.
7. Write the faking code. Understand what you did. Repeat
Adding faking code is easy with Isolator. That doesn’t mean it’s what you actually meant to happen. So if you come by a type called XYZ collection, make sure it implements IEnumerable before using the collection APIs. After it works, and you build a return value, go back to the tested code. Implement enough faking code that satisfies the tested code, and the scenario. Does the returned collection need to be filled with objects? Initialized objects? Add all the setup code to the test, and make sure you understand what you’re doing. Iterate through all the dependencies, and add faking code.
8. Run the test. Recheck if fails. Make corrections.
After completing the entire fake setup, we realized the assertion should be negated. Stupid isn’t it? But it just goes to show that after going through the first steps, there’s still some thinking to do.
9. The test passes.
Grab a cup of coffee, you earned it. Then test another scenario.