万字长文:一文详解单元测试干了什么
阿里妹导读
导读
单元测试框架
JUnit
TestNG
Spock
Mockito
EasyMock
PowerMock
JMock
我们在用什么?
private BenefitRecordQueryServiceI benefitRecordQueryServiceI;
public List<BenefitRecordExtendVO> queryBenefitRecordList(Long companyId, String scene) {
Validate.notNull(companyId, "companyId cannot be null");
Validate.notBlank(scene, "scene cannot be null");
BenefitRecordQueryParam param = new BenefitRecordQueryParam();
param.setCompanyId(companyId);
param.setScene(scene);
MultiResponse<BenefitRecordExtendDTO> res = benefitRecordQueryServiceI
.pageQueryBenefitRecordWithExtendAttrs(param);
if (res == null || !res.isSuccess()) {
log.error("pageQueryBenefitRecordWithExtendAttrs error, companyId:{}, scene:{}, res:{}",companyId, scene, JSON.toJSONString(res));
throw new SupplierFosterException("pageQueryBenefitRecordWithExtendAttrs error");
}
return DataObjectUtils.transferDeep(res.getData(), BenefitRecordExtendVO.class);
}
private BenefitAdaptorImpl benefitAdaptor;
private BenefitRecordQueryServiceI benefitRecordQueryServiceI;
public void testQueryBenefitRecordListWhenSuccess() {
Long companyId = 123L;
String scene = "cnfm";
MultiResponse<BenefitRecordExtendDTO> res = new MultiResponse<>();
List<BenefitRecordExtendDTO> resList = new ArrayList<>();
BenefitRecordExtendDTO benefitRecordExtendDTO = new BenefitRecordExtendDTO();
benefitRecordExtendDTO.setBenefitCode("rfq");
resList.add(benefitRecordExtendDTO);
res.setSuccess(true);
res.setData(resList);
Mockito.when(benefitRecordQueryServiceI.pageQueryBenefitRecordWithExtendAttrs(
Mockito.any())).thenReturn(res);
List<BenefitRecordExtendVO> result = benefitAdaptor.queryBenefitRecordList(companyId, scene);
Assert.assertEquals(1, result.size());
BenefitRecordExtendVO benefitRecordRes = result.get(0);
Assert.assertNotNull(benefitRecordRes);
Assert.assertEquals("rfq", benefitRecordRes.getBenefitCode());
Mockito.verify(benefitRecordQueryServiceI).pageQueryBenefitRecordWithExtendAttrs(
Mockito.any());
}
@InjectMocks干了什么,mock的benefitAdaptor对象有什么特性?
@Mock干了什么,mock的benefitRecordQueryServiceI对象有什么特性?
Mockito.when().thenReturn()干了什么,如何模拟方法调用的?
Mockito.verify()干了什么,如何验证方法的调用运行?
mockito怎么运行的?
@RunWith(MockitoJUnitRunner.class)
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@InjectMocks
构造器注入:Mockito会寻找被标注类的构造器,并尝试使用可用的mock对象作为参数来实例化类。它首先尝试使用最多参数的构造器,如果失败,则尝试较少参数的构造器。
属性注入:如果构造器注入不可行或者不成功,Mockito会尝试将mock对象直接设置到被标注类的属性中,这包括私有属性。它会通过反射来忽略访问修饰符,直接向属性赋值。
方法注入:如果前两种方式都不可行,Mockito会尝试调用类中的setter方法来注入mock对象。
public class MockitoAnnotations {
public static void initMocks(Object test) {
AnnotationEngine annotationEngine = ...;
annotationEngine.process(test.getClass(), test);
}
}
public class InjectMocksAnnotationEngine implements AnnotationEngine {
public void process(Class<?> clazz, Object testInstance) {
// 获取所有 @Mock 标注的字段
Set<Field> mockFields = ...;
// 获取所有 @InjectMocks 标注的字段
Set<Field> injectMocksFields = ...;
for (Field injectMocksField : injectMocksFields) {
// 创建 @InjectMocks 标注字段的实例
Object fieldInstance = createInstance(injectMocksField.getType());
injectMocksField.setAccessible(true);
// 将实例设置到测试类的字段中
injectMocksField.set(testInstance, fieldInstance);
PropertySetterInjector propertySetterInjector = new PropertySetterInjector(mockFields);
ConstructorInjector constructorInjector = new ConstructorInjector(mockFields);
if (!constructorInjector.tryConstructorInjection(fieldInstance, injectMocksField)) {
propertySetterInjector.tryPropertyOrSetterInjection(fieldInstance, injectMocksField);
}
}
}
}
public class ConstructorInjector {
public boolean tryConstructorInjection(Object fieldInstance, Field injectMocksField) {
// 使用反射尝试通过构造器注入 mock 对象
}
}
public class PropertySetterInjector {
public void tryPropertyOrSetterInjection(Object fieldInstance, Field injectMocksField) {
// 使用反射尝试通过属性或setter方法注入 mock 对象
}
}
使用目的
@InjectMocks主要用于单元测试,注入的是mock对象,用来模拟真实对象的行为。
Spring的依赖注入用于实际的应用运行中,注入的是真实的、由Spring容器创建和管理的bean对象。
运行环境
@InjectMocks在测试环境中使用,不依赖Spring容器。
Spring依赖注入是在应用的生产环境中使用,依赖于Spring容器的生命周期和管理。
对象类型
使用@InjectMocks注入的对象是一个用于模拟的代理对象。
Spring中注入的对象是完全功能的实例。
生命周期
Mockito不负责mock对象的生命周期管理,一旦测试用例运行完毕,mock对象就会被丢弃。
Spring容器负责bean的整个生命周期,包括创建、初始化、注入、销毁等。
对象创建
@Mockito通过动态代理的方式创建mock对象。
Spring通过实例化类定义来创建bean。
@Mock
// MockitoAnnotations.initMocks方法的简化伪代码
public static void initMocks(Object testClassInstance) {
Class<?> testClass = testClassInstance.getClass();
Field[] fields = testClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Mock.class)) {
Mock mock = field.getAnnotation(Mock.class);
Object mockObject = createMockObject(field.getType(), mock);
field.setAccessible(true);
field.set(testClassInstance, mockObject);
}
}
}
private static Object createMockObject(Class<?> fieldType, Mock mockAnnotation) {
// 创建mock配置
MockSettings mockSettings = configureMockSettings(mockAnnotation);
// 使用Mockito API创建mock对象
return Mockito.mock(fieldType, mockSettings);
}
private static MockSettings configureMockSettings(Mock mockAnnotation) {
// 根据@Mock注解的属性配置MockSettings
MockSettings mockSettings = Mockito.withSettings();
if (!"".equals(mockAnnotation.name())) {
mockSettings.name(mockAnnotation.name());
}
if (mockAnnotation.stubOnly()) {
mockSettings.stubOnly();
}
// 其他配置参数
mockSettings.defaultAnswer(mockAnnotation.answer());
return mockSettings;
}
// Mockito.mock方法的简化伪代码
public static <T> T mock(Class<T> classToMock, MockSettings mockSettings) {
// 检查是否可以被mock
if (isNotMockable(classToMock)) {
throw new MockitoException("Cannot mock/spy class: " + classToMock.getName());
}
// 创建mock设置,如果未提供则使用默认设置
if (mockSettings == null) {
mockSettings = withSettings();
// 创建mock对象
T mockInstance = createMockInstance(classToMock, mockSetting
// 返回mock对象
return mockInstance;
}
private static <T> T createMockInstance(Class<T> classToMock, MockSettings mockSettings) {
// 根据mock设置创建一个MockCreationSettings对象
MockCreationSettings<T> settings = new MockCreationSettings<>(classToMock, mockSetting
// 获取MockMaker插件,它负责创建mock实例
MockMaker mockMaker = Plugins.getMockMaker
// 使用MockMaker创建mock实例
T mock = mockMaker.createMock(settings, new MockHandlerImpl<T>(settings
// 返回创建的mock实例
return mock;
}
Mockito.when()
Mockito.verify()
public class Mockito {
public static <T> T verify(T mock) {
return verify(mock, times(1));
}
public static <T> T verify(T mock, VerificationMode mode) {
MockingProgress progress = MockingProgressImpl.threadSafeMockingProgress();
progress.verificationStarted(mode);
return mock;
}
}
public class MockHandlerImpl<T> implements MockHandler<T> {
public Object handle(Invocation invocation) throws Throwable {
MockingProgress progress = MockingProgressImpl.threadSafeMockingProgress();
VerificationMode verificationMode = progress.pullVerificationMode();
if (verificationMode != null) {
// 验证调用
verificationMode.verify(invocation);
return null;
} else {
// 执行实际的mock行为
return invocation.callRealMethod();
}
}
}
结语
微信扫码关注该文公众号作者