Back to topics from the Elegant Code Open spaces lunch. And this was a doosy.
We were talking about getting developers to embrace TDD in their development methodologies. As it so happened, one of the developers that need to be embraced was there — which always make for a more interesting discussion.
His main argument against TDD was that it almost required interfaces to do it. Why do we need interfaces? Because in order to mock something, it has to have an interface or an abstract class (using Rhino Mocks and Moq. Sorry TypeMock, they aren’t breaking open the wallet). So to do TDD, it is almost required to have an interface in every non-domain class in the system.
Why didn’t he like interfaces? Partially because it was more work to deal with them. Using resharper you can’t use Ctrl-B to go to reference, you have to use Ctrl-Alt-B to Go to Implementation. Then you have to keep everything in sync. And if you aren’t using ReSharper it is much worse. Lets not be petty about this, implementing interfaces and keeping them up to date can be a bit of work, EXPECIALLY when the interface will only be used by one class. Now it is just plumbing.
This isn’t the only compromise we make for TDD. We also remove most private methods. We make methods private by not adding them to the interface. This becomes even more pronounced when you look at other languages (cough**ruby**cough) that don’t require some of this plumbing.
Here was my argument for interface and tdd.
First off, if you learn to use ReSharper effectively, dealing with interfaces becomes almost no work at all. There is a refactoring called “Pull Member Up” that adds members in a class to an interface. And if you add something to the interface directly, alt-enter will implement a stub on all of the classes that implement the interface. Really, it can be almost pain free.
Second, from my Head First Design Patterns book: Program to interfaces, not implementations. This is also in the GoF (Gang of Four) Design Patterns book. Interfaces are about not being tied down to a particular implementation. Make things swapable. It is also about making sure your class doesn’t care how any dependency class is implemented. You can also read these points on the Wikipedia page on Design Patterns:
- clients remain unaware of the specific types of objects they use, as long as the object adheres to the interface
- clients remain unaware of the classes that implement these objects; clients only know about the abstract class(es) defining the interface
Third, this is another way to combat code complexity. When you design an interface you make yourself think a bit more about the implementation (at least I do), think about how you want the code to be used. You don’t always get more done by write more code faster. It often pays to take a moment to think about what you are doing. When you do this you can often get away with writing less code and create a better implementation.
Forth: more time spent early on with TDD mean less time spent later in the debugger. Less time you have to spend in the debugger the faster you can release your product. Now, I’m not a 100% TDD guy. There are some things that are just so hard to test that you start diminishing returns. But Presenters, and services should be very testable.
Fifth: code reuse. I have had much better luck with code reuse do to TDD than I have have without it. Before TDD I would try to share classes around and end up having to untangle a host of classes to get the code reusable. Post TDD, much less work. With effective use of IoC containers it is almost no work.
Sixth: If you want the goodness of an IoC, stick to creating interfaces. That statement isn’t 100% true, but because of limitations in the .net framework and our tool set, this is what we have to do.
Finally, one of the last arguments against TDD that I heard that night was “but I have to ship my code on time”, as if we TDDer’s get a pass on timelines. Dude, if you’re reading this, I’m sorry for jumping all over on that one — but you deserved it.
Let me take this opportunity to respond in kind.
First off, the question up for discussion was (paraphrased): “How can I reduce friction involved in adopting TDD on my projects.” My main point is that one of the friction points – for me – in adopting TDD is the necessity to introduce changes in your design that exist SOLELY for facilitating unit testing. My primary example – and forgive me for not coming up with too many others off the top of my head – was creating interfaces that are only implemented once. Please do not misrepresent my argument by claiming that I dislike interfaces.
(I will like to point out right now for the readers that this discussion is centered on .NET, C# particular – my response would be different for a dynamic language like python or *cough*ruby*cough*.)
Interfaces are good – when they are used appropriately. To tell me that every class should implement an interface is, in my opinion, an insult to classes and the encapsulation principle of OOP. Every class already implements an interface *by definition* – the set of public members that it exposes to it’s clients. And if you don’t want to expose that method in the “interface” you make it private. Adding an explicit interface that is only going to be implemented by one class is redundant, and creates what, to my mind, is unnecessary mental baggage. Sure, you can limit this a bit by using resharper (yes, I use resharper, no I didn’t know about that alt-ctrl-b, I’ll add it to my aresenal, merci bien), but why should I have to?
But enough about interfaces, and back to my original point: Making concessions in your design to facilitate unit testing is a source of friction for people trying to implement TDD in their own teams. Part of this problem is that the tools for TDD are not fully mature yet. Another part of this problem is that the .NET library is full of sealed and internal classes that can make testing a royal pain in the ass. Just look at all the stuff that went into the new System.Web.Abstractions to facilitate unit testing of ASP.NET MVC and System.Web.Routing.
So, to the originator of the question: If you want people to adopt TDD – make it as easy as possible to do so. Give people the best tools possible, teach them to use the tools, and provide good examples on some of the tricky spots. The tools are getting better… and with any luck, more of the stuff coming from the MS firehose will be like MVC. As this happens, more and more developers will “see the light” and hopefully critical mass will be achieved. This is the only way it will happen. Trying to force something on programmers is like trying to herd cats – the more you push, the more they are going to ignore you.
And this brings me to another issue that I think causes friction. Too often do I hear TDD being sold like a religion: “My Way is the only Way, and if you don’t do it my Way than you are going to hell.” This kind of attitude does nobody any good, and only serves to turn a lot of people off. I am not accusing the author of this article of this, but I have seen it before, and there is no more sure-fire way to kill TDD than to claim it is a silver bullet. It simply is not.
Finally, I would like to apologize for coming off as saying that TDDer’s get a pass on timelines. That wasn’t what I meant at all. What I meant was that if you compare testing up front to debugging later, then some mention of a client’s priorities is important. To paraphrase a common idiom: You can have feature complete, fully tested, or done on time – choose two. It has been my personal experience that many of my clients are more accepting of being feature complete by deadline than dropping features due to unit tests. Or, in other words, time spent debugging later is, in some cases, “cheaper” than time spent testing up front.
But then again, I’m only a lowly ASP.NET developer,
~The Dude abides
Hey man, thank you for responding.
I’m going to start with the end of your response first. Specifically: “You can have feature complete, fully tested, or done on time – choose two”
There are a couple of items at work here. First, what do you mean by fully tested? To steal another common idiom (from Tony Rasa earlier today on another post): “simple as possible, but no simpler”. I apply the same thing to my tests. I test enough to be sure that the code works under normal circumstances (the minimum). Then as I encounter problems I add more tests.
Now how long do you think it takes to create those first tests? An extra 5 minutes? Maybe 10? If we were talking about extra hours I would agree with you. But for extra minutes? No.
Second, your code quality is tied directly to your reputation and the reputation of your company. The more bugs your software has when it releases, the doubt your custom has on your abilities. We are both consultants. Our jobs hang on our reputations. As such, it is in your best interest to always produce the highest quality code you can.
Third: in the end you still have to do integration testing (testing the entire product, by hand). When testing the entire product, how much of the code are you really testing? How much work is it to test all of the logic in all of the layers of your product? How assured are you that you did once you are done?
The tests are there as a quick sanity check.
As for TDD as a religion. There are many religions in CS. TDD, DDD, BDD, NHibernate, IOC, Agile, etc. Many of these are supposed to make you uncomfortable. It is absolutely on purpose. The reason is to force you to think about how you write code (not just about the code you are writing).
I brought this up before, the way we made software in the past is not good enough. We need to do better. These new religions do not get us there either, but each one gets us a little bit closer. Probably the worst thing that all of these ideas have going for them is a mass of adherents that have no debate skills (but they can all yell).
Oh, and I’m just a lowly .net developer as well.
If you don’t have tests showing that it works, how do you know it’s done?
Also, I believe that if you really don’t want to use interfaces because you don’t consider test code to be a first class citizen (and therefore the fact that your test code and your production code both use your class somehow doesn’t equate to your class being used in more than one place), you can still use tools like TypeMock to easily do your unit testing.
If you do decide that your tests *are* code and *are* important, then suddenly all of your classes serve at least 2 masters, and therefore it is appropriate (in many if not most cases) to apply interfaces to them.
I think I’ll start with the beginning of your response first… just to be contrary. 😉
What I mean by fully tested is fully tested – for some value of “fully.” I’m being purposefully vague here because I think 100% code coverage is a futile waste of time, and I think most people reading would agree. However, when I’m writing code, I am also doing some kind of testing to verify that I am meeting the requirements of whatever feature – otherwise I wouldn’t be feature complete either. So what I mean by “fully tested” is somewhere between 0% and 100%.
I realize that is pretty confusing, so I’ll try to clarify by rewording my statement: You can either spend your time writing application code, or writing tests for that code. Given a finite amount of time (and assuming that amount of time isn’t enough for both) you have to choose either to write less application code, or write less tests. Personally, I tend to choose the latter option.
I realize code quality is tied directly to my reputation and my company’s reputation. But so is ability to deliver on schedule. Depending on the client, and their expectations, all the testing in the world is useless if you miss the deadline. The trick, of course, is to find some sort of balance… and as we both know, that can be difficult.
I’m a little curious on your definition of TDD, though. What you are describing – writing some sanity checks and regression tests – is exactly what I do now, but that isn’t what I’ve understood TDD to be. As I understand it, TDD is the process of designing software by writing the tests first. I want to make sure we are discussing the same topic.
I have to disagree with you that making people uncomfortable is a good thing. At least if your goal is to try and convert people to your cause, and if you truly believe that TDD is as good as you say it is, then you should want more people to adopt it. And treating it as a “your way is wrong, my way is right” kind of issue is no way to win converts.
“Probably the worst thing that all of these ideas have going for them is a mass of adherents that have no debate skills (but they can all yell).” Exactly.
I posted some more thoughts on this here:
http://stevesmithblog.com/blog/interfaces-and-testing/
“If you don’t have tests showing that it works, how do you know it’s done?”
I’m trying really hard to be nice about this, but I’m really tired of hearing this cliché. There are many forms of testing; what I thought I was discussing was unit testing and TDD in particular. There are many ways to know you are done – when the client is happy is the one I usually go by.
“Also, I believe that if you really don’t want to use interfaces because you don’t consider test code to be a first class citizen.”
You’re absolutely right. I don’t consider test code to be a first class citizen. I doubt many people would disagree with me if I said it is a bad thing if your production code had a dependency on your unit test DLL. That to me makes it less than a first class citizen – if it were to suddenly disappear, your customers wouldn’t know the difference. I’m not claiming that it’s not important, however.
Come on Steve, you never finished anything before TDD?
I could guess that The Dude would know he was done when he finished integration and acceptance testing.
No amount of unit tests can tell you you are done or even that your system works, only that the code you have written functions as you think it should.
I for one would like to hear a better answer to the original question, if all there is to offer is “your code sucks because you don’t test” then I’m not sure I’m sold either.
@Jason: Ok, this is getting fun.
My definition of done is the client is either happy with the software or out of money (this usually comes from change request — ie. “I know that was what I wanted then, but this is what I want now”).
My question on “fully tested” was in response to experience in teaching TDD. Contrarian developer tend to look at TDD and say you are not done until every little edge case is accounted for for every class (which is impossible). Also, we are in agreement with TDD. It is test first, and Test Driven Design.
Side note: this is one reason I like the notion of BDD (Behavior Driven Design). You are validating the behavior is correct. Less on the notion of testing.
Also, a bad architecture, or bad testing methodology can be a major drain on productivity. Which gets to what some of your point is really after, which is:
“show me TDD done well, and then I’ll believe you.”
Which gets much harder, because few of us are working on open source, and none of us have time to setup sample applications. Although, CodeCamp Server is supposed to have some very good unit test metrics.
Finally, on the reputation part. That really comes back to priorities — which are really a company policy more than a personal preference. My priority is for tested, working software. I would rather be a bit late than release a bug.
This can also get back to what type of software you are writing. If it is accounting software…no one can afford a money bug. That has to work the first time.
@Chris: I think we’re coming to an agreement here.
My current policy on unit tests is to test the tricky code, and the important code, and to write tests as issues become apparent. Worse than a bug is a regression bug…
I am not overly familiar with BDD – I’m still trying to figure out what the difference is. I must research further.
“Also, a bad architecture, or bad testing methodology can be a major drain on productivity. Which gets to what some of your point is really after, which is:
“show me TDD done well, and then I’ll believe you.””
I think you hit the nail on the head with this paragraph. A bad design, or a bad test can be worse than no design or no test at all. I think a lot of people have had bad experiences with unit tests, get frustrated, and write it off as useless. Hence, friction.
I’ve been contemplating putting together an example application using MVC, and some of the things I’ve been learning by using LTS in my last 3 projects. Making this easy to test would be one of the primary goals of the assignment for myself. Of course, as you said, it’s hard to find time…
Finally, it is my priority to have working software too… I tend to be a perfectionist about that too… but it really comes down to the priorities of who is paying the bills.
Good discussion,
~jw
@Jason: this is kind of a separate topic — teaching style.
First off, there are not many good teachers out there. But there are a lot of loud people. Just as with religion, I would differentiate between Bible thumper and apologists. I strive to be an apologist. But I also see the need to be confrontational from time to time.
Not all of my best teachers were the coddling type. Some of them were confrontational. They did this just to get the thought processes flowing. It doesn’t work for everyone, but it does work. I know lots over VERY good preachers who do just this.
As you know, making people better developers is more than TDD. Much more. TDD all by itself wont magically make people better, but TDD alongside better practices often start the process.
Which leads to one of my personal items: always be improving, always be learning. At no point should you be satisfied with where you are.
I think it’s pretty obvious I subscribe to the confrontational theory of teaching. One of the reasons I spoke up on Tuesday was to play devil’s advocate – it sounded like everyone else in the room agreed with each other, and nothing gets done in an echo chamber. I like to argue – I don’t see it as a bad thing.
My comments on “religion” in this industry has more to do with … leadership style. Tony is really good at coming up with existing names for the ideas that I have, and has told me that what I’m trying to convey is known as “Falling into the pit of success.” I’ve always found that if you want someone to do something, the best thing is to make it easy for them. I don’t consider it coddling, but rather subtle coercion.