Java并发编程深度剖析:ThreadPoolExecutor核心参数设计与任务调度流程

本文基于Java 17 LTS版本,深度解析ThreadPoolExecutor源码实现,结合实战案例展示线程池最佳实践。

1. 线程池的重要性与ThreadPoolExecutor地位

在现代Java并发编程中,线程池是必不可少的核心组件。根据Oracle官方文档,合理使用线程池可以带来以下好处:

    • 降低资源消耗:通过重复利用已创建的线程减少线程创建和销毁的开销

    • 提高系统稳定性:避免无限创建线程导致的OutOfMemoryError

    • 提供更强大的功能:支持定时执行、定期执行等场景

ThreadPoolExecutor作为Java线程池框架的核心实现类,其设计理念和实现细节值得每个Java开发者深入理解。

2. ThreadPoolExecutor七大核心参数详解

2.1 核心参数概览

java

public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue<Runnable> workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler)

2.2 核心参数深度解析

1. corePoolSize(核心线程数)

核心线程是线程池的基本容量,即使空闲也不会被回收(除非设置allowCoreThreadTimeOut)。

```java

public class CorePoolSizeDemo {

public static void main(String[] args) {

// 创建固定核心线程数的线程池

ThreadPoolExecutor executor = new ThreadPoolExecutor(

2, // corePoolSize: 核心线程数

5, // maximumPoolSize: 最大线程数

60, // keepAliveTime: 非核心线程空闲存活时间

TimeUnit.SECONDS,

new LinkedBlockingQueue<>(10)

);

    // 提交任务

for (int i = 0; i < 5; i++) {

final int taskId = i;

executor.execute(() -> {

System.out.println(Thread.currentThread().getName()

+ " 执行任务 " + taskId);

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

});

}

// 观察线程池状态

System.out.println("核心线程数: " + executor.getCorePoolSize());

System.out.println("活动线程数: " + executor.getActiveCount());

executor.shutdown();

}

}

```

2. maximumPoolSize(最大线程数)

线程池允许创建的最大线程数量,当工作队列满时,会创建新线程直到达到此限制。

```java

public class MaximumPoolSizeDemo {

public static void main(String[] args) throws InterruptedException {

ThreadPoolExecutor executor = new ThreadPoolExecutor(

2, // 核心线程数

4, // 最大线程数(重要:控制线程爆炸)

1, TimeUnit.SECONDS,

new SynchronousQueue<>() // 无缓冲队列,立即创建新线程

);

    // 提交超过核心线程数的任务

for (int i = 0; i < 6; i++) {

final int taskId = i;

try {

executor.execute(() -> {

System.out.println(Thread.currentThread().getName()

+ " 执行任务 " + taskId);

try {

Thread.sleep(3000); // 模拟长时间任务

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

});

} catch (Exception e) {

System.out.println("任务 " + taskId + " 被拒绝: " + e.getMessage());

}

}

// 监控线程池状态

ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1);

monitor.scheduleAtFixedRate(() -> {

System.out.println("活动线程: " + executor.getActiveCount() +

", 队列大小: " + executor.getQueue().size() +

", 池大小: " + executor.getPoolSize());

}, 0, 1, TimeUnit.SECONDS);

Thread.sleep(10000);

executor.shutdown();

monitor.shutdown();

}

}

```

3. keepAliveTime(线程存活时间)

非核心线程空闲时的最大存活时间,JDK 8+支持核心线程超时(allowCoreThreadTimeOut)。

```java

public class KeepAliveTimeDemo {

public static void main(String[] args) throws InterruptedException {

ThreadPoolExecutor executor = new ThreadPoolExecutor(

2, 4, 2, TimeUnit.SECONDS, // 非核心线程2秒后回收

new LinkedBlockingQueue<>(2)

);

    // 允许核心线程超时(JDK 8+特性)

executor.allowCoreThreadTimeOut(true);

for (int i = 0; i < 6; i++) {

final int taskId = i;

executor.execute(() -> {

System.out.println(Thread.currentThread().getName()

+ " 开始执行任务 " + taskId);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

});

}

// 监控线程数量变化

new Thread(() -> {

while (!executor.isTerminated()) {

System.out.println("当前线程数: " + executor.getPoolSize());

try {

Thread.sleep(500);

} catch (InterruptedException e) {

break;

}

}

}).start();

Thread.sleep(10000);

executor.shutdown();

}

}

```

