Writing Clean Tests – Naming Matters

It is pretty hard to figure out a good definition for clean code because everyone of us has our own definition for the word clean. However, there is one definition which seems to be universal:

Clean code is easy to read.

This might come as a surprise to some of you, but I think that this definition applies to test code as well. It is in our best interests to make our tests as readable as possible because:

If our tests are easy to read, it is easy to understand how our code works.

If our tests are easy to read, it is easy to find the problem if a test fails (without using a debugger).

It isn’t hard to write clean tests, but it takes a lot of practice, and that is why so many developers are struggling with it.

I have struggled with this too, and that is why I decided to share my findings with you.

This is the second part of my tutorial which describes how we can write clean tests. This time we will learn what kind of an effect naming has to the readability of our tests. We will also learn rules which help us to transform our test cases into executable specifications.

The Devil Is in the Details

It is relatively easy to write tests which seem clean. However, if we want to go the extra mile and change our tests into a executable specification, we have to pay extra attention to the naming of test classes, test methods, test class’ fields, and local variables.

Let’s find out what this means.

Naming Test Classes

When we think about the different test classes which we create in a typical project, we notice that these classes can be divided into two groups:

The first group contains tests which tests the methods of a single class. These tests can be either unit tests or integration tests written for our repositories.

The second group contains integration tests which ensure that a single feature is working properly.

A good name identifies the tested class or feature. In other words, we should name our test classes by following these rules:

If the test class belongs to the first group, we should name it by using this formula: [The name of the tested class]Test. For example, if we are writing tests for the RepositoryUserService class, the name of our test class should be: RepositoryUserServiceTest. The benefit of this approach is that if a test fails, this rule helps us figure out which class is broken without reading the test code.

If the class belongs to the second group, we should name it by using this formula: [The name of the tested feature]Test. For example, if we would be writing tests for the registration feature, the name of our test class should be RegistrationTest. The idea behind this rule is that if a test fails, using this naming convention helps us to figure out what feature is broken without reading the test code.

Naming Test Methods

In other words, if we follow this naming convention, we should name our test methods as follows:

If we write tests for a single class, we should name our test methods by using this formula: [the name of the tested method]_[expected input / tested state]_[expected behavior]. For example, if we write a unit test for a registerNewUserAccount() method which throws an exception when the given email address is already associated with an existing user account, we should name our test method as follows: registerNewUserAccount_ExistingEmailAddressGiven_ShouldThrowException().

If we write tests for a single feature, we should name our test methods by using this formula: [the name of the tested feature]_[expected input / tested state]_[expected behavior]. For example, if we write an integration test which tests that an error message is shown when a user tries to create a new user account by using an email address which is already associated with an existing user account, we should name out test method as follows registerNewUserAccount_ExistingEmailAddressGiven_ShouldShowErrorMessage().

This naming convention ensures that:

The name of a test method describes a specific business or technical requirement.

The name of a test method describes expected input (or state) and the expected result for that input (state).

In other words, if we follow this naming convention we can answer to the following questions without reading the code of our test methods:

What are the features of our application?

What is the expected behavior of a feature or method when it receives an input X?

Also, if a test fails, we have a pretty good idea what is wrong before we read the source code of the failing test.

Naming Test Class’ Fields

Fields which contains the other objects (testing utilities) which are used in our test cases.

We should name these fields by using the same rules which we use when we name the fields found from the application code. In other words, the name of each field should describe the “purpose” of the object which is stored to that field.

This rule sounds pretty “simple” (naming is always hard), and it has been easy for me to follow this rule when I name the tested class and the other classes which are used my tests. For example, if I have to add a TodoCrudService field to my test class, I use the name crudService.

When I have added fields which contain test doubles to my test class, I have typically added the type of the test double to the end of the field name. For example, if I have added a TodoCrudService mock to my test class, I have used the name crudServiceMock.

It sounds like a good idea but I have come to conclusion that it is a mistake. It is not a major problem but the thing is that a field name should describe the “purpose” of the field, not its type. Thus, we should not add the type of the test double to the field name.

Naming Local Variables

When we name the local variables used in our test methods, we should follow the same principles used when we name the variables found from our application code.

In my opinion, the most important rules are:

Describe the meaning of the variable. A good rule of thumb is that the variable name must describe the content of the variable.

Don’t use shortened names which aren’t obvious for anyone. Shortened names reduces readability and often you don’t gain anything by using them.

Don’t use generic names such as dto, modelObject, or data.

Be consistent. Follow the naming conventions of the used programming language. If your project has its own naming conventions, you should honor them as well.

Enough with theory. Let’s put these lessons into practice.

Putting Theory into Practice

Let’s take a look at a modified unit test (I made it worse) which is found from the example application of my Spring Social tutorial.

This unit test is written to test the registerNewUserAccount() method of the RepositoryUserService class, and it verifies that this method is working correctly when a new user account is created by using a social sign provider and a unique email address.

It is clear that this test case still has some problems but I think that our changes improved its readability. I think that the most dramatic improvements are:

The name of test method describes the expected behavior of the tested method when a new user account is created by using a social sign in provider and a unique email address. The only way we could get this information from the “old” test case was to read the source code of the test method. This is obviously a lot slower than reading just the method name. In other words, giving good names to test methods saves time and helps us to get a quick overview about the requirements of the tested method or feature.

the other changes transformed a generic CRUD test into a “use case”. The “new” test method describes clearly

What steps does this use case have.

What the registerNewUserAccount() method returns when it receives a registration, which is made by using a social sign in provider and has a unique email address.

In my opinion, the “old” test case failed to do this.

I am not entirely happy with the name of the RegistrationForm object but it is definitely better than the original name.

Summary

We have now learned that naming can have a huge positive effect to the readability of our test cases. We have also learned a few basic rules which helps us to transform our test cases into executable specifications.

However, our test case still has some problems. These problems are:

The code which creates new RegistrationForm objects simply sets the property values of the created object. We can make this code better by using test data builders.

The standard JUnit assertions, which verify that the information of the returned User object is correct, are not very readable. Another problem is that they only check that the property values of the returned User object are correct. We can improve this code by turning assertions into a domain-specific language.

I will describe both techniques in the future.

In the meantime, I would love to hear what kind of naming conventions do you use.

Newsletter

Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

Email address:

Recent Jobs

No job listings found.

Join Us

With 1,240,600 monthly unique visitors and over 500 authors we are placed among the top Java related sites around. Constantly being on the lookout for partners; we encourage you to join us. So If you have a blog with unique and interesting content then you should check out our JCG partners program. You can also be a guest writer for Java Code Geeks and hone your writing skills!

Disclaimer

All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners. Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries. Examples Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.