单元测试 vs Main方法调试:何时使用哪种方式?
在Java开发过程中,我们经常需要编写测试代码来验证功能是否正常工作。但是,并非所有的测试代码都应该写成单元测试。本文将通过实际案例探讨何时使用JUnit单元测试,何时更适合使用main方法进行调试。
1. 单元测试的核心价值
正确用途
- 验证业务逻辑正确性
- 支持持续集成和自动化测试
- 提供可重复执行的质量保障
- 成为代码重构的安全网
典型应用场景
java
// 业务逻辑测试示例
@Test
public void testUserService() {
UserService userService = new UserService();
User user = userService.findById(1L);
assertNotNull(user);
assertEquals("张三", user.getName());
}
2. 不当使用单元测试的反面教材
原始的JwtTest.java问题分析
让我们看看一个典型的不当使用案例:
java
public class JwtTest {
@Test
public void testGenerateToken() {
String token = getToken();
System.out.println(token);
}
private String getToken() {
Map<String, Object> claims = new HashMap<>();
claims.put("id", 1);
claims.put("nickname", "张三");
claims.put("email", "zhangsan@main.com");
String token = JWT.create()
.withClaim("user", claims)
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 10))
.sign(Algorithm.HMAC256("reflection"));
return token;
}
@Test
public void testParseToken() {
// 用当时手动的token会出现过期问题,需要生成新的
// String token ="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Im5pY2tuYW1lIjoi5byg5LiJIiwiaWQiOjEsImVtYWlsIjoiZm9pZG9hamZAZmlzZi5jb20ifSwiZXhwIjoxNzUyMDQ3OTk3fQ.URM-AHQ2lf_3jsLAxbB7Q96igJcRQZxS98V8Tliy0to";
String token = getToken();
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("reflection")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
Map<String, Claim> claims = decodedJWT.getClaims();
System.out.println(claims.get("user"));
}
}
存在的问题
-
测试目的不明确
- 这些测试主要是为了学习
JWT
库的使用方式,而非验证业务逻辑
- 这些测试主要是为了学习
-
浪费构建资源
- 在项目打包时会执行这些"伪测试",消耗不必要的时间
-
容易导致构建失败
- 如代码注释所述,固定token过期导致[testParseToken](file:///Users/reflection/demoProjects/big-event/src/test/java/com/itheima/JwtTest.java#L37-L46)测试失败
- 影响正常的构建流程
-
误导团队成员
- 其他开发者可能误以为这是重要的业务测试
3. 需要Spring环境的工具调试场景
RedisTest.java问题分析
有些调试场景需要完整的Spring环境支持,例如:
java
@SpringBootTest
public class RedisTest {
@Autowired
private RedisTemplate<String,String > redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void testRedis() {
// 存储数据
redisTemplate.opsForValue().set("name", "张三");
// 获取数据
String name = redisTemplate.opsForValue().get("name");
System.out.println(name);
}
@Test
public void testRedisDelete(){
String value = redisTemplate.opsForValue().getAndDelete("name");
System.out.println(value);
}
@Test
public void testRedisKeys(){
Set<String> keys = redisTemplate.keys("*");
System.out.println("keys = " + keys);
}
@Test
public void testRedisTokenDelete(){
//修改密码以后,要使用户的所有jwt token 作废,user:login:userId:*
Set<String> keys = stringRedisTemplate.keys("user:login:1:*");
System.out.println("keys.size() = " + keys.size());
if(CollectionUtil.isNotEmpty(keys)) {
stringRedisTemplate.delete(keys);
System.out.println("delete . . .");
}
}
}
场景特点
-
需要Spring容器支持
- 必须使用[@SpringBootTest](file:///Users/reflection/demoProjects/big-event/src/test/java/com/itheima/redis/RedisTest.java#L12-L12)注解启动Spring环境
- 依赖[@Autowired](file:///Users/reflection/demoProjects/big-event/src/test/java/com/itheima/redis/RedisTest.java#L14-L14)注入的Bean对象
-
本质上仍是工具调试
- 主要目的是学习
RedisTemplate
和StringRedisTemplate
的使用 - 验证Redis操作的基本语法和效果
- 并非验证项目核心业务逻辑
- 主要目的是学习
更好的解决方案:使用@Disabled注解
对于这类需要Spring环境但又是工具调试性质的代码,推荐使用[@Disabled](file:///Users/reflection/demoProjects/big-event/pom.xml#)注解:
java
@SpringBootTest
public class RedisToolDemo {
@Autowired
private RedisTemplate<String,String > redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
@Disabled("工具调试代码,手动执行")
public void testRedisOperations() {
// 存储数据
redisTemplate.opsForValue().set("name", "张三");
// 获取数据
String name = redisTemplate.opsForValue().get("name");
System.out.println("获取到的值: " + name);
// 删除数据
Boolean deleted = redisTemplate.delete("name");
System.out.println("删除结果: " + deleted);
}
@Test
@Disabled("工具调试代码,手动执行")
public void testRedisTokenDelete(){
// 模式匹配删除
Set<String> keys = stringRedisTemplate.keys("user:login:1:*");
System.out.println("匹配到的key数量: " + keys.size());
if(CollectionUtil.isNotEmpty(keys)) {
stringRedisTemplate.delete(keys);
System.out.println("已删除匹配的keys");
}
}
}
4. Main方法调试的正确使用
适用场景
- 学习新API的使用方法
- 临时验证某个工具类的功能
- 调试第三方库的使用方式
- 编写示例代码供后续参考
改造后的JwtTest.java
java
public class JwtTest {
public static void main(String[] args) {
// JWT工具API调试和学习
System.out.println("=== JWT Token Generation ===");
String token = generateToken();
System.out.println("Generated Token: " + token);
System.out.println("\n=== JWT Token Parsing ===");
parseToken(token);
}
private static String generateToken() {
Map<String, Object> claims = new HashMap<>();
claims.put("id", 1);
claims.put("nickname", "张三");
claims.put("email", "zhangsan@main.com");
return JWT.create()
.withClaim("user", claims)
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60)) // 1分钟有效期
.sign(Algorithm.HMAC256("reflection"));
}
private static void parseToken(String token) {
try {
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("reflection")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
Map<String, Claim> claims = decodedJWT.getClaims();
System.out.println("Parsed claims: " + claims.get("user").asMap());
} catch (Exception e) {
System.err.println("Token verification failed: " + e.getMessage());
}
}
}
5. 选择原则
应该使用单元测试的情况
✅ 验证业务功能正确性
✅ 需要持续集成自动化测试
✅ 测试结果需要被监控和报告
✅ 团队协作需要统一测试标准
应该使用Main方法调试的情况
✅ 学习新API的使用方法
✅ 临时验证某个工具类的功能
✅ 调试第三方库的使用方式
✅ 编写示例代码供后续参考
应该使用@Disabled注解的情况
✅ 需要Spring等框架环境支持
✅ 本质上是工具调试而非业务测试
✅ 需要手动触发执行
✅ 不应参与自动化构建流程
6. 最佳实践建议
明确分离三类代码
java
// ✅ 正确做法:业务测试类使用JUnit(正常执行)
public class UserServiceTest {
@Test
public void testSaveUser() {
// 真正的业务逻辑测试
}
}
// ✅ 正确做法:工具调试类使用main方法
public class JwtToolDemo {
public static void main(String[] args) {
// API学习和调试
}
}
// ✅ 正确做法:需要Spring环境的工具调试使用@Disabled
@SpringBootTest
public class RedisToolDemo {
@Test
@Disabled("工具调试代码,手动执行")
public void testRedisOps() {
// 需要Spring环境的工具调试
}
}
7. 实际项目影响
构建流程中的问题
- 打包时执行无意义的测试浪费时间
- 过期token等调试代码导致构建失败
- 混淆真正的业务测试和调试代码
开发效率的影响
- 增加不必要的测试执行时间
- 可能掩盖真正的测试问题
- 降低团队对测试质量的信任
8. 总结
选择单元测试还是main方法调试的关键在于明确测试目的:
- 单元测试:面向业务逻辑验证,追求稳定性和可重复性
- Main方法调试:面向API学习和临时验证,追求灵活性和便捷性
- @Disabled测试:需要框架环境支持的工具调试,兼顾便利性和规范性
合理的代码组织不仅能提高开发效率,还能保证项目质量和构建稳定性。记住,代码即文档,好的测试组织方式本身就是一种良好的编程习惯。