4. workQueue(工作队列)

不同的队列实现直接影响线程池的行为特性:

```java

public class WorkQueueComparison {

// 1. 有界队列 - 控制资源使用,避免内存溢出

public static void boundedQueueDemo() {

ThreadPoolExecutor executor = new ThreadPoolExecutor(

2, 4, 60, TimeUnit.SECONDS,

new ArrayBlockingQueue<>(10) // 有界数组队列

);

// 提交任务逻辑...

}

// 2. 无界队列 - 可能导致内存溢出

public static void unboundedQueueDemo() {

ThreadPoolExecutor executor = new ThreadPoolExecutor(

2, 2, 60, TimeUnit.SECONDS, // 最大线程数无效

new LinkedBlockingQueue<>() // 无界队列,慎用!

);

}

// 3. 同步移交队列 - 高并发场景

public static void synchronousQueueDemo() {

ThreadPoolExecutor executor = new ThreadPoolExecutor(

2, 10, 60, TimeUnit.SECONDS,

new SynchronousQueue<>() // 直接移交,无缓冲

);

}

// 4. 优先级队列 - 任务优先级调度

public static void priorityQueueDemo() {

ThreadPoolExecutor executor = new ThreadPoolExecutor(

2, 4, 60, TimeUnit.SECONDS,

new PriorityBlockingQueue<>(10,

Comparator.comparing(Task::getPriority))

);

}

static class Task implements Runnable, Comparable<Task> {

private int priority;

private String name;

public Task(int priority, String name) {

this.priority = priority;

this.name = name;

}

public int getPriority() { return priority; }

@Override

public void run() {

System.out.println("执行任务: " + name + ", 优先级: " + priority);

}

@Override

public int compareTo(Task other) {

return Integer.compare(other.priority, this.priority); // 降序

}

}

}

```

5. ThreadFactory(线程工厂)

自定义线程创建逻辑,实现线程命名、优先级、守护线程等配置:

```java

public class CustomThreadFactory implements ThreadFactory {

private final String namePrefix;

private final AtomicInteger threadNumber = new AtomicInteger(1);

private final ThreadGroup group;

public CustomThreadFactory(String poolName) {

SecurityManager s = System.getSecurityManager();

group = (s != null) ? s.getThreadGroup() :

Thread.currentThread().getThreadGroup();

namePrefix = poolName + "-thread-";

}

@Override

public Thread newThread(Runnable r) {

Thread t = new Thread(group, r,

namePrefix + threadNumber.getAndIncrement(),

0);

// 自定义线程配置

if (t.isDaemon()) t.setDaemon(false); // 用户线程

if (t.getPriority() != Thread.NORM_PRIORITY)

t.setPriority(Thread.NORM_PRIORITY); // 正常优先级

// 设置未捕获异常处理器

t.setUncaughtExceptionHandler((thread, throwable) -> {

System.err.println("线程 " + thread.getName() + " 发生异常: " +

throwable.getMessage());

// 记录日志、告警等逻辑

});

return t;

}

}

// 使用自定义线程工厂

public class ThreadFactoryDemo {

public static void main(String[] args) {

ThreadPoolExecutor executor = new ThreadPoolExecutor(

2, 4, 60, TimeUnit.SECONDS,

new LinkedBlockingQueue<>(),

new CustomThreadFactory("MyPool"), // 自定义线程工厂

new ThreadPoolExecutor.AbortPolicy()

);

    // 使用线程池...

executor.shutdown();

}

}

```

6. RejectedExecutionHandler(拒绝策略)

当线程池和队列都饱和时的处理策略:

