Java 测试:单元测试、集成测试、端到端测试 — 完整指南

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Java测试:从单元测试到端到端测试

高质量的测试是可靠软件的基础。在Java世界中,存在一个成熟的测试金字塔,包含三个主要层级:unit(单元测试)、integration(集成测试)和e2e(端到端测试)。本文将逐一解析这些测试类型,提供Java实践示例,并讲解如何在项目中正确构建测试策略。

1. 单元测试:金字塔的基础

单元测试在隔离环境中验证程序的独立组件——通常是一个类或方法。它们执行速度快,不需要外部依赖(数据库、API),并且应该覆盖大部分业务逻辑。

Java单元测试工具

  • JUnit 5 — 编写测试的标准框架。
  • Mockito — 用于创建模拟对象的库。
  • AssertJ — 提供流畅断言以实现可读性检查。

使用JUnit 5和Mockito的单元测试示例

假设我们有一个依赖PaymentGateway的OrderService服务。我们希望在不调用真实支付网关的情况下测试折扣计算逻辑。

// 待测试类
public class OrderService {
    private final PaymentGateway paymentGateway;

    public OrderService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }

    public double calculateDiscount(double amount) {
        if (amount > 1000) {
            return amount * 0.1; // 10%折扣
        }
        return 0;
    }
}

// 单元测试
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith(MockitoExtension.class)
public class OrderServiceTest {

    @Mock
    private PaymentGateway paymentGateway;

    @InjectMocks
    private OrderService orderService;

    @Test
    public void testCalculateDiscount_AmountAbove1000_Returns10Percent() {
        double result = orderService.calculateDiscount(1500);
        assertEquals(150.0, result, 0.001);
    }

    @Test
    public void testCalculateDiscount_AmountBelow1000_ReturnsZero() {
        double result = orderService.calculateDiscount(500);
        assertEquals(0.0, result);
    }
}

最佳实践:使用@BeforeEach初始化公共数据,避免直接测试私有方法,遵循"一个测试只验证一个检查点"的原则。

2. 集成测试:验证交互

集成测试验证多个模块如何协同工作——例如服务与数据库、消息队列或外部API的交互。与单元测试不同,这里使用真实或内存中的依赖。

集成测试工具

  • Spring Boot Test — 用于测试Spring应用程序。
  • Testcontainers — 用于启动Docker容器(数据库、Kafka)。
  • H2 Database — 用于快速测试的内存数据库。

使用Spring Boot的集成测试示例

测试与真实数据库连接的REST控制器(测试中使用H2)。

// 控制器
@RestController
@RequestMapping("/api/users")
public class UserController {
    private final UserRepository userRepository;

    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new Runti                

博客

书籍推荐