大家好,我是G探险者!
今天我们简单聊聊单元测试的哪些事儿~
两天时间我玩明白了单元测试的套路。
这里我分享一下思路。
在我眼里单元测试室什么?
请看这张草图:
单元测试主要关注单个代码单元(通常是类或方法)的逻辑正确性,而不是功能测试的全面性。具体来说:
单元测试的目的
- 验证逻辑路径:单元测试旨在验证代码逻辑中的不同路径是否按预期执行。这包括条件分支(如if-else语句)、循环、异常处理等。
- 隔离测试:通过mock或stub来隔离被测试的单元,使其不依赖外部资源(如数据库、网络服务)或其他复杂对象。
- 快速反馈:单元测试应该快速执行,提供即时的反馈,帮助开发者在早期发现并修复问题。
- 保证代码质量:通过覆盖不同的逻辑路径和边界情况,确保代码在各种情况下都能正确运行。
与功能测试的区别
- 单元测试:专注于验证单个代码单元的内部逻辑,不要求模拟复杂的实际场景。变量值的准确性不是重点,重点是逻辑是否按预期执行。
- 功能测试:验证整个系统或子系统的功能是否满足需求,通常在集成环境中进行,涉及实际的外部资源和复杂的交互。
单元测试中的Mock实践
-
分析需要mock的对象和行为
- 确定哪些外部依赖(如数据库、网络服务)需要被mock。
- 只mock那些必要的对象,保持测试的简洁性。
-
创建mock对象的方式
- 使用
@Mock
注解或Mockito.mock()
方法创建mock对象。 - 可以使用
@RunWith(MockitoJUnitRunner.class)
或MockitoAnnotations.initMocks(this)
来初始化@Mock
注解的字段。
- 使用
-
mock的目的
- 隔离被测试单元,确保测试专注于目标代码逻辑。
- 控制依赖对象的行为和状态,模拟各种异常和边界条件,验证代码的健壮性和正确性。
-
mock对象和行为
- 使用
when().thenReturn()
模拟方法返回特定值。 - 使用
when().thenThrow()
模拟方法抛出异常。 - 使用
thenReturn()
多次或thenAnswer()
模拟连续调用时的不同结果。 - 使用
verify()
方法验证方法的调用次数和顺序,确保逻辑执行的正确性。
- 使用
这里我总结了一些些单元测试基本套路吧,整理一个模板。
分析方法
假设我们有一个类 MyClass
和其中的一个方法 myMethod
。在分析该方法时,你需要考虑以下几个方面:
- 输入参数:方法接受哪些参数?
- 依赖对象:方法中使用了哪些类的实例?
- 行为动作:方法内部调用了哪些其他方法?是否有外部依赖,如数据库、网络调用等?
- 输出结果:方法返回什么?是否有副作用(如修改了类的状态、写入日志等)?
分析示例
假设 MyClass
的 myMethod
如下:
public class MyClass {
private MyDependency dependency;
public MyClass(MyDependency dependency) {
this.dependency = dependency;
}
public String myMethod(String input) {
String transformedInput = input.toUpperCase();
boolean isValid = dependency.validate(transformedInput);
if (isValid) {
return "Valid: " + transformedInput;
} else {
return "Invalid: " + transformedInput;
}
}
}
分析步骤
- 输入参数:
input
(字符串) - 依赖对象:
MyDependency
(通过构造函数注入) - 行为动作:调用了
dependency.validate(transformedInput)
- 输出结果:返回一个字符串,形式为
"Valid: " + transformedInput
或"Invalid: " + transformedInput
单元测试模板
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.junit4.PowerMockRunnerDelegate;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(MockitoJUnitRunner.class)
public class MyClassTest {
@Mock
private MyDependency mockDependency;
@InjectMocks
private MyClass myClass;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testMyMethod_ValidInput() {
// Arrange
String input = "test";
String transformedInput = "TEST";
when(mockDependency.validate(transformedInput)).thenReturn(true);
// Act
String result = myClass.myMethod(input);
// Assert
assertEquals("Valid: TEST", result);
verify(mockDependency).validate(transformedInput);
}
@Test
public void testMyMethod_InvalidInput() {
// Arrange
String input = "test";
String transformedInput = "TEST";
when(mockDependency.validate(transformedInput)).thenReturn(false);
// Act
String result = myClass.myMethod(input);
// Assert
assertEquals("Invalid: TEST", result);
verify(mockDependency).validate(transformedInput);
}
}
模板解析
- 导入依赖:导入了Mockito和JUnit的静态方法。
- 测试类定义:定义了
MyClassTest
测试类。 - Mock对象:使用
@Mock
注解创建MyDependency
的mock对象。 - 测试准备:在
setUp
方法中初始化mock对象,并创建MyClass
实例。 - 测试方法:定义两个测试方法,分别测试
myMethod
在validate
方法返回true
和false
的情况下的行为。
注意事项
- 测试覆盖:确保测试覆盖了所有可能的输入和边界情况。
- 行为验证:使用
verify
方法验证依赖对象的方法调用情况。 - 异常处理:如果方法可能抛出异常,编写相应的测试用例。
通过这个模板,你可以系统地分析并编写单元测试,确保测试的全面性和准确性。
题外话
当然现在idea 里面有相关的些单元测试的插件,比如Squaretest,或者你直接通过ChatGPT来写。但是你得知道原理。