According to test-driven development you should start with a unit test, not with an implementation. Unit-testing has made its way into most notable programming environments. Still the ways how tests are named and organized are quite different.
This article was inspired by Roy Osherove ("The Art of Unit Testing") that argues about naming standards for unit tests. He proposes
UnitOfWork_StateUnderTest_ExpectedBehavior
as naming convention, where "UnitOfWork" may be the name of the called method, "StateUnderTest" describing its input data, and "ExpectedBehavior" the output result to be asserted, sometimes simply "fail" or "succeed".
→ findNameById_ExistingId_SucceedsReturningName
Target is to have a readable, short but precise name for a test where you know immediately what's wrong when it fails, without reading its implementation.
Variants could be:
UnitOfWOrk_ExpectedBehavior_ whenStateUnderTest → findNameById_SucceedsReturningName_whenExistingId
Without "UnitOfWork":
whenStateUnderTest_ expectExpectedBehavior → whenExistingId_expectSuccessReturningName
givenPreconditions_ whenStateUnderTest_ thenExpectedBehavior → givenIdExists_whenNonNullId_thenSucceedReturningName
You may decorate the names with words like "when", "then", "should", "given", "if", "expect", "fail", "succeed", .... Try to form syntactically correct English sentences.
You could also name the test after the feature or user-story it is about to check. Suitable for integration tests.
Let's assume that following application classes need to be covered by unit tests. They represent a team that can have several members, but when adding more than 7, an exception will be thrown. (This may be business logic, but can be placed into domain model objects in the sense of Domain-Driven Design.)
Mind that I made this source as short as possible and left out @Entity
annotations and JPA-required setters and getters.
1 | import java.util.Objects; |
Member
is the list-element class. I restricted equality to the name
property, this is to avoid duplicates when adding members to a team.
Following is the list-container class, holding up to 7 members:
1 | import java.util.HashSet; |
You see that the addMember()
method implements the "maximum members" business logic.
What we need to do now is test the class Team
. It exposes following public API, or "Units of Work":
dedication
Set getMembers()
addMember(Member)
In Java, a "unit test" is a method inside a test class. Remember that unit tests should not depend on each other, so classes are used just as test-containers. Testing frameworks like JUnit would allocate a new instance of the test class for the execution of each of it's methods. (This is different to application classes where several methods may work on the same instance fields, the fields representing the "state" of the class.)
In Java, test classes are named like the class they are testing. So if you have a class Team
you want to test, you would write a TeamTest
class. Inside you'd have test-methods (actually these are "tests") that check each public piece of the class to test. For convenience the test-class could hold an instance of the class to be tested in a field, being initialized in a @Before setUp()
procedure.
import static org.junit.Assert.*;
import org.junit.*;
public class TeamTest
{
private Team team;
@Before
public void setUp() {
team = new Team(TEAM_DEDICATION);
}
// TODO: add tests
}
Naming a test method is not so easy. Obviously the name of the called method should be in the test method's name, that is what Osherove's "UnitOfWork" demands.
Generally a name should express what's under that name. This applies to both classes and methods.
A class managing aname
and anidentity
may be calledNameAndId
, orNamedId
, orIdentifiedName
.
A method returning aname
when anid
parameter is given may be namedfindNameById
.→ We don't want to be forced to read what's under the name, because we may find other names there that we again have to investigate.
The same goes for test method names. We need to downsize semantics, recommendably without using abbreviations.
Here is the outline of the test-class. I didn't add test implementations, because this is about naming. (Implementation is on bottom of the article.)
....
public class TeamTest
{
....
@Test
public void dedication_Constructed_Succeeds() {
}
@Test
public void getMembers_Empty_Succeeds() {
}
@Test
public void getMembers_One_Succeeds() {
}
@Test
public void addMember_NotNull_Succeeds() {
}
@Test
public void addMember_Null_Fails() {
}
@Test
public void addMember_ExceedingMaximum_Fails() {
}
@Test
public void addMember_Duplicate_SucceedsButNoChange() {
}
}
Remember that we have the @Test
annotation in Java, so we don't need the "test" prefix any more to mark test methods. This helps to make the name shorter.
This outline expresses which possible uses get tested. Would you know how to implement each of these methods?
NullPointerException
This is a compromise between readability and systematic naming approach. Because we test all methods of class Team
, we need to take the "UnitOfWork" into the test method's name. Underscores separate unit (method name), input (state) and output (expected result) and make this systematically readable.
When talking about Java unit tests it is important to not call the class TeamTest
a test. It is a test container. The contained methods are tests!
Here is my implementation of the tests. Remember that one unit test should test no more than one thing. Thus getMembers_One_Succeeds()
goes just for the size of members, while addMember_NotNull_Succeeds()
ensures that it is the added member which is in the list. To avoid repetetive code, only one test asserts that the member list never is null.
1 | import static org.junit.Assert.*; |
Unit tests make up about a third of development efforts. They duplicate application logic, i.e. when you change the application, you will also have to change the tests.
Note that a script-language like JavaScript strongly demands unit-testing, because it has no type-system. Here, the only way to find typing-pitfalls is to actually execute the code through a unit test. With strongly typed languages like Java your source code will be more stable, nevertheless also Java is much more runtime-bound nowadays than it was initially. Thus unit tests are here to stay.
ɔ⃝ Fritz Ritzberger, 2019-12-04