Skip to content

What is Unit Testing?

What is unit testing? Unit testing involves writing test code for the smallest functional units. In Java programs, the smallest functional unit is a method. Therefore, unit testing a Java program means testing individual Java methods.

What are the Benefits of Unit Testing?

What are the benefits of unit testing? Before learning about unit testing, we can first understand Test-Driven Development (TDD).

TDD

Test-Driven Development, or TDD, refers to writing interfaces first, followed by writing tests. After writing the tests, we then proceed to write the actual implementation code. During the implementation process, we write and test simultaneously. When all tests pass, it indicates that the implementation is complete:

Write Interface


Write Tests


┌─▶ Write Implementation
│    │
│ N  ▼
└── Run Tests
     │ Y

    Task Complete

This is the legendary……

JUnit

Of course, this is an ideal scenario. In most cases, we have already written the implementation code and need to test the existing code.

Let's look at an example to see how to write tests. Suppose we have written a class to calculate factorials, which has only one static method to compute the factorial:

n! = 1 × 2 × 3 × ... × n

The code is as follows:

java
public class Factorial {
    public static long fact(long n) {
        long r = 1;
        for (long i = 1; i <= n; i++) {
            r = r * i;
        }
        return r;
    }
}

To test this method, a natural idea is to write a main() method and then run some test code:

java
public class Test {
    public static void main(String[] args) {
        if (fact(10) == 3628800) {
            System.out.println("pass");
        } else {
            System.out.println("fail");
        }
    }
}

This way, we can run the test code by executing the main() method.

However, using the main() method for testing has many drawbacks:

  1. You can only have one main() method, making it impossible to separate test code.
  2. It does not print out the test results and expected results, for example, expected: 3628800, but actual: 123456.
  3. It is difficult to write a set of general-purpose test codes.

Therefore, we need a testing framework to help us write tests.

Benefits of Unit Testing

JUnit is an open-source unit testing framework for the Java language, specifically designed for Java and widely used. JUnit is the de facto standard framework for unit testing, and every Java developer should learn and use JUnit to write unit tests.

The advantage of using JUnit for unit testing is that we can organize test code very easily and run them at any time. JUnit will provide successful and failed tests and can also generate test reports. These reports not only include the success rate of the tests but also calculate the code coverage, i.e., how much of the tested code itself has been covered by tests. For high-quality code, the test coverage should be above 80%.

Additionally, almost all IDE tools integrate JUnit, allowing us to write and run JUnit tests directly within the IDE. The latest version of JUnit is 5.

Let's take a look at the content of FactorialTest.java:

java
package com.itranswarp.learnjava;

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

public class FactorialTest {

    @Test
    void testFact() {
        assertEquals(1, Factorial.fact(1));
        assertEquals(2, Factorial.fact(2));
        assertEquals(6, Factorial.fact(3));
        assertEquals(3628800, Factorial.fact(10));
        assertEquals(2432902008176640000L, Factorial.fact(20));
    }
}

The core test method testFact() is annotated with @Test, as required by JUnit. This annotation marks methods with @Test as test methods. Inside the test method, we use assertEquals(1, Factorial.fact(1)) to indicate that we expect Factorial.fact(1) to return 1. assertEquals(expected, actual) is the most commonly used test method, defined in the Assertions class. Assertions also defines other assertion methods, such as:

  • assertTrue(): expects the result to be true
  • assertFalse(): expects the result to be false
  • assertNotNull(): expects the result to be non-null
  • assertArrayEquals(): expects the result to be an array and equal to the expected array element-wise
  • ...

Running unit tests is very simple , If the test results do not match the expectations, assertEquals() will throw an exception, and we will get a test failure result.

In the Failure Trace, JUnit will provide detailed error results:

org.opentest4j.AssertionFailedError: expected: <3628800> but was: <362880>
    at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
    at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:195)
    at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:168)
    at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:163)
    at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:611)
    at com.itranswarp.learnjava.FactorialTest.testFact(FactorialTest.java:14)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at ...

The first line of the failure message means that it expected 3628800 but actually returned 362880. At this point, we either fix the implementation code or correct the test code until the test passes.

When using floating-point numbers, since they cannot be compared precisely, we need to call the overloaded method assertEquals(double expected, double actual, double delta) to specify a margin of error:

java
assertEquals(0.1, Math.abs(1 - 9 / 10.0), 0.0000001);

Benefits of Unit Testing

Unit testing ensures that individual methods operate as expected. If a method's code is modified, you only need to ensure that its corresponding unit tests pass to consider the change correct. Additionally, the test code itself can serve as example code, demonstrating how to call the method.

By using JUnit for unit testing, we can use assertions to test expected results, conveniently organize and run tests, and easily view test results. Moreover, JUnit can be run directly in the IDE or easily integrated into automated tools like Maven.

When writing unit tests, we should follow certain guidelines:

  1. The unit test code itself must be very simple and easy to understand at a glance. Do not write tests for the test code.
  2. Each unit test should be independent of others and not rely on the order of execution.
  3. Tests should cover not only common test cases but also pay special attention to boundary conditions, such as inputs being 0, null, or empty strings "", etc.

Summary

JUnit is a unit testing framework specifically designed to run the unit tests we write:

A JUnit test includes several @Test methods and uses Assertions for assertions. Note that when using floating-point numbers, assertEquals() requires specifying a delta.

What is Unit Testing? has loaded