在开发中,单元测试(Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。简单来说,就是测试数据的稳定性是否达到程序的预期。
JUnit 是一个 Java 语言的单元测试框架。 Junit 测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。Junit 是一套框架,继承 TestCase 类,就可以用 Junit 进行自动测试了。
JUnit:https://junit.org/
JUnit GitHub:https://github.com/junit-team
本文将完全复制 “ Springboot基础知识(08)- spring-boot-starter-web(Web启动器)” 里的 SpringbootWeb 项目的代码和配置到新项目 SpringbootWebJunit,并在新项目 SpringbootWebJunit 的基础上使用 Junit 测试基于 MyBatis 的数据库增删改查(CRUD)操作。
1. 导入 JDBC、MariaDB、MyBatis 等相关依赖包
访问 http://www.mvnrepository.com/ 查询依赖包。
修改 pom.xml:
1 <project ... > 2 ... 3 4 <dependencies> 5 ... 6 7 <dependency> 8 <groupId>org.mariadb.jdbc</groupId> 9 <artifactId>mariadb-java-client</artifactId> 10 </dependency> 11 <dependency> 12 <groupId>org.springframework.boot</groupId> 13 <artifactId>spring-boot-starter-data-jdbc</artifactId> 14 </dependency> 15 <dependency> 16 <groupId>org.mybatis.spring.boot</groupId> 17 <artifactId>mybatis-spring-boot-starter</artifactId> 18 <version>2.2.0</version> 19 </dependency> 20 21 <dependency> 22 <groupId>org.projectlombok</groupId> 23 <artifactId>lombok</artifactId> 24 <version>1.18.8</version> 25 </dependency> 26 27 ... 28 </dependencies> 29 30 ... 31 </project>
注:
(1) 使用 IDEA 向导创建的 "org.apache.maven.archtypes:maven-archtype-quickstart" 类型项目,会默认自动添加 JUnit 依赖包, 根据项目需要调整 JUnit 的版本,本文改成了 4.12。
(2) lombok 提供了一些简化实体类定义的注解。要使用 lombok,IDEA 需要安装 lombok 插件,这里以 Windows 版的 IDEA 为例,安装步骤如下:
菜单 File -> Settings -> 选中左侧 Plugin -> 搜索 "lombok" -> Install lombok plugin -> Restart IDEA
在 IDE 中项目列表 -> SpringbootWebJunit -> 点击鼠标右键 -> Maven -> Reload Project
2. 配置数据库(MariaDB)
1) 创建数据库 testdb 和 user 表,SQL 脚本如下
1 CREATE TABLE `user` ( 2 `id` int(11) NOT NULL AUTO_INCREMENT, 3 `username` varchar(50) NOT NULL, 4 `password` varchar(255) DEFAULT NULL, 5 `age` int(11) DEFAULT NULL, 6 `createtime` timestamp NULL DEFAULT NULL, 7 PRIMARY KEY (`id`) 8 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2) 实体类
创建 src/main/java/com/example/entity/User.java 文件
1 package com.example.entity; 2 3 import java.util.Date; 4 import java.io.Serializable; 5 6 import lombok.Data; 7 import lombok.NoArgsConstructor; 8 import lombok.experimental.Accessors; 9 10 @NoArgsConstructor // 无参构造函数 11 @Data // 提供类的 get、set、equals、hashCode、canEqual、toString 方法 12 @Accessors(chain = true) 13 public class User implements Serializable { 14 private Integer id; 15 private String username; 16 private String password; 17 private Integer age; 18 private Date createtime; 19 20 }
3. 配置 MyBatis
1) 修改 src/main/resources/application.properties 文件
1 spring.main.banner-mode=off 2 3 # Web server 4 server.display-name=SpringBootWebJunit 5 server.address=localhost 6 server.port=9090 7 8 # 数据源配置 9 spring.datasource.username=root 10 spring.datasource.password=123456 11 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb 12 spring.datasource.driver-class-name=org.mariadb.jdbc.Driver 13 14 mybatis.mapper-locations=classpath:mapper/*.xml
2) 创建 src/main/java/com/example/mapper/UserMapper.java 文件
1 package com.example.mapper; 2 3 import org.apache.ibatis.annotations.Mapper; 4 import org.apache.ibatis.annotations.Param; 5 6 import com.example.entity.User; 7 8 @Mapper 9 public interface UserMapper { 10 int insert(User user); 11 int insertSelective(User user); 12 13 User selectByPrimaryKey(Integer id); 14 User selectByUsername(String username); 15 16 int updateByPrimaryKey(User user); 17 int updateByPrimaryKeySelective(User user); 18 void update(@Param("username") String username, @Param("password") String password); 19 20 int deleteByPrimaryKey(Integer id); 21 }
当 mapper 接口较多时,可以在 Spring Boot 主启动类上使用 @MapperScan 注解扫描指定包下的 mapper 接口,而不再需要在每个 mapper 接口上都标注 @Mapper 注解。
3) 创建 src/main/resources/mapper/UserMapper.xml 文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4 <mapper namespace="com.example.mapper.UserMapper"> 5 <resultMap id="BaseResultMap" type="com.example.entity.User"> 6 <id column="id" jdbcType="BIGINT" property="id"/> 7 <result column="username" jdbcType="VARCHAR" property="username"/> 8 <result column="password" jdbcType="VARCHAR" property="password"/> 9 <result column="age" jdbcType="INTEGER" property="age"/> 10 <result column="createtime" jdbcType="DATE" property="createtime"/> 11 </resultMap> 12 <sql id="Base_Column_List"> 13 id, username, password, age, createtime 14 </sql> 15 <insert id="insert" parameterType="com.example.entity.User"> 16 INSERT INTO user (id, username, password, age, createtime) 17 VALUES (#{id,jdbcType=BIGINT}, #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, 18 #{age,jdbcType=INTEGER}, #{createtime,jdbcType=DATE}) 19 </insert> 20 <update id="update"> 21 UPDATE user 22 SET password = #{password} 23 WHERE username = #{username} 24 </update> 25 <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap"> 26 SELECT 27 <include refid="Base_Column_List"/> 28 FROM user 29 WHERE id = #{id,jdbcType=BIGINT} 30 </select> 31 <select id="selectByUsername" parameterType="java.lang.String" resultMap="BaseResultMap"> 32 SELECT 33 <include refid="Base_Column_List"/> 34 FROM user 35 WHERE username = #{username,jdbcType=VARCHAR} 36 </select> 37 <insert id="insertSelective" parameterType="com.example.entity.User"> 38 INSERT INTO user 39 <trim prefix="(" suffix=")" suffixOverrides=","> 40 <if test="id != null"> 41 id, 42 </if> 43 <if test="username != null"> 44 username, 45 </if> 46 <if test="password != null"> 47 password, 48 </if> 49 <if test="age != null"> 50 age, 51 </if> 52 <if test="createtime != null"> 53 createtime, 54 </if> 55 </trim> 56 <trim prefix="values (" suffix=")" suffixOverrides=","> 57 <if test="id != null"> 58 #{id,jdbcType=BIGINT}, 59 </if> 60 <if test="username != null"> 61 #{username,jdbcType=VARCHAR}, 62 </if> 63 <if test="productId != null"> 64 #{password,jdbcType=VARCHAR}, 65 </if> 66 <if test="age != null"> 67 #{age,jdbcType=INTEGER}, 68 </if> 69 <if test="createtime != null"> 70 #{createtime,jdbcType=DATE}, 71 </if> 72 </trim> 73 </insert> 74 <update id="updateByPrimaryKeySelective" parameterType="com.example.entity.User"> 75 UPDATE user 76 <set> 77 <if test="username != null"> 78 username = #{username,jdbcType=VARCHAR}, 79 </if> 80 <if test="password != null"> 81 password = #{password,jdbcType=VARCHAR}, 82 </if> 83 <if test="age != null"> 84 age = #{age,jdbcType=INTEGER}, 85 </if> 86 <if test="createtime != null"> 87 createtime = #{createtime,jdbcType=DATE}, 88 </if> 89 </set> 90 WHERE id = #{id,jdbcType=BIGINT} 91 </update> 92 <update id="updateByPrimaryKey" parameterType="com.example.entity.User"> 93 UPDATE user 94 SET username = #{username,jdbcType=VARCHAR}, 95 password = #{password,jdbcType=VARCHAR}, 96 age = #{age,jdbcType=INTEGER}, 97 createtime = #{createtime,jdbcType=DATE}, 98 WHERE id = #{id,jdbcType=BIGINT} 99 </update> 100 <delete id="deleteByPrimaryKey" parameterType="java.lang.Long"> 101 DELETE FROM user 102 WHERE id = #{id,jdbcType=BIGINT} 103 </delete> 104 105 </mapper>
4. 服务和控制
1) 创建 src/main/java/com/example/service/UserService.java 文件
1 package com.example.service; 2 3 import com.example.entity.User; 4 5 public interface UserService { 6 7 int insert(User user); 8 User selectByPrimaryKey(Integer id); 9 User selectByUsername(String username); 10 int updateByPrimaryKey(User user); 11 int deleteByPrimaryKey(Integer id); 12 }
2) 创建 src/main/java/com/example/service/UserServiceImpl.java 文件
1 package com.example.service; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.stereotype.Service; 5 6 import com.example.entity.User; 7 import com.example.mapper.UserMapper; 8 9 @Service("userService") 10 public class UserServiceImpl implements UserService { 11 @Autowired 12 UserMapper UserMapper; 13 14 @Override 15 public int insert(User user) { 16 return UserMapper.insert(user); 17 } 18 19 @Override 20 public User selectByPrimaryKey(Integer id) { 21 return UserMapper.selectByPrimaryKey(id); 22 } 23 24 @Override 25 public User selectByUsername(String username) { 26 return UserMapper.selectByUsername(username); 27 } 28 29 @Override 30 public int updateByPrimaryKey(User user) { 31 return UserMapper.updateByPrimaryKey(user); 32 } 33 34 @Override 35 public int deleteByPrimaryKey(Integer id) { 36 return UserMapper.deleteByPrimaryKey(id); 37 } 38 39 }
3) 创建 src/main/java/com/example/controller/UserController.java 文件
1 package com.example.controller; 2 3 import org.springframework.web.bind.annotation.RestController; 4 import org.springframework.web.bind.annotation.GetMapping; 5 import org.springframework.web.bind.annotation.PostMapping; 6 import org.springframework.beans.factory.annotation.Autowired; 7 8 import com.example.entity.User; 9 import com.example.service.UserService; 10 11 @RestController 12 public class UserController { 13 @Autowired 14 UserService UserService; 15 16 @GetMapping("/hello") 17 public String hello() { 18 return "hello"; 19 } 20 21 }
注:如果不使用 JUnit 测试 UserService 的增删改查 (CRUD) 操作,我们需要在 UserController 里添加增删改查 (CRUD) 操作的方法,同时需要添加这些方法对应的 UI 代码 (HTML/CSS/Javascript)。
5. JUnit 测试用例
创建 test/java/com/example/service/UserServiceTest.java 文件
1 package com.example.service; 2 3 import static org.junit.Assert.*; 4 5 import java.util.Date; 6 7 import com.example.App; 8 import org.junit.Test; 9 import org.junit.runner.RunWith; 10 import org.junit.FixMethodOrder; 11 import org.junit.runners.MethodSorters; 12 import org.springframework.beans.factory.annotation.Autowired; 13 import org.springframework.boot.test.context.SpringBootTest; 14 import org.springframework.test.context.junit4.SpringRunner; 15 16 import com.example.entity.User; 17 18 @RunWith(SpringRunner.class) 19 @SpringBootTest(classes = App.class) 20 @FixMethodOrder(MethodSorters.NAME_ASCENDING) // DEFAULT:默认的顺序;JVM:按方法定义的顺序执行; NAME_ASCENDING:按方法名字母顺序执行; 21 public class UserServiceTest { 22 @Autowired 23 UserService userService; 24 25 @Test 26 public void unit01_addUser() { 27 28 User user = userService.selectByUsername("admin"); 29 assertNull(user); 30 31 // Create 32 user = new User(); 33 user.setUsername("admin"); 34 user.setPassword("123456"); 35 user.setAge(21); 36 user.setCreatetime(new Date()); 37 38 int ret = userService.insert(user); 39 System.out.println("UserServiceTest -> unit01_addUser(): ret = " + ret); 40 assertTrue( ret > 0 ); 41 42 } 43 44 @Test 45 public void unit02_selectUser() { 46 47 // Select 48 User user = userService.selectByUsername("admin"); 49 assertTrue( 21 == user.getAge()); 50 } 51 52 @Test 53 public void unit03_updateUser() { 54 55 // Update 56 User user = userService.selectByUsername("admin"); 57 user.setAge(30); 58 int ret = userService.updateByPrimaryKey(user); 59 System.out.println("UserServiceTest -> unit03_updateUser(): ret = " + ret); 60 assertTrue( ret > 0 ); 61 62 } 63 64 @Test 65 public void unit04_deleteUser() { 66 67 // Delete 68 User user = userService.selectByUsername("admin"); 69 int ret = userService.deleteByPrimaryKey(user.getId()); 70 System.out.println("UserServiceTest -> unit04_deleteUser(): ret = " + ret); 71 assertTrue( ret >= 0 ); 72 73 } 74 75 }
注:设置 @FixMethodOrder(MethodSorters.NAME_ASCENDING) ,测试过程会依次执行如下方法:
unit01_addUser()
unit02_selectUser()
unit03_updateUser()
unit04_deleteUser()
运行测试用例:
Edit Configurations
Click "+" add new configuration -> Select "JUnit"
Name: UserServiceTest
Class: com.example.service.UserServiceTest
-> Apply / OK
Click Run "UserServiceTest"
...