The Devver Blog

A Boulder startup improving the way developers work.

Tips for Unit Testing

For the past few weeks, I’ve been doing a series of posts on my thoughts on unit testing. Although I originally published them in little, bite-sized posts, I wanted to collect them all here in one massive post for those of you with bigger reading appetites.

I also wanted to add one thought to sort of tie all these tips together. Unit testing is all about improving productivity. It’s important to realize that the ROI for testing looks something like this:

this graph is very exact

A very professional-looking graph. I guess ROI should really be ‘benefit’, but whatever, you get it.

If you are just getting started with unit testing, you’re at the bottom of the curve, so you’re going to sink a lot of time into testing without much benefit. Similarly, once you’ve done a lot of testing on a project, trying to test that last little bit may require more time than it’s worth. The goal of these tips is to help you maximize the benefit-to-time ratio, wherever you may be in this curve.


We’re big on automated testing here at Devver, but I know a lot of companies aren’t as into it. There’s been plenty written about all the reasons you should be writing tests, but over the next week or so, I’ll give you some tips on how to get started (and if you’ve already got some tests, how to improve and expand your test suite).

I can’t claim to have come up with these best practices, so I’ll litter this post with links to those resources that have taught me something.

A quick word about terminology. When I say “tests” I mean any type of automated tests, which may include unit, functional, integration or any other types of tests. When I say “production code” I simply mean the code goes into the actual product – i.e. the code being tested.

Tip 1: You’ll probably suck at testing
Writing tests can be frustrating at first. It is usually a lot harder and more time consuming than you’d expect. Unfortunately, some developers assume that the cost of writing tests is fixed and conclude that the benefits can’t possible justify the time spent – so they quit writing tests.

Writing test code is an art unto itself. There are a whole new set of tricks and skills to learn and it’s difficult to do correcty right away. Stick with it. The better you get, the faster you’ll write tests, and the more your tests will pay off.

Tip 2: Most code is not written to be tested
Another surprising thing you’ll find when you start testing is that your production code is not very testable. This isn’t surprising – if there were no tests previously, there was no reason to design for testability. This will make your first tests way harder to write and less valuable (i.e. they are less likely to catch real bugs)

There are a few tricks to get around this. First, try testing only new code or just test a smaller side project to start to get the hang of it. When you’re ready to start testing your legacy application, try the following.

1. Write a few very high-level tests. These tests will likely exercise almost the whole system and will interact with the application at the highest-level interface.
2. Refactor out one component of the application so it is more decoupled and testable
3. Continually run your high-level tests to make sure you haven’t broken anything major
4. Write more focused tests for the component you pulled out in step #2
5. Go back to step #2

If you need more help with this, pick up a copy of Working Effectively w/ Legacy Code. There is also some additional information here.

Again, stick with it. As you write more tests, your application will be more testable (bonus: it’s likely be easier to understand, more loosely coupled, easier to refactor, and more DRY as well!). As it becomes more testable, it’ll be easier to write additional tests. This creates a positive loop where things get better and easier as you go.

Tip 3: Test code isn’t production code
Another common mistake is to treat test code just like production code. For instance, you’d like your code production code to be as dry as possible. But in test code, it’s actually more important for tests to be readable and independent than to be dry. As a result, you’ll want your tests to be more “moist” than dry. Specifically, you’ll want to use literals a lot more in test code than you would in production.

In general, the most important properties of good tests are:

Independent – No test should affect the outcome of any other test. Put another way, you should be able to run your tests in any order and always have the same outcome. A corollary of this is that setup/teardown methods are evil (both because they increase dependence and they decrease readability)
Readable – The intent of each test should be immediately obvious (both by it’s name and by its code).
Fast – Each test should run as quickly as possible, so the entire suite is also fast. The faster the suite, the more you’ll run the tests, and the greater benefit you’ll get (because you’ll catch regressions quickly)
Precise – Each test should focus on testing one thing (and only one thing) well*. Ideally, if a test fails, you should know exactly what part of your production code broke by just glancing at the name of the test. Also, if your tests are precise, it’s less likely that a change in your code will require you to change many different tests. In practice, precise tests are short and only have one assertion or expectation per test.

*Note: this doesn’t apply to integration tests, which should make sure all components play nicely together.

Tip 4: Always write one test

When writing new code, it’s easy to avoid testing because it seems so daunting to test all the functionality. Rather than thinking of testing as an all-or-nothing proposition, try to write just one good test for the new functionality.

You’ll find that having just one test is much, much better than having no tests at all. Why? First of all, it’ll catch catastrophic errors, even if it doesn’t catch bugs in edge cases. Secondly, writing even one test may force you to refactor your production code slightly to make it more testable (which in turn, makes future tests easier to write). Finally, it gives you “test momentum”. If you have no tests, you’ll be inclined to delay testing, since there is more overhead to get started. But if you already have just one test in place, it’ll be much easier to add tests as you think of them (and to write regression tests as you find bugs).

By the way, don’t worry about testing at exactly the right level. Having one functional test is way better than having no tests at all. You can always come back and break the “bigger” test down into more targeted, precise tests.

Tip 5: Improve your tests over time

Here’s a terrible idea – decide you are going to spend a whole week building a test suite for your project. First of all, you’ll likely just get frustrated and burn out on testing. Secondly, you’ll probably write bad tests at first, so even if you get a bunch of tests written, you’re going to need to go back and rewrite them one you figure out how slow, brittle, or unreadable they are.

