Spring Boot的热部署与单元测试

       一.在项目开发的过程中,常常会改动页面数据或修改数据结构.为了显示改动效果,往往需要重启应用查看改变得效果,否则将不能看到新增代码的效果,这一过程很多时候是非常浪费时间的,导致开发效率很低.开发热部署可以在改变程序代码的时候,自动实现项目的重新启动和部署,大大提高了开发调试的效率.

       在Spring Boot中添加热部署其实是非常简单的,只需在Maven打包工程的Pom.xml里添加配置信息即可.需要添加的依赖如下:

		<!-- 添加spring-boot-starter-web模块依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- Spring Boot spring-boot-devtools 热部署依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
			<scope>true</scope>
		</dependency>
	<!-- 添加spring-boot-maven-plugin -->
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<!-- 如果没有该项配置,devtools不会起作用,即应用不会restart -->
					<fork>true</fork>
				</configuration>
			</plugin>
		</plugins>
	</build>

添加该配置文件后,启动App.class服务,热部署即可生效.

二.测试用例在系统开发中有着非常重要的作用,通过模块的单元测试可以及时发现因为修改代码导致的新问题并及时解决.下面介绍Spring Boot的单元测试功能

1.包含热部署及单元测试的所有依赖pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.weichai</groupId>
	<artifactId>UnitTest</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>UnitTest</name>
	<url>http://maven.apache.org</url>

	<!-- spring-boot-starter-parent是Spring Boot的核心启动器, 包含了自动配置、日志和YAML等大量默认的配置,大大简化了我们的开发。 
		引入之后相关的starter引入就不需要添加version配置, spring boot会自动选择最合适的版本进行添加。 -->
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.0.RELEASE</version>
		<relativePath />
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<!-- 添加spring-boot-starter-web模块依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- 添加spring-boot-starter-thymeleaf模块依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<!-- Spring Boot spring-boot-devtools 热部署依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
			<scope>true</scope>
		</dependency>

		<!-- spring-boot-starter-test 依赖.... -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<!-- 添加spring-boot-maven-plugin -->
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<!-- 如果没有该项配置,devtools不会起作用,即应用不会restart -->
					<fork>true</fork>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

为了方便进行单元测试,这里用Spring Data JPA写了一个简单的数据接口功能.

2.数据接口的开发

package com.weichai.UnitTest.entity;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "tb_student")
public class Student implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	private String name;
	private String address;
	private int age;
	private char sex;

	public Student() {

	}

	public Student(String name, String address, int age, char sex) {
		super();
		this.name = name;
		this.address = address;
		this.age = age;
		this.sex = sex;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public char getSex() {
		return sex;
	}

	public void setSex(char sex) {
		this.sex = sex;
	}
}
package com.weichai.UnitTest.dao;

import org.springframework.data.jpa.repository.JpaRepository;

import com.weichai.UnitTest.entity.Student;

public interface StudentDao extends JpaRepository<Student, Integer> {

}
package com.weichai.UnitTest.service;

import java.util.Optional;

import javax.annotation.Resource;
import javax.transaction.Transactional;
import org.springframework.stereotype.Service;

import com.weichai.UnitTest.dao.StudentDao;
import com.weichai.UnitTest.entity.Student;

@Service
public class StudentService {

	//定义数据访问层接口对象 
	@Resource
	private StudentDao studentDao;
	
	@Transactional
	public void save(Student stu) {
		studentDao.save(stu);
	}
	
	public Student selectByKey(Integer id) {
		Optional<Student> op = studentDao.findById(id);
		return op.get();
	}
}
package com.weichai.UnitTest.controller;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.weichai.UnitTest.entity.Student;
import com.weichai.UnitTest.service.StudentService;

@RestController
@RequestMapping("/student")
public class StudentController {

	@Resource
	private StudentService studentService;
	
	/**
	 * 保存学生信息
	 * @param stu
	 * @return
	 */
	@PostMapping(value="/save")
	public Map<String,Object> save(@RequestBody Student stu) {
		studentService.save(stu);
		Map<String,Object> params = new HashMap<String, Object>();
		params.put("code", "success");
		return params;
	}
	
