C# Test Naming Conventions: A Practical Guide
A C# test naming convention that scales: MethodName_Given{Scenario}_Should{ExpectedResult}. Read failures at a glance, group tests by functionality, and keep suites navigable for humans and agents.
One of the most challenging aspects of software development is naming. In order to maximize the value of the tests we write, it is important to follow a naming convention that helps with quick understanding of test failures, grouping tests by functionality, and providing a simple yet meaningful approach to test naming.
The convention of MethodName_Given{Scenario}_Should{ExpectedResult} is preferred. This convention is easy to understand and allows for quick identification of the method being tested, the scenario being evaluated, and the expected outcome.
- MethodName - The name of the method being tested on the object.
- Given{Scenario} - A higher-level idea or context for the test. Avoid using specific technical terms if possible.
- Should{ExpectedResult} - The expected outcome of the test. This helps the reader to understand the test without reading the method body.
C# Example
[TestFixture]
public class FizzBuzzerTests
{
[TestCase(3)]
[TestCase(6)]
[TestCase(9)]
public void FizzBuzz_GivenMultipleOf3_ShouldReturnFizz(int input)
{
//---------------Arrange-------------------
var fizzBuzzer = new FizzBuzzer();
//---------------Act----------------------
var result = fizzBuzzer.FizzBuzz(input);
//---------------Assert-----------------------
Assert.AreEqual("Fizz", result);
}
}
The test name FizzBuzz_GivenMultipleOf3_ShouldReturnFizz breaks down as:
- FizzBuzz - The name of the method being tested.
- GivenMultipleOf3 - A higher-level idea or context for the test. It names the category of input (any multiple of 3), not the specific values in the test cases.
- ShouldReturnFizz - The expected outcome of the test. This helps the reader to understand what the test is checking for.
When this test fails, the test runner output alone tells you which method broke, under what circumstances, and what it should have done, before you open the file.
Growing the suite
As the kata progresses, the names line up into a readable specification of the behavior:
FizzBuzz_GivenMultipleOf3_ShouldReturnFizz
FizzBuzz_GivenMultipleOf5_ShouldReturnBuzz
FizzBuzz_GivenMultipleOf3And5_ShouldReturnFizzBuzz
FizzBuzz_GivenNumberNotDivisibleBy3Or5_ShouldReturnNumberAsString
Most test runners sort or group by name, so tests for the same method sit
together, and the Given sections show at a glance which scenarios are covered
and which are missing.
Common mistakes
- Naming the data instead of the scenario.
FizzBuzz_Given9_ShouldReturnFizztells the reader about one input.GivenMultipleOf3tells them the rule the input represents, so the name stays true when you add more test cases. - Describing the implementation.
GivenModuloReturnsZeroleaks how the code works and goes stale the moment you refactor it. Describe the situation in the language of the problem domain instead. - Vague outcomes.
ShouldWorkorShouldBeCorrectforce the reader into the test body. Name the observable result: a returned value, a thrown exception, a state change. - Several behaviors in one test. If the name needs “And” in the
Shouldpart, such asShouldSaveAndSendEmail, the test is probably checking two things. Split it so each failure points at one behavior.
Other languages
The same structure works outside C#. Frameworks that allow sentence-style names express it as nested descriptions instead of underscores:
describe("FizzBuzz", () => {
describe("given a multiple of 3", () => {
it("should return Fizz", () => {
expect(new FizzBuzzer().fizzBuzz(9)).toBe("Fizz");
});
});
});
def test_fizz_buzz_given_multiple_of_3_should_return_fizz():
assert FizzBuzzer().fizz_buzz(9) == "Fizz"
For how names improve over time, see Stages of Naming; for keeping test vocabulary aligned with the business, see Ubiquitous Language in Tests.