讲师中心 微信公众号
AI工具推荐 视频效率加速

java代码怎样用单元测试验证功能 java代码测试编写的基础教程​

阿晨姑娘_9295

阿晨姑娘_9295

发布时间:2025-08-11 16:52:02

|

1141人浏览过

|

来源于php中文网

原创

使用junit 5作为核心测试框架,通过maven或gradle引入依赖;2. 配合mockito进行依赖隔离,使用@mock创建模拟对象,@injectmocks注入被测实例;3. 遵循first原则:测试需快速、独立、可重复、自我验证且及时编写;4. 采用given-when-then模式组织测试逻辑;5. 通过mocking验证方法调用,stubbing设定返回值;6. 外部依赖如数据库使用h2等内存库或testcontainers;7. 时间等动态因素通过注入clock等可控制接口实现稳定测试;8. 测试应聚焦公共方法,避免直接测试私有方法,确保高可维护性和可读性。这些实践共同保障java单元测试的有效性和代码质量。

java代码怎样用单元测试验证功能 java代码测试编写的基础教程​

在Java开发中,单元测试是确保代码质量和功能正确性的基石,它让开发者能独立验证程序最小可测试单元的行为。它不是什么高深的魔法,而更像是一种工程实践,让你在代码变动时心里有底,减少了“改动一处,崩溃一片”的恐惧。从我个人的经验来看,这玩意儿真能让你睡个安稳觉,尤其是在维护老旧代码或者多人协作的项目里。

解决方案

要让Java代码通过单元测试来验证功能,核心在于使用合适的测试框架,并遵循一些基本原则。通常,我们用JUnit作为测试框架,配合Mockito来处理依赖。

首先,你需要将JUnit库引入你的项目。如果你用Maven,在

pom.xml
里加上:

立即学习“Java免费学习笔记(深入)”;

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>

如果是Gradle,则在

build.gradle
里:

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
}

接着,你需要为你要测试的类创建一个对应的测试类。比如,你有一个

Calculator
类:

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 IllegalArgumentException("Divisor cannot be zero");
        }
        return a / b;
    }
}

那么,你会在

src/test/java
目录下创建一个
CalculatorTest
类:

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

class CalculatorTest {

    @Test
    @DisplayName("测试加法功能")
    void testAdd() {
        Calculator calculator = new Calculator();
        int result = calculator.add(2, 3);
        assertEquals(5, result, "2 + 3 应该等于 5"); // 验证结果是否符合预期
    }

    @Test
    @DisplayName("测试除法功能")
    void testDivide() {
        Calculator calculator = new Calculator();
        int result = calculator.divide(10, 2);
        assertEquals(5, result, "10 / 2 应该等于 5");
    }

    @Test
    @DisplayName("测试除数为零的异常")
    void testDivideByZero() {
        Calculator calculator = new Calculator();
        // 验证是否抛出特定的异常
        assertThrows(IllegalArgumentException.class, () -> calculator.divide(10, 0), "除数为零时应抛出 IllegalArgumentException");
    }
}

这里面,

@Test
注解标记了一个测试方法。
assertEquals
、
assertThrows
等是JUnit提供的断言方法,用来验证代码的行为是否符合预期。每个测试方法应该独立、原子化,只测试一个特定的功能点或一个代码路径。

Java单元测试的常用框架和工具怎么选?

谈到工具,那么我们自然会想到JUnit。它是Java世界里单元测试的绝对主力,几乎是标配。目前主流的是JUnit 5(也叫JUnit Jupiter),它比之前的JUnit 4提供了更灵活的扩展模型和更丰富的功能,比如更强大的参数化测试、动态测试等。如果你在启动一个新项目,直接上JUnit 5准没错。

但光有JUnit还不够,很多时候我们的类会有外部依赖,比如它可能需要调用一个数据库服务、一个远程API或者另一个复杂的业务组件。直接在单元测试里去初始化这些依赖,不仅会拖慢测试速度,还会让测试变得不稳定(比如网络波动)。这时候,Mockito就登场了。Mockito是一个强大的Mocking框架,它可以让你创建“假”的对象(mock对象),这些假对象可以模拟真实对象的行为,让你能够隔离被测试的单元,只关注它自身的逻辑。

举个例子,如果你的

UserService
需要一个
UserRepository
来获取用户数据,但在测试
UserService
的
createUser
方法时,你不想真的去操作数据库,就可以用Mockito来mock一个
UserRepository
:

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.assertNotNull;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class) // 启用MockitoExtension
class UserServiceTest {

