JavaWeb-JUnit,JUL与Maven

笔记学习于柏码知识库

使用JUnit进行单元测试

以JUnit4为例。部分内容在JUnit5中被废弃

使用@Test注解来标记测试案例,点击测试按钮进行测试。编写的方法有以下要求:

  • 方法必须是public的
  • 不能是静态方法
  • 返回值必须是void
  • 必须是没有任何参数的方法

可以通过断言来对结果进行判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void method(){
int[] arr = {0, 4, 5, 2, 6, 9, 3, 1, 7, 8};

//错误的冒泡排序
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if(arr[j] > arr[j + 1]){
int tmp = arr[j];
arr[j] = arr[j+1];
// arr[j+1] = tmp;
}
}
}

Assert.assertArrayEquals(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, arr);
}

可以使用@Before来添加前置操作:(最新版本中@Before被废弃,进行了细分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class TestMain {

private SqlSessionFactory sqlSessionFactory;
@Before
public void before(){
System.out.println("测试前置正在初始化...");
try {
sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(new FileInputStream("mybatis-config.xml"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("测试初始化完成,正在开始测试案例...");
}

@Test
public void method1(){
try (SqlSession sqlSession = sqlSessionFactory.openSession(true)){
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
Student student = mapper.getStudentBySidAndSex(1, "男");

Assert.assertEquals(new Student().setName("小明").setSex("男").setSid(1), student);
System.out.println("测试用例1通过!");
}
}

@Test
public void method2(){
try (SqlSession sqlSession = sqlSessionFactory.openSession(true)){
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
Student student = mapper.getStudentBySidAndSex(2, "女");

Assert.assertEquals(new Student().setName("小红").setSex("女").setSid(2), student);
System.out.println("测试用例2通过!");
}
}
}

使用@After添加结束动作:

1
2
3
4
@After
public void after(){
System.out.println("测试结束,收尾工作正在进行...");
}

Junit5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MainTest {

//因为配置文件位于内部,我们需要使用Resources类的getResourceAsStream来获取内部的资源文件
private static SqlSessionFactory factory;

//在JUnit5中@Before被废弃,它被细分了:
@BeforeAll // 一次性开启所有测试案例只会执行一次 (方法必须是static)
// @BeforeEach 一次性开启所有测试案例每个案例开始之前都会执行一次
@SneakyThrows
public static void before(){
factory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream("mybatis.xml"));
}


@DisplayName("Mybatis数据库测试") //自定义测试名称
@RepeatedTest(3) //自动执行多次测试
public void test(){
try (SqlSession sqlSession = factory.openSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
System.out.println(testMapper.getStudentBySid(1));
}
}
}

JUL日志系统

基本介绍

简单使用:

1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
// 首先获取日志打印器
Logger logger = Logger.getLogger(Main.class.getName());
// 调用info来输出一个普通的信息,直接填写字符串即可
logger.info("我是普通的日志");
}
}

日志分为7个级别。级别低于默认级别的日志信息,无法输出到控制台。可以设置自定义控制台来修改打印级别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
Logger logger = Logger.getLogger(Main.class.getName());

//修改日志级别
logger.setLevel(Level.CONFIG);
//不使用父日志处理器
logger.setUseParentHandlers(false);
//使用自定义日志处理器
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.CONFIG);
logger.addHandler(handler);

logger.log(Level.SEVERE, "严重的错误", new IOException("我就是错误"));
logger.log(Level.WARNING, "警告的内容");
logger.log(Level.INFO, "普通的信息");
logger.log(Level.CONFIG, "级别低于普通信息");
}

也可以使用文件处理器,此时控制台和文件的日志处理器都会生效:

1
2
3
4
//添加输出到本地文件
FileHandler fileHandler = new FileHandler("test.log");
fileHandler.setLevel(Level.WARNING);
logger.addHandler(fileHandler);

也可以自定义样式,控制台处理器就默认使用的是SimpleFormatter,而文件处理器则是使用的XMLFormatter。

1
2
3
4
5
//使用自定义日志处理器(控制台)
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.CONFIG);
handler.setFormatter(new XMLFormatter());
logger.addHandler(handler);

还可以设置过滤器并配置过滤规则:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws IOException {
Logger logger = Logger.getLogger(Main.class.getName());

//自定义过滤规则
logger.setFilter(record -> !record.getMessage().contains("普通"));

logger.log(Level.SEVERE, "严重的错误", new IOException("我就是错误"));
logger.log(Level.WARNING, "警告的内容");
logger.log(Level.INFO, "普通的信息");
}

Properties文件

Properties文件配置格式:

1
2
name=Test
desc=Description

可以将配置文件映射为类Map的对象:

1
2
3
4
5
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.load(new FileInputStream("test.properties"));
System.out.println(properties);
}

日志配置文件

使用配置文件来规定日志打印器默认值:

1
2
3
4
# RootLogger 的默认处理器为
handlers= java.util.logging.ConsoleHandler
# RootLogger 的默认的日志级别
.level= CONFIG

