Java JUnit Unit Testing Framework

Java 常用类库Java Common Libraries


JUnit is one of the most popular unit testing frameworks for the Java programming language, used to write and run repeatable automated tests.

JUnit was created by Kent Beck and Erich Gamma and is a member of the xUnit family. Unit testing is the process of checking and verifying the smallest testable parts of software, usually methods or classes.

The main features of JUnit include:

  • Provides annotations to identify test methods
  • Provides assertions to verify expected results
  • Supports test suites
  • Provides a test runner

Why Do We Need Unit Testing?

Unit testing is an indispensable part of modern software development. It brings the following benefits:

  1. Early problem detection: Discover and fix errors during development
  2. Improved code quality: Encourages developers to write more modular, testable code
  3. Documentation value: Test cases themselves are the best documentation of code behavior
  4. Refactoring safety net: Ensures that modifying code doesn't break existing functionality
  5. Reduced debugging time: Quickly locate problems

Using JUnit in Projects

For JUnit 5:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.8.2</version>
    <scope>test</scope>
</dependency>

For JUnit 4:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

JUnit 5 Basics

JUnit 5 is the latest version, consisting of three main modules:

  • JUnit Platform (the foundation for running tests)
  • JUnit Jupiter (new programming model and extension model)
  • JUnit Vintage (supports running JUnit 3 and 4 tests)

Basic Annotations

Legacy annotations:

  • @Test: Marks a method as a test method

  • @Before: Runs before each test method

  • @After: Runs after each test method

  • @BeforeClass: Runs before all tests (static method)

  • @AfterClass: Runs after all tests (static method)

  • @Ignore: Ignores a test method

Example

import org.junit.*;

public class CalculatorTest {
   
    @BeforeClass
    public static void setUpBeforeClass() {
        System.out.println("Before all tests");
    }
   
    @Before
    public void setUp() {
        System.out.println("Before each test");
    }
   
    @Test
    public void testAddition() {
        Calculator calculator = new Calculator();
        int result = calculator.add(2, 3);
        Assert.assertEquals(5, result);
    }
   
    @Test
    @Ignore("Not implemented yet")
    public void testSubtraction() {
        // test code
    }
   
    @After
    public void tearDown() {
        System.out.println("After each test");
    }
   
    @AfterClass
    public static void tearDownAfterClass() {
        System.out.println("After all tests");
    }
}

JUnit 5 is the next-generation version, with the following new annotations:

  • @BeforeEachreplaces@Before

  • @AfterEachreplaces@After

  • @BeforeAllreplaces@BeforeClass

  • @AfterAllreplaces@AfterClass

  • @Disabledreplaces@Ignore

Example

import org.junit.jupiter.api.*;

@Test
void testMethod() {
    // test code
}

@BeforeEach
void setUp() {
    // Runs before each test method
}

@AfterEach
void tearDown() {
    // Runs after each test method
}

@BeforeAll
static void initAll() {
    // Runs once before all test methods
}

@AfterAll
static void tearDownAll() {
    // Runs once after all test methods
}

Common Assertion Methods

JUnit provides a rich set of assertion methods to verify test results:

  • assertEquals(expected, actual)

  • assertTrue(condition)

  • assertFalse(condition)

  • assertNull(object)

  • assertNotNull(object)

  • assertSame(expected, actual)(Checks whether it's the same object)

  • assertNotSame(unexpected, actual)

  • assertArrayEquals(expectedArray, actualArray)

Example

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

@Test
void testAssertions() {
    // Equality assertion
    assertEquals(expected, actual);
   
    // True assertion
    assertTrue(condition);
   
    // Null assertion
    assertNull(object);
   
    // Exception assertion
    assertThrows(ExpectedException.class, () -> {
        // Code that throws an exception
    });
   
    // Timeout assertion
    assertTimeout(Duration.ofMillis(100), () -> {
        // Code that should complete within the specified time
    });
}

Writing Your First JUnit Test

Let's learn how to write JUnit tests with a simple example.

1. Create the Class Under Test

Example

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
   
    public int divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("Divisor cannot be zero");
        }
        return a / b;
    }
}

2. Create the Test Class

Example

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

class CalculatorTest {
    private Calculator calculator;
   
    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }
   
    @Test
    void testAdd() {
        assertEquals(5, calculator.add(2, 3));
    }
   
    @Test
    void testDivide() {
        assertEquals(2, calculator.divide(6, 3));
    }
   
    @Test
    void testDivideByZero() {
        assertThrows(ArithmeticException.class, () -> {
            calculator.divide(1, 0);
        });
    }
}

JUnit Advanced Features

Parameterized Tests

JUnit 5 provides the@ParameterizedTestannotation, allowing the same test to be run multiple times with different parameters:

Example

@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void testWithValueSource(int argument) {
    assertTrue(argument > 0 && argument < 4);
}

Test Lifecycle

Understanding the JUnit test lifecycle is important for writing effective tests:

  1. @BeforeAllMethod execution (only once)
  2. For each test method:
    • Create a test class instance
    • @BeforeEachMethod execution
    • Test method execution
    • @AfterEachMethod execution
  3. @AfterAllMethod execution (only once)

Test Suites

You can use the@Suiteannotation to combine multiple test classes to run together:

Example

import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;

@Suite
@SelectClasses({CalculatorTest.class, AnotherTest.class})
public class TestSuite {
}

Best Practices

  1. Test naming: Test method names should clearly express their intent, such asshouldReturnTrueWhenInputIsValid()
  2. Single responsibility: Each test method should test only one functionality
  3. Independent tests: Tests should not have dependencies on each other
  4. Fast feedback: Keep tests running fast
  5. Test coverage: Aim for reasonable coverage, but don't blindly pursue 100%
  6. Test data: Use meaningful test data
  7. Avoid testing implementation details: Test behavior rather than implementation

Frequently Asked Questions

What's the Difference Between JUnit 4 and JUnit 5?

The main differences include:

  • JUnit 5 requires Java 8 or higher
  • Annotations moved from theorg.junitpackage to theorg.junit.jupiter.apiPackage
  • @Beforeand@Afterchanged to@BeforeEachand@AfterEach
  • @BeforeClassand@AfterClasschanged to@BeforeAlland@AfterAll
  • JUnit 5 introduces an extension model to replace JUnit 4's rules

How to Run JUnit Tests?

You can run JUnit tests in the following ways:

  1. Right-click on the test class or method in the IDE and select "Run"
  2. Using Maven:mvn test
  3. Using Gradle:gradle test
  4. Using the JUnit Console Launcher

How to Test Private Methods?

It's generally not recommended to test private methods directly; instead, test them indirectly through public methods. If you must test private methods, consider:

  1. Using reflection
  2. Change the method to package-private (no modifier) and create a test class with the same package structure in the test directory
  3. Refactor the code to extract the private method's logic into a separate class

Summary

JUnit is an essential testing tool for Java developers. Mastering it can significantly improve code quality and development efficiency. Start with simple unit tests and gradually learn more advanced features, such as parameterized tests and test suites. Remember, good tests should be valued just as much as production code; keeping test code clean and maintainable is equally important.

Java 常用类库Java Common Libraries

Other Extensions