Testing Misconceptions #1: Exploratory Programming 7

Posted by james
on Monday, October 08
So, my last essay on testing was ycombinatored, and then reddited the next day. Cool! I'm honored to have so many people reading, and discussing my article. It's a pleasure.

I found some of the discussion very interesting. It seems like a lot of developers still don't believe in unit testing their code. In fact, many made arguments that questioned, or even outright dismissed the value of unit testing (for more such comments, see the reddit and ycombinator threads, or the thread of comments on the article itself). What surprised me most, though, was the number of misconceptions people have about what testing actually is, why we test, and how long it takes. Many, if not most, of the anti-testing arguments are based on entirely false premises.

In this on-going series, I'll put those misconceptions to the test (pun intended), and provide my take on what the truth is.

Testing Myth #1: I can't test first, because I don't have an overall picture of my program.

BTUF (Big Test Up Front) incurrs [sp] many of the same risks as BDUF (Big Design Up Front). It assumes you are creating artifacts now that will last and not change drastically in the future.
Yes, TDD implies that there is a more or less exact specification. Otherwise, if you're just experimenting, you would have to write the test and your code, and that's going to make you less inclined to throw it away and test out something else (see "Planning is highly overrated").
When I really have latitude in my goals, my code is just about impossible to pin down until it's 95% implemented.
How can you test something if you don't even know how or if it works? You need to hack on it and see if you can get things going before you nail it down, no?
According to this group, testing first is impossible because they're not sure exactly what they're writing. Some of them go so far as to equate testing first with big up front designs. The assumption, in both cases, is that writing your tests first means writing all of your tests first, or at least enough to require a general overview of your program. Nothing could be further from the truth.

It seems likely to me that this group's misconception stems from mixing up unit testing with acceptance testing. Acceptance testing, whether automated or manual, would require an overall specification for how (at least some major portion of) the system should function. Nobody is suggesting that you write your acceptance tests first.

Unit tests verify components of your program in isolation. They should be as small as possible. And, in fact, if your unit tests know enough about your program that they're starting to look like acceptance tests, their effectiveness is going to be diminished considerably. That is, you don't want your unit tests to have an overall picture of what you're building. They should have as little of that picture as possible.

Separating Concerns

Writing tests first doesn't mean you can't explore. It means that the exploration process happens in your tests, instead of your code - which is great! In your tests is where the exploration process belongs.

When you explore in your implementation code, you're trying to answer two questions at once: "What should my code do?" and "What's the best way to implement my code's functionality?". Instead of trying to juggle both concerns at once, testing first divides your exploration in to two stages. It creates a separation of concerns. You might even say that TDD is like MVC for your coding process.

You begin your exploration, of course, by putting together some preliminary tests for the first bit of functionality you're going to write. By considering the output before the implementation, you gain several advantages. The classic example, here, is that you get the experience of using your interface, before you've invested any time in bad API design ideas you may have had. But wait, there's more!

You also get the opportunity to focus on what your code will do. Before I began practicing TDD, I would regularly be almost all the way through writing a block of code before I realized that the idea just wasn't going to work. The thing is, when you're exploring, and you're focused on one or two implementation lines at a time, the result of the code becomes an afterthought. By spending that minute or two up front thinking about what should come out of your code, you'll save yourself a ton of backtracking, and rethinking later on.

That's all for today

I hope you enjoyed the first installment of Testing Misconceptions. I'd love to hear your feedback, or ideas for topics. Please feel free to leave them in the comments, or shoot me an email. Please check back for more episodes.

We don't write tests. There just isn't time for luxuries. 27

Posted by james
on Monday, August 27
Over the last few weeks and months, I've often had the displeasure of being reminded of the common opinion that writing automated tests slows down the development process. "We just didn't have time to write tests" or "Nope, we don't have any test coverage... we just didn't have time." are common expressions of this problematic trend in software development. In order to disregard something widely accepted as being a necessary practise, writing automated tests must really take forever, right? Wrong. I call this the testing-slows-us-down argument, and, frankly, I don't buy it at all. Here's why.