As they say, the best writing is rewriting. You should try out new techniques (and rewrite) old test code. But it’s OK to have patchwork tests.

You just found out fixtures suck? (they do). Or that those ‘setup’ methods make your tests less readable? Are you excited about using mocks? Great, apply your new technique to some new tests, rewrite a few old tests, and call it a day. Don’t try to rewrite your whole suite, because you’ll be kicking yourself when you rewrite your suite again after you decide technique X isn’t perfect in all cases.

Just like in production code, good practices take awhile to bake and prove themselves. See how maintainable, easy to understand, easy to read a new technique is. You can always move more tests over.

Tip 6: Don’t be dogmatic

There are a lot of best practices for testing that may or may not apply to your situation. Should you have one assertion per test? Should you use mocks and stubs? Should you use Test Driven Development? Or Behavior Driven Development? Should you do interaction or state-based testing? While all of these practices have real benefits, remember that their applicability and value depends largely on your project, schedule, and team.

Don’t be afraid to play, but don’t feel like you need to convert everything to the one, true way to test. It’s fine to have a suite that mixes and matches these best practices. In other words, context is king.

Tip 7: Be reasonable

There are lots of reasons why tests are great, but if your practices aren’t ultimately making your code better and you more productive, it’s not worth it. You have to always think about the return on your time investment.

There are domains in which automated testing is very difficult and doesn’t provide a lot of value, like GUI testing. I would recommend writing tests for the interface that the GUI calls, but actually testing that things show up correctly is quite tricky and error prone.

Also, 100% code coverage shouldn’t necessarily be your goal. As you get better at writing tests, I think you’ll find they provide a lot of value, but at some point, covering that last small percentage of code may require way more effort than it’s worth.

Tip 8: Keep learning!

Just like learning new programming languages makes you a better developer, learning about new testing approaches, libraries, and tools will make you a better tester. The state of the art of testing is changing very rapidly these days – new frameworks and techniques are released almost every month. Keep looking at example code and trying out new stuff.

For instance, here’s a few tools that you may not be using but are very cool: Heckle and RushCheck

Finally, if you want to learn more, subscribe to Jay Field’s blog – he has lots of good (if sometimes controversial) thoughts about testing.

And with that, I’ll wrap up this series on testing. If you have your own testing tips, please share them!


At Devver, we’re building some awesome, cloud-based tools for Ruby hackers. If you’re interested, sign up for our mailing list.

About these ads

Written by Ben

July 7, 2008 at 4:31 pm

Posted in Testing

11 Responses

Subscribe to comments with RSS.

  1. Tests should be DRY. If DRYing them up makes them less readable, you’re doing it wrong.

    Greg

    July 7, 2008 at 11:08 pm

  2. @Greg – whilst I partially agree, it is better to have moist tests than none at all. My tests start wet, then I get them moist and finally I try to get them dry. The point being that the test code only exists to find bugs in the production code. if wet code does that initially then I don’t see a problem with it. You can always refactor to your hearts content later.

    Jack

    July 8, 2008 at 2:25 am

  3. I have slowly become more and more a believer of unit testing. One thing I find myself doing is instead of writing little single scripts or going into the interpreter directly is writing little unit tests when I am exploring new code. I have done this a lot recently when working with Amazon’s simpleDB, write code to write an object to the DB, before hooking it into any of my application, I write a test and make sure everything is doing what I expect.

    website design

    July 8, 2008 at 2:24 pm

  4. @Greg – I tried, man, I tried. I worked hard to make my tests DRY for months, and trust me, it just created more problems than it solved.

    Readability was one problem, but more importantly, making tests DRY makes them lest independent, which is a maintainability nightmare.

    I’d say that if you find yourself needing to DRY up your tests (to reduce duplication), then your tests aren’t specific enough. If each test is verifying one specific thing, you shouldn’t need to write a bunch of helper methods.

    Ben

    July 8, 2008 at 9:32 pm

  5. Read xUnit Test Patterns, there are a lot of test patterns that have DRY underpinnings.

    Greg

    July 8, 2008 at 10:25 pm

  6. [...] Tips for Unit Testing – Ben posts a collection of good tips on unit testing [...]

  7. @Greg – thanks for the book suggestion, I’ll check it out.

    As always, I suspect it’s a balance. Some amount of DRYness in tests is probably a good thing (for instance a few helper methods for operations that really are universal).

    However, if you push things too far, you start seeing things like fixtures and setup/teardown methods, which have good intentions, but I think many people have found to be very brittle. That’s why I try to keep my tests somewhere in the middle (AKA “moist”).

    Ben

    July 9, 2008 at 7:25 am

  8. Good stuff… however I want to amplify the point that automated testing is more than ‘unit’ testing.

    We use dbUnit for integration testing of our Hibernate mappings and it has been a tremendous success. We migrated from an OODB to Postgres in a giant refactoring and dbUnit was vital to the effort.

    Michael Easter

    July 10, 2008 at 9:10 am

  9. Nice tips, a good way to prevent beginners from early despair.

    Pingback from the Typemock dev team blog – http://blog.typemock.com

    Doron

    July 14, 2008 at 5:30 am

  10. [...] Tips For Unit Testing: Ben has some great tips for unit testing, especially for those just getting started. For the management folks, he’s explained it with the best graphic ever! [...]

  11. [...] was one more blog, “Tips for Testing“, that reinforces the increased ROI of unit testing over time. You should at least skim his [...]


Comments are closed.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: