This is a big one. I’ll try not to apologize for its philosophical nature, because I know I can’t help it. That’s how I learn. So I’ll just say that if you’re into looking at things philosophically, I’d very much appreciate your perspective on this question.
What’s test-driven development really about?
I want to understand TDD. I am not asking whether I should test first, or how small the tests should be. I’ve read and conversed a lot about red, green, refactor and I’m learning to do it, day by day. I am as elated with a clear red test as I am with a well-earned green one. (Refactoring is harder to get, but that’s for another story.) I’m also not asking whether TDD produces nicer code, or whether it’s ok to only have unit tests holding up some of the code. (My dentist says “You don’t have to floss all your teeth. Just the ones you want to keep.) I don’t need to be reminded that code with tests embedded is way less scary to change.
Oh, and I don’t need to be convinced that it’s a good idea. I already love it.
So what am I asking, then?
I’m asking what we are doing when we test drive design. And I’ll divulge that I’m asking partly because I’ve been talking with @kaleidic about TDD (and BDD) as it might apply to functional programming, and realize that my curiosity is really about understanding benefits we gain from TDD, and how we gain them. Unit testing might look different applied to function-level programming, and if that’s the case, different how? And what can we change without losing the heart of what testing provides?
As far as I can tell, a unit test doesn’t prove that a given algorithm is “right”. For any given test, we can produce a pass without actually solving the problem in front of us. So the unit test doesn’t cause us to write “good” code. That has to happen inside us, doesn’t it?
Here’s an illustration, in case that wasn’t clear. I can decide I could really use the square of some number. I could write a test that asks if I get four when I send two. And I could get four because I have a method that multiplies the number it’s given by two. There’s nothing inherent in the test that creates the right code for the job. Yes, more tests might help, but when the solution appears, it won’t be because the tests produced it or made it inevitable. So far, the only way I know for the solution to appear is if somebody thinks it up.
Do unit tests offer a statement of the problem? Of the solution?
I’ve been working on this one for a while. I think I’ve concluded that they don’t do either of those things, but they do something intimately connected. They offer a picture of the solved-problem.
Walking on a balance beam, I look at the end of the beam, and let life emerge a way for me to get there. I’m thinking about what the solved problem looks like (me at the end of the beam, having not fallen off) rather than looking intently at the problem (avoiding falling) or the solution (which my body is better at creating than my thinker is).
When I’m solving a math problem (or other kinds, actually) I like to write out what I know and where I want to get with it. Given: Sue’s balloons. Sam’s balloons. Joe’s balloons. Result: the color of Joe’s balloons. That helps me think about the things in between—which are also givens of a sort, except that I haven’t noticed them yet. Once I notice the right ones, they’ll lead me down the path. So writing a statement of the solved-problem helps me begin to see how to get there.
My impression (so far) is that one thing I get from TDD is a statement of the solved-problem. Since unit tests are little, the steps between are easier to get hold of, but the test is still like looking at the end of a balance beam, and letting what needs to happen happen.
Do unit tests provide criticism?
Popper taught us that we can’t support theories by testing them. We can only poke at them in an attempt to break them. If we give it a good go, and the theory holds up, then we stick with it until it breaks, often in the face of a better theory that has come along. We don’t get certainty, but we do get strengthened confidence in our theories (or ideas).
So I imagine that unit tests also provide criticism of our code. We want to cause our tests to fail (when & where our code isn’t working). That is, given that our code is incomplete or broken, we want tests to tell us that, so we can distrust the code in question, and find new code that we trust (because it doesn’t fail tests).
This would mean that a good test is one that will break most easily if the code is bogus (or nonexistent), and will not break if the code is just fine, thankyouverymuch.
So here are the bottom line questions about TDD
- Have I missed some of the purposes that TDD serves? Which ones?
- How can I use TDD with skill, rather than just following some rote instructions, and hoping for the best?
- Given that TDD offers us teh awesum, how to extend that to functional/denotative/function-level programming? Can we? Do we want to?