应该测试 DAO 层吗?

网上有很多人讨论单元测试是否应该包含 DAO 层的测试。笔者觉得,对于一些主要是crud的业务来说,service层和controller层都会非常薄,而主要的逻辑都落在mapper上。这时候对service层和controller层写单测没有太多意义。可以只写mapper层的单测。

另一方面,mapper层的测试可以有效地避免一些低级的sql错误。

定义单测

单元测试是只针对一个单元的测试,比如说,一个 Service 类的一个每个公共函数。而这个函数所有调用了外部依赖的地方都需要被隔离,比如说外部类的依赖,或者是请求了某个服务器。

也就是说单元测试仅仅是测试当前类的某个函数本身的逻辑,而不涉及到外部的逻辑。因此执行单测应该是很快速的。

在 Java 中单测常用的依赖主要分为测试框架与 Mock 框架。测试框架就是执行和管理测试方法的框架,一般用 JUnit。而 Mock 框架就是用于模拟外部依赖,将被测试的函数的所有外部依赖全部隔离。

一些误区

在网上见到太多的单测教程,写得一塌糊涂。甚至连单测的概念都搞不清楚就发表文章,真的是误人子弟。

关于常见的误区,这篇博客列举得很到位: 如何写好单元测试:Mock 脱离数据库+不使用@SpringBootTest

最关键的一点是不要使用 @SpringBootTest(classes=XXXApplication.class) 注解测试类。这样会直接启动一个 springboot 进程,对稍微复杂一点的项目就至少要花 1 分钟以上来运行了。如果项目使用了远程配置中心,SOA 等中间件,那建议出去泡杯茶。

所以为啥大家不想写单测?等这么久,人走茶凉了都。但是实际上这都是错误的实现手法。下面这篇文章讲解了在 SpringBoot 项目中不同集成层次的测试类的例子: Testing in Spring Boot | Baeldung

总地来说,分清楚集成测试与单元测试的区别。不要把单测写成集成测试。

DAO 层测试的实现

选型

下面这篇文章总结得很好: 写有价值的单元测试-阿里云开发者社区

数据库测试需要保证测试不会影响到外部环境,且生成的数据在测试完成后需要自动销毁。一般有几种方法:

  1. 连接开发环境的数据库,并且在测试后回滚。不推荐
  2. 使用 docker 容器:testContainer。在测试时启动 mysql 容器,在结束后自动回收。缺点:需要每个测试的机子都安装 docker 并下载该容器。这就导致:
    1. 需要推动其他开发者安装该镜像
    2. 需要推动 devops 在线上 CI/CD 流水线安装 docker。(放弃吧)
  3. 使用内存数据库,不会对数据进行持久化。比较常用的有 h2。

如果是个人开发项目,或者不会用到集成部署流水线。可以尝试使用 testContainer,因为其不仅可以对接 mysql 测试,对一些中间件如 redis,mq 等都可以模拟。但是对大型团队开发的复杂项目还是建议直接用内存数据库吧。

另外,Mybatis 提供了一个测试依赖包,集成了 h2,参考: mybatis-spring-boot-test-autoconfigure – Introduction 。但是缺点是需要依赖不同版本的 springboot,笔者开发的项目使用的 springboot 版本较老,且不宜更新,所以就直接手动配置 h2 了。

代码

我们需要手动创建 4 个 bean 来注入:

  1. DataSource,用于 jdbc 连接对应的 h2 数据库。
  2. Server。h2 的 gui server 服务,可以用连接数据库查看数据。不是必需的。
  3. SqlSessionFactory。为 mybatis 创建一个 sqlSessionFactory,指明 mapper 的 xml 文件所在位置
  4. MapperScannerConfigurer。用于将 mybatis 中的 mapper 接口生成代理 bean。

    其中几个需要注意的点:
  5. @ComponentScan 需要填上当前项目中的 mapper 接口的位置
  6. 创建 DataSource 时,addScript() 指定的是自己准备的建表与初始化数据的 sql。路径在 test/resources/db/schema-h2.sql
  7. 创建 sqlSessionFactory 时,指定 resources 中的 mapper.xml 文件。
  8. 创建 mapperScannerConfigurer 时,指定 mapper 接口的 package 以及上一步创建的 factory 的 bean 的名字,这里使用的都是默认的名字,即方法的名称。
@Configuration
@ComponentScan({ "com.my.app.mapper" })
public class BaseTestConfig {
@Bean()
public DataSource dataSource() {
EmbeddedDatabaseBuilder databaseBuilder = new EmbeddedDatabaseBuilder(); return databaseBuilder
.setType(EmbeddedDatabaseType.H2)
//启动时初始化建表语句
.addScript("classpath:db/schema-h2.sql")
.build();
} @Bean(name = "h2WebServer", initMethod = "start", destroyMethod = "stop")
//启动一个H2的web server, 调试时可以通过localhost:8082访问到H2的内容
//JDBC URL: jdbc:h2:mem:testdb
//User Name: sa
//Password: 无
//注意如果使用断点,断点类型(Suspend Type)一定要设置成Thread而不能是All,否则web server无法正常访问!
public Server server() throws Exception {
//在8082端口上启动一个web server
return Server.createWebServer("-web", "-webAllowOthers", "-webDaemon", "-webPort", "8082");
} @Bean()
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//加载所有的sqlmapper文件
Resource[] mapperLocations = resolver.getResources("classpath*:mapper/*.xml");
sessionFactory.setMapperLocations(mapperLocations);
return sessionFactory.getObject();
} @Bean()
public MapperScannerConfigurer mapperScannerConfigurer() {
//只需要写DAO接口,不用写实现类,运行时动态生成代理
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("com.my.app.mapper");
configurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
return configurer;
} }

