7 日志技术和单元测试
作者:来自ArimaMisaki创作
[TOC]
7.1 日志技术
7.1.1 基本概念
程序中的日志:程序中的日志可以用来记录程序运行过程的信息。
输入信息的转变方式:
- 传统记录日志:使用输出语句观察程序运行的过程
- 如今记录日志:使用API查看日志
输出语句的弊端:
- 信息只能展示在控制台
- 不能将其记录到其他位置
- 想取消记录的信息需要修改代码才可以完成
记录日志的优势:
- 可以被定向到不同的处理器,用于在控制台显示,用于存储在文件中等。
- 可以随时以开关的形式控制是否记录日志,无需修改源代码。
- 可以采用不同的方式格式化,如纯文本或XML。
- 可以对记录进行过滤。过滤器可以根据过滤实现器指定的标准丢弃那些无用的记录项。
7.1.2 日志技术体系结构
日志规范:一些接口,提供给日志的实现框架设计的标准。
日志框架:绝活哥或者第三方公司已经做好的日志记录实现代码,后人可直接使用。
两种日志规范:
- 官方日志规范接口Commons Logging,简称JCL
- 第三方日志接口Simple Logging Facade for Java,简称slf4j
日志实现框架:
- Log4j
- JUL
- Logback
- 其他实现
7.1.3 Logback的用法
说明:由log4j的创始人设计的一个开源日志组件,性能比log4j要好。其基于slf4j的日志规范实现的框架
Logback技术模块:
- logback-core:logback-core模块为其他两个模块奠定了基础,必须要有
- logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j的API
- logback-acccess:模块与Tamcat和Jetty等Servlet容器集成,以提供HTTP访问日志功能
Logback的初步使用:
- 在项目新建文件夹lib,导入Logback的相关jar包到该文件夹下,并添加到项目依赖库中去。
- 将Logback的核心配置文件logback.xml直接拷贝到src目录下。
- 在代码中获取日志的对象。
public static final Logger LOGGER = LoggerFactory.getLogger("类对象");
附-logback.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。
默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="true">
<!-- 定义变量,可通过 ${log.path}和${CONSOLE_LOG_PATTERN} 得到变量值 -->
<property name="log.path" value="D:/log" />
<property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} |-[%-5p] in %logger.%M[line-%L] -%m%n"/>
<!-- 输出到控制台 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- Threshold=即最低日志级别,此appender输出大于等于对应级别的日志
(当然还要满足root中定义的最低级别)
-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<encoder>
<!-- 日志格式(引用变量) -->
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 追加到文件中 -->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<file>${log.path}/hello2.log</file>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 滚动追加到文件中 -->
<appender name="file2" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/hello.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录
文件超过最大尺寸后,会新建文件,然后新的日志文件中继续写入
如果日期变更,也会新建文件,然后在新的日志文件中写入当天日志
-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 新建文件后,原日志改名为如下 %i=文件序号,从0开始 -->
<fileNamePattern>${log.path}/hello-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 每个日志文件的最大体量 -->
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>8kb</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 日志文件保留天数,1=则只保留昨天的归档日志文件 ,不设置则保留所有日志-->
<maxHistory>1</maxHistory>
</rollingPolicy>
</appender>
<root level="trace">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="file"/>
<appender-ref ref="file2"/>
</root>
</configuration>
基本使用:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Demo {
//创建日志对象
public static Logger LOGGER = LoggerFactory.getLogger("Demo.class");
public static void main(String[] args) {
try {
LOGGER.debug("main方法开始执行了");
LOGGER.info("我开始记录第二行日志,我们开始做除法");
int a = 10;
int b = 0;
LOGGER.trace("a = "+a);
LOGGER.trace("b="+b);
System.out.println(a/b);
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("功能出现异常"+e);
}
}
}
7.1.4 Logback的配置
说明:Logback日志系统的特性都是通过核心配置文件logback.xml控制的
配置说明:
标签 | 说明 |
---|---|
\<append> | 设置输出位置和日志信息的详细格式 |
\<target> | 设置日志颜色 |
\<file> | 设置日志输出路径 |
\<maxFileSize> | 指定单个日志文件的大小,一旦溢出则生成新日志文件 |
\<fileNamePattern> | 指定拆分的日志集中放置的位置 |
\<pattern> | 使用正则匹配来完全提示信息,其中%d表示日期,%thread表示线程名,%-nlevel表示级别从左显示n个字符宽度,%c表示类名,%msg表示日志信息 |
输出到配置台的配置标志:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
输出到系统文件的配置标志:
<appender name="file2" class="ch.qos.logback.core.rolling.RollingFileAppender">
关联日志输出内容:
<root level="trace">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="file"/>
<appender-ref ref="file2"/>
</root>
日志级别设置:
- 日志级别程度依次为:TRACE<DEBUG<INFO<WARN<ERROR,默认级别是debug。对应日志对象中的方法名。
- 日志级别作用:用于控制系统中哪些日志级别是可以输出的,只输出级别不低于设定级别的日志信息。
- ALL和OFF分别是打开和关闭全部日志信息。
日志等级修改:只需修改level的值即可。
<root level="trace">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="file"/>
<appender-ref ref="file2"/>
</root>
7.2 单元测试
说明:单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,单元测试就是针对Java方法的测试,进而检查方法的正确性。
在之前的学习中,如果我们要测试一个方法,都是采用是个main方法,里面包含多个方法来测试,但这样做会导致问题:如果中间一个方法失效,则下面的方法测试也会受到影响。
7.2.1 Junit单元测试框架
说明:
- Junit是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应当学习并使用Junit编写单元测试。
- 此外,几乎所有的IDE工具都继承了Junit。我们可以直接在IDE中编写并运行Junit测试,Junit目前最新的版本是5。
优点:
- Junit可以灵活的选择执行哪些测试方法,可以一键执行全部测试方法。
- Junit可以生成全部方法的测试报告。
- 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试。
7.2.2 单元测试的快速入门
步骤讲解:
- 将Junit的jar包导入到项目中,如果你在联网的情况下,可以先写Test注解后按alt+回车让其自动导入。
- 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法。
- 在测试方法上使用@Test注解:标注该方法是一个测试方法。
- 在测试方法中完成被测试方法的预期正确性测试。
- 选中测试方法,选择Junit运行,如果测试良好是绿色,如果失败则为红色。
局部测试和总体测试:如果我们在测试类中任意空白位置右键Run,则会测试所有方法;如果我们在测试类中的方法上右键Run,则会测试单个方法。
public class UserService {
public String loginName(String loginName,String passWord){
if("admin".equals(loginName) && "123456".equals(passWord)) return "登录成功";
else return "用户名或者密码有问题";
}
public void selectNames(){
System.out.println(18/0);
System.out.println("查询全部用户名称成功");
}
}
import org.junit.Assert;
import org.junit.Test;
/**测试类*/
public class TestUserService {
@Test
public void testLoginName(){
UserService userService = new UserService();
String rs = userService.loginName("admin","123456");
Assert.assertEquals("您的功能业务数据可能出BUG","登录成功",rs);
}
@Test
public void testSelectNames(){
UserService userService = new UserService();
userService.selectNames();
}
}
7.2.3 Junit常用注解
Junit4.x注解:开始执行的方法常用于初始化资源;执行完之后的方法常用于释放资源。
注解 | 说明 |
---|---|
@Test | 测试方法 |
@Before | 用来修饰实例方法,该方法会在每个测试方法执行之前执行一次 |
@After | 用来修饰实例方法,该方法会在每个测试方法执行之后执行一次 |
@BeforeClass | 用来修饰静态方法,该方法会在所有测试方法执行之前执行一次 |
@AfterClass | 用来修饰静态方法,该方法会在所有测试方法执行之后执行一次 |
Junit5.x注解:
注解 | 说明 |
---|---|
@Test | 测试方法 |
@BeforeEach | 用来修饰实例方法,该方法会在每个测试方法执行之前执行一次 |
@AfterEach | 用来修饰实例方法,该方法会在每个测试方法执行之后执行一次 |
@BeforeAll | 用来修饰静态方法,该方法会在所有测试方法执行之前执行一次 |
@AfterAll | 用来修饰静态方法,该方法会在所有测试方法执行之后执行一次 |