OK, not completely "created by," but You won't believe how old TDD is
I have a mixed relationship with Test-Driven Development (TDD). It's a valuable method that I've enjoyed--when I've used it. What matters more to me than test-first is creating automated tests early rather than late.
Despite my on-again, off-again history with TDD, I've written about it in tutorial form. This series also includes examples of mocking and dependency injection. What to mock is, I think, especially difficult to get right.
- Catching Up On Coding - TDD Part 1 | Software Meadows
- Catching Up On Coding - TDD Part 2 | Software Meadows
- Catching Up On Coding - TDD Part 3 | Software Meadows
In 2001, at the beginning of the third millenium, I first learned about some eXtreme Programming (XP) practices, among them TDD. I wanted to try this out, but ran into a snag: Visual Basic 6 didn't have a good unit testing framework. 1 So, I wrote my own. I also wrote it to test private methods, because that's where the action was, and I hadn't yet wrapped my head around why testing public methods was key to keeping tests separate from code.
Even then, I got the TDDers thrill from seeing all green after running tests. However, my organization wasn't invested in TDD, so I set it aside. This was the story for the next couple of decades. The places I worked didn't care about more agile methods, and my efforts to introduce and use TDD didn't go far. Over time, I realized something else. My background in creative writing and music composition led me intuitively write code in a particular way:
- Draft the code, getting some things working quickly.
- Significantly rewrite as the better structure reveals itself.
TDD didn't fit into this way of coding new projects, because my code would often change a lot as I settled on the most effective patterns. In business parlance, this is a "fail early, fail often" approach. Writing tests first at this stage worked against me because the methods I was testing were in flux. Heck, the whole approach was in flux. I could delete them at any moment. It took me a long time to reconcile how to integrate unit testing and TDD into my personal, fluid--and effective--style of coding.
One developer I talked with said his team didn't practice "test first," but instead "test eventually." I stored this tidbit away, though something nagged at me. How often was "test eventually" turning into "test never?"
Another obstacle on my road to successful unit testing was how to deal with databases. This wasn't just my obstacle, of course. It hung up loads of developers. The Repository Pattern was known, but not well-understood or encouraged in the Microsoft world. Abstracting the database seemed--and often was--tedious, time-consuming, and prohibitively maintenance-heavy. What kind of software was I writing all the time? Yep, database-centric.
Things didn't because easier with the adoption of Entity Framework (or Linq2Sql). When it came to testing, Microsoft's own documentation often recommended Repository Pattern, and sometimes Unit of Work.2 This was frankly a mess. Why? Because EF was itself built using Repository (DbSet) and Unit of Work (DbContext) patterns.3 So, developers were writing an abstraction on top of an abstraction.
Again, I was hampered by a couple of things:
- When a regular employee, I wasn't working in an agile environment.
- When a contractor, I was often constrained by client expectations.
Over the years, I've returned to TDD several times and have come to some tentative conclusions.
- For me, on a greenfield project, test-first shouldn't be introduced immediately, but only after going through my drafting/iterating process to help clarify how "the code wants to be written" (I agree, a squishy, woo-woo statement.)
- After that, TDD is a big help, primarily because it forces tests to be written in parallel to coding and forces useful abstractions and separation of concerns.
- Sometimes, though, TDD needs to be set aside while a coding solution is explored via a deep dive. Avoid premature testing. In these cases, the developer's mind needs to flow.
- Testing reveals architecture problems. This is another top value. In order for code to be testable, it needs to be well-architected using dependency injection and coding to interfaces.
I'm insisting on unit tests in my code, and forcing myself to use TDD in my workflow. This will require reading about modern TDD, and being confronted with terrific articles such as TDD Harms Architecture by "Uncle Bob" Martin.
TDD is not design. Some people want the acronym to be Test Driven Design. But TDD isn't designing anything, except in the very loosest sense that programmers are always designing a method before coding it. We at least imagine how it's going to work, and that's--again, loosely--design. Robert Martin says it emphatically:
The idea that the high level design and architecture of a system emerge from TDD is, frankly, absurd. Before you begin to code any software project, you need to have some architectural vision in place. TDD will not, and can not, provide this vision. That is not TDD’s role.
And goes on to say
However, this does not mean that designs do not emerge from TDD – they do; just not at the highest levels. The designs that emerge from TDD are one or two steps above the code;...
That's easy. xUnit.net. I've loved it since its early days. Now, I've often used MSTest because it's A) built-in, and/or B) it's what the development shop was already using. But I prefer xUnit.net and its features such as Theories.
Using one of the "Should"4 libraries makes unit testing even more readable.
Testing is hard. Unit testing is hard. But early, automated testing builds confidence in the system, and TDD provides a valuable feedback loop that stacks nicely on other methods such as continuous integration.
This isn't quite true. There was a framework, but my misunderstanding of why only public methods should be tested interfered with me using it.↩
Such as this untestable craziness: Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application (9 of 10) | Microsoft Docs↩
But not until EF 4.1, and mocking wasn't easy-ish until 6.0.↩