Testing Hypothetically
Today was an interesting day. After wrangling the parts together for a nice AT from the inventory of existing craziness and struggling with early UTs for most of the day, I became utterly disgusted. I had not been pairing, because sometimes I want to see what I've learned and what I have not learned. Solo time may not be the agile ideal, but sometimes I need to ponder what I have learned and hypothesize what I have not learned.
My task was really very simple, but the code I was working through had many moving parts, deep inheritance hierarchies, indirection through delegates & events (callbacks and callback registries) and level after level of code. Just phrasing the test in the language that the existing code provided me was more than I could hold in my poor leaky head at one time. It was a simple problem in a messy body of code. Finally I called over a partner who was wiser than I in the ways of TDD.
The partner didn't really bother to figure out how the code was structured. He wrote the test as-if the system were structured properly underneath it. There was some fidgeting with mocks and some puzzling issues with their usage (never satisfactorily explained, BTW), but for the most part he did not do what I had done. Rather than figuring out how to make the test exercise the existing system and then refactoring it to bring it up to a higher standard, he just started as if he could do as he pleased. Then (with more mock-savvy than I currently own) he pushed this into the code.
As we finished (perhaps a measly ½ hour later) it dawned on me that the difference between our approaches was one of the things that had been making me nuts for months. He was cutting a swath through the design to get a feature in place. He coded his tests to a hypothetical simple system rather than the mess I was trying to unravel. He got done much more quickly than I could.
I don't know that I ever heard it expressed that you write your tests to the hypothetical, rather than the actual. Or maybe I wasn't in a place where I could hear that message (having not digested much in the way of TDD literature). Either way, it seemed to finally coalesce a lot of the things I've been hearing, seeing, and (lately) reading.
Because I was dealing with the system as-is, I was sensitive to all of the vagaries of that design. I was painfully aware of the ugliness of the design (though the code isn't so bad in style, function length, and naming). I dealt with the indirectness of method invocations and long inheritance chains. I was painfully aware of all the warts and wrinkles.
Because some of my partners dealt with changes, they weren't really aware of the larger ugliness. But think about that for a minute. They were able to function as if that ugliness and complexity were not really there. Oh, sure, that sometimes breaks down and one has to clean up the design as-written to match a design as-desired (refactoring mostly), but it doesn't really affect them normally. As long as the system allows them to make changes readily, there is really no problem in their field of view. OTOH, I'm so aware of problems that I am sometimes petrified and need a partner to help cut through it.
The difference between us is part of the difference between a TDD approach (theirs) and an old OO designer (me), and it points out strengths and weaknesses. Now, if I can learn to navigate changes their way and still sense design without being overwhelmed, I'll really have something.
!commentForm
"He was cutting a swath through the design to get a feature in place."
Ho does this work with the idea of test-code-refactor? was he able to refactor effectively or only to "to get a feature in place"?
Ho does this work with the idea of test-code-refactor? was he able to refactor effectively or only to "to get a feature in place"?
After you were done, and the new "as-if" tests passed, what then? Did you have a codebase with a lump on the side, or were you able to to refactor to smooth out the intervention? Is the resulting system better or worse as a result?
At the end of every story, Brian Marick asks, "What's now easier to do?" What's the answer for this story, Tim?
It's not really like "a lump on the side", but rather like "a smooth spot on the lumpiness." We ended up no worse than we started. The change that went in in 1/2 hour is a change that was "going with the flow". It didn't make the dialog any more reasonable or more sensible, but the complexity that was there was not increased; it's not worse. It's better in that it does the new thing as well.
This has me questioning some axioms. We know that beautiful is better than ugly, simple is better than complex, and explicit is better than implicit (see <cite>Zen Of Python</cite> by Tim Peters). But what about when you don't experience the ugliness? What if you have a set of habits whereby you don't slow down because of it? Is that a kind of goodness unto itself?
I've relied on good design and flexible code until this point in my career. I've tended to go for rewrites when faced with design that is ad-hoc, implicit, and polytentacular. Now here's a new way to be productive, where people don't even notice that it's all of these things. If the design doesn't slow you down, and the tests pass, how bad is the design again?
Admittedly, David explained to me that with TDD you're always reworking yesterdays' decisions to make them compatible with today's decisions. Whatever shape the code is in, it was shaped by the requirements of its day. Sort of like not blaming Newton for failing to anticipate Einstein.
So as I'm coming to terms with TDD, I'm also trying to determine how much it changes the world as I know it.
This has me questioning some axioms. We know that beautiful is better than ugly, simple is better than complex, and explicit is better than implicit (see <cite>Zen Of Python</cite> by Tim Peters). But what about when you don't experience the ugliness? What if you have a set of habits whereby you don't slow down because of it? Is that a kind of goodness unto itself?
I've relied on good design and flexible code until this point in my career. I've tended to go for rewrites when faced with design that is ad-hoc, implicit, and polytentacular. Now here's a new way to be productive, where people don't even notice that it's all of these things. If the design doesn't slow you down, and the tests pass, how bad is the design again?
Admittedly, David explained to me that with TDD you're always reworking yesterdays' decisions to make them compatible with today's decisions. Whatever shape the code is in, it was shaped by the requirements of its day. Sort of like not blaming Newton for failing to anticipate Einstein.
So as I'm coming to terms with TDD, I'm also trying to determine how much it changes the world as I know it.
There's a difference between ignoring design problems and staying focused on something small. Tim had tried to understand the entire system from top to bottom before even writing the first test. My approach is to put on blinders and write the first test first, just as I did on the first day of the project.
Using that approach, I don't get hung up on all of the details up front. If the class I'm testing needs a service, I write an interface for it, mock it and put writing the service on the back burner until the class I'm dealing with is done. Then I switch contexts and move down to the services the first class needed. There I take the same approach. This is nothing new or original. It's just part of what I understand TDD to mean.
This approach sometimes introduces duplication, or "almost-duplication". Maybe the service I added to the second layer is very close to a service that already exists. That's where the blinders become porous. The next step is to refactor to eliminate the duplication. Test-code-refactor. It's the same whether it's the first day of the project or the 100th.
Using that approach, I don't get hung up on all of the details up front. If the class I'm testing needs a service, I write an interface for it, mock it and put writing the service on the back burner until the class I'm dealing with is done. Then I switch contexts and move down to the services the first class needed. There I take the same approach. This is nothing new or original. It's just part of what I understand TDD to mean.
This approach sometimes introduces duplication, or "almost-duplication". Maybe the service I added to the second layer is very close to a service that already exists. That's where the blinders become porous. The next step is to refactor to eliminate the duplication. Test-code-refactor. It's the same whether it's the first day of the project or the 100th.
Actually, it was just the one dialog and it's sub-dialogs and the interactions that affect the save button that I was trying to understand, but diagramming that took about two sheets of paper with me writing small. I didn't even go beyond just those classes and their immediate superclasses.
That's why it's so amazing that you're cutting through all that and working with agility. And, of course, I've never done TDD on day one of a project. I've only joined in-progress. I don't know what it's like on the first day.
But it' was amazing that it was so easy when the tests weren't written to work with the existing code, but with hypothetical methods and capabilities. It was incredibly much easier.
That's why it's so amazing that you're cutting through all that and working with agility. And, of course, I've never done TDD on day one of a project. I've only joined in-progress. I don't know what it's like on the first day.
But it' was amazing that it was so easy when the tests weren't written to work with the existing code, but with hypothetical methods and capabilities. It was incredibly much easier.
Man, this post is very helpful in having a hopeful understanding SD as a whole. I have found myself reluctant to make the change [...] to software-developer due to the perplexities I have seen my colleagues get themselves into ...to the point that they're spirit is broken. Your sentiments are encouraging. |
I've been working with a similar Mock, TDD maven for about six months, and you've coalesced a similar set of ideas in my brain. The paradigm shift is similar to the shifts I made from spaghetti to structured, and structured to O-O. Very different world-views though build on the same blocks. I think it involves a recognition of the emergent properties associated with the new world-view and then making the leap to the next level.
Tim, I've spent the last few hours reading your blog. I've been trying to wrap my head around TDD for the last year or so (off and on). After a few failed attempts (I work alone) I knew I wasn't 'getting it', but was determined to continue searching and reading and trialling until it sunk in. I may be 42 and a relative novice to programming (only 5 years since I switched careers), but the stubborn pigheadedness my wife loves and hates wouldn't allow me to admit I was just too 'dumb' to get it.
Today, and more particularly with this article, I had a breakthrough. The seas have parted and I 'get it'.
I can't put my finger on exactly what 'it' is just yet and I know it sounds stupid... but my pessimism has turned to optimism.
Thanks for putting your journey into TDD down in this blog. Between your articles and the other discussions on this site I feel I have learnt a great deal.
Maybe one day I can buy you all a beer.
Cheers,
Glen G (New Zealand)
Today, and more particularly with this article, I had a breakthrough. The seas have parted and I 'get it'.
I can't put my finger on exactly what 'it' is just yet and I know it sounds stupid... but my pessimism has turned to optimism.
Thanks for putting your journey into TDD down in this blog. Between your articles and the other discussions on this site I feel I have learnt a great deal.
Maybe one day I can buy you all a beer.
Cheers,
Glen G (New Zealand)
Add Child Page to TestingHypothetically