Skip to main content

How to write a unit test in TDD development team

💻 Tech

This article shows the summarized way how to write a unit test following the rules I have established for my team.

The team is following TDD (Test Driven Development) style so every unit tests are created before the implementation of the methods.
Then this way is also intended to specify the requirements of implementing method, function etc.

By implementing a unit test for this method, isAfterServiceGraduateDate, step by step instructions and the details of the rule will be shown.

public class LoginLogic {

    // ...

    /**
     * @return whether current date is after or equal to graduate year's 1st April
     * NOTE FOR READERS: In Japan, students commonly graduates at the end of March
     */
    boolean isAfterServiceGraduateDate() {
        // Let's create a test before implementation!!
    }
}

Step1: Determine the test type of the method

Firstly, I determine which kind of test is needed for the implementing method.
The answer must be Unit, Integration or E2E Test.
This time it is a Unit Test.

See the references in the bottom of this article if you’d like to know what are the clear definitions of each test.

Step2: List up test cases thoroughly

At first, I recite the points of view needed for listing up test cases.

  • What kind of data could be passed as an argument?
  • What is the condition of returning true/false?
  • Is boundary value analysis needed?

Then I consider every point carefully.

In conclusion, three cases are listed up this time.

  1. Check NullPointerException occurs when the argument is null
  2. Check the method returns true/false when correct arguments are passed
  3. Check the method returns exact outcome when the argument is the boundary value

Step3: Write a test

After passing the steps above, I start writing a test.
At the moment I’ll be following the rules here:

  • The test should assess only the function originally implemented in the target method (ex. If an injected class’s method is called in the method, that should be mocked)
  • If Data Base access is needed in the method, the function should be separated as another method (in another class preferably) and should be mocked
  • The test execution time should be less than 0.1 second

If following the rules above is too hard, it’s better to re-design the method (in many cases it should be broken up into smaller units.)

I show you the final outcome below but this might not so familiar to you (test framework is Spock and the language is groovy.) So ask me if you have a question.

In this example, you can see every test cases are covered.
Note: The test framework is Spock: http://spockframework.org/

class LoginLogicUnitTest extends Specification {

  private CandidateLoginLogic logic

  def setup(){
    logic = new CandidateLoginLogic()

    // Mocking autowired methods
    OriginalTimeManager timeManager = Mock()
    logic.metaClass.setAttribute(logic, "timeManager", timeManager)
  }

  @Unroll
  def "IsAfterServiceGraduateDate currentDate: #currentDate"() {
    setup:
    // stubbing mocked test's retun value
    logic.timeManager.getCurrentLocalDate() >> currentDate

    when:
    def result = logic.isAfterServiceGraduateDate(graduateYear)

    then:
    result == flg

    where:
    // In this case the test are executed three times
    // Every tests are executed with the assigned values below
    currentDate               | graduateYear | flg
    LocalDate.of(2018, 3, 31) | 2018         | false
    LocalDate.of(2018, 4, 1)  | 2018         | true
    LocalDate.of(2018, 4, 2)  | 2018         | true
  }

  def "isAfterServiceGraduateDate NullPointerException throwability"() {
    setup:
    logic.timeManager.getCurrentLocalDate() >> LocalDate.of(2018, 4, 2)

    when:
    logic.isAfterServiceGraduateDate(null)

    then:
    thrown NullPointerException
  }
}

Step4: Write the actual method

Finally, I implemented the method.
Here's the result.

public class LoginLogic {

   @Autowired
   private OriginalTimeManager timeManager; // This is an in-house class for managing data related to dates

   /**
    * @param graduateYear
    * @return whether current date is after or equal to graduate year's 1st April
    * @throws NullPointerException if the param graduateYear is null
    */
   boolean isAfterServiceGraduateDate(@NonNull Integer graduateYear) {
      LocalDate serviceGraduateDate = LocalDate.of(graduateYear, 4, 1); // In Japan, students commonly graduates at the end of March
      LocalDate currentLocalDate = timeManager.getCurrentLocalDate();
      return currentLocalDate.isAfter(serviceGraduateDate) || currentLocalDate.isEqual(serviceGraduateDate);
   }
}

References

testcycle

Tell me what you think of this article! 👉️ @curryisdrink