Everybody Tests

Everybody has to test their code. It's just that those of us who write automated tests write code to do our testing, where non-testers use humans (themselves, usually) to manually verify correct behavior. So, we can be sure that the testing-slows-us-down argument rests on the premise that manually verifying behavior is faster than writing automated tests.

Investing Time

The two methods of testing distribute your time investment differently. Since automated tests run very quickly, the time investment is made in writing them. Running the tests is nearly instantaneous. With manual testing, it is the opposite. Designing the tests takes nearly zero time, whereas actually running the tests takes a measurable amount of time each time you need the test. So, with automated testing, you get most of your time investment out of the way at the outset. Once it's written, it's written. With manual tests, your time investment grows each time you test for something. In order for the testing-slows-us-down argument to remain valid, the total time investment made on manual testing must be less than the time investment required to write the automated tests.

In these terms, the testing-slows-us-down argument can be expressed as follows: The time it takes to write the automated tests for a feature is greater than the total time that will be spent manually testing that feature throughout the lifetime of the project.

Since one of my goals for this article is proving the "no time to test, we need to launch our product" people wrong, I want to show that this (short-sighted) argument is as, if not more, flawed than the testing-slows-us-down argument. I'm going to phrase the product-launch argument as follows: The time it takes to write the tests for a feature is greater than the total time that will be spent manually testing that feature until product launch.

Breaking Even

If it takes twenty minutes to write your automated tests, and one minute to test your feature manually, the break even point for an automated tester is when they've run their tests twenty times. After that point, automated tests become cheaper and cheaper (per test). Manual tests continue to become increasingly expensive over their lifetime. So, the testing-slows-us-down argument can be rephrased once more, making use of our new terminology: The automated testing break even point will not be reached before product launch.

Developers Developers Developers

Because the cost of manual testing grows considerably each time you run your tests, bringing on extra developers adds a multiplier to your (growing) test cost. Anytime any developer wants to merge their branch back in to trunk, they're going to have to take a look over the whole source tree to make sure they didn't break anything. This means, for the reasonably attentive developer, testing code that they didn't write - going over all of the features. It's the manual testing equivalent of running the whole test suite. Not only is this a time-consuming process, but it is incredibly error prone.

Debugging Time!

No developer can be reasonably expected to remember, and meticulously verify all of expected functionality from an application, at each check-in. That's a superhuman expectation. You might even say it's a job better suited to a machine? Seriously, though, code is interdependent. Changes in one area can have impacts all over the application. When relying on humans to verify application behavior, a lot of bugs are going to slip through the cracks.

More bugs means more time spent debugging, which, incidentally, means more time spent testing, for the manual tester. Moreover, it's not uncommon for the same bug to surface repeatedly. We've all seen it. It's called regression, and it's why us automated testers have something wonderful called regression tests.

With every bug, automated testers (like always), make a one time investment. Since automated testing is far more likely to prevent regressions than manual testing, the time benefits here are two-fold. First, once the regression test is written, it's written, and the behavior doesn't need to be repeatedly verified by hand. Second, the bug is far less likely to resurface. Manual testers may argue, here, that they are capable of adding the regression tests to their regular passes over their code, keeping the bugs out, just the same. While this may be the case for the most superhuman of individuals, the liklihood that an entire team may be capable of such incredible manual testing is very low.

So, as a general rule, I think it's safe to say that automated tests significantly reduce debugging time, by providing a much higher degree of accuracy, and acting as a powerful weapon for preventing regression.

Release Already

It seems pretty clear to me, after a thorough analysis of the testing-slows-us-down argument that its proponents are, at the least, misguided. Automated testing is at least as fast as manual testing. In writing this article, I thought a lot about why so many people have this common misconception. I think it mostly stems from one of the following.

The most common cause of this misconception is likely naivete. Many of the challenges that really bring out the best in automated testing (and, consequently, the worst in manual testing) are far more evident with bigger projects. While I maintain that automated testing is at least as fast as manual testing on all projects, it's likely that bigger projects will see much bigger benefits. The problem, here, is that big projects often start out as small ones. And, unfortunately, growing pains can cause some of the most difficult problems with keeping software working properly.

