06. 生产级基石:单元测试与日志管理¶
🎯 本节目标:告别“野路子”开发
你是否还在用 System.out.println 调试代码?
你是否每次修改代码后,都要手动启动项目、打开浏览器、点击按钮才能验证对错?
本节我们将掌握两项区分“业余”与“专业”的关键技能:
- 🪵 日志系统 (Logging):程序的“黑匣子”,生产环境排错的唯一依靠。
- 🧪 单元测试 (JUnit):程序的“安检员”,一键自动化验证逻辑,让你放心重构。
🪵 第一部分:日志系统 (Logging)¶
在 Java Web 开发中,**严禁使用 System.out.println**。
因为它有两个致命缺点:
- 性能差:频繁的 IO 操作会拖慢服务器。
- 不可控:无法开关,上线后控制台满屏垃圾信息,掩盖了真正的报错。
1. 核心概念:门面模式 (Facade Pattern)¶
Spring Boot 默认使用 SLF4J 作为日志门面,Logback 作为具体实现。很多同学不理解为什么要搞两个东西,这里涉及到一个经典的设计模式——门面模式。
通俗理解:门面模式 (Facade)
想象你去一家大公司办事:
- 没有门面(惨痛经历):你需要分别跑去财务部盖章、去行政部签字、去后勤部拿钥匙。你需要认识所有部门的人,累个半死。
- 有门面(轻松体验):你只找前台接待员 (SLF4J)。你对她说“我要办入职”,她负责去后台联系财务、行政、后勤 (Logback/Log4j) 帮你搞定一切。
好处:你只需要和“接待员”打交道。即使后台的财务换人了(切换日志框架),你也不需要知道,你的代码完全不用改。这也是“解耦”的核心体现。
技术映射:
- Client (你的代码):只调用
org.slf4j.Logger。 - Facade (SLF4J):前台接待员,负责“转发”请求,屏蔽底层复杂性。
- Subsystem (Logback):真正干活的苦力,负责把日志写进文件。
一句话总结:“有事找管家,别来烦我。”
2. 图解:为什么要解耦?¶
我们来看一下,如果没有 SLF4J 这个“门面”,当你想从 Log4j 切换到 Logback 时会发生什么?
graph LR
subgraph "❌ 强耦合 (没有门面)"
Code1[你的业务代码] -->|直接 import| Lib1[Log4j]
note1[💀 痛点: <br/>如果要换成 Logback<br/>必须修改业务代码的<br/>几千个 import 语句]
Lib1 -.-> note1
style Code1 fill:#ffcdd2,stroke:#b71c1c
end
subgraph "✅ 解耦 (使用 SLF4J 门面)"
Code2[你的业务代码] -->|只 import| Facade(👔 SLF4J 接口)
Facade -.->|运行时自动找| Lib2[Log4j]
Facade -.->|运行时自动找| Lib3[Logback]
note2[🚀 优势: <br/>底层随便换<br/>上层代码不用动<br/>这就是解耦!]
Facade -.-> note2
style Code2 fill:#c8e6c9,stroke:#2e7d32
style Facade fill:#fff9c4,stroke:#fbc02d,stroke-width:2px
end
3. 日志级别 (Level)¶
日志是有优先级的,从低到高依次为:
| 级别 | 说明 | 适用场景 |
|---|---|---|
| TRACE | 追踪 | 极详细的流水账(通常不用)。 |
| DEBUG | 调试 | 开发时的关键变量、流程判断(生产环境通常关闭)。 |
| INFO | 信息 | (默认) 系统启动、关键业务完成(如“用户A登录成功”)。 |
| WARN | 警告 | 潜在问题,但不影响运行(如“磁盘空间不足80%”)。 |
| ERROR | 错误 | 发生异常,业务中断(如“数据库连接失败”)。 |
4. 实战:使用 Lombok 的 @Slf4j¶
得益于 Lombok,我们不需要手动创建 Logger 对象,只需一个注解。
5. 配置日志 (application.properties)¶
🧪 第二部分:单元测试 (JUnit 5)¶
单元测试 (Unit Test) 是指对软件中的最小可测试单元(通常是方法)进行检查。Spring Boot 内置了 spring-boot-starter-test,包含了 JUnit 5、Mockito 等神器。
1. 编写第一个测试用例¶
测试类通常位于 src/test/java 下,包路径与主代码保持一致。
2. 关键概念:断言 (Assertion)¶
断言就是“程序里的测谎仪”。如果实际结果与期望不符,测试就会变红(失败)。
assertEquals(expect, actual): 判断相等。assertTrue(condition): 判断为真。assertNotNull(obj): 判断非空。assertThrows(...): 判断是否抛出了指定的异常。
🎭 第三部分:测试 Web 接口 (MockMvc)¶
如果不启动 Tomcat,怎么测试 Controller 的接口呢?Spring 提供了 MockMvc,它能模拟发送 HTTP 请求。
🤖 特别篇:用 AI 辅助生成测试用例¶
🚀 AI 时代开发新姿势
写单元测试虽然重要,但重复的断言代码写起来很枯燥。 现在,我们可以让 AI (如 DeepSeek, ChatGPT, 通义千问) 帮我们打工!
👉 我们要学会做 “飞行员”,让 AI 做 “副驾驶”。
1. 场景一:一键生成测试代码¶
当你写完 UserService 的业务逻辑后,试着把代码复制给 AI,让它完成繁琐的测试编写工作。
🔮 第一步:复制 Prompt (提示词) 给 AI
"我是一个 Java 初学者。请基于 JUnit 5 和 Mockito,为下面的 UserService 类生成单元测试代码。
要求:
1. 覆盖‘正常添加用户’和‘用户名重复抛出异常’两个场景。
2. 代码中包含中文注释,解释每一行在做什么。
3. 附带代码:[在此处粘贴你的 UserService 代码]"
👨💻 第二步:执行与验收 (机长操作)
- 搬运:将 AI 生成的代码复制到 IDEA 的
src/test/java对应包下。 - 验证:运行测试类,盯着进度条——直到它变成全绿 (Pass)。
- 审查:如果报错(比如 AI 瞎编了方法名),请手动修正。 > 记住:AI 是你的副驾驶,你才是对最终结果负责的机长!
2. 场景二:AI 帮你“看病” (日志分析)¶
当你看到控制台报了一大堆 Exception,看不懂怎么办?请使用以下“问诊单”:
🏥 复制此 Prompt (提示词) 给 AI
我的 Spring Boot 项目报错了,这是控制台的日志堆栈。请帮我分析:
- 核心错误原因是什么?
- 我应该去检查哪一行代码?
- 给出修复建议。
错误日志:
[在此处粘贴那一大段红色的报错信息]
📝 总结¶
| 特性 | 传统/业余做法 | 现代/专业做法 | 优势 |
|---|---|---|---|
| 输出信息 | System.out.println |
Slf4j + Logback | 性能高、分级别、可持久化到文件 |
| 验证逻辑 | 写个 main 方法跑一下 |
JUnit 单元测试 | 一键运行、可重复执行、CI/CD 基础 |
| 接口测试 | 打开浏览器/Postman 点点点 | MockMvc | 无需启动服务器,自动化验证 |
陈老师的建议
从今天开始,养成两个好习惯:
1. 遇事不决查日志:报错了先看控制台的 ERROR 信息,不要盲目改代码。
2. 写完代码跑测试:确保你的代码逻辑在交给别人之前,自己已经验证过了。