    @Mock // 标记这是一个mock对象
    private UserRepository userRepository;

    @InjectMocks // 将mock对象注入到这个实例中
    private UserService userService;

    @Test
    void testCreateUser() {
        User user = new User("testUser");
        when(userRepository.save(user)).thenReturn(user); // 当调用save方法时,返回user对象

        User createdUser = userService.createUser(user);
        assertNotNull(createdUser);
        // 进一步验证 createdUser 的属性是否符合预期
    }
}

// 假设有这样的类
class User {
    String name;
    public User(String name) { this.name = name; }
    // getters, setters...
}

interface UserRepository {
    User save(User user);
}

class UserService {
    private final UserRepository userRepository;

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

    public User createUser(User user) {
        // 业务逻辑,可能还有其他操作
        return userRepository.save(user);
    }
}

除了JUnit和Mockito,有时你可能还会看到Hamcrest,它提供了一套更具可读性的断言匹配器,让你的测试代码读起来更像自然语言。不过,JUnit 5自带的断言已经足够强大,Hamcrest并非必需。构建工具如Maven和Gradle本身就集成了运行测试的能力,你只需要配置好,它们就能自动发现并执行你的单元测试。选择这些工具,更多是基于项目需求和团队习惯,但JUnit和Mockito是Java单元测试的黄金搭档,这是我的肺腑之言。

Docker Image Puller
Docker Image Puller

使用 Python 脚本下载 Docker 镜像并打包为 tar 文件。用户说“下载镜像”“拉取镜像”“pull xxx”“帮我下载 nginx 镜像”等时触发。支持 SOCKS5 代理直连(推荐)和国内镜像站加速两种模式。

下载

编写高质量Java单元测试有哪些核心原则和实践?

写测试,不仅仅是让代码跑起来不报错那么简单,更重要的是要写出“好”的测试。我个人在实践中总结了几点,觉得挺有用的。

一个很重要的原则是FIRST原则:

  • Fast (快速):测试应该运行得非常快。如果你的单元测试跑起来要几分钟甚至几十分钟,那开发者就不愿意频繁运行它们,测试的价值就大打折扣。这意味着要避免真实的IO操作(数据库、网络),用Mocking来替代。
  • Independent (独立):每个测试都应该独立于其他测试。测试的顺序不应该影响结果,一个测试的失败不应该导致其他测试也失败。这要求每个测试方法在执行前都应该有一个干净的环境,并且不留下任何会影响后续测试的副作用。
  • Repeatable (可重复):在任何环境、任何时间运行测试,都应该得到相同的结果。这排除了那些依赖外部不稳定因素(如时间、随机数、网络状态)的测试。
  • Self-validating (自我验证):测试应该能够自动判断通过或失败,不需要人工检查日志或输出。断言就是为此服务的。
  • Timely (及时):在编写生产代码之前或同时编写测试(测试驱动开发TDD),而不是等功能开发完了再补测试。这有助于你更好地设计代码,因为它迫使你从可测试性的角度思考。

在实践中,我发现Given-When-Then模式非常有助于组织测试代码。

  • Given (前置条件):设置测试的初始状态,包括创建对象、配置mock行为等。
  • When (执行动作):调用被测试的方法或执行某个操作。
  • Then (结果验证):验证执行动作后的结果是否符合预期,通过断言来完成。

例如,上面

testAdd
方法就可以看作是:

  • Given:
    Calculator calculator = new Calculator();
  • When:
    int result = calculator.add(2, 3);
  • Then:
    assertEquals(5, result, "2 + 3 应该等于 5");

此外,测试的粒度也很关键。单元测试应该只关注一个“单元”的逻辑,通常是一个方法。不要在一个单元测试里测试太多东西,那样一旦失败了,你很难快速定位问题。同时,要覆盖所有重要的路径,包括正常路径、边界条件(比如最大值、最小值、空值、空集合)和异常路径。对于那些抛出异常的情况,我们也要有专门的测试来验证异常是否被正确抛出。

最后,一个我常犯的错误(也看到很多人犯)是过度测试私有方法。私有方法是实现细节,它们的服务于公共方法。你应该通过测试公共方法来间接验证私有方法的行为,而不是直接去测试私有方法。如果你发现一个私有方法变得非常复杂,难以通过公共方法来测试,那可能意味着这个私有方法应该被抽取成一个独立的公共方法(或者一个独立的辅助类),这样它就能被直接测试了。

Java单元测试中如何处理外部依赖和复杂场景?

处理外部依赖是单元测试中最让人头疼,但也最能体现水平的地方。毕竟,真实世界的应用很少是完全独立的。

