一.问题提出
很多朋友在软件开发过程中,需要尽快的知道自己开发的程序是否满足功能需要,通常通过自测的方法,能够验证程序的正确与否。在现代软件开发方法中,提倡在测试用例的开发要先于功能代码的开发,也称之为测试驱动开发。
但是在单元测试的实践中,通常需要验证自己的业务有依赖数据库中数据的正确与否,有时并不是很顺利,因为验证功能的同时,会有很多测试用到的数据存在于数据库中,并影响到用例的下次执行。
本篇文章要解决的问题就是通过一种内存数据库,可以执行单元测试中的相关的SQL语句,同时又不会对开发测试数据库表中的数据产生干扰。
二.环境准备
为了简化环境准备,项目采用SpringBoot框架,持久层框架采用MyBatis-plus,不了解MyBatis-Plus的同学可先自行了解相关知识。
三.使用步骤
1.组件依赖
pom.xml文件中的组件依赖
- SpringBoot2的Finchley版
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 用的组件依赖坐标及版本
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 编译及依赖
<build>
<plugins>
<!--编译插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<!-- spring-boot:run 启动插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- spring-boot:run 中文乱码解决 -->
<configuration>
<fork>true</fork>
<!--增加jvm参数-->
<jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>application*.yml</exclude>
</excludes>
<includes>
<include>**/*.*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2.项目结构
项目采用分层结构
- domain为实体对象层,mapper为数据访问接口层,service为业务逻辑层;
- 在Test包底下,有测试方法类UserServiceTest
- 在test/resources文件夹底下有相关的配置文件
3.功能代码
1.UserVo类中定义实体对象
- @Data注解为lombok语法,省却了get和set方法
- @TableName为Mp框架提供的注解,用于将实体关联到数据库对应的表中
package com.xiongxin.boot.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
@TableName("user")
public class UserVo {
//主键
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
//用户名
@TableField("username")
private String username;
//密码
@TableField("password")
private String password;
}
2.UserMapper中定义相关的数据访问方法
- 这里定义的UserMapper接口,继承BaseMapper接口,就拥有对应的操作方法
package com.xiongxin.boot.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiongxin.boot.domain.UserVo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<UserVo> {
}
3.UserService业务逻辑层代码
package com.xiongxin.boot.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiongxin.boot.domain.UserVo;
import com.xiongxin.boot.mapper.UserMapper;
import org.springframework.stereotype.Service;
@Service
public class UserService extends ServiceImpl<UserMapper, UserVo> {
}
4.最终项目启动类
- BootApplication 为boot项目的启动类
com.xiongxin.boot.BootApplication
package com.xiongxin.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
}
4.测试代码
1.UserServiceTest测试类
- @RunWith(SpringRunner.class)为boot框架提供的测试类
- @SpringBootTest为boot框架提供的测试方法
- 测试步骤,将执行保存一个用户的方法,再断言数据库中的记录行数为1
package com.xiongxin.boot.service;
import com.xiongxin.boot.domain.UserVo;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Resource
UserService userService;
@Test
public void h2() {
userService.save(new UserVo().setUsername("xiongxin").setPassword("xiongxin"));
Assert.assertEquals(1, userService.list().size());
}
}
2.Resources中的配置文件
- application.yml放在test/resources中,表示运行测试方法时会加载相关这个文件
- 为了能够看到sql执行的情况,设置了打印sql的包为 com.xiongxin.boot.mapper
#mysql
spring:
datasource:
url: jdbc:h2:mem:db_h2;MODE=MYSQL;INIT=RUNSCRIPT FROM './src/test/resources/schema.sql'
driver-class-name: org.h2.Driver
username: root
password: root
# 打印sql
logging:
level:
com.xiongxin.boot.mapper: debug
3.h2数据库的创表语句
- schema.sql
drop table if exists user;
CREATE TABLE user
(
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(255) DEFAULT NULL,
password varchar(255) DEFAULT NULL,
PRIMARY KEY (id)
);
5.运行结果
运行单元测试方法,可以看到控制台打印出日志
- 观察日志,可以看到断言结果正确,并且打印了SQL语句的执行情况
==> Preparing: INSERT INTO user ( username, password ) VALUES ( ?, ? )
==> Parameters: xiongxin(String), xiongxin(String)
<== Updates: 1
==> Preparing: SELECT id,username,password FROM user
==> Parameters:
<== Total: 1
四.总结说明
通过h2单元测试,能够非常方便的验证开发功能的正确与否,同时编写完成的用例可以反复执行,提高了编码效率,提升了软件的质量,便于提早发现程序中潜在的bug。