You are naming your tests wrong!
Giving your tests expressive names is important. Proper naming helps understand what the test verifies and how the underlying system behaves. In this post, we’ll take a look at a popular, yet inferior naming convention and see how it can be improved.
So, how should you name a unit test? I’ve seen and tried a lot of naming conventions over the past decade. One of the most prominent, and probably one of the least helpful, is the following convention:
[MethodUnderTest]_[Scenario]_[ExpectedResult]
Where:
-
MethodUnderTest
is the name of the method you are testing. -
Scenario
is the condition under which you test the method. -
ExpectedResult
is what you expect the method under test to do in the current scenario.
It’s unhelpful because it encourages you to focus on implementation details instead of the behavior. On the contrary, simple phrases in plain English do a much better job: they are more expressive and don’t box you in a rigid naming structure. With simple phrases, you can describe the system behavior in a way that’s meaningful to a customer or a domain expert.
Let’s take an example (sut
stands for system under test):
public class CalculatorTests
{
[Fact]
public void Sum_of_two_numbers()
{
double first = 10;
double second = 20;
var sut = new Calculator();
double result = sut.Sum(first, second);
Assert.Equal(30, result);
}
}
How could the test’s name be re-written using the [MethodUnderTest]_[Scenario]_[ExpectedResult]
convention? Probably something like this:
public void Sum_TwoNumbers_ReturnsSum()
That’s because the method under test is Sum
, the scenario includes two numbers, and the expected result is a sum of those two numbers.
The new name looks logical to a programmer’s eye, but does it really help the test readability? Not at all. The latter version is all Greek to an uninformed person. Think about it. Why are there two Sum
words in the name of the test? And what is this Returns
phrasing all about? Where is the sum returned to? You can’t know.
One could argue that it doesn’t really matter what a non-programmer would think of this name. After all, unit tests are written by programmers for programmers, not domain experts. And programmers are good at deciphering cryptic names — it’s their job!
This is true, but only to a certain degree. Cryptic names impose a cognitive tax on everyone, programmer or not. They require additional brain capacity to figure out what exactly the test verifies and how it relates to business requirements. It may not seem like much but the mental burden adds up over time. It slowly but surely increases the maintenance cost for the entire test suite. It’s especially noticeable if you return to the test after you’ve forgotten about the feature’s specifics, or try to understand a test written by a colleague. Reading someone else’s code is already difficult enough. Any help in understanding it is of much use.
The initial name written in plain English reads much simpler. It is a down-to-earth description of the behavior under test. Here are the two versions again:
public void Sum_of_two_numbers()
public void Sum_TwoNumbers_ReturnsSum()
Unit test naming guidelines
Adhere to the following guidelines to write expressive, easily readable test names:
-
No rigid naming policy. You simply can’t fit a high-level description of a complex behavior into a narrow box of such a policy. Allow freedom of expression.
-
Name the test as if you were describing the scenario to a non-programmer who is familiar with the problem domain. A domain expert or a business analyst are good examples.
-
Separate words by underscores. It helps improve readability, especially of long names.
Notice that I didn’t use underscores when naming the test class, CalculatorTests
. Classes' names are normally not as long and so they read fine without underscores.
Also notice that although I use the pattern [ClassName]Tests
when naming test classes, it doesn’t mean that the tests are limited to verifying only that ClassName
. The unit in unit testing is a unit of behavior, not a class. This unit can span across one or several classes, the actual size is irrelevant. Still, you have to start somewhere. View the class in [ClassName]Tests
as just that: an entry point, an API, using which you can verify a unit of behavior.
Example: renaming a test towards the guidelines
Let’s now take a test as an example and try to gradually improve its name using the guidelines above. Here’s a test checking that a delivery with a past date is invalid. The test’s name is written using the rigid naming policy that doesn’t help with the test readability:
[Fact]
public void IsDeliveryValid_InvalidDate_ReturnsFalse()
{
DeliveryService sut = new DeliveryService();
DateTime pastDate = DateTime.Now.AddDays(-1);
Delivery delivery = new Delivery
{
Date = pastDate
};
bool isValid = sut.IsDeliveryValid(delivery);
Assert.False(isValid);
}
This test checks that DeliveryService
properly identifies a delivery with an incorrect date as invalid. How would you re-write the test’s name in plain English? The following would be a good first try:
public void Delivery_with_invalid_date_should_be_considered_invalid()
Notice two things in the new version:
-
The name now makes sense to a non-programmer. Which means that programmers will have an easier time understanding it too.
-
The name of the SUT’s method -
IsDeliveryValid
- is no longer part of the test’s name.
The second point is a natural consequence of rewriting the test’s name in plain English and thus can be easily overlooked. However, this consequence is important and can be elevated into a guideline of its own.
Don’t include the name of the SUT’s method into the test’s name. Remember, you don’t test code, you test application behavior. Therefore, it doesn’t matter what the name of the method under test is. As I mentioned previously, the SUT is just an entry point, a means to invoke a behavior. You can decide to rename the method under test to, say, IsDeliveryCorrect
and it will have no effect on the SUT’s behavior. On the other hand, if you follow the original naming convention, you’ll have to rename the test. This once again shows that targeting code instead of behavior couples tests to that code’s implementation details, which negatively affects the test suite maintainability.
The only exception to this guideline is when you work on utility code. Such code doesn’t contain business logic - its behavior doesn’t go much beyond simple auxiliary functionality and thus doesn’t mean anything to the business people. It’s fine to use the SUT’s method names there.
But let’s get back to our example. The new version of the test’s name is a good start but it can be improved further. What does it mean for a delivery date to be invalid, exactly? Looking at the test, we can see that an invalid date is any date in the past. Which makes sense - you should only be allowed to choose a delivery date in the future.
So let’s be specific and reflect this knowledge in the test’s name:
public void Delivery_with_past_date_should_be_considered_invalid()
This is better but still not ideal. It’s too verbose. We can get rid of the word considered
without any loss of meaning:
public void Delivery_with_past_date_should_be_invalid()
The wording should be
is another common anti-pattern. A test is a single, atomic fact about a unit of behavior. There’s no place for a wish or a desire when stating a fact. Name the test accordingly. Replace should be
with is
:
public void Delivery_with_past_date_is_invalid()
And finally, there’s no need to avoid basic English grammar. Articles help the test read flawlessly. Add the article a to the test’s name:
public void Delivery_with_a_past_date_is_invalid()
There you go. This final version is a straight-to-the-point statement of a fact, which itself describes one of the aspects of the application behavior under test. In this particular case, the aspect of determining whether a delivery can be done.
Summary
-
Don’t use a rigid test naming policy.
-
Name the tests as if you were describing the scenario in it to a non-programmer familiar with the problem domain.
-
Separate words in the test name by underscores.
-
Don’t include the name of the method under test in the test name.
Check out my upcoming book
I wrote a book about unit testing. To get a 40% discount, click here: book discount.
Subscribe
Comments
comments powered by Disqus