Mocking和Stubbing是核心策略。正如前面提到的Mockito,它能帮助我们模拟那些难以在测试环境中真实运行的外部服务。

  • Mocking:当你需要验证一个依赖对象的方法是否被调用了,或者被调用了多少次,以及调用时传入的参数是否正确时,你会用到Mocking。比如,测试一个服务类是否正确地调用了DAO层的保存方法。
  • Stubbing:当你需要一个依赖对象在被调用时返回一个特定的值,以便被测试的方法能够继续执行下去时,你会用到Stubbing。比如,模拟一个用户仓库返回一个特定的用户对象。

Mockito的

when().thenReturn()
就是典型的stubbing,而
verify()
就是mocking:

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.mockito.Mockito.*; // 导入 verify

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private PaymentGateway paymentGateway; // 模拟支付网关

    @Mock
    private NotificationService notificationService; // 模拟通知服务

    @InjectMocks
    private OrderService orderService;

    @Test
    void testPlaceOrderSuccessfully() {
        Order order = new Order("ORD123", 100.0);

        // Stubbing: 当 paymentGateway.charge 被调用时,返回 true
        when(paymentGateway.charge(order.getAmount())).thenReturn(true);

        boolean result = orderService.placeOrder(order);

        assertTrue(result);
        // Mocking: 验证 paymentGateway.charge 方法是否被调用了一次
        verify(paymentGateway, times(1)).charge(order.getAmount());
        // Mocking: 验证 notificationService.sendSuccessNotification 方法是否被调用了一次
        verify(notificationService, times(1)).sendSuccessNotification(order);
        // 验证 notificationService.sendFailureNotification 方法是否从未被调用
        verify(notificationService, never()).sendFailureNotification(any(Order.class));
    }

    @Test
    void testPlaceOrderFailure() {
        Order order = new Order("ORD124124", 200.0);

        when(paymentGateway.charge(order.getAmount())).thenReturn(false);

        boolean result = orderService.placeOrder(order);

        assertFalse(result);
        verify(paymentGateway, times(1)).charge(order.getAmount());
        verify(notificationService, times(1)).sendFailureNotification(order);
        verify(notificationService, never()).sendSuccessNotification(any(Order.class));
    }
}

// 假设的服务类和依赖
class Order {
    String id;
    double amount;
    public Order(String id, double amount) { this.id = id; this.amount = amount; }
    public double getAmount() { return amount; }
    // getters...
}

interface PaymentGateway {
    boolean charge(double amount);
}

interface NotificationService {
    void sendSuccessNotification(Order order);
    void sendFailureNotification(Order order);
}

class OrderService {
    private final PaymentGateway paymentGateway;
    private final NotificationService notificationService;

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

    public boolean placeOrder(Order order) {
        if (paymentGateway.charge(order.getAmount())) {
            notificationService.sendSuccessNotification(order);
            return true;
        } else {
            notificationService.sendFailureNotification(order);
            return false;
        }
    }
}

对于数据库依赖,单元测试通常不直接连接真实的数据库。这会让测试变得很慢且不稳定。常用的方法是:

  • 使用内存数据库:比如H2 Database或HSQLDB。它们可以在JVM内部运行,不需要外部安装,测试结束后数据自动清空。这非常适合那些只涉及简单CRUD操作的DAO层测试。
  • 使用Testcontainers:这是一个更现代的解决方案,它允许你在Docker容器中启动真实的数据库实例(或者其他任何服务,比如Kafka、Redis)。这样你的测试就运行在一个与生产环境非常接近的真实数据库实例上,但又保持了测试的隔离性和可重复性。虽然它不是严格意义上的“单元”测试,但对于集成测试或需要更真实环境的测试来说,Testcontainers是个非常棒的选择。

处理时间依赖:如果你的代码依赖于当前时间(比如

new Date()
或
System.currentTimeMillis()
),这会让测试变得不可重复。你可以通过注入一个
Clock
接口(Java 8
java.time.Clock
)或一个时间提供者接口来解决,然后在测试中注入一个固定时间的实现。

import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

class TimeDependentService {
    private final Clock clock;

    public TimeDependentService(Clock clock) {
        this.clock = clock;
    }

    public long getCurrentTimestamp() {
        return clock.millis();
    }
}

class TimeDependentServiceTest {
    @Test
    void testGetCurrentTimestamp() {
        // 创建一个固定时间的Clock
        Clock fixedClock = Clock.fixed(Instant.parse("2023-10-27T10:00:00Z"), ZoneId.of("UTC"));
        TimeDependentService service = new TimeDependentService(fixedClock);

        long timestamp = service.getCurrentTimestamp();
        assertEquals(1698391200000L, timestamp); // 2023-10-27T10:00:00Z 的毫秒时间戳
    }
}