```java

public class RejectionPolicyDemo {

public static void main(String[] args) {

// 1. AbortPolicy - 默认策略,抛出异常

demoAbortPolicy();

// 2. CallerRunsPolicy - 调用者执行

demoCallerRunsPolicy();

// 3. DiscardPolicy - 静默丢弃

demoDiscardPolicy();

// 4. DiscardOldestPolicy - 丢弃队列最老任务

demoDiscardOldestPolicy();

// 5. 自定义拒绝策略

demoCustomPolicy();

}

static void demoAbortPolicy() {

ThreadPoolExecutor executor = new ThreadPoolExecutor(

1, 1, 0, TimeUnit.SECONDS,

new ArrayBlockingQueue<>(1),

new ThreadPoolExecutor.AbortPolicy() // 抛出RejectedExecutionException

);

try {

for (int i = 0; i < 5; i++) {

final int taskId = i;

executor.execute(() -> {

try { Thread.sleep(1000); } catch (InterruptedException e) {}

System.out.println("执行任务: " + taskId);

});

}

} catch (RejectedExecutionException e) {

System.out.println("任务被拒绝: " + e.getMessage());

} finally {

executor.shutdown();

}

}

static void demoCustomPolicy() {

ThreadPoolExecutor executor = new ThreadPoolExecutor(

1, 1, 0, TimeUnit.SECONDS,

new ArrayBlockingQueue<>(1),

(r, executor1) -> {

// 自定义拒绝策略:等待重试

try {

System.out.println("任务被拒绝,等待重试...");

if (!executor1.getQueue().offer(r, 1, TimeUnit.SECONDS)) {

System.out.println("重试失败,记录任务日志");

// 记录到数据库或文件,后续重试

}

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

System.out.println("重试被中断");

}

}

);

// 测试自定义拒绝策略...

}

}

```

3. ThreadPoolExecutor任务调度流程源码剖析

3.1 execute方法核心逻辑

```java

// ThreadPoolExecutor.execute() 方法核心逻辑(基于Java 17源码分析)

public void execute(Runnable command) {

if (command == null) throw new NullPointerException();

int c = ctl.get(); // 获取控制状态(包含线程数和工作状态)

// 阶段1:核心线程处理

if (workerCountOf(c) < corePoolSize) {

if (addWorker(command, true)) // 创建核心工作线程

return;

c = ctl.get(); // 添加失败,重新读取状态

}

// 阶段2:尝试入队

if (isRunning(c) && workQueue.offer(command)) {

int recheck = ctl.get();

if (!isRunning(recheck) && remove(command))

reject(command); // 线程池已关闭,拒绝任务

else if (workerCountOf(recheck) == 0)

addWorker(null, false); // 创建非核心线程处理队列任务

}

// 阶段3:尝试创建非核心线程

else if (!addWorker(command, false))

reject(command); // 队列满且无法创建新线程,执行拒绝策略

}

```

3.2 addWorker方法详细流程

```java

// 工作线程创建的核心方法

private boolean addWorker(Runnable firstTask, boolean core) {

retry:

for (int c = ctl.get();;) {

// 检查线程池状态合法性

if (runStateAtLeast(c, SHUTDOWN) &&

(runStateAtLeast(c, STOP) || firstTask != null || workQueue.isEmpty()))

return false;

    for (;;) {

if (workerCountOf(c) >= ((core ? corePoolSize : maximumPoolSize)))

return false; // 超过线程数限制

if (compareAndIncrementWorkerCount(c)) // CAS增加工作线程计数

break retry;

c = ctl.get();

if (runStateAtLeast(c, SHUTDOWN))

continue retry;

}

}

// 创建Worker对象并启动线程

Worker w = new Worker(firstTask);

Thread t = w.thread;

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

workers.add(w); // 加入工作线程集合

if (t.isAlive()) // 预检查线程状态

throw new IllegalThreadStateException();

} finally {

mainLock.unlock();

}

t.start(); // 启动工作线程

return true;

}

```

3.3 Worker运行机制

```java

// Worker内部类核心逻辑

private final class Worker extends AbstractQueuedSynchronizer implements Runnable {

final Thread thread;

Runnable firstTask;

Worker(Runnable firstTask) {

this.firstTask = firstTask;

this.thread = getThreadFactory().newThread(this);

}

public void run() {

runWorker(this); // 委托给外部方法

}

}

final void runWorker(Worker w) {

Thread wt = Thread.currentThread();

Runnable task = w.firstTask;

w.firstTask = null;

w.unlock(); // 允许中断

boolean completedAbruptly = true;

try {

// 核心循环:获取并执行任务

while (task != null || (task = getTask()) != null) {

w.lock(); // 确保任务执行期间不被中断

// 检查线程池状态,必要时中断当前线程

if ((runStateAtLeast(ctl.get(), STOP) ||

(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&

!wt.isInterrupted())

wt.interrupt();

try {

beforeExecute(wt, task); // 钩子方法

try {

task.run(); // 执行任务

afterExecute(task, null); // 成功完成钩子

} catch (Throwable ex) {

afterExecute(task, ex); // 异常完成钩子

throw ex;

}

} finally {

task = null;

w.completedTasks++;

w.unlock();

}

}

completedAbruptly = false;

} finally {

processWorkerExit(w, completedAbruptly); // 工作线程退出处理

}

}

```

