Let’s talk about unit test naming policies. I’ll first describe where I stand on this topic and we’ll then discuss the Given-When-Then pattern.
1. Preamble
I wrote about how to name unit tests in my Unit Testing book. I’ve also extracted this particular topic into an article that I (somewhat provocatively) named "You are naming your tests wrong!".
In short, I advocate against rigid unit test naming policies. The reason is that your unit tests should describe your system’s behavior in a way that’s understandable not only to programmers, but to non-technical people too.
Rigid naming policies preclude this. You simply can’t fit a high-level description of a complex behavior into a narrow box of such a policy. You must allow freedom of expression.
In the article (and in the book), I took the infamous [MethodUnderTest]_[Scenario]_[ExpectedResult]
pattern as an example of a rigid naming policy.
Look at the following test that follows that pattern:
[Fact] public void IsDeliveryValid_InvalidDate_ReturnsFalse()
Now compare it to this one:
[Fact] public void Delivery_with_a_past_date_is_invalid()
The latter version is clearly better. It reads like plain English and describes the use case under test using business terms. It conveys the system’s observable behavior (properties that are meaningful to the end user), not its implementation details (the opposite of observable behavior).
A good test name should describe the system’s what-to's, not its how-to's.
2. The Given-When-Then pattern
There is an alternative to [MethodUnderTest]_[Scenario]_[ExpectedResult]
: the Given-When-Then pattern.
Here’s an example of such a test:
public class BankAccountShould { [Fact] public void Have_balance_of_zero_when_created() { /* ... */ } [Fact] public void Have_balance_increased_after_a_deposit() { /* ... */ } }
With this pattern, the unit test class describes the object under test (bank account), whereas tests themselves — the use cases related to that object.
This pattern has two distinct advantages:
-
It allows for shortening of the test names — The object under test is always the same, hence you don’t need to repeat it in the tests; it is extracted into the name of the class.
-
It helps organize the tests with a simple hierarchical structure. — If you want to test the bank account under multiple conditions, you can create a separate class for each of those conditions (e.g.
BankAccountForPreferredClientsShould
). You can then put all of the resulting classes under a single folder, so that they are nicely grouped together.
So, what about this pattern? Does it fall into the category of rigid naming policies that you need to avoid?
To answer this question, we need to step back and recall the reason why we avoid name rigidity in the first place: it prevents us from describing the code’s behavior in a human-readable way.
If the naming policy doesn’t damage test names readability, then this policy is fine. Just remember to always prefer readability over the policy. Be ready to make exceptions when that policy impedes readability.
In my experience, you almost never end up with readable test names if you follow the [MethodUnderTest]_[Scenario]_[ExpectedResult]
pattern. With the Given-When-Then pattern, it’s not as clear-cut.
For example, the tests above also read like a plain-English description of the system’s behavior. I don’t see any issues with these tests except for the use of word Should
. The tests would definitely benefit from removing that word:
public class BankAccount { [Fact] public void Has_balance_of_zero_when_created() { /* ... */ } [Fact] public void Has_balance_increased_after_a_deposit() { /* ... */ } }
But there are also examples of Given-When-Then that don’t work as well:
public class PricingCalculatorShould { [Fact] public void Return_zero_given_zero_quantity() { /* ... */ } [Fact] public void Throw_argument_OutOfRangeException_given_quantity_less_than_zero() { /* ... */ } }
Here, the word given
is present in both tests — it’s part of the naming policy. This is a red flag that shows the preference of always sticking to the policy over the test readability.
Another issue here is the Return
word. Beware of programming terms in your test names, even as ubiquitous as the return
keyword. From a business standpoint, it isn’t clear what these programming terms refer to. In this example, where and what exactly does the calculator return?
Always try to name your tests such that they make sense to non-programmers who are familiar with the problem domain.
Finally, there’s also an issue with the second test. The OutOfRangeException
is an implementation detail and should never be part of your test names. It’s best to re-phrase the name from the customer’s point of view and show what exactly that exception manifests:
public class PricingCalculator { [Fact] public void Shows_zero_for_zero_quantity() { /* ... */ } [Fact] public void Cannot_calculate_quantities_less_than_zero() { /* ... */ } }
3. Conclusion
Naming policies aren’t inherently bad. It’s only when they inhibit test readability do they become a problem. If you follow a naming policy such as the Given-When-Then, always be ready to compromise on that policy in favor of test name readability if for some reason a test name doesn’t fit the policy.
--Vlad
Enjoy this message? Here are more things you might like:
Workshops — I offer a 2-day workshop for organizations on Domain-Driven Design and Unit Testing. Reply to this email to discuss.
Unit Testing Principles, Patterns and Practices — A book for people who already have some experience with unit testing and want to bring their skills to the next level.
Learn more »
My Pluralsight courses — The topics include Unit Testing, Domain-Driven Design, and more.
Learn more »