Over the years I’ve found myself becoming more and more passionate about test-driven development. However, I constantly ran into the same problem over and over again: my ability to understand and use TDD could not keep pace with my desire to learn it. When looking through various blogs online, I have noticed that I’m not the only one that struggled with TDD, so I decided to write a short post about it in hopes of helping out some other aspiring developer.
TDD is a methodology for building software more-so than a methodology for testing software. I should mention, when I talk about testing in the context of TDD, I’m specifically referring to unit testing.
There’s a good article by Bob Martin about TDD, and it covers the methodology in three simple points:
You are not allowed to write any production code unless it is to make a failing unit test pass.You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
Another way to think about it is Red-Green-Refactor. Write a failing test (red). Make the test pass (green). Clean up all the regrets you’ve made along the way (refactor).
It is extremely nice to have huge test coverage to fall back on when things start breaking, but it is not the purpose of TDD.
If TDD isn’t primarily about testing (as you would intuitively think), what is it really about? It’s about design. It’s about making sure your classes do one thing and they do it well. It’s about making sure you can switch out dependencies at run-time and not worry about everything blowing up. It’s about SOLID.
When you start writing code using TDD, you’ll notice that it’s really hard to have complex, non-injected, dependencies between classes because having these dependencies makes it very difficult to test. True, you can still have ten objects injected into the constructor of some other object, but that is easily dealt with using various methods (a post for another day).
We’re going to make a simple program that converts a number into a pyramid. For instance, if you give an input of 1
, you’ll get an output of /\\
. But, if you give an input of 5
, you’re gonna get an output of
/\\
/ \\
/ \\
/ \\
/ \\
If you’ve read anything about TDD, you’ve probably come across a few words: MSTest, NUnit, xUnit, *Unit. There are various libraries out there to help you unit test, but we’re gonna gloss over that for now and just start with the basics by writing our own test runner. Never write your own test runner for production code.
Our test runner will be extremely basic and only do the bare minimum. It will also only work with the bare minimum. I’ll leave it to the reader to make it more generic, as an exercise, and we’ll assume the only thing we care about testing is our Pyramid
class.
First, we’re going to need some sort of way to determine that a method in a class is a test. I’ve decided a good and simple way of designating this is to create a custom attribute named Test
.
public class TestAttribute : Attribute { }
It doesn’t need to do anything other than decorate our methods, so we’re not doing anything else with it. Next, we need to implement a simple test runner that will take a class that we give it (PyramidTests
in our case), loop over the methods in that class, pull out the ones that are decorated with our attribute, and invoke them.
public class PyramidTestRunner
{ public static void RunTests() { var testClass = new PyramidTests(); foreach (var method in testClass.GetType().GetMethods()) { if (method.IsDefined(typeof(TestAttribute))) { method.Invoke(testClass, new object[] { }); } } }}