The problem with this approach is that defects become harder to find and worse, if you change things you have no real idea how they affect other parts of the system. A few years ago, I started adding Unit tests to my code. I'd write my code and then add unit tests around it to break it. This would be good, as I'd end up with more confidence in my code and how it worked. I could change things and know quickly if I broke something. However, this style of coding ended up with untestable or "hard to test" code. I'd end up with methods which required complex setup scenarios to test - and then if I changed the inner workings of the code, all of the old tests would start failing due to the setup being wrong or out of date. Worse, if you needed to change a method signature or modify an interface, it would mean hundreds of tests to change. I'm ashamed to admit it, but at times like this it became easier to discard the tests or restart the class or project.
I felt like I was stuck in a loop. I wanted to "do good" and have tested code, but also I found it hard to achieve. Something needed to change.
I picked up a copy of "Clean Code" by "Uncle" Bob Martin. Anyone who's into "the agile scene" will know of him, he's some sort of evangelist god of good code. I read Clean Code from cover to cover and took a lot of it in. This book evangelizes testable code like no other. It also evangelizes "clean" code. Clean code is simply "code that you understand without documentation". Much of the book talks about removing coding "smells" and references Martin Fowler & Kent Beck's "Refactoring" in the process. I came away from Clean Code realising that I write dirty code. Really dirty code, and *this* is why I can't test it. I also realised that the reason I can't test it is because it was written without being able to be tested; and this is because it's dirty. And when I can't test it, I don't know if it works or not. In fact, I've written stuff that I don't know if it can be run or not.
I've spent most of my life writing code. Some of it is admittedly hacky but stuff I thought was "production" quality is hacky. To find out more, I went into Fowler & Beck's "Refactoring". This book is simply a mine of useful information. The single biggest takeaway from the book is that you must leave any code (including your own) in a better, more understandable and testable state than you left it. This can be as simple as giving variables "good names" (not "i", but something that makes sense to the context) and removing duplication by reducing complex functions into smaller, more composable and understandable parts. It also reinforces SOLID concepts and makes you realise that a lot of the code you write isn't actually great OO code - even though you think it is.
So after spending a lot of the December holiday break with the stark realisation that I write dirty, untestable code whilst thinking I was doing "ok" and knowing that there is a better way, I decided to start doing it.
Two tools jumped out at me. ReSharper and nCrunch. First - nCrunch gives you instant visibility on whether your code is tested or not. Each and every line of executable code has a dot next to it... Red, Green and Black. The dots update in near realtime, to let you know if your code is working or not. Red means your code is covered by a failing test, green means it's covered by a passing test and black is almost the worst kind - it means that it's not covered by a test at all. in short, nCrunch takes out the barrier to attempting TDD and actually makes you embrace it. Having used it daily for a month, I now ensure that the "black" is minimal - in fact it's easier to write a test and make it pass than to face figuring out how to cover the black. You actively try and avoid a black... because not knowing is worse than knowing you screwed up. Black means you might have superfluous code - stuff you wrote without needing.
One big thing in Fowler & Beck's "Refactoring" book is that you try and migrate your code to a cleaner state through refactoring. Refactoring is the act of keeping existing code usable, whilst changing it to a new, cleaner state. A key factor in this is having the confidence to change it, which means having test coverage to prove you haven't broken it. My prior approach to refactoring would be more of the scorched earth process. I'd rip out old classes and methods, having code which didn't compile for hours, or even days... and then after it all, finding out my tests were broken and needed fixing. At this point, I'd often ditch the tests... which I know now is a cardinal sin. Fowler and Beck advocate an approach which means slowly migrating to your new state, whilst ensuring all tests pass with each change. nCrunch provides a mechanism to allow this - you know straight away when things don't compile and things don't work. So you fix them, and move on.
Resharper provides a bunch of useful tools to help you refactor; be it renaming stuff, moving namespaces, changing scope, etc - just generally helping you clean code up line by line. Combined with nCrunch, I can make changes to code and have it verified within seconds - in fact, before I even think I've done, I know I have... because everything is green.
With both of these tools, I've had code which has compiled for 99% for my working day. And I've had test coverage of over 90% for the most part. I realised the other day, that I'd done a large part of coding without having to manually compile once and hadn't hit the debugger in days. Yet I knew my code worked, because my tests were all green.... and there were literally hundreds of them. I recently added a new feature to my code using tests and refactoring; adding this feature resulted in the removal of the old behaviour. Using the techniques I'd learned and the new tools, I was able to add the new feature and migrate my codebase from the old one over the course of few hours. When it came to the point of removing a fundamental class in the old system, I hit delete and hat literally one failing test out of hundreds. And this test was to do with the old behaviour and not relevant to the new system. At this point, I knew I'd hit a pivotal moment in my life.
I'm now in a position that I never thought I'd be. I'm embracing TDD and adding or making large changes to my codebase without fear; I know with confidence that my code works and it's actually more understandable than it used to be. As a result, I feel like I'm becoming a better programmer. Not because I'm smart, but because I know what I write works. And this is a great position to be in.