about 1 year ago

The convention of writing test cases

Functions of test become more and more complicated to understand because of different kinds of coding style by developers. It is hard to maintein the test cases written by other developers as the test codes are difficult to read.

As a result, having a unique convention of how to write the test function is fundamental.

This document contains the following topics:

  • How to name a test function
  • What is the structure of a test function
  • How to make test content more understandable
  • Libraries for testing

How to name a test function

According to 7 popular unit test naming which gives different opinions of how to name unit test function.

As a combination of such suggestion, I prefer to use:

public void test_{functionName}_WHEN_special_situation_THEN_expected_behavior() {
    // test codes

} 

To begin with:

  • test is prefixed of all test functions, and it is often considered redundant, however, it helps to search all the test function in a test class
  • functionName is used in camel case and is the target function that aims to test
  • special_situation after WHEN is to describe the different input and given condition for the target function.
  • expected_behavior after THEN: to describe the expectation behavior after invoking the target function under the special_situtation

Example:
Assuming that a class, AccountService, containing a public function createAccount.

public class AccountService {
    public Account createAccount(AccountInfo accountInfo) {
        // implement codes

    }
}

Then the unit test of AccountService should look like following:

public class AccountServiceTest {
    
    @Test
    public void test_createAccount_WHEN_no_exist_accountId_THEN_create_succ() {
        // ... test codes

    }
    
    @Test
    public void test_createAccount_WHEN_existed_accountId_THEN_throw_AlreadyExistAccountException() {
        // ... test codes

    }
}

When to separate words with underscore _?

It depends on whether a words should be classified as a group that specified one thing or an item and mostly it should be composed by an adjective and a noun.

For examples:

  • _completedRequest_ represents for a completed http request
  • _throw_AlreadyExistAccountException means that will throw a specified exception class
  • _accountId_ is a certain property of a class

What is the structure of a test function

As mentioned in Clean Code #ch9, a test function is mostly composed by 3 parts: Prepare, Operation, Check; Then also are considered as another terms Given, When, Then for BDD.

Given

The preparation before the target function starts
Usually, the pre-test condition such as fake data in database or mock function should be defined prior to the preparation.

When

Invoke the target function (with parameters) you want to test.

Then

Finally, is a serial of validation action and it means checking what happens after calling the target function, for example, checking whether data set into the database or whether the core component is invoked.

Example

@Test
public void test_createAccount_WHEN_no_exist_accountId_THEN_create_succ() {
    // given

    String accountId = "nonExistAccount";
    AccountInfo accountInfo = new AccountInfo(accountId);
    accountInfo.setEmail("");
    when(accountRepository.findByAccountId(accountId)).thenReturn(null);
    // when

    Account account = accountService.createAccount(accountInfo);
    // then

    assertThat(account.getAccoundId()).isEqualTo(accountId);
    verify(accountRepository, times(1)).save(account);
    
}
  • Notably, sometimes Given is not necessary when we don't have any assumption or pre-test condition. Especially for some static util funciton.

How to make test content more understandable

The test code should be readable rather than following the code structure. If you even can't understand what a test is going on, then you no longer to be able to maintain this test.

There are 3 tips of making the test easier to read for other developers:

1. extract the behavior:

Most of time, we don't care much about the test details. So it'd better to extract the main behaviors into a function with enough narrativity naming. It is helpful to let others understand the purpose at the first moment they look into the test function:

@Test
public void test_createAccount_WHEN_no_exist_accountId_THEN_create_succ() {
    // GIVEN

    String accountId = "nonExistAccount";
    mockAccountNotExist(accountId);
    // WHEN

    Account account = accountService.createAccount(accountInfo);
    // THEN

    assertAccountIsCreated(accountId, account);
}

private void mockAccountNotExist(String accountId) {
    AccountInfo accountInfo = createAccountInfo(accountId);
    when(accountRepository.findByAccountId(accountId)).thenReturn(null);
}

private void assertAccountIsCreated(String accountId, Account account) {
    assertThat(account.getAccoundId()).isEqualTo(accountId);
    verify(accountRepository, times(1)).save(account);
}

The test structure is more clear when the details are hidden in the other more function.

2. One test function is for one test purpose

Don't mix up different purposes within one test function, because it betray the naming design rule. And it will lead to no recognization of what situation cause a fail test.

3. Separating the Give, When, Then with enough vertical distance.

It is a good way to insert the comment line separating these 3 parts, like our sample code. // give

Libraries for testing

  • For MOCK function purpose, the Mockito is the best choice to do function isolation.

    PowerMockito is NOT ADVICED TO USE although it is also another powerful test framework. Because of mock private functions does break the code design and result in useless verification when the private function is changed.

  • For do VERIFICATION, AssertJ is also the good for verifying behavior. It has completeness and fluent-design APIs, moreover, the usage is very intuitive. See the article for more information about the difference between several assertion libraries.

← [引導型敏捷領導力]#1 - 先說個人收獲 「淺談高效撰寫單元測試」AgileMeetup會後感想 →