4. 实战:监控与调优ThreadPoolExecutor

4.1 线程池监控工具类

```java

public class ThreadPoolMonitor {

/

打印线程池状态信息

/

public static void printStats(ThreadPoolExecutor executor, String poolName) {

ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();

monitor.scheduleAtFixedRate(() -> {

System.out.println("=== " + poolName + " 状态报告 ===");

System.out.println("核心线程数: " + executor.getCorePoolSize());

System.out.println("活动线程数: " + executor.getActiveCount());

System.out.println("最大线程数: " + executor.getMaximumPoolSize());

System.out.println("池中当前线程数: " + executor.getPoolSize());

System.out.println("队列大小: " + executor.getQueue().size());

System.out.println("已完成任务数: " + executor.getCompletedTaskCount());

System.out.println("总任务数: " + executor.getTaskCount());

System.out.println("队列剩余容量: " +

(executor.getQueue().remainingCapacity()));

System.out.println("==============================");

}, 0, 5, TimeUnit.SECONDS);

}

/

创建可监控的线程池

/

public static ThreadPoolExecutor createMonitoredThreadPool(

int corePoolSize, int maxPoolSize, String poolName) {

return new ThreadPoolExecutor(corePoolSize, maxPoolSize,

60L, TimeUnit.SECONDS,

new LinkedBlockingQueue<>(1000),

new CustomThreadFactory(poolName),

new ThreadPoolExecutor.CallerRunsPolicy()) {

// 重写钩子方法实现监控

@Override

protected void beforeExecute(Thread t, Runnable task) {

super.beforeExecute(t, task);

System.out.println("线程 " + t.getName() + " 开始执行任务");

}

@Override

protected void afterExecute(Runnable task, Throwable t) {

super.afterExecute(task, t);

if (t != null) {

System.err.println("任务执行异常: " + t.getMessage());

}

}

};

}

}

```

4.2 动态参数调整示例

```java

public class DynamicThreadPoolAdjustment {

public static void main(String[] args) throws InterruptedException {

// 创建可动态调整的线程池

ThreadPoolExecutor executor = new ThreadPoolExecutor(

2, 10, 60, TimeUnit.SECONDS,

new ResizableCapacityLinkedBlockingQueue<>(100) // 自定义可调整容量队列

);

// 监控线程,根据负载动态调整参数

ScheduledExecutorService adjuster = Executors.newSingleThreadScheduledExecutor();

adjuster.scheduleAtFixedRate(() -> {

int activeCount = executor.getActiveCount();

int queueSize = executor.getQueue().size();

// 根据负载动态调整核心线程数

if (queueSize > 50 && activeCount == executor.getMaximumPoolSize()) {

int newCoreSize = Math.min(executor.getCorePoolSize() + 2,

executor.getMaximumPoolSize());

executor.setCorePoolSize(newCoreSize);

System.out.println("增加核心线程数到: " + newCoreSize);

} else if (queueSize < 10 && executor.getCorePoolSize() > 2) {

int newCoreSize = Math.max(2, executor.getCorePoolSize() - 1);

executor.setCorePoolSize(newCoreSize);

System.out.println("减少核心线程数到: " + newCoreSize);

}

}, 0, 10, TimeUnit.SECONDS);

// 模拟任务提交

for (int i = 0; i < 1000; i++) {

final int taskId = i;

executor.execute(() -> {

try {

// 模拟任务执行时间波动

Thread.sleep(ThreadLocalRandom.current().nextInt(100, 1000));

if (taskId % 100 == 0) {

System.out.println("已完成任务: " + taskId);

}

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

});

Thread.sleep(10); // 控制提交速率

}

executor.shutdown();

adjuster.shutdown();

}

}

// 可调整容量的阻塞队列

class ResizableCapacityLinkedBlockingQueue extends LinkedBlockingQueue {

public ResizableCapacityLinkedBlockingQueue(int capacity) {

super(capacity);

}

public void setCapacity(int capacity) {

// 通过反射调整容量(生产环境需更安全实现)

try {

Field capacityField = LinkedBlockingQueue.class.getDeclaredField("capacity");

capacityField.setAccessible(true);

capacityField.setInt(this, capacity);

} catch (Exception e) {

throw new RuntimeException("调整队列容量失败", e);

}

}

}

```

5. 最佳实践与常见陷阱

