0

0

Spring Boot中抽象类方法的JUnit单元测试策略:Spy与测试子类

碧海醫心

碧海醫心

发布时间:2025-10-30 11:59:01

|

647人浏览过

|

来源于php中文网

原创

spring boot中抽象类方法的junit单元测试策略:spy与测试子类

本文探讨了在Spring Boot应用中,如何使用JUnit 5和Mockito有效单元测试抽象类中依赖抽象方法的具体实现。我们将通过一个CSV服务示例,详细介绍两种核心策略:利用Mockito的spy进行部分模拟,以及创建测试专用的子类来重写抽象方法,从而实现对文件读取等外部依赖的隔离和控制,确保测试的准确性和独立性。

在开发Spring Boot应用时,我们经常会遇到抽象类(Abstract Class)的设计模式,它允许我们定义一个通用骨架,并由具体的子类来实现其抽象方法。然而,当我们需要对抽象类中那些依赖于子类实现的具体方法进行单元测试时,可能会遇到挑战。本文将以一个处理CSV文件的服务为例,详细介绍两种有效的单元测试策略:使用Mockito的spy进行部分模拟,以及创建测试专用子类。

1. 问题场景:测试抽象类的具体方法

假设我们有一个抽象的CsvService,它提供了一个通用的readFromCsv方法来读取CSV文件。这个方法依赖于几个抽象方法,如getFileName()、getColumns()和getData(),这些方法由具体的子类实现。

// CsvBean 接口(标记接口或包含通用字段)
public interface CsvBean {}

// Airport类实现CsvBean
public class Airport implements CsvBean {
    private int id;
    private String code;

    public Airport() {}
    public Airport(int id, String code) {
        this.id = id;
        this.code = code;
    }
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getCode() { return code; }
    public void setCode(String code) { this.code = code; }
    // toString, equals, hashCode omitted for brevity
}

// 抽象的CsvService
public abstract class CsvService {

    // 假设存在一个日志记录器,实际代码中应注入或使用SLF4J
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CsvService.class);
    private static final String FILE_READ_ERROR = "Error reading CSV file";

    public List readFromCsv(Class type, CsvToBeanFilter filter) {
        List data = new ArrayList<>();

        try {
            // 从类路径加载资源,依赖getFileName()
            Resource resource = new ClassPathResource("data/" + getFileName());
            Reader reader = new FileReader(resource.getFile());

            ColumnPositionMappingStrategy strategy =
                new ColumnPositionMappingStrategy<>();
            strategy.setType(type);
            strategy.setColumnMapping(getColumns()); // 依赖getColumns()

            CsvToBean csvToBean = new CsvToBeanBuilder(reader)
                    .withMappingStrategy(strategy) // 添加映射策略
                    .withFilter(filter)
                    .build();

            data = getData(csvToBean); // 依赖getData()
            reader.close();

        } catch (IOException ex) {
            log.error(FILE_READ_ERROR, ex);
            ex.printStackTrace();
        }
        return data;
    }

    protected abstract String getFileName();
    protected abstract String[] getColumns();
    protected abstract List getData(CsvToBean csvToBean);
}

其具体实现类AirportService如下:

@Service
public class AirportService extends CsvService {

    @Override
    protected String getFileName() {
        return "airports.csv"; // 实际的文件名
    }

    @Override
    protected String[] getColumns() {
        return new String[]{"id", "code"}; // CSV列名
    }

    @Override
    protected List getData(CsvToBean csvToBean) {
        List airports = new ArrayList<>();
        for (Airport bean : csvToBean) {
            // 假设Airport构造函数可以直接接收解析后的bean
            Airport airport = new Airport(bean.getId(), bean.getCode());
            airports.add(airport);
        }
        return airports;
    }
}

我们的目标是单元测试CsvService中的readFromCsv()方法。这个方法内部会调用getFileName()来确定要读取的文件,并通过ClassPathResource尝试从类路径加载该文件。如果getFileName()返回的是真实文件名,测试就会尝试读取实际的文件,这违背了单元测试的隔离原则。我们需要一种方法来模拟getFileName()的行为,使其返回一个指向测试数据的路径,或者完全避免文件系统操作。

为了实现这一点,我们首先需要在src/test/resources/data/目录下创建一个模拟的CSV文件,例如mock_airport_data.csv,内容如下:

id,code
101,DK
102,US

2. 策略一:使用Mockito.spy进行部分模拟

Mockito的spy允许我们对一个真实对象进行“部分模拟”。这意味着对象的大部分行为仍是真实的,但我们可以选择性地模拟其某些方法。这对于测试抽象类的具体方法非常有用,因为我们可以创建一个具体子类的spy实例,然后模拟其抽象方法(或任何其他方法)的行为。

Audo Studio
Audo Studio

AI音频清洗工具(噪音消除、声音平衡、音量调节)

下载

核心思路:

  1. 创建一个AirportService的spy实例。
  2. 使用Mockito.doReturn().when(spy).method()来模拟getFileName()和getColumns()方法,使其返回测试所需的值。这样,readFromCsv在执行时会使用我们模拟的文件名和列映射,而不是真实的文件。
  3. 让getData()方法调用真实的实现,因为它会处理由readFromCsv提供的CsvToBean实例,从而验证整个CSV解析流程。

示例代码:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import com.opencsv.bean.CsvToBeanFilter; // 假设CsvToBeanFilter是OpenCSV库的接口
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class CsvServiceSpyTest {

    private AirportService serviceSpy; // 使用spy实例
    @Mock
    private CsvToBeanFilter filter; // 模拟CSV过滤器

    @BeforeEach
    void setup() {
        // 创建AirportService的spy实例
        serviceSpy = Mockito.spy(new AirportService());
    }

    @Test
    void testReadFromCsv_withSpy() {
        // 1. 模拟getFileName(),使其返回测试资源文件
        Mockito.doReturn("mock_airport_data.csv").when(serviceSpy).getFileName();
        // 2. 模拟getColumns(),提供测试所需的列名
        Mockito.doReturn(new String[]{"id", "code"}).when(serviceSpy).getColumns();

        // 3. 模拟过滤器行为,这里假设允许所有行
        when(filter.allowLine(any(String[].class))).thenReturn(true);

        // 调用待测试方法
        List result = serviceSpy.readFromCsv(Airport.class, filter);

        // 断言结果
        assertNotNull(result);
        assertEquals(2, result.size());
        assertEquals(101, result.get(0).getId());
        assertEquals("DK", result.get(0).getCode());
        assertEquals(102, result.get(1).getId());
        assertEquals("US", result.get(1).getCode());

        // 验证抽象方法是否被调用
        Mockito.verify(serviceSpy).getFileName();
        Mockito.verify(serviceSpy).getColumns();
        // 验证getData是否被调用(它会处理由readFromCsv生成的CsvToBean)
        Mockito.verify(serviceSpy).getData(any());
    }
}

注意事项:

  • Mockito.spy()用于对真实对象进行部分模拟。
  • doReturn().when()语法用于模拟spy对象的方法,特别是当方法可能被真实调用时(如抽象方法)。
  • 确保模拟的getFileName()指向一个存在于src/test/resources目录下的测试文件,以便ClassPathResource能够找到它。

3. 策略二:创建测试专用子类

另一种方法是在测试类内部创建一个AirportService的匿名子类(或独立的内部类),并重写其抽象方法以提供测试所需的数据。这种方法避免了spy的复杂性,使测试逻辑更加清晰。

**核心思路:

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

106

2025.08.06

spring boot框架优点
spring boot框架优点

spring boot框架的优点有简化配置、快速开发、内嵌服务器、微服务支持、自动化测试和生态系统支持。本专题为大家提供spring boot相关的文章、下载、课程内容,供大家免费下载体验。

135

2023.09.05

spring框架有哪些
spring框架有哪些

spring框架有Spring Core、Spring MVC、Spring Data、Spring Security、Spring AOP和Spring Boot。详细介绍:1、Spring Core,通过将对象的创建和依赖关系的管理交给容器来实现,从而降低了组件之间的耦合度;2、Spring MVC,提供基于模型-视图-控制器的架构,用于开发灵活和可扩展的Web应用程序等。

390

2023.10.12

Java Spring Boot开发
Java Spring Boot开发

本专题围绕 Java 主流开发框架 Spring Boot 展开,系统讲解依赖注入、配置管理、数据访问、RESTful API、微服务架构与安全认证等核心知识,并通过电商平台、博客系统与企业管理系统等项目实战,帮助学员掌握使用 Spring Boot 快速开发高效、稳定的企业级应用。

69

2025.08.19

Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性
Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性

Spring Boot 是一个基于 Spring 框架的 Java 开发框架,它通过 约定优于配置的原则,大幅简化了 Spring 应用的初始搭建、配置和开发过程,让开发者可以快速构建独立的、生产级别的 Spring 应用,无需繁琐的样板配置,通常集成嵌入式服务器(如 Tomcat),提供“开箱即用”的体验,是构建微服务和 Web 应用的流行工具。

34

2025.12.22

Java Spring Boot 微服务实战
Java Spring Boot 微服务实战

本专题深入讲解 Java Spring Boot 在微服务架构中的应用,内容涵盖服务注册与发现、REST API开发、配置中心、负载均衡、熔断与限流、日志与监控。通过实际项目案例(如电商订单系统),帮助开发者掌握 从单体应用迁移到高可用微服务系统的完整流程与实战能力。

114

2025.12.24

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

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

439

2023.10.13

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

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

299

2023.10.23

菜鸟裹裹入口以及教程汇总
菜鸟裹裹入口以及教程汇总

本专题整合了菜鸟裹裹入口地址及教程分享,阅读专题下面的文章了解更多详细内容。

0

2026.01.22

热门下载

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

精品课程

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

共23课时 | 2.8万人学习

C# 教程
C# 教程

共94课时 | 7.3万人学习

Java 教程
Java 教程

共578课时 | 49.5万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号