My assumption is that the ones who aren't naive are just lazy. Learning how, what, and when to write automated tests can be a difficult undertaking, but it's well worth it. Like writing the tests themselves, a little bit of up-front investment in your skillset will save you loads of time, and headache later on. So, do yourself a favor, and learn to test. You'll thank yourself for it.

Update: See my first response in a new series to some of the discussion surrounding this article.

When to test

Posted by james
on Friday, July 13

Always

A lot of people seem to struggle with knowing when to test their code. The simple answer is always, and everything.

Always test every line of code that you write.

Know that your code works (and interacts) as expected


First and foremost, writing tests provides you with a safety net. Making changes and refactoring code becomes a calculated maneuver, instead of a guessing game. Stop asking yourself: Did I break something? And know that if the tests didn't break, you're safe.

When you first write a block of code, manual testing might be sufficient to ensure that it works correctly, because its purpose is fresh in your mind. But, what about all the code it interacts with? Can you possibly remember test for all of the things you might have affected? Can the next user of your code remember to test for all of the things they might have affected? Of course not, and that's exactly why it's up to you to build those tests for them.

Living proof


Take a look at this snippet that got checked in to svn today:
belongs_to :user, :company
Had the author of this code written tests for his code, or even run the generated test suite for his model, he might have realized that he'd made a typo, and that the belongs_to method doesn't accept multiple models as its first argument, and that the correct code would have looked like this:
belongs_to :user
belongs_to :company
Test first, test always.

3 ways to improve your bullshit methodology

Posted by james
on Wednesday, July 04
Marc Cournoyer writes a great post, detailing 5 ways to know whether your methodology is working (or whether its bullshit), specifically re: TDD/XP.

I have been trying to improve my TDD practice for some time now. I am slowly getting better at writing tests first (and just writing tests, of course), but it does represent quite a significant shift in thinking. And, when you're used to writing the code first, as Marc says, that's where you're naturally going to go when the pressure is on. So, how do we stop this behavior? How do we get in to the test-first zone?

Here are 3 things that have started working for me:

1. When you're stuck on what to test, make a list of possible inputs and selected outputs


One of the biggest challenges for me has been overcoming my tendency towards doing something like exploratory testing of my own code, as I write it. This was the bad habit of not knowing what my code was going to do, before I wrote it. I'd spend some time fiddling around with a few lines that I thought might accomplish what I wanted, and looking at output, until it looked right (sound familiar?). With TDD, you have to start by thinking about what your code will output.
Take a second before you write any tests, and make a list of input parameters, and output expectations. Once you have this list, you'll see that it is much easier to know what you need to test for, and it will even help you write your code afterwards, too. This is an easy one, but it illustrates the point:
# PostsController#show
#
# Inputs:
#   params[:id]
# Outputs:
#   @post <-- contains the Post which corresponds to the params[:id] input parameter
#   OR
#   throws ActiveRecord::RecordNotFound if Post w/id == params[:id] does not exist

2. Make it an exercise and practice, practice, practice


Take 2 hours at home, in your spare time, and give yourself too much to do. Outline more features than you can realistically implement in that timeframe, and go for it. Racing the clock helps, because that's what you'll be facing on a real project. It sounds cheesy, but it has really worked well for me.

3. Use autotest

(the direct link is here, but it seems to be down right now)

The easier, and more comfortable testing is, the more likely you are to do it. Autotest watches all of your files, and when one changes, it runs the appropriate tests. All you have to do is save the relevant file, and look over at your terminal window to see the results. No more hitting refresh in your browser, or even running tests manually.
Marc also told me about CruiseControl.rb. I haven't had a chance to play with it yet, but it looks very cool. The idea is that if somebody checks something in to source control that breaks any tests, they are alerted immediately. Anything that makes testing easier is probably better.

What methodology-improvement tips do you have?