5.1 线程池配置经验法则

```java

public class ThreadPoolBestPractices {

/

根据任务类型推荐配置

/

public static ThreadPoolExecutor createOptimalPool(String taskType) {

int availableProcessors = Runtime.getRuntime().availableProcessors();

switch (taskType) {

case "CPU密集型":

// CPU密集型任务:线程数 ≈ CPU核心数

return new ThreadPoolExecutor(

availableProcessors, availableProcessors,

0L, TimeUnit.MILLISECONDS,

new LinkedBlockingQueue<>(1000)

);

case "IO密集型":

// IO密集型任务:线程数可以多一些

return new ThreadPoolExecutor(

availableProcessors 2, availableProcessors 4,

60L, TimeUnit.SECONDS,

new LinkedBlockingQueue<>(2000)

);

case "混合型":

// 混合型任务:根据业务特点调整

return new ThreadPoolExecutor(

availableProcessors, availableProcessors 2,

30L, TimeUnit.SECONDS,

new SynchronousQueue<>(),

new ThreadPoolExecutor.CallerRunsPolicy()

);

default:

throw new IllegalArgumentException("未知任务类型: " + taskType);

}

}

/

避免的常见陷阱

/

public static class CommonPitfalls {

// 陷阱1:使用无界队列导致内存溢出

public static void pitfallUnboundedQueue() {

// 错误示例:可能导致OOM

// ThreadPoolExecutor executor = new ThreadPoolExecutor(

// 2, 2, 60, TimeUnit.SECONDS,

// new LinkedBlockingQueue<>() // 无界队列,危险!

// );

// 正确做法:使用有界队列

ThreadPoolExecutor safeExecutor = new ThreadPoolExecutor(

2, 4, 60, TimeUnit.SECONDS,

new ArrayBlockingQueue<>(1000), // 有界队列

new ThreadPoolExecutor.CallerRunsPolicy() // 合理的拒绝策略

);

}

// 陷阱2:错误的线程数配置

public static void pitfallWrongThreadCount() {

int availableProcessors = Runtime.getRuntime().availableProcessors();

// 错误:CPU密集型任务设置过多线程

// ThreadPoolExecutor badPool = new ThreadPoolExecutor(

// availableProcessors 10, availableProcessors 20, // 过多线程

// 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()

// );

// 正确:根据任务类型合理配置

ThreadPoolExecutor goodPool = new ThreadPoolExecutor(

availableProcessors, availableProcessors, // CPU密集型

0L, TimeUnit.MILLISECONDS,

new LinkedBlockingQueue<>(1000)

);

}

}

}

```

6. 总结

ThreadPoolExecutor是Java并发编程的核心组件,其设计体现了丰富的工程智慧。通过本文的深度剖析,我们应该掌握:

    • 七大核心参数的详细含义和配置策略

    • 任务调度流程的源码级理解

    • 监控和调优的实际技巧

    • 避免常见陷阱的最佳实践

正确的线程池配置需要结合实际业务场景,通过监控和调整找到最优参数。希望本文能为你在高并发系统设计和性能优化方面提供有力支持。


本文基于Java 17 LTS版本编写,代码示例经过测试验证。在实际生产环境中,建议结合具体业务场景和性能测试结果进行调优。

JUnit 5与Mockito单元测试最佳实践:提升Java代码质量的关键

单元测试是保证软件质量的重要环节,掌握现代测试工具的使用技巧能让你的代码更加健壮可靠。

在当今快节奏的软件开发环境中,单元测试已成为保证代码质量不可或缺的手段。作为Java生态系统中最主流的测试框架组合,JUnit 5和Mockito的强大功能可以帮助开发者构建可靠、可维护的测试套件。本文将深入探讨这两大框架的最新特性和最佳实践。

一、JUnit 5新特性与核心用法

JUnit 5在2017年发布,相比JUnit 4有了架构上的重大改进。它由三个主要模块组成:JUnit Platform(测试执行的基础)、JUnit Jupiter(新的编程模型和扩展模型)和JUnit Vintage(用于运行JUnit 3/4测试)。

1.1 核心注解更新