创建一个这样的 Configuration 类后,后面的 MapperTest 类只需要用 @Import 引入这个配置类即可,或者将注解全部放在一个基类上,让后面的 mapper 测试类都继承这个基类,就不需要在每个测试类上都加注解了:

@RunWith(SpringJUnit4ClassRunner.class)
@Import(BaseTestConfig.class)
public class BaseMapperTest {
@Autowired
private MyMapper myMapper;
@Test
public void test(){
Object o = myMapper.selectOne();
assertNotNull(o);
}
}

我们应该测试 DAO 层吗?的更多相关文章

  1. 使用Unitils测试DAO层

    Spring 的测试框架为我们提供一个强大的测试环境,解决日常单元测试中遇到的大部分测试难题:如运行多个测试用例和测试方法时,Spring上下文只需创建一次:数据库现场不受破坏:方便手工指定Sprin ...

  2. 使用 Spring 2.5 TestContext 测试DAO层

    资源准备:   mysql5.0 spring-2.5  hibernate-3.2  junit-4.jar 创建表 DROP TABLE IF EXISTS `myproject`.`boys`; ...

  3. dbunit进行DAO层Excel单元测试

    DAO层测试难点 可重复性,每次运行单元测试,得到的数据是重复的 独立性,测试数据与实际数据相互独立 数据库中脏数据预处理 不能给数据库中数据带来变化 DAO层测试方法 使用内存数据库,如H2.优点: ...

  4. 基于dbunit进行mybatis DAO层Excel单元测试

    DAO层测试难点 可重复性,每次运行单元测试,得到的数据是重复的 独立性,测试数据与实际数据相互独立 数据库中脏数据预处理 不能给数据库中数据带来变化 DAO层测试方法 使用内存数据库,如H2.优点: ...

  5. DAO层设计Junit测试

    DAO层的设计: 在实际的开发中有一种项目的程序组织架构方案叫做MVC模式. MVC模式就是按照程序的功能将它们分成三层,分别是Modle层 (模型层).View(显示层).Controller(控制 ...

  6. 使用springboot实现一个简单的restful crud——02、dao层单元测试,测试从数据库取数据

    接着上一篇,上一篇我们创建了项目.创建了实体类,以及创建了数据库数据.这一篇就写一下Dao层,以及对Dao层进行单元测试,看下能否成功操作数据库数据. Dao EmpDao package com.j ...

  7. mybatis实战教程(mybatis in action)之十:mybatis SqlSessionSupport 的使用,构件DAO 层的应用

    前面的系列mybatis 文章,已经基本讲到了mybatis的操作,但都是基于mapper隐射操作的,在mybatis 3中这个mapper 接口貌似充当了以前在ibatis 2中的 DAO 层的作用 ...

  8. mapper.xml是怎样实现Dao层接口

    上午写了一个简单的 从xml读取信息实例化一个Bean对象.下午就开始想mybatis是怎么通过xml文件来实现dao层接口的,一开始想直接用Class.forName(String name)然后调 ...

  9. 通过对DAO层的封装减少数据库操作的代码量

     在学框架之前,写项目时总是要花大量的时间去写数据库操作层代码,这样会大大降低我们的效率,为了解决这个问题,我花了两天时间利用反射机制和泛型将DAO层进行了封装,这样我们只需要写sql语句,不需要再写 ...

随机推荐

  1. LINUX系统虚拟机环境的安装

    安装VM和Centos Step 1 去BIOS里修改设置开启虚拟化设备支持 设置BIOS: 1.开机按F2.F12.DEL.ESC等进入BIOS,一般来说可以看屏幕的左下角有提示按键进入BIOS,进 ...

  2. 基本命令学习 -(3)Linux压缩和解压缩命令汇总

    关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 前言 Linux下的压缩和解压缩工具比较多,有时经常记不住,这里给大家汇总一下,方便大家查阅. ...

  3. RabbitMQ 3.9( 基础 )

    1.认识MQ 1.1.什么是MQ? MQ全称:message queue 即 消息队列 这个队列遵循的原则:FIFO 即 先进先出 队列里面存的就是message 1.2.为什么要用MQ? 1.2.1 ...

  4. typeScript类型总结

    1.对象类型 {} 用来指定对象中可以包含哪些属性,在属性名后加?表示属性是可选的 语法:{属性名:属性值类型,属性名:属性值类型} 示例: let a:{name:string,age?:numbe ...

  5. 关键字 global和nonlocal

    globale 表示从全局把一个变量(比如a)引入局部,后面的变量全是此变量a 使用   globale 变量名 # 全局变量一般是不能随意的修改的 # a = 10 # def func(): # ...

  6. 一个关于 useState 的误解

    一个关于 useState 的误解 本文写于 2020 年 11 月 17 日 前两天有人问了我一个问题,他有一段这样的代码: function App() { const [n, setN] = u ...

  7. JavaScript 单线程之异步编程

    Js 单线程之异步编程 先了解一个概念,为什么 JavaScript 采用单线程模式工作,最初设计这门语言的初衷是为了让它运行在浏览器上面.它的目的是为了实现页面的动态交互,而交互的核心是进行 Dom ...

  8. 虚拟机:ESX

    VMware ESXi 与ESX 产品之比较   VMware vSphere 5.0 以后版本,所有底层虚拟化产品都改为ESXi产品,本文主要比较了ESXi与ESX的各自特点,以便对大家是否要把现有 ...

  9. 手绘图解java类加载原理

    摘要:这也许是全网"最大"."最细"."最深"的java类加载原理图解了. 本文分享自华为云社区<[读书会第12期]这也许是全网&qu ...

  10. SpringMVC请求流程源码分析

    一.SpringMVC使用 1.工程创建 创建maven工程. 添加java.resources目录. 引入Spring-webmvc 依赖. <dependency> <group ...