Quality Coding
Shares
Emoji: Label

Unit Test Naming: The 3 Most Important Parts

Shares

​You run your unit tests, and get a failure. How many times have you had to dig through the test code to try to understand what it’s complaining about?

Why does this matter? Because every time you have to read test code, you lose precious time. The feedback cycle lengthens, and you’re breaking your stride.​

Instead, I try to code using fast feedback. When I run unit tests and get a failure, I want to understand what I broke. And I want to do this without reading the test code. The test name alone can give you the information you need.

In this post, I’ll describe the unit test naming pattern that can give you feedback from the name alone.

What’s in a name? — Romeo and Juliet, Act 2, Scene 2

More...

Good test naming: three elements

​Instead, let’s put more information into the test name. When a test fails, I want to know three things:

  1. What operation are we testing;
  2. Under what circumstances; and
  3. What is the expected result?

Disclosure: The book links below are affiliate links. If you buy anything, I earn a commission, at no extra cost to you.

Roy Osherove, author of The Art of Unit Testing, provides a good unit test naming style that incorporates these three elements. Here’s how he describe it in a blog post Naming standards for unit tests:

UnitOfWork_StateUnderTest_ExpectedBehavior

In behavior-driven vocabulary, the same three parts are expressed as given/when/then, just in a different order.

Applying this naming to XCTest

I have adopted this unit test naming convention, with slight changes:

  • Apple’s XCTest framework still lacks a way to annotate test methods. So we have to start with a test prefix.
  • Then I describe the “unit of work.”
  • I usually begin the “state under test” with the prefix with.
  • I usually begin the “expected behavior” with the prefix should.

All told, I apply my modifications to Roy Osherove’s template to get test names that look like this:

It may look odd to combine camel casing with underscores—a sort of “mutant snake camel case.” ?? Some folks can’t stand it! You may object that it goes against Swift naming conventions. “If it’s Swift, a function name must be camel case.” In production code, yes, because a function name should express one idea.

But for a test name, we’re trying to express three things. We often want to test the same operation, but with different inputs. I find the underscores are clear separators, dividing the name into its distinct parts.

​My test names don’t follow this template slavishly. If the unit of work is obvious (from the name of the test class), I may omit it. If the expected behavior is obvious, I may omit it. But whether explicit or implied, the three elements must be present without guesswork.

In the next section, let’s look at how we can lean on implied elements.

Should the test name include the method name?

How much of all this needs to be explicit? In particular, should the “operation” be a method name? Sometimes. But we need to guard against creating noise.

Dave Schinkel of WeDoTDD.com says that method names have no place in test names:

When any developer… reads test names… to get a handle on a certain area of the codebase, they shouldn’t be tripped up by having to read back implementation details such as method names…. Adding implementation terms just adds noise to the test name. That just makes it more for your eyes to stumble over, keep that stuff out of test names!

In other words, we should aim for test names that describe behavior, not implementation.

Consider the Bowling Game TDD Kata. The object of the exercise is to score a game of bowling. Instead of a test name like

 test_score_withTwentyZerosRolled_shouldBeZero

it’s easier to call it

 test_gutterGame

The simpler name assumes more domain knowledge about bowling. If the coders share that knowledge, we can use a simpler name, making the three parts implicit:

  • We already know we’re testing how to score a game of bowling.
  • A “gutter game” means every roll went in the gutter. The ball didn’t knock down a single pin.
  • The score for this scenario is zero.

The rules of bowling are widely known. But this isn’t true for most domains. When in doubt, be explicit and spell out the operation, the input, and the outcome.

Can we really describe the “unit of work” without using the method name? Yes, this can work when testing small types, like something with two non-private methods. When we’re uncomfortable taking this step, it could be that our type is too big. Following the Single Responsibility Principle should result in smaller types.

Then when should we use method names? When the method name adds useful information. Much of what we do as iOS programmers is interact with Apple’s frameworks. A framework means we provide methods for Apple to call. iOS developers build up knowledge about what each method is for, and there are quite a few. For such code, it can make sense to include the method name—or some summary of it—in the test.

Pick a unit test naming convention that works for you

Whatever naming style you choose for unit tests, try to communicate the three parts. Choose a balance between verbose and terse that works for your team. You can even change your mind, and rename your tests. Aim for clarity, and keep improving names as you discover better ways to do so.

How do you like to name your unit tests, and under what circumstances? Please share in the comments below.

​I encourage you to read Roy Osherove’s book. Don’t say, “I code in Swift, not C#, so this book’s not for me.” This book is full of insights regardless of programming language.


About the Author Jon Reid

Programming was fun when I was a kid. But working in Silicon Valley, I saw poor code lead to fear, with real human costs. Looking for ways to make my life better, I learned about Design Patterns, Refactoring, and Test-Driven Development (TDD). Programming became fun again! I've now been doing TDD in Apple environments for 18 years. I'm committed to software crafting as a discipline, hoping we can all reach greater effectiveness and joy.

follow me on:
  • Ellen says:

    Another really great benefit of clear test method names: If you ever have to port your app to another platform, you can tell very quickly which tests are platform-specific and can be ignored, vs. which tests are business-logic centric and should be reused.

  • Saul Mora says:

    This is great advice for the younglings out there. I tend to do use this same methodology, albeit indirectly because I generally lean toward the BDD style testing frameworks like Kiwi, Specta and Quick/Nimble. They somewhat force you into answering the 3 parts by having a spec name (what are we testing), some context (what is the state the object under test is in) and the assertion (something should be true about this object). Each of the parts then builds up into a readable test name in the runner so that when a test breaks you already have a fairly detail test name giving you more information regarding the failure. Anyhow, carry on!

  • Phil Nash says:

    I’m with Saul on this one.
    Which is to say I completely agree with you, Jon, that this attention to naming is crucial. In fact I take a considerable portion of my conference talks on my own test framework driving the same point home.

    So the fact that you have to fight XCTest so much to achieve such an important thing is, to me, a bit of a smell. As Saul points out there are other frameworks out there that are much better tuned to this sort of thing (I could add my framework, Catch, to his list – which supports Objective-C). Unless your hands are tied I would recommend considering one of those frameworks.

    For a taste. take a look at this (getting old now) blog post of mine: http://www.levelofindirection.com/journal/2013/6/28/catch-10.html under the heading, “On your best behaviour”.

  • Thanks for this great advice, Jon. I have started to use this naming scheme and it feels better now.

    About the BDD frameworks. I tried BDD and realized that this style of testing isn’t suited for me. I still think to technical during the test creation and often already have an idea how I could implement the feature. I know this isn’t the way to go. I still have to learn.

    And I’m always at the bleeding edge (Swift 2.0, iOS 9, …) and to often I had to wait for frameworks to catch up.

  • >