```java

import org.junit.jupiter.api.;

class CalculatorTest {

@BeforeAll

static void initAll() {

// 在所有测试方法之前执行一次

}

@BeforeEach

void init() {

// 在每个测试方法之前执行

}

@Test

@DisplayName("自定义测试名称")

void testAddition() {

Calculator calculator = new Calculator();

assertEquals(4, calculator.add(2, 2));

}

@Test

@Disabled("暂时跳过此测试")

void testSkipped() {

// 这个测试不会执行

}

@AfterEach

void tearDown() {

// 在每个测试方法之后执行

}

@AfterAll

static void tearDownAll() {

// 在所有测试方法之后执行一次

}

}

```

JUnit 5引入了@BeforeEach@AfterEach替代JUnit 4的@Before@After,使命名更加语义化。@DisplayName注解允许为测试方法提供更具描述性的名称。

1.2 参数化测试的强大功能

参数化测试是JUnit 5的亮点特性,大大减少了重复代码:

```java

@ParameterizedTest

@ValueSource(ints = {1, 3, 5, -3, 15})

void testIsOdd(int number) {

assertTrue(number % 2 != 0);

}

@ParameterizedTest

@CsvSource({

"1, 1, 2",

"2, 3, 5",

"10, 20, 30"

})

void testAddition(int a, int b, int expected) {

assertEquals(expected, a + b);

}

```

1.3 断言机制的增强

JUnit 5提供了更丰富的断言方法:

```java

@Test

void testAssertions() {

// 分组断言,所有断言都会执行

assertAll("用户信息验证",

() -> assertEquals("John", user.getFirstName()),

() -> assertEquals("Doe", user.getLastName())

);

// 异常断言

Exception exception = assertThrows(IllegalArgumentException.class,

() -> user.setAge(-1));

assertEquals("年龄不能为负数", exception.getMessage());

// 超时断言

assertTimeout(Duration.ofMillis(100), () -> {

// 执行时间不应超过100毫秒

user.processData();

});

}

```

二、Mockito深度应用技巧

Mockito是Java领域最流行的mock框架,最新版本(当前为5.x)加强了对final类/方法的mock能力,并提升了与JUnit 5的集成度。

2.1 依赖注入与mock创建

```java

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;

@ExtendWith(MockitoExtension.class)

class UserServiceTest {

@Mock

private UserRepository userRepository;

@Mock

private EmailService emailService;

@InjectMocks

private UserService userService; // 自动注入mock依赖

@Test

void createUser_ShouldSaveUserAndSendEmail() {

// Given - 设置测试数据和行为

User user = new User("test@example.com", "John");

when(userRepository.save(any(User.class))).thenReturn(user);

doNothing().when(emailService).sendWelcomeEmail(anyString());

// When - 执行被测方法

User savedUser = userService.createUser("test@example.com", "John");

// Then - 验证结果和行为

assertNotNull(savedUser);

verify(userRepository, times(1)).save(any(User.class));

verify(emailService, times(1)).sendWelcomeEmail("test@example.com");

}

}

```

2.2 高级mock技术

```java

@Test

void testAdvancedMocking() {

// 参数匹配器

when(userRepository.findByEmail(startsWith("test"))).thenReturn(Optional.of(user));

// 验证调用顺序

InOrder inOrder = inOrder(userRepository, emailService);

inOrder.verify(userRepository).save(user);

inOrder.verify(emailService).sendWelcomeEmail(user.getEmail());

// 模拟连续调用行为

when(userRepository.count())

.thenReturn(1L)

.thenReturn(2L)

.thenThrow(new RuntimeException("数据库错误"));

// 参数捕获

ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);

verify(userRepository).save(userCaptor.capture());

assertEquals("John", userCaptor.getValue().getName());

}

```

2.3 静态方法mock(谨慎使用)

从Mockito 3.4.0开始支持静态方法mock,但应谨慎使用:

```java

@Test

void testStaticMethodMocking() {

try (MockedStatic utilities = mockStatic(UtilityClass.class)) {

utilities.when(UtilityClass::getConfigValue).thenReturn("test-value");

    assertEquals("test-value", UtilityClass.getConfigValue());

}

// 退出try块后,静态mock自动关闭

}

```

三、单元测试最佳实践

3.1 测试命名规范

好的测试名称应该清晰表达测试的意图:

```java

// 不好的命名

@Test

void test1() {}

// 好的命名 - 使用下划线分隔

@Test

void withdrawMoney_WhenInsufficientBalance_ShouldThrowException() {}

// 或者使用显示名称

@Test

@DisplayName("用户提现:当余额不足时应抛出异常")

void withdrawMoneyWithInsufficientBalance() {}

```

3.2 测试结构组织

