The importance of unit test names
The names of our unit tests might seem like the most insignificant part of our code. After all, they usually don’t even get shipped with the release code of our software, so therefore they cannot impact runtime functionality. To make it worse, the naming of a test does not impact the implementation of a test, so they cannot impact the running of these tests either. So the question becomes why I consider naming of ones tests to be important at all.
This blog post is not about whether something should be named appropriately. Most developers are aware of this and why it is important (I hope). However, through the years I have been programming I have seen developers tend to drop the ball when it comes to writing unit tests. Suddenly it is acceptable to name variables “fooOne”, “fooTwo”, or methods to be “methodWeAreTestingTest”, both of which should not be acceptable. Our test code should be taken as serious as our production code and therefore be held to the same level of scrutiny.
In this blog post I will be talking about naming schemes, which can serve as a way to have different developers write something which more or less is a cohesive whole. Naming schemes are an excellent framework for making predictable names, so they’re easier to identify and they’re trying to discourage bad tests by making them more difficult to name.
In his blog post titled “Excuses”, Robert C. Martin argues that unit tests have the same responsibility as double entry bookkeeping has for accountants. This is an argument he has made on multiple occasions. The argument here is that by having to write everything twice, one in the form of the implementation and the other in the form of the test us, developers, are less likely to make mistakes. Here I agree with Robert, as we use tests to verify our code. I do think we can take it a bit further without adding much effort, by having the test name to be the double bookkeeping of the tests.
Choose a naming scheme
Before we continue we might want to consider the various naming schemes out there. When writing unit tests we’re (kinda) allowed to break some normal language rules, like how functions are made. DZone has a good article summing some of these up. I won’t argue for having one of the other as that is not the scope of this post, but personally I’m a fan of Roy Osherove’s style:
[UnitOfWork_StateUnderTest_ExpectedBehavior]
I recommend reading Roy’s post about it, though in simple terms this means:
UnitOfWork
This should be the name of the method which holds the functionality we’re testing, not necessarily the method we’re calling. For example if we are testing something which happens in a private method, this should be named after the private method. If we are testing something which happens in a public method, then this should be named after the public method.
StateUnderTest
What needs to be true for our outcome to happen? Do specific values need to be set? This part tells us what setup we need, or what kind of data our applications need for our outcome to happen.
ExpectedBehavior
This is what we expect will happen. This should tell us something about how we are verifying the result and what that result must be. Are we verifying a mock? Are we asserting on a return value? Are we expecting an exception? If so, what kind?
Let’s look at this style in practice. Take the following code:
public enum AnimalType {
CAT,
DOG
}
public class AnimalService {
public String getAnimalSound(AnimalType animalType) {
if (animalType == AnimalType.DOG)
return "Woff!";
if (animalType == AnimalType.CAT)
return "Mjau";
return null; //No sound
}
}
And then consider the following test:
class AnimalServiceTest {
AnimalService animalService = new AnimalService();
@Test
void getAnimalSound_animalIsCat_soundOfACatReturned() {
String expected = "Mjau";
String result = animalService.getAnimalSound(AnimalType.CAT);
assertEquals(expected, result);
}
}
Ask yourself, is the name correct here?
In my humble opinion, yes. I think it is correct and here’s why:
getAnimalSound
This part is all about where the logic we want to test is. In this case the logic can be found in the AnimalService, and in the method called getAnimalSound. That means the correct name for this is “getAnimalSound”
animalIsCat
Here we’re trying to hint at what needs to be true for the expected result to occur. It is often a part of either the setup of a test or the parameters sent into our code by the test. In this case we are sending in AnimalType.CAT as a parameter. So having the name “animalIsCat” seems appropriate.
soundOfACatReturned
In the expected outcome we want to hint at two things. One being what the actual outcome is, and the other how we are verifying the result. In this case we are expecting the returning string to be the sound of a cat, so the name “soundOfACatReturned” is appropriate, but “soundOfCatReturned” or “catStringReturned” could also be acceptable.
How does a naming scheme help developers?
Makes names less opinionated
Naming is a big part of our job as developers. By giving the naming clear rules which relates directly to the code we are providing a framework which makes naming tests easier. When providing feedback on test names it is no longer a case of “I think this could be named better”, instead it is “the first part of this test says that we are verifying method A, but actually the functionality is in method B”.
By introducing a naming scheme we have provided context where we can argue about the names which do not boil down to preference. It does not take out minor points about phrasing, but as long as the test name doesn't lie it is valid.
Making it easier to name tests
People find rigid rules and structures to be difficult to work with, but I find it liberating. In this case I no longer have to worry about whether or not a test is correctly named. I can only ask myself these questions:
Is the functionality I am testing where I say it is?
Am I setting up what I’m claiming to be setting up?
Am I truthful about how I am verifying the result, and what I am verifying?
If the answer to all of these tests is “Yes”, then I have a correctly named test!
Having a clear and understandable way to name tests also makes it easier for newcomers to the team to do the right things, it simply removes uncertainty.
Making it easier for code reviewers
There is no denying that I have strong opinions when it comes to code reviews. It becomes easier for code reviews to get a feeling whether something is adequately tested or not when the tests are written in a predictable way.
Take our previous test as an example: By looking at this test we would expect to see around 3 tests. So by going into the test class and simply searching for the test name we should see about 3 tests starting with “getAnimalSound”. If we want to check which 3 of them is verifying the dog sound we can simply read the stateUnderTest part which will probably contain the word dog.
Makes it easier to scope tests
I assume you know basic traits of a good unit test, if not Google can help you out (this post would be too big otherwise). One of the issues that is often brought up is that tests can become too big, or they may not have an assertion and so forth. Such mistakes happen. By forcing test names to match the test implementation we force the developer to consider what they are actually testing.
For example, if a test does not have an assert, what does on put in the “expectedBehavior” part?
One thing that I have personally experienced is that it is difficult to name things when it is not clear what their purpose or goal is. By having this structure we achieve much the same. We are forcing that a test has a goal. We are forcing that a test must put the application in some sort of state. This is a good thing as far as a test is concerned.
Makes it easier to maintain tests over time
This might sound like a bold statement, and it is. I won’t claim that the name of your tests will make or break the tests over time, but hear me out. By having a standardized way makes it easier for developers to understand what a test is supposed to do. A common naming scheme allows us to easier answer the question “Is this test still relevant?” or “Is this test still true?”.
Makes tests easier to understand for non-developers
Now, hear me out - I understand that unit tests are by developers, for developers. That said it doesn’t hurt that our non-developers can understand what is going on. By making the test implementation into something that is close to normal English we can extract information which can be understood by non-developers. Exactly what that would be I am uncertain of, but at least this might be an option now, right!? It opens opportunities to collaborate with QA in a new way. Maybe QA needs to see whether certain tests have been written and so forth.
I’m not saying this is the biggest reason to use this type of naming scheme, but its a bonus nonetheless in my eyes. Whether or not this is something useful to you probably comes down to how your company works. I’m just saying that its’s a thing which becomes easier, not that you have to do it.
Can't I just read the test implementation? Isn’t that just as effective?
The implementation of the test is the most important, absolutely. The implementation defines what the test actually does. The name of that test won’t make it any more or less correct. What the test name can do is to reveal the intended purpose of the test. Predictable naming makes it easier to detect when intention has misaligned itself with implementation, which has happened to me multiple times. However, if the test passes, the test name matches the test implementation and the test name is sensible, then in most likely the test is good as well.
It is the same with the name of a method, by looking at the name we should see what the intended functionality of that method is, but only by looking at the implementation we can see what actually happens. At that point the question becomes: Is the name wrong or is the implementation actually incorrect? We can have the same value with our tests.
Conclusion
The naming of tests might not make or break an application, and it is by far not a guarantee that a test holds quality. Many teams get by without having any real structure of the names of their unit tests. Personally I believe this is a mistake, as having a structure like this does not add much effort, but can give quite a few benefits.
Whether something like this is for you and your team is up to you, but I have yet to see a good argument of not having an agreed upon naming scheme for unit tests.