文章目录
- 前言
- 一、开发阶段
- 1. 单元测试
- 2. 总结
- 二、集成测试
- 1. 热部署
- 三、投产上线
- 1. 打成 jar 包
- 1. 编译构建
- 2. 启动
- 2. 打成 war 包
- 1. 编译构建
- 2. 启动
- 四、生产运维
- 1. 指标监控
- 2. 应用运维
- 五、总结
前言
SpringBoot项目测试
在系统开发过程中,Spring Boot 项目如何测试,如何部署,在生产中有什么好的部署方案吗?
下面简单介绍Spring Boot 如何开发、调试、打包到最后的投产上线。
一、开发阶段
1. 单元测试
在开发阶段的时候最重要的是单元测试了, Spring Boot 对单元测试的支持已经很完善了。
- 依赖
在 pom 包中添加 spring-boot-starter-test 包引用
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
- 基础开发测试类
以最简单的 helloworld 为例,在测试类的类头部需要添加:@RunWith(SpringRunner.class)
和 @SpringBootTest
注解,在测试方法的顶端添加@Test
即可,最后在方法上点击右键run
就可以运行。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Application.class},webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT
)
@Slf4j
@ActiveProfiles("local")
public class BaseSpringTest {
}
注解解读
SpringBootTest.WebEnvironment
是Spring Boot中的一个枚举类型,用于指定在测试或开发环境中创建的ApplicationContext
的类型。它主要有以下几种模式:
MOCK:如果类路径中存在servlet API,则创建一个带有模拟servlet环境的WebApplicationContext;如果存在Spring WebFlux,则创建一个ReactiveWebApplicationContext;否则,创建一个普通的ApplicationContext。这是默认模式1。
DEFINED_PORT:启动一个真实的嵌入式Web服务器,并绑定到指定的端口。
RANDOM_PORT:启动一个嵌入式Web服务器,并绑定到随机端口。
NONE:不启动任何Web服务器,仅加载核心ApplicationContext。这种模式适用于不需要模拟HTTP请求或验证控制器行为的测试,如对服务层方法、DAO或其他非Web组件的测试2。
使用场景和选择依据
MOCK:适用于需要模拟Web环境的测试场景,例如集成测试。
DEFINED_PORT:适用于需要绑定到特定端口的测试场景。
RANDOM_PORT:适用于需要绑定到随机端口的测试场景,以避免端口冲突。
NONE:适用于不需要Web环境的测试场景,可以显著减少测试启动和执行的时间2。
通过合理选择WebEnvironment模式,可以优化Spring Boot应用的开发和测试过程,确保在不同环境下都能高效运行。
- 特别开发测试类
实际使用中,可以按照项目的正常使用,去注入数据层代码,或者是 Service 层代码,进行测试验证, spring-boot-starter-test
提供很多基础用法,更难得的是增加了对 Controller 层测试的支持。
//简单验证结果集是否正确
Assert.assertEquals(3, userMapper.getAll().size());
//验证结果集,提示
Assert.assertTrue("错误,正确的返回值为200", status == 200);
Assert.assertFalse("错误,正确的返回值为200", status != 200);
引入了 MockMvc
支持了对 Controller 层的测试,简单示例如下:
public class HelloControlerTests {private MockMvc mvc;//初始化执行@Beforepublic void setUp() throws Exception {mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();}//验证controller是否正常响应并打印返回结果@Testpublic void getHello() throws Exception {mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();}//验证controller是否正常响应并判断返回结果是否正确@Testpublic void testHello() throws Exception {mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andExpect(content().string(equalTo("Hello World")));}
}
2. 总结
单元测试是验证代码的第一道屏障,要养成每写一部分代码就进行单元测试的习惯,不要等到全部集成后再进行测试,集成后因为更关注整体运行效果,很容易遗漏掉代码底层的bug。
二、集成测试
整体开发完成之后进入集成测试阶段。
Spring Boot 项目的启动入口在 Application 类中,直接运行 run 方法就可以启动项目,但是在测试的过程中需要不断的去调试代码,如果每修改一次代码就手动重启一次服务,显然很麻烦。 Spring Boot 非常贴心的给出了热部署的支持,很方便在 Web 项目中调试使用。
1. 热部署
- 依赖
pom 需要添加以下的配置
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency>
</dependencies>
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><fork>true</fork></configuration></plugin>
</plugins>
</build>
添加以上配置后,项目就支持了热部署,非常方便集成测试。
三、投产上线
根据部署成果物,一般分为两种:
-
打包成 jar 包直接执行
-
打包成 war 包放到 tomcat 服务器下。
1. 打成 jar 包
1. 编译构建
- 执行命令。
cd 项目根目录(和pom.xml同级)
mvn clean package
## 或者执行下面的命令
## 排除测试代码后进行打包
mvn clean package -Dmaven.test.skip=true
打包完成后 jar 包会生成到 target 目录下,命名一般是 项目名+版本号.jar。
2. 启动
- 启动 jar 包命令
java -jar target/spring-boot-scheduler-1.0.0.jar
这种方式,只要控制台关闭,服务就不能访问了。
- 后台启动
下面我们使用在后台运行的方式来启动:
nohup java -jar target/spring-boot-scheduler-1.0.0.jar
也可以在启动的时候选择读取不同的配置文件
java -jar app.jar --spring.profiles.active=dev
也可以在启动的时候设置 jvm 参数
java -Xms10m -Xmx80m -jar app.jar
2. 打成 war 包
1. 编译构建
- maven 项目,修改 pom 包。将jar修改成war。
<packaging>war</packaging>
- 打包时排除tomcat
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><scope>provided</scope>
</dependency>
在这里将 scope 属性设置为 provided,这样在最终形成的 WAR 中不会包含这个 JAR 包,因为 Tomcat 或 Jetty 等服务器在运行时将会提供相关的 API 类。
- 注册启动类
创建 ServletInitializer.java,继承 SpringBootServletInitializer ,覆盖 configure(),把启动类 Application 注册进去。外部 Web 应用服务器构建 Web Application Context 的时候,会把启动类添加进去。
public class ServletInitializer extends SpringBootServletInitializer {@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder application) {return application.sources(Application.class);}
}
- 执行构建
mvn clean package -Dmaven.test.skip=true
会在 target 目录下生成:项目名+版本号.war文件。
2. 启动
将构建好的,war包文件,拷贝到 tomcat 服务器中,启动即可。
四、生产运维
根据部署成果物,一般分为两种:
-
打包成 jar 包直接执行
-
打包成 war 包放到 tomcat 服务器下。
1. 指标监控
查看 JVM 参数的值,可以根据 Java 自带的 jinfo 命令
jinfo -flags pid
来查看 jar 启动后使用的是什么 gc、新生代、老年代分批的内存都是多少,示例如下:
-XX:CICompilerCount=3 -XX:InitialHeapSize=234881024 -XX:MaxHeapSize=3743416320 -XX:MaxNewSize=1247805440 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=78118912 -XX:OldSize=156762112 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
XX:CICompilerCount :最大的并行编译数
-XX:InitialHeapSize 和 -XX:MaxHeapSize :指定 JVM 的初始和最大堆内存大小
-XX:MaxNewSize :JVM 堆区域新生代内存的最大可分配大小
-XX:+UseParallelGC :垃圾回收使用 Parallel 收集器
2. 应用运维
- 如何重启?
简单粗暴,直接 kill 掉进程,再次启动 jar 包
ps -ef|grep java
##拿到对于Java程序的pid
kill -9 pid
## 再次重启
Java -jar xxxx.jar
当然这种方式比较传统和暴力,所以一般使用下面的方式来管理。
- 脚本执行,如果使用的是maven,需要包含以下的配置
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><executable>true</executable></configuration>
</plugin>
启动方式:
1、可以直接 ./yourapp.jar 来启动
2、注册为服务
也可以做一个软链接指向你的jar包并加入到 init.d中,然后用命令来启动。
init.d 例子:
ln -s /var/yourapp/yourapp.jar /etc/init.d/yourapp
chmod +x /etc/init.d/yourapp
这样就可以使用 stop或者是 restart命令去管理你的应用。
/etc/init.d/yourapp start|stop|restart
或者
service yourapp start|stop|restart
五、总结
后面继续介绍 Spring Boot 结合Docker 的使用,以及自动化运维 。
本文的引用仅限自我学习如有侵权,请联系作者删除。
参考知识
Spring Boot 如何优雅的进行测试打包部署