1 依赖管理
1.1 依赖配置
1.1.1 基本配置
依赖:指当前项目运行所需要的jar包。
一个项目中可以引入多个依赖:
例如:在当前工程中,我们需要用到logback来记录日志,此时就可以在maven工程的pom.xml文件中,引入logback的依赖。具体步骤如下:
1. 在pom.xml中编写<dependencies>标签
2. 在<dependencies>标签中使用<dependency>引入坐标
3. 定义坐标的 groupId、artifactId、version
4. 点击刷新按钮,引入最新加入的坐标
<dependencies>
<!-- 依赖 : spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.4</version>
</dependency>
</dependencies>
注:如果引入的依赖在本地仓库中不存在,将会连接远程仓库 / 中央仓库后下载依赖(比较耗时) 若不知道依赖的坐标信息,可以到mvn的中央仓库(https://mvnrepository.com/)中搜索
1.1.2 依赖传递
我们上面在pom.xml中配置了一项依赖,即spring-context,但通过右侧的maven面板可以看到,其实引入进来的依赖并不止这一项,有很多的依赖都引入进来了。如下图所示:
为何出现这样的现象呢? 这里涉及到maven中很重要的一个特性,即Maven中的依赖传递。
所谓maven的依赖传递,是指如果在maven项目中,A 依赖了B,B依赖了C,C依赖了D,那么在A项目中也会有C、D依赖,因为依赖会传递。
如果传递下来的依赖在项目开发中我们不需要,可通过Maven中的排除依赖功能,来将这个依赖排除掉。
1.1.3 排除依赖
排除依赖:指主动断开依赖的资源,被排除的资源无需指定版本。
配置形式如下:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.4</version>
<!--排除依赖, 主动断开依赖的资源-->
<exclusions>
<exclusion>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</exclusion>
</exclusions>
</dependency>
1.2 生命周期
1.2.1 介绍
Maven的生命周期就是为了对所有的构建过程进行抽象和统一。 描述了一次项目构建,经历哪些阶段。
在Maven出现之前,项目构建的生命周期就已经存在,软件开发人员每天都在对项目进行清理,编译,测试及部署。虽然大家都在不停地做构建工作,但公司和公司间、项目和项目间,往往使用不同的方式做类似的工作。
Maven从大量项目和构建工具中学习和反思,然后总结了一套高度完美的,易扩展的项目构建生命周期。这个生命周期包含了项目的清理,初始化,编译,测试,打包,集成测试,验证,部署和站点生成等几乎所有构建步骤。
Maven对项目构建的生命周期划分为3套(相互独立):
- clean:清理工作。
- default:核心工作。如:编译、测试、打包、安装、部署等。
- site:生成报告、发布站点等。
三个周期又包含许多具体阶段,其中我们主要了解五个阶段:
- clean:移除上一次构建生成的文件
- compile:编译项目源代码
- test:使用合适的单元测试框架运行测试(junit)
- package:将编译后的文件打包,如:jar、war等
- install:安装项目到本地仓库
IDEA工具为了方便程序员使用maven生命周期,在右侧的maven工具栏中给出了快速访问通道。
说明:在同一套生命周期中,我们在执行后面的生命周期时,前面的生命周期都会执行。
1.2.2 执行
在日常开发中,当我们要执行指定的生命周期时,有两种执行方式:
1. 在idea工具右侧的maven工具栏中,选择对应的生命周期,双击执行
2. 在DOS命令行中,通过maven命令执行
方式一:选择对应的生命周期,双击执行
以clean为例:
编译后,clean前:
clean后:
方式二:在命令行中执行生命周期
1.打开maven项目对应的磁盘目录
2.在当前目录下打开CMD
3.输入 mvn clean 回车运行
同理,我们也可以在命令执行:
mvn compile
mvn test
mvn package
mvn install
2 单元测试
2.1 介绍
测试:是一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。
阶段划分:单元测试、集成测试、系统测试、验收测试。
1) 单元测试
介绍:对软件的基本组成单位进行测试,最小测试单位。
目的:检验软件基本组成单位的正确性。
测试人员:开发人员
2) 集成测试
介绍:将已分别通过测试的单元,按设计要求组合成系统或子系统,再进行的测试。
目的:检查单元之间的协作是否正确。
测试人员:开发人员
3) 系统测试
介绍:对已经集成好的软件系统进行彻底的测试。
目的:验证软件系统的正确性、性能是否满足指定的要求。
测试人员:测试人员
4) 验收测试
介绍:交付测试,是针对用户需求、业务流程进行的正式的测试。
目的:验证软件系统是否满足验收标准。
测试人员:客户/需求方
测试方法:白盒测试、黑盒测试 及 灰盒测试。
1) 白盒测试
清楚软件内部结构、代码逻辑。
用于验证代码、逻辑正确性。
2) 黑盒测试
不清楚软件内部结构、代码逻辑。
用于验证软件的功能、兼容性、验收测试等方面。
3) 灰盒测试
结合了白盒测试和黑盒测试的特点,既关注软件的内部结构又考虑外部表现(功能)。
2.2 Junit入门
2.2.1 单元测试
单元测试:就是针对最小的功能单元(方法),编写测试代码对其正确性进行测试。
Junit:最流行的Java测试框架之一,提供了一些功能,方便程序进行单元测试(第三方公司提供)。
之前我们进行程序的测试 都是main方法中进行,但在main方法进行测试时会存在如下问题:
1. 测试代码与源代码未分开,难维护。
2. 一个方法测试失败,影响后面方法。
3. 无法自动化测试,得到测试报告。
而如果我们使用了Junit单元测试框架进行测试,将会有以下优势:
1. 测试代码与源代码分开,便于维护。
2. 可根据需要进行自动化测试。
3. 可自动分析测试结果,产出测试报告。
2.2.2 入门程序
1. 在pom.xml中,引入JUnit的依赖,如下。
2. 在test/java目录下,创建测试类,并编写对应的测试方法,并在方法上声明@Test注解。
3. 运行单元测试 (测试通过:绿色;测试失败:红色)。
<!--Junit单元测试依赖-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
例如在主程序中引入UserService类:
import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;
public class UserService {
/**
* 给定一个身份证号, 计算出该用户的年龄
* @param idCard 身份证号
*/
public Integer getAge(String idCard){
if (idCard == null || idCard.length() != 18) {
throw new IllegalArgumentException("无效的身份证号码");
}
String birthday = idCard.substring(6, 14);
LocalDate parse = LocalDate.parse(birthday, DateTimeFormatter.ofPattern("yyyyMMdd"));
return Period.between(parse, LocalDate.now()).getYears();
}
/**
* 给定一个身份证号, 计算出该用户的性别
* @param idCard 身份证号
*/
public String getGender(String idCard){
if (idCard == null || idCard.length() != 18) {
throw new IllegalArgumentException("无效的身份证号码");
}
return Integer.parseInt(idCard.substring(16,17)) % 2 == 1 ? "男" : "女";
}
}
在测试类中进行测试代码的编写,例如:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class UserServiceTest {
@Test
public void testGetaAge(){
UserService userService = new UserService();
Integer age = userService.getAge("100000200010011011");
System.out.println(age);
}
@Test
public void testGetGenderWithAssert(){
UserService userService = new UserService();
String gender = userService.getGender("100000200110011011");
Assertions.assertEquals("男", gender,"性别获取逻辑有问题");
}
}
注:
测试类的命名规范为:XxxxTest
测试方法的命名规定为:public void xxx(){...}
2.3 断言
Junit提供了一些辅助方法,用来帮我们确定被测试的方法是否按照预期的效果正常工作,这种方式称为断言。
断言方法 | 描述 |
---|---|
assertEquals(Object exp, Object act, String msg) | 检查两个值是否相等,不相等就报错。 |
assertNotEquals(Object unexp, Object act, String msg) | 检查两个值是否不相等,相等就报错。 |
assertNull(Object act, String msg) | 检查对象是否为null,不为null,就报错。 |
assertNotNull(Object act, String msg) | 检查对象是否不为null,为null,就报错。 |
assertTrue(boolean condition, String msg) | 检查条件是否为true,不为true,就报错。 |
assertFalse(boolean condition, String msg) | 检查条件是否为false,不为false,就报错。 |
assertSame(Object exp, Object act, String msg) | 检查两个对象引用是否相等,不相等,就报错。 |
2.4 常见注解
在JUnit中还提供了一些注解,还增强其功能,常见的注解有以下几个:
注解 | 说明 | 备注 |
---|---|---|
@Test | 测试类中的方法用它修饰才能成为测试方法,才能启动执行 | 单元测试 |
@BeforeEach | 用来修饰一个实例方法,该方法会在每一个测试方法执行之前执行一次。 | 初始化资源(准备工作) |
@AfterEach | 用来修饰一个实例方法,该方法会在每一个测试方法执行之后执行一次。 | 释放资源(清理工作) |
@BeforeAll | 用来修饰一个静态方法,该方法会在所有测试方法之前只执行一次。 | 初始化资源(准备工作) |
@AfterAll | 用来修饰一个静态方法,该方法会在所有测试方法之后只执行一次。 | 释放资源(清理工作) |
@ParameterizedTest | 参数化测试的注解 (可以让单个测试运行多次,每次运行时仅参数不同) | 用了该注解,就不需要@Test注解了 |
@ValueSource | 参数化测试的参数来源,赋予测试方法参数 | 与参数化测试注解配合使用 |
@DisplayName | 指定测试类、测试方法显示的名称 (默认为类名、方法名) |
演示 @BeforeEach,@AfterEach,@BeforeAll,@AfterAll 注解:
public class UserServiceTest {
@BeforeEach
public void testBefore(){
System.out.println("before...");
}
@AfterEach
public void testAfter(){
System.out.println("after...");
}
@BeforeAll //该方法必须被static修饰
public static void testBeforeAll(){
System.out.println("before all ...");
}
@AfterAll //该方法必须被static修饰
public static void testAfterAll(){
System.out.println("after all...");
}
@Test
public void testGetAge(){
Integer age = new UserService().getAge("110002200505091218");
System.out.println(age);
}
@Test
public void testGetGender(){
String gender = new UserService().getGender("612429198904201611");
System.out.println(gender);
}
}
输出结果:
演示 @ParameterizedTest ,@ValueSource ,@DisplayName 注解:
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@DisplayName("测试-学生业务操作")
public class UserServiceTest {
@DisplayName("测试-获取年龄")
@Test
public void testGetAge(){
Integer age = new UserService().getAge("110002200505091218");
System.out.println(age);
}
@DisplayName("测试-获取性别")
@Test
public void testGetGender(){
String gender = new UserService().getGender("612429198904201611");
System.out.println(gender);
}
@DisplayName("测试-获取性别3")
@ParameterizedTest
@ValueSource(strings = {"612429198904201611","612429198904201631","612429198904201626"})
public void testGetGender3(String idcard){
String gender = new UserService().getGender(idcard);
System.out.println(gender);
}
}
输出结果:
注:在maven项目中,test目录存放单元测试的代码,是否可以在main目录中编写单元测试呢 ? 可以,但是不规范
2.5 依赖范围
依赖的jar包,默认情况下可以在任何地方使用:在main目录下,可以使用;在test目录下,也可以使用。
在maven中,如果希望限制依赖的使用范围,可以通过 <scope>…</scope> 设置其作用范围。
作用范围:
主程序范围有效。(main文件夹范围内)
测试程序范围有效。(test文件夹范围内)
是否参与打包运行。(package指令范围内)
如果对Junit单元测试的依赖,设置了scope为 test,就代表该依赖只在测试程序中可以使用,在主程序中是无法使用的。
例如:
<!--junit依赖-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
注:更改pom文件后记得点击更新
scope的取值常见的如下:
scope值 | 主程序 | 测试程序 | 打包(运行) | 范例 |
---|---|---|---|---|
compile(默认) | Y | Y | Y | log4j |
test | - | Y | - | junit |
provided | Y | Y | - | servlet-api |
runtime | - | Y | Y | jdbc驱动 |
3 Maven常见问题
问题现象:Maven项目中添加的依赖,未正确下载,造成右侧Maven面板中的依赖报红,再次reload重新加载也不会再下载。
产生原因:由于网络原因,依赖没有下载完整导致的,在maven仓库中生成了xxx.lastUpdated文件,该文件不删除,不会再重新下载。
解决方案:
1. 根据maven依赖的坐标,找到仓库中对应的 xxx.lastUpdated 文件,删除,删除之后重新加载项目即可。
2. 更快速的方法:在maven安装包目录下进入控制台,通过命令 (del /s *.lastUpdated) 批量递归删除指定目录下的 xxx.lastUpdated 文件,删除之后重新加载项目即可。
3. 重新加载依赖,依赖下载了之后,maven面板可能还会报红,此时可以关闭IDEA,重新打开IDEA加载此项目即可。