遵循Given-When-Then模式使测试更清晰:

```java

@Test

void transferMoney_WhenValidRequest_ShouldSucceed() {

// Given - 准备测试数据

Account fromAccount = new Account("1", 1000);

Account toAccount = new Account("2", 500);

when(accountRepository.findById("1")).thenReturn(Optional.of(fromAccount));

when(accountRepository.findById("2")).thenReturn(Optional.of(toAccount));

// When - 执行被测方法

transferService.transferMoney("1", "2", 200);

// Then - 验证结果

assertEquals(800, fromAccount.getBalance());

assertEquals(700, toAccount.getBalance());

verify(transactionLogger).logTransfer("1", "2", 200);

}

```

3.3 避免常见测试陷阱

不要过度mock:

```java

// 不好的做法:过度mock导致测试与实现细节耦合

@Test

void badTest() {

when(user.getAddress().getCity().toUpperCase()).thenReturn("BEIJING");

// 测试细节过于具体,容易因重构而失败

}

// 好的做法:mock边界,测试业务逻辑

@Test

void goodTest() {

when(addressService.getCity(user)).thenReturn("Beijing");

String result = userService.getUserCityUpperCase(user);

assertEquals("BEIJING", result);

}

```

保持测试独立性:

```java

@Test

void independentTest1() {

// 这个测试不依赖外部状态

}

@Test

void independentTest2() {

// 这个测试也不依赖前一个测试的状态

}

```

四、集成JUnit 5与Mockito的完整示例

```java

@ExtendWith(MockitoExtension.class)

class OrderServiceTest {

@Mock

private InventoryService inventoryService;

@Mock

private PaymentService paymentService;

@Mock

private ShippingService shippingService;

@InjectMocks

private OrderService orderService;

@Test

@DisplayName("创建订单 - 库存充足且支付成功时应创建订单")

void createOrder_WhenInStockAndPaymentSuccess_ShouldCreateOrder() {

// Given

OrderRequest request = new OrderRequest("product1", 2, "user1");

when(inventoryService.isInStock("product1", 2)).thenReturn(true);

when(paymentService.processPayment("user1", 200.0)).thenReturn(true);

// When

OrderResult result = orderService.createOrder(request);

// Then

assertTrue(result.isSuccess());

verify(shippingService).scheduleShipping(any(Order.class));

}

@Test

@DisplayName("创建订单 - 库存不足时应失败")

void createOrder_WhenOutOfStock_ShouldFail() {

// Given

OrderRequest request = new OrderRequest("product1", 2, "user1");

when(inventoryService.isInStock("product1", 2)).thenReturn(false);

// When

OrderResult result = orderService.createOrder(request);

// Then

assertFalse(result.isSuccess());

assertEquals("库存不足", result.getMessage());

verify(paymentService, never()).processPayment(anyString(), anyDouble());

}

@Test

@Timeout(5) // 5秒超时

void processLargeOrder_ShouldCompleteInTime() {

// 测试性能要求

assertTimeoutPreemptively(Duration.ofSeconds(5), () -> {

orderService.processLargeOrder(createLargeOrder());

});

}

}

```

五、持续集成中的测试策略

在现代CI/CD流水线中,单元测试应该快速执行(通常不超过10分钟)。合理组织测试套件:

```java

// 标记为快速测试,每次提交都运行

@Tag("fast")

class FastUnitTest {}

// 标记为慢速测试,仅在特定阶段运行

@Tag("slow")

class SlowIntegrationTest {}

```

在Maven中配置测试过滤:

xml

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-surefire-plugin</artifactId>

<configuration>

<groups>fast</groups>

</configuration>

</plugin>

总结

JUnit 5和Mockito的组合为Java开发者提供了强大的单元测试能力。通过遵循本文介绍的最佳实践,你可以构建出可维护、可靠且高效的测试套件。记住,好的单元测试不仅能够捕获bug,更能作为代码的活文档,促进更好的软件设计。

随着测试驱动开发(TDD)和行为驱动开发(BDD)的普及,掌握这些测试工具的使用已成为现代Java开发者的必备技能。不断练习并应用这些最佳实践,将使你的代码质量提升到新的水平。


本文基于JUnit 5.9.x和Mockito 5.x版本编写,内容更新至2024年最新实践。

Logo

立足具身智能前沿赛道,致力于搭建全球化、开源化、全栈式技术交流与实践共创平台。

更多推荐