使用配置文件来进行配置:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws IOException {
//获取日志管理器
LogManager manager = LogManager.getLogManager();
//读取我们自己的配置文件
manager.readConfiguration(new FileInputStream("logging.properties"));
//再获取日志打印器
Logger logger = Logger.getLogger(Main.class.getName());
logger.log(Level.CONFIG, "我是一条日志信息"); //通过自定义配置文件,我们发现默认级别不再是INFO了
}

也可以修改ConsoleHandler的默认配置:

1
2
3
4
5
6
# 指定默认日志级别
java.util.logging.ConsoleHandler.level = ALL
# 指定默认日志消息格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定默认的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8

使用Lombok快速开启日志

1
2
3
4
5
6
7
@Log
public class Main {
public static void main(String[] args) {
System.out.println("自动生成的Logger名称:"+log.getName());
log.info("我是日志信息");
}
}

Mybatis日志系统

通过配置开启日志系统:

1
<setting name="logImpl" value="STDOUT_LOGGING" />

可以进一步配置日志系统,并修改格式。

修改配置文件:

1
<setting name="logImpl" value="JDK_LOGGING" />

格式化打印格式:

1
2
3
4
5
6
7
8
public class TestFormatter extends Formatter {
@Override
public String format(LogRecord record) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String time = format.format(new Date(record.getMillis())); //格式化日志时间
return time + " : " + record.getMessage() + "\n";
}
}

设置logging.properties:

1
2
3
4
handlers= java.util.logging.ConsoleHandler
.level= ALL
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = com.test.TestFormatter

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Log
public class TestMain {

private SqlSessionFactory sqlSessionFactory;
@Before
public void before(){
try {
sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(new FileInputStream("mybatis-config.xml"));
LogManager manager = LogManager.getLogManager();
manager.readConfiguration(new FileInputStream("logging.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}

@Test
public void test(){
try(SqlSession sqlSession = sqlSessionFactory.openSession(true)){
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
log.info(mapper.getStudentBySidAndSex(1, "男").toString());
log.info(mapper.getStudentBySidAndSex(1, "男").toString());
}
}
}

Maven

pom.xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
<artifactId>MavenTest</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

</project>

groupId、artifactId、version这三个元素合在一起,用于唯一区别每个项目,通过Maven导入其他的依赖只需要填写这三个基本元素就可以。

依赖导入

创建dependencies节点:

1
2
3
<dependencies>
//里面填写的就是所有的依赖
</dependencies>

可以在查询依赖。以lombok为例:

1
2
3
4
5
6
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>

可以在IDEA安装根目录/plugins/maven/lib/maven3/conf文件夹,编辑settings.xml文件切换国内源。在mirros标签中添加:

1
2
3
4
5
6
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>

依赖作用域

通过scope属性决定,包括:

  • compile:默认,编译、运行、测试均有效
  • provided:编译、测试有效,运行无效,如Lombok
  • runtime:在运行、测试有效,编译无效。
  • test:仅测试有效。如Junit。
  • system:本地导入,作用域与provided一样,需要添加systemPath

如导入JUnit5依赖:

1
2
3
4
5
6
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>

所有的测试用例全部编写到Maven项目划分的test目录下,位于此目录下的内容不会在最后被打包到项目中,只用作开发阶段测试使用。

Maven还提供了一个resource文件夹,可以将一些静态资源,比如配置文件,放入到这个文件夹中,项目在打包时会将资源文件夹中文件一起打包的Jar中。如Mybatis配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="cacheEnabled" value="true"/>
<setting name="logImpl" value="JDK_LOGGING" />
</settings>
<!-- 需要在environments的上方 -->
<typeAliases>
<package name="com.test.entity"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study"/>
<property name="username" value="test"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.test.mapper.TestMapper"/>
</mappers>
</configuration>

可选依赖

默认在导入依赖时,不会导入可选的依赖

1
<optional>true</optional>

排除依赖

使用exclusions标签排除依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.1</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</exclusion>
</exclusions>
</dependency>

继承关系

一个Maven项目可以继承自另一个Maven项目。

创建子项目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>MavenTest</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>ChildModel</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

</project>

parent节点,表示此Maven项目是父Maven项目的子项目,子项目直接继承父项目的groupId,子项目会直接继承父项目的所有依赖,除非依赖添加了optional标签。

可以由父Maven统一管理依赖,包括版本。子项目按需使用依赖,并无需指定版本号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
</dependencies>
</dependencyManagement>
1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

测试

通过使用test命令,可以一键测试所有位于test目录下的测试案例,请注意有以下要求:

  • 测试类的名称必须是以Test结尾,比如MainTest
  • 测试方法上必须标注@Test注解,实测@RepeatedTest无效

需要将配置插件升级到高版本,才能完美兼容Junit5,使@RepeatedTest、@BeforeAll生效。

1
2
3
4
5
6
7
8
9
10
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<!-- JUnit 5 requires Surefire version 2.22.0 or higher -->
<version>2.22.0</version>
</plugin>
</plugins>
</build>

打包项目

打包分为两种情况:

  • 作为Jar依赖,使用package命令即可。
  • 作为可执行程序

对于第二种情况,使用另一个插件进行打包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.test.Main</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

打包前会进行测试,可以选择执行Maven目标来手动执行Maven命令,输入mvn package -Dmaven.test.skip=true 来以跳过测试的方式进行打包。