导读
单元测试框架
Java中存在很多单元测试框架,每种框架有着自己独特的特点,根据不同的需求和团队要求,每位同学所使用的框架不尽相同,目前主流的测试框架有且不仅有以下几种:
JUnit
JUnit是Java中最常用的单元测试框架。该框架提供了丰富的测试与断言方法,例如:assert、assertTrue、assertEquals等,使用方法比较简单。JUnit目前已经更新到JUnit5版本,该版本提供了更多的新特性,例如:动态测试,依赖注入等,使得该框架更为健壮。
TestNG
Spock
Mockito
Mockito不是一个完整的单元测试框架,而是专注于mock对象的创建、验证。它通常与JUnit或TestNG结合使用来简化对复杂依赖的测试。是目前集团内最主流的测试框架,下文中将对该框架进行详细阐述。
EasyMock
EasyMock是一套通过简单方法对于给定的接口生成mock对象的类库,通过使用Java代理机制动态生成模拟对象。该框架提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序等,还可以令mock对象返回指定的值或抛出指定异常。开发者通过EasyMock可以方便的构造mock对象而忽略对象背后真正的业务逻辑。一般情况下,EasyMock与JUnit或TestNG配合使用。
PowerMock
PowerMock是一种用于Java单元测试的框架,它扩展了其他mocking框架的能力,比如EasyMock和Mockito。PowerMock的主要特点是它可以mock静态方法、私有方法、final方法、构造函数,甚至系统类(如System、String等),这些通常是传统mocking框架所做不到的。有了这些功能,PowerMock在一些复杂场景下进行单元测试更加方便。虽然PowerMock提供了强大的功能,但由于它修改了类加载器和字节码操作,可能会导致一些测试方法与JVM或第三方库之间的兼容性问题。所以,在使用PowerMock时需要权衡其提供的功能和可能带来的复杂性。
JMock
JMock是一种用于Java单元测试的框架,属于一种轻量级框架,该框架采用了行为驱动开发(BDD)的测试风格。用来在单元测试中mock接口或类的依赖项,对代码进行隔离测试,而无需关心整个系统的其他部分。JMock支持通过声明式的方式来指定对象间的交互行为。
我们在用什么?
目前集团内主流的单元测试框架用的是Mockito框架,该框架的单元测试流程为:
private BenefitRecordQueryServiceI benefitRecordQueryServiceI;
public List<BenefitRecordExtendVO> queryBenefitRecordList(Long companyId, String scene) {
Validate.not(companyId, "companyId cannot be ");
Validate.notBlank(scene, "scene cannot be ");
BenefitRecordQueryParam param = new BenefitRecordQueryParam();
param.setCompanyId(companyId);
param.setScene(scene);
MultiResponse<BenefitRecordExtendDTO> res = benefitRecordQueryServiceI
.pageQueryBenefitRecordWithExtendAttrs(param);
if (res == || !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.assertNot(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 == ) {
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 != ) {
// 验证调用
verificationMode.verify(invocation);
return ;
} else {
// 执行实际的mock行为
return invocation.callRealMethod();
}
}
}
结语
本文由高可用架构转载。技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/te-unit/8165.html