【JAVA】单元测试的简单应用

单元测试是验证软件中最小可测试部分正确性的自动化测试。在Java中,单元测试通常针对类的方法或函数进行。以下是单元测试的一般写法,以及一些常用的单元测试框架。

1. 准备工作

在开始编写单元测试之前,需要确保项目中包含了单元测试框架。Java中常用的单元测试框架有:

  • JUnit: 最流行的Java单元测试框架。
  • TestNG: 功能强大的测试框架,与JUnit兼容但提供了更多特性。
  • Mockito: 主要用于模拟对象和行为的工具。

2. 创建测试类

测试类通常放在与源代码相同的包中,但位于不同的源集(source set)里。测试类的命名约定通常是在被测试类名后加上"Test"后缀。

3. 编写测试方法

测试方法通常遵循以下命名约定:test + 方法描述,必须使用public访问修饰符,并且是void返回类型。JUnit 4使用注解@Test来标识测试方法。

4. 断言(Assertions)

断言是单元测试中的核心,用于验证代码的预期输出。JUnit提供了多种断言方法,如:

  • assertEquals: 验证两个值是否相等。
  • assertTrue: 验证一个条件是否为真。
  • assertFalse: 验证一个条件是否为假。
  • assertNotNull: 验证对象不为null。
  • assertNull: 验证对象为null。

示例:使用JUnit 4编写单元测试

假设我们有以下简单的加法类:

java 复制代码
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

对应的单元测试可能如下:

java 复制代码
import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class CalculatorTest {

    @Test
    public void testAdd() {
        Calculator calculator = new Calculator();
        assertEquals("10 + 5 must equal 15", 15, calculator.add(10, 5));
    }
    
    @Test
    public void testAddZero() {
        Calculator calculator = new Calculator();
        assertEquals("Adding zero must return the same number", 5, calculator.add(5, 0));
    }
    
    @Test(expected = IllegalArgumentException.class)
    public void testAddNegative() {
        Calculator calculator = new Calculator();
        calculator.add(-1, 5);
    }
}

使用Mockito进行单元测试

当测试涉及与其他对象交互或需要模拟外部依赖时,可以使用Mockito来创建模拟对象。

java 复制代码
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

public class ServiceTest {

    @Mock
    private Dependency dependency;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testSomeBehavior() {
        Service service = new Service(dependency);
        
        // 设置模拟行为
        when(dependency.someMethod()).thenReturn("expected value");
        
        // 执行测试
        String result = service.someBehavior();
        
        // 验证方法调用
        verify(dependency).someMethod();
        
        // 断言结果
        assertEquals("Method should return expected value", "expected value", result);
    }
}

实例1:验证方法调用

假设我们有一个UserService类,它依赖于一个UserRepository接口来获取用户信息。

java 复制代码
public interface UserRepository {
    User findUserById(int id);
}

public class UserService {
    private final UserRepository userRepository;

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

    public User getUser(int id) {
        return userRepository.findUserById(id);
    }
}

单元测试使用Mockito验证getUser方法是否正确调用了UserRepositoryfindUserById方法。

java 复制代码
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

public class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    private UserService userService;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        userService = new UserService(userRepository);
    }

    @Test
    public void testGetUser() {
        User expectedUser = new User(1, "John Doe");
        when(userRepository.findUserById(anyInt())).thenReturn(expectedUser);

        User user = userService.getUser(1);

        verify(userRepository).findUserById(1);
        assertEquals("The returned user should be the expected user", expectedUser, user);
    }
}

实例2:测试异常处理

假设UserRepositoryfindUserById方法可能抛出一个自定义异常UserNotFoundException

java 复制代码
public class UserNotFoundException extends Exception {
    public UserNotFoundException(int id) {
        super("User with id " + id + " not found.");
    }
}

// UserService类保持不变

单元测试验证当用户未找到时,UserService是否正确地将异常传递出去。

java 复制代码
@Test(expected = UserNotFoundException.class)
public void testGetUserWhenUserNotFound() {
    when(userRepository.findUserById(anyInt())).thenThrow(new UserNotFoundException(1));

    userService.getUser(1);
}

实例3:使用Mockito验证顺序

假设OrderService类需要按照特定的顺序调用两个依赖项PaymentServiceEmailService

java 复制代码
public class OrderService {
    private final PaymentService paymentService;
    private final EmailService emailService;

    public OrderService(PaymentService paymentService, EmailService emailService) {
        this.paymentService = paymentService;
        this.emailService = emailService;
    }

    public void processOrder(Order order) {
        paymentService.processPayment(order);
        emailService.sendConfirmationEmail(order);
    }
}

单元测试验证processOrder方法中服务的调用顺序。

java 复制代码
import static org.mockito.Mockito.*;

@Test
public void testProcessOrder() {
    @Mock
    PaymentService paymentService;
    @Mock
    EmailService emailService;
    OrderService orderService = new OrderService(paymentService, emailService);
    Order order = new Order();

    orderService.processOrder(order);

    verify(paymentService).processPayment(order);
    verify(emailService).sendConfirmationEmail(order);
    // 验证调用顺序
    verify(paymentService, Mockito.ordering()).processPayment(order);
    verify(emailService, Mockito.ordering()).sendConfirmationEmail(order);
}

实例4:使用Spy进行部分模拟

假设我们想测试NetworkService类的真实行为,但同时要模拟一个方法。

java 复制代码
public class NetworkService {
    public String sendRequest(String url) {
        // 实际发送网络请求的代码
        return "response";
    }
}

单元测试中,我们使用Spy来模拟sendRequest方法,而其他方法则使用真实实现。

java 复制代码
@Test
public void testNetworkService() {
    NetworkService spyService = Mockito.spy(new NetworkService());
    doReturn("mocked response").when(spyService).sendRequest(anyString());

    String response = spyService.sendRequest("http://example.com");

    assertEquals("The response should be mocked", "mocked response", response);
    // 验证sendRequest是否被调用一次
    verify(spyService).sendRequest("http://example.com");
}

注意事项

  • 测试应该独立于彼此运行,不依赖于外部资源如数据库或网络服务。
  • 测试应该快速执行,以便频繁运行。
  • 测试代码应该具有可读性,易于理解。
  • 测试覆盖率是一个有用的指标,但100%的覆盖率并不总是必要的或最佳的。
相关推荐
小强库计算机毕业设计10 小时前
SpringBoot+Vue3 学生宿舍管理系统实战
java·spring boot·后端·vue·学生宿舍管理系统
李冰然10 小时前
深入Java集合框架:HashMap源码解析(JDK 8)
java
人道领域11 小时前
【0-1的agent进阶篇】RAG:从Embedding到检索增强生成的底层逻辑
java·embedding·agent·rag
桦说编程12 小时前
盘点并发集合里那些容易误判的行为
java·后端·性能优化
IT爱学堂12 小时前
java版数据结构和算法+AI算法和技能学习指南
java·开发语言
evans在进步13 小时前
LeetCode 394:字符串解码——Java 单栈模拟与嵌套解析详解
java·python·leetcode
AI人工智能+电脑小能手13 小时前
大白话说Java设计模式-17-装饰器模式(源码剖析篇)
java·spring·设计模式·装饰器模式·源码分析·io流
m0_5474866614 小时前
《Java程序设计与实践 》全套PPT课件2026
java·开发语言
重庆小透明14 小时前
深入探寻微服务【第五篇微服务限流实战】
微服务·junit·架构
土司大王14 小时前
LeetCode hto100——字母异位词分组
java·算法·leetcode