总的来说,处理复杂场景的核心思想就是“隔离”。通过Mocking、Stubbing、内存数据库或者Testcontainers,我们把被测试的单元从复杂的外部环境中剥离出来,让它在一个可控、可重复、快速的环境中运行,这样才能真正做到“单元”测试。这需要一点设计上的考量,让你的代码更容易被测试,也就是常说的“可测试性”。

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
VibeKnow
VibeKnow Hot

一款AI视频创作工具,主要用于全球首个AI知识视频创作平台,文档、文章、网页,一键生成视频,适合需要提升相关任务效率的用户。

Seko
Seko Hot

一款AI视频创作工具,主要用于商汤科技推出的创编一体的AI短视频创作Agent,适合需要提升相关任务效率的用户。

音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

相关专题

更多
Java Maven专题
Java Maven专题

本专题聚焦 Java 主流构建工具 Maven 的学习与应用,系统讲解项目结构、依赖管理、插件使用、生命周期与多模块项目配置。通过企业管理系统、Web 应用与微服务项目实战,帮助学员全面掌握 Maven 在 Java 项目构建与团队协作中的核心技能。

3840

2025.09.15

Java Maven/Gradle 构建与依赖管理合集
Java Maven/Gradle 构建与依赖管理合集

系统讲解 Java 项目构建工具的使用与进阶配置,涵盖 Maven 的 POM 文件结构、生命周期(clean/compile/package/install/deploy)与插件机制、依赖范围(compile/provided/test/runtime)与传递依赖管理、多模块聚合与继承、私有 Nexus 仓库发布,以及 Gradle 的 Groovy / Kotlin DSL 语法、Task 自定义与增量构建、依赖版本目录(Versi

369

2026.05.09

kafka消费者组有什么作用
kafka消费者组有什么作用

kafka消费者组的作用:1、负载均衡;2、容错性;3、广播模式;4、灵活性;5、自动故障转移和领导者选举;6、动态扩展性;7、顺序保证;8、数据压缩;9、事务性支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2806

2024.01.12

kafka消费组的作用是什么
kafka消费组的作用是什么

kafka消费组的作用:1、负载均衡;2、容错性;3、灵活性;4、高可用性;5、扩展性;6、顺序保证;7、数据压缩;8、事务性支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

630

2024.02.23

rabbitmq和kafka有什么区别
rabbitmq和kafka有什么区别

rabbitmq和kafka的区别:1、语言与平台;2、消息传递模型;3、可靠性;4、性能与吞吐量;5、集群与负载均衡;6、消费模型;7、用途与场景;8、社区与生态系统;9、监控与管理;10、其他特性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

604

2024.02.23

Java 流式处理与 Apache Kafka 实战
Java 流式处理与 Apache Kafka 实战

本专题专注讲解 Java 在流式数据处理与消息队列系统中的应用,系统讲解 Apache Kafka 的基础概念、生产者与消费者模型、Kafka Streams 与 KSQL 流式处理框架、实时数据分析与监控,结合实际业务场景,帮助开发者构建 高吞吐量、低延迟的实时数据流管道,实现高效的数据流转与处理。

670

2026.02.04

软件测试常用工具
软件测试常用工具

软件测试常用工具有Selenium、JUnit、Appium、JMeter、LoadRunner、Postman、TestNG、LoadUI、SoapUI、Cucumber和Robot Framework等等。测试人员可以根据具体的测试需求和技术栈选择适合的工具,提高测试效率和准确性 。

4690

2023.10.13

java测试工具有哪些
java测试工具有哪些

java测试工具有JUnit、TestNG、Mockito、Selenium、Apache JMeter和Cucumber。php还给大家带来了java有关的教程,欢迎大家前来学习阅读,希望对大家能有所帮助。

3081

2023.10.23

Playwright 测试失败怎么办
Playwright 测试失败怎么办

Playwright测试失败专题汇集超时、定位器匹配、浏览器提前关闭、依赖与插件配置、文件上传、SSL及Docker启动等故障文章,并介绍通过截图、报告和Trace定位错误。

0

2026.10.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 9.4万人学习

vscode其实很简单
vscode其实很简单

共72课时 | 32.4万人学习

JavaScript OOP调试技巧视频教程
JavaScript OOP调试技巧视频教程

共5课时 | 1.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn