单元测试实战(三)JPA 的测试

为鼓励单元测试,特分门别类示例各种组件的测试代码并进行解说,供开发人员参考。

本文中的测试均基于JUnit5。

单元测试实战(一)Controller 的测试

单元测试实战(二)Service 的测试    

单元测试实战(三)JPA 的测试

单元测试实战(四)MyBatis-Plus 的测试

单元测试实战(五)普通类的测试

单元测试实战(六)其它

我们常用的DAO层开发框架包括JPA(Spring Boot Data Jpa)和MyBatis-Plus。

框架都提供根据一定规约自动生成查询/更新的操作,如代理接口。这一部分责任在框架本身,我们不需测试。因此DAO的测试应主要针对自定义查询/更新操作。

JPA的测试注解是@DataJpaTest,Mybatis-Plus的是@MyBatisPlusTest;它们的思想是一致的,此次先讲JPA。

概述

JPA组件表现为Repository对象。如果是Spring Data JPA标准的Repository,且使用接口代理,那么理论上是不需要测试的;但实际中不排除想验证订制代码(native SQL、JPQL、标准查询API,以及胶水代码)的需求。

JPA测试有专门的@DataJpaTest注解,是Spring boot测试框架提供的功能,因此它是有Spring上下文的,使用JUnit的SpringExtension扩展类。

测试还是遵循经典三段式:given、when、then;即:假设xxx……那么当yyy时……应该会zzz。

在每个测试之前应清理/重置测试数据,即操作的数据实体。

断言应主要检查数据存取行为是否符合预期。

依赖

JPA测试除了依赖JUnit和Spring boot测试框架外还依赖一个内存数据库,我们用H2,如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>

示例

以下是用代理接口实现的UserRepository:

package com.aaa.api.auth.repository;
 
import org.springframework.data.jpa.repository.JpaRepository;
import com.aaa.api.auth.entity.User;
import org.springframework.stereotype.Repository;
 
import java.util.List;
import java.util.Optional;
 
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUserCode(String userCode);
 
    List<User> findTop10ByNameContaining(String keyword);
}

上面的Repository虽然没有订制代码,但不妨碍我们用它来演示Repository测试的写法。

以下是对UserRepository进行测试的测试类:

package com.aaa.api.auth.repository;
 
import com.aaa.api.auth.entity.User;
import jakarta.persistence.Query;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.TestPropertySource;
 
import java.util.List;
 
import static org.assertj.core.api.Assertions.assertThat;
 
@DataJpaTest
//@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@TestPropertySource(properties = {
        //"spring.datasource.driver-class-name=org.h2.Driver",
        //"spring.datasource.url=jdbc:h2:mem:api_auth_test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MySQL;DATABASE_TO_LOWER=TRUE;NON_KEYWORDS=user",
        //"spring.datasource.username=sa",
        "spring.jpa.hibernate.ddl-auto=create-drop",
        "spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop",
        "spring.jpa.properties.hibernate.globally_quoted_identifiers=true"
})
class UserRepositoryTest {
 
    @Autowired
    private UserRepository repo;
 
    @Autowired
    private TestEntityManager entityManager;
 
    private final User u1 = new User();
    private final User u2 = new User();
    private final User u3 = new User();
 
    @BeforeEach
    void setUp() {
        u1.setName("张三");
        u1.setUserCode("zhangsan");
        u1.setRole(User.ADMIN);
        u1.setEmail("zhangsan@aaa.net.cn");
        u1.setMobile("13600001234");
 
        u2.setName("李四");
        u2.setUserCode("lisi");
        u2.setRole(User.ADMIN);
        u2.setEmail("lisi@aaa.net.cn");
        u2.setMobile("13800001234");
 
        u3.setName("王五");
        u3.setUserCode("wangwu");
        u3.setRole(User.USER);
        u3.setEmail("wangwu@aaa.net.cn");
        u3.setMobile("13900001234");
    }
 
    @Test
    void testSave() {
        Query q = entityManager.getEntityManager().createQuery("from User");
        repo.save(u1);
        assertThat(q.getResultList()).hasSize(1);
        repo.save(u2);
        assertThat(q.getResultList()).hasSize(2);
        User u = repo.save(u3);
        assertThat(q.getResultList()).hasSize(3);
        u3.setRole(User.ADMIN);
        repo.save(u3);
        assertThat(q.getResultList()).hasSize(3);
        assertThat(entityManager.find(User.class, u.getId()).getRole()).isEqualTo(User.ADMIN);
    }      
 
    @Test
    void testFindAll() {
        entityManager.persist(u1);
        entityManager.persist(u2);
        entityManager.persist(u3);
        List<User> entities = repo.findAll();
        assertThat(entities).size().isEqualTo(3);
        assertThat(entities.get(0).getId()).isNotNull();
        assertThat(entities.get(1).getId()).isNotNull();
        assertThat(entities.get(2).getId()).isNotNull();
    }
 