    @GetMapping(value="/get/{id}")
    @ResponseBody
    public Student queryStuById(@PathVariable(value = "id") Integer id){
    	Student stu = studentService.selectByKey(id);
    	return stu;
    }
}

3.使用Spring Boot的单元测试对Service服务层进行测试

package com.weichai.UnitTest;

import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.weichai.UnitTest.entity.Student;
import com.weichai.UnitTest.service.StudentService;

/**
 * 服务层的测试
 * @author lhy
 * @date 2018.11.12
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest {

	// 注入Service
	@Autowired
	private StudentService service;
	
	@Test
	public void findById() throws Exception {
        Student stu = service.selectByKey(1);   //服务层调用数据持久层接口查询
        Assert.assertThat(stu.getName(), CoreMatchers.is("孙悟空"));   //测试查询出的学生姓名与给定的姓名是否一致,不一致为False
	}
}

控制台输出包含该条Log:

Hibernate: select student0_.id as id1_0_0_, student0_.address as address2_0_0_, student0_.age as age3_0_0_, student0_.name as name4_0_0_, student0_.sex as sex5_0_0_ from tb_student student0_ where student0_.id=?

4.使用Spring Boot的单元测试对Controller控制层进行测试

package com.weichai.UnitTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

/**
 * 控制层数据接口的测试
 * @author lhy
 * @date 2018.11.12
 */
@RunWith(SpringRunner.class)                   //让测试运行于Spring的测试环境
@SpringBootTest(classes = App.class)           //App.class是项目的启动类
public class ControllerTest {

	//注入Spring容器
	@Autowired
	private WebApplicationContext wac;
	// MockMvc实现了对Http请求的模拟,可以不必启动工程就可以测试接口
    private MockMvc mvc;
    @Before
    public void setupMockMvc(){
    	// 初始化MockMvc对象
        mvc = MockMvcBuilders.webAppContextSetup(wac).build(); 
    }
    
    /**
     * 新增学生测试用例
     * @throws Exception
     */
    @Test
    public void addStudent() throws Exception{
        String json="{\"name\":\"海哥\",\"address\":\"山东烟台\",\"age\":\"22\",\"sex\":\"男\"}";    //要传输的数据
        mvc.perform(MockMvcRequestBuilders.post("/student/save")                 //执行请求调用控制器的某一个方法
                .contentType(MediaType.APPLICATION_JSON_UTF8)                    //代表发送的数据格式
                .accept(MediaType.APPLICATION_JSON_UTF8)                         //代表接收的数据格式
                .content(json.getBytes())                                        //传json参数
        )
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andDo(MockMvcResultHandlers.print());                                   //控制台输入整个响应信息
    }
    
    /**
     * 获取指定ID学生信息的测试用例
     * @throws Exception
     */
    @Test
    public void queryStuById() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/student/get/1")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
        )                                                                          //添加执行完成后的断言
       .andExpect(MockMvcResultMatchers.status().isOk())                           //查看请求的状态响应码是否为200
       .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("孙悟空"))          //获取name字段,比对是否为孙悟空,不是就测试不通过
       .andExpect(MockMvcResultMatchers.jsonPath("$.address").value("广州"))
       .andDo(MockMvcResultHandlers.print());                                      //控制台输入整个响应信息
    }
}

观察控制台,显示信息如下:

Hibernate: select student0_.id as id1_0_0_, student0_.address as address2_0_0_, student0_.age as age3_0_0_, student0_.name as name4_0_0_, student0_.sex as sex5_0_0_ from tb_student student0_ where student0_.id=?

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /student/get/1
       Parameters = {}
          Headers = {Content-Type=[application/json;charset=UTF-8], Accept=[application/json;charset=UTF-8]}
             Body = null
    Session Attrs = {}

Handler:
             Type = com.weichai.UnitTest.controller.StudentController
           Method = public com.weichai.UnitTest.entity.Student com.weichai.UnitTest.controller.StudentController.queryStuById(java.lang.Integer)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Content-Type=[application/json;charset=UTF-8]}
     Content type = application/json;charset=UTF-8
             Body = {"id":1,"name":"孙悟空","address":"广州","age":700,"sex":"男"}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

可以看出测试成功!


版权声明:本文为linhaiyun_ytdx原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。