    @Test
    void testFindByUserCode() {
        entityManager.persist(u1);
        entityManager.persist(u2);
        entityManager.persist(u3);
        User u1 = repo.findByUserCode("zhangsan").orElse(null);
        assertThat(u1).isNotNull();
        assertThat(u1.getRole()).isEqualTo(User.ADMIN);
        User u2 = repo.findByUserCode("lisi").orElse(null);
        assertThat(u2).isNotNull();
        assertThat(u2.getRole()).isEqualTo(User.ADMIN);
        User u3 = repo.findByUserCode("wangwu").orElse(null);
        assertThat(u3).isNotNull();
        assertThat(u3.getRole()).isEqualTo(User.USER);
 
        u3.setRole(User.ADMIN);
        entityManager.persist(u3);
        u3 = repo.findByUserCode("wangwu").orElse(null);
        assertThat(u3).isNotNull();
        assertThat(u3.getRole()).isEqualTo(User.ADMIN);
    }
 
    @Test
    void testFindTop10ByNameContaining() {
        entityManager.persist(u2);
        entityManager.persist(u3);
        List<User> users = repo.findTop10ByNameContaining("张三");
        assertThat(users).isEmpty();
        users = repo.findTop10ByNameContaining("李四");
        assertThat(users).hasSize(1);
        users = repo.findTop10ByNameContaining("五");
        assertThat(users).hasSize(1);
    }
 
}

测试类说明:

第16行,标注本测试为@DataJpaTest。

18-25行是数据源和JPA属性的订制。

@DataJpaTest、@WebMvcTest以及@SpringBootTest等测试其实是需要Spring Boot的Configuration的;因此我们在测试目录里可能(尤其在多模块工程里)有个专用于测试的Mock Application,注解为@SpringBootApplication(空类,不需要main方法),并且在src/test/resources下还可以有application.properties(或yaml)。而更灵活的方式是使用第18行的@TestPropertySource的properties属性。

@DataJpaTest这个注解会在测试时自动将数据源替换为内存数据库H2的;如果不想要这种替换,或者要订制其url等属性,那么就可以将第17行、第19-21行的注释放开。

第24行的spring.jpa.properties.hibernate.globally_quoted_identifiers=true可以避免数据库保留字与表名/列名冲突。

第29行,我们将待测试类对象作为测试类的一个属性,并使用@Autowired进行注入。

第32行,我们注入了一个TestEntityManager;它作为一个实用工具,帮我们在测试中插数据、查数据,这样就避免了直接使用repo的方法插数据、查数据,因为这些方法本身就是待测目标。

第34-36行提供了三个测试数据,并在setUp()方法中进行初始化/重置。@BeforeEach注解使得setUp()方法在每个测试之前都会执行一遍。

从59行开始,是测试方法。该测试类中,没有任何Mock对象,因此也就不存在given - when - then三段式结构。测试方法都是直接对测试数据进行CRUD操作并检查操作结果,代码都是自解释的。

总结

对于JPA Repository的测试,推荐使用@DataJpaTest注解。

如需订制内存数据库(datasource)的属性,则令@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE),然后在@TestPropertySource.properties里写订制属性。

Repository应设计为单纯的对数据实体的CRUD操作,因此通常不需Mock任何依赖对象。

### 小AI服务端部署教程与配置指南 #### 一、硬件环境评估 为了成功部署AI助手,需先确认个人电脑的硬件性能是否满足需求。通常情况下,AI模型对CPU、内存以及显卡的要求较高。具体可参考以下标准[^1]: - **处理器(CPU)**:建议至少配备Intel i7或AMD Ryzen 7级别的多核心处理器。 - **内存(RAM)**:推荐8GB以上;如果计划加载大型语言模型,则需要16GB甚至更高。 - **图形处理单元(GPU)**:对于涉及深度学习推理的任务,NVIDIA系列显卡能够显著加速运算过程。 #### 二、软件依赖安装 完成初步的硬件检测之后,进入实际操作阶段前还需准备必要的开发工具链及框架库文件。以下是几个重要环节描述[^2]: - 安装Docker引擎版本号应不低于v20.10.x,通过命令`docker --version`验证当前状态; - 使用官方文档指导创建自定义镜像项目目录结构并编写对应的Dockerfile脚本示例如下所示: ```dockerfile FROM python:3.9-slim-buster WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["python", "server.py"] ``` 上述代码片段展示了如何构建一个基础Python应用容器化流程. #### 三、启动参数设置 当所有前期准备工作就绪后,可以利用下面这条典型指令来初始化后台进程模式下的实例运行状况监控机制[-d选项表示分离前台显示界面]: ```bash docker run -d \ --name qwen-deepseek-instance \ -p 5000:5000 \ -v $(pwd)/data:/app/data \ your_custom_image_tag ``` 此部分特别强调了端口映射(-p标记)的重要性以便外部客户端访问内部API接口资源. --- #### 四、注意事项 尽管本地化方案提供了诸多便利之处,但仍存在一些潜在风险因素需要注意规避。例如数据安全防护措施不足可能导致敏感信息泄露等问题发生。因此,在整个实施过程中始终要把加强访问控制策略放在首位考虑范围之内. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值