欢迎大家来到IT世界,在知识的湖畔探索吧!
导读
单元测试框架
Java中存在很多单元测试框架,每种框架有着自己独特的特点,根据不同的需求和团队要求,每位同学所使用的框架不尽相同,目前主流的测试框架有且不仅有以下几种:
JUnit
JUnit是Java中最常用的单元测试框架。该框架提供了丰富的测试与断言方法,例如:assert、assertTrue、assertEquals等,使用方法比较简单。JUnit目前已经更新到JUnit5版本,该版本提供了更多的新特性,例如:动态测试,依赖注入等,使得该框架更为健壮。
https://junit.org/junit5/
TestNG
https://testng.org/
Spock
https://spockframework.org/
Mockito
Mockito不是一个完整的单元测试框架,而是专注于mock对象的创建、验证。它通常与JUnit或TestNG结合使用来简化对复杂依赖的测试。是目前集团内最主流的测试框架,下文中将对该框架进行详细阐述。
http://site.mockito.org/
EasyMock
EasyMock是一套通过简单方法对于给定的接口生成mock对象的类库,通过使用Java代理机制动态生成模拟对象。该框架提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序等,还可以令mock对象返回指定的值或抛出指定异常。开发者通过EasyMock可以方便的构造mock对象而忽略对象背后真正的业务逻辑。一般情况下,EasyMock与JUnit或TestNG配合使用。
https://easymock.org/
PowerMock
PowerMock是一种用于Java单元测试的框架,它扩展了其他mocking框架的能力,比如EasyMock和Mockito。PowerMock的主要特点是它可以mock静态方法、私有方法、final方法、构造函数,甚至系统类(如System、String等),这些通常是传统mocking框架所做不到的。有了这些功能,PowerMock在一些复杂场景下进行单元测试更加方便。虽然PowerMock提供了强大的功能,但由于它修改了类加载器和字节码操作,可能会导致一些测试方法与JVM或第三方库之间的兼容性问题。所以,在使用PowerMock时需要权衡其提供的功能和可能带来的复杂性。
https://github.com/powermock/powermock
JMock
JMock是一种用于Java单元测试的框架,属于一种轻量级框架,该框架采用了行为驱动开发(BDD)的测试风格。用来在单元测试中mock接口或类的依赖项,对代码进行隔离测试,而无需关心整个系统的其他部分。JMock支持通过声明式的方式来指定对象间的交互行为。
http://jmock.org/
我们在用什么?
目前集团内主流的单元测试框架用的是Mockito框架,该框架的单元测试流程为:

欢迎大家来到IT世界,在知识的湖畔探索吧!
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);}
欢迎大家来到IT世界,在知识的湖畔探索吧!
欢迎大家来到IT世界,在知识的湖畔探索吧!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对象。




欢迎大家来到IT世界,在知识的湖畔探索吧!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 对象}}
注解处理器的初始化:
首先调用MockitoAnnotations.initMocks(this),或者使用MockitoJUnitRunner或JUnit的MockitoExtension。
这些方法会扫描测试类,查找所有由Mockito提供的注解(如@Mock,@Spy,@Captor,@InjectMocks)并进行处理。
mock对象的创建:
对于每个使用@Mock注解的字段,Mockito会创建一个相应的mock对象。
这是通过调用Mockito.mock()方法完成的(下文会讲怎么mock的),该方法使用动态代理或字节码操作来生成mock对象。
查找注入点:
对于每个使用@InjectMocks注解的字段,Mockito会寻找变量注入的点。
首先,Mockito会选择参数最多的、参数完全匹配的构造器注入mock对象。
如果没有合适的构造器,它会尝试属性注入,最后考虑setter方法。
注入过程:
一旦找到注入点(构造器、属性或setter方法),Mockito使用反射API来完成注入过程。
对于构造器,它会使用找到的mock对象实例化新对象。
对于属性或setter方法,它会直接注入mock对象。
- 使用目的
- @InjectMocks主要用于单元测试,注入的是mock对象,用来模拟真实对象的行为。
- Spring的依赖注入用于实际的应用运行中,注入的是真实的、由Spring容器创建和管理的bean对象。
- 运行环境
- @InjectMocks在测试环境中使用,不依赖Spring容器。
- Spring依赖注入是在应用的生产环境中使用,依赖于Spring容器的生命周期和管理。
- 对象类型
- 使用@InjectMocks注入的对象是一个用于模拟的代理对象。
- Spring中注入的对象是完全功能的实例。
-
生命周期
- Mockito不负责mock对象的生命周期管理,一旦测试用例运行完毕,mock对象就会被丢弃。
-
Spring容器负责bean的整个生命周期,包括创建、初始化、注入、销毁等。
- 对象创建
- @Mockito通过动态代理的方式创建mock对象。
- Spring通过实例化类定义来创建bean。
@Mock


answer:
此属性表示为mock对象指定一个默认的行为,这个行为将应用于所有未打桩的方法调用。
它会接受一个Answers枚举类型的值。
例如,Answers.RETURNS_DEFAULTS会使未配置打桩的方法返回对应类型的默认值(0、false、等)。
其他如Answers.RETURNS_SMART_S可以返回智能空值,这些空值在使用时会抛出异常,并在异常信息中打印出哪个未打桩的方法被调用了。
stubOnly:
表示是否创建一个仅用于打桩的mock对象,如果设置为true,创建的mock对象不会记录任何方法调用,也就不能用于验证方法是否被调用。
name:
对mock对象的命名。
命名mock对象有助于错误调试,当验证失败时,异常信息中会包含这个名称。
extraInterfaces:
可以为mock对象实现一些其他接口。
serializable:
表示需要mock对象是否应该是可序列化的,如果设置为true,生成的mock对象将会实现Serializable接口。
这样的话,注入的mock对象可以在需要序列化和反序列化的测试场景中使用。
@Mock注解的逻辑入口和@InjectMocks一致,都是从MockitoAnnotations.initMocks(this)开始执行,同样创建一个AnnotationEngine引擎:







调用createMockType方法,该方法基于提供的MockCreationSettings生成mock对象的类型;
从Plugins中获取InstantiatorProvider的实例,然后调用getInstantiator方法获取Instantiator。
Instantiator是负责创建mock对象的组件;
使用Instantiator的newInstance方法创建第一步生成的类型的新对象;
创建MockMethodInterceptor拦截器对象,主要拦截对mock对象所进行的调用,并根据settings里的设置执行相应的逻辑;
检查mock对象是否实现了MockAccess接口。
如果实现了,将mockMethodInterceptor设置为其拦截器;
返回创建的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注解的属性配置MockSettingsMockSettings 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) {// 检查是否可以被mockif (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;}
注解处理器的初始化:
首先调用MockitoAnnotations.initMocks(this),或者使用MockitoJUnitRunner或JUnit的MockitoExtension。
这些方法会扫描测试类,查找@Mock注解标记的属性。
配置注解属性:
根据注解参数创建MockCreationSettings对象,它包含了创建mock对象所需的所有设置。
Mock对象的创建:
使用Mockito内部的MockMaker实例和前面步骤中创建的MockCreationSettings,生成mock对象。
方法Mock对象到字段:
通过反射将生成的mock对象赋给测试类中的对应字段。
Mockito.when()






获取打桩实例:
从invocationForStubbing中获取代表打桩调用的Invocation对象。
打桩完成通知:
MockingProgress对象打桩过程已经完成,这一步可能会更新其内部状态。
回答验证:
如果Answer实例实现了ValidableAnswer接口,那么对第一步获取的Invocation对象进行验证。
同步打桩列表:
在synchronized代码块中,同步打桩列表stubbed。
添加或更新打桩列表:
如果打桩对象设置为连续调用,将Answer对象设置到打桩列表的首位。
否则,将创建一个新的StubbedInvocationMatcher实例并设置到打桩列表首位。
返回打桩匹配器:
返回打桩列表中第一个元素,并强转为匹配器类型。
Mockito.verify()



mock对象检查:
通过mockingDetails(mock)检查传入的对象是否真的是一个mock对象。
参数验证:
确认mock对象是否仅用于打桩,而非验证。
如果是一个仅用于打桩的mock对象,那么就不能对其进行验证。
通知验证开始:
如果验证过程涉及监听器,调用VerificationStartedNotifier.notifyVerificationStarted()方法来通知所有的验证开始监听器。
获取MockingProgress:
获取当前线程的MockingProgress实例来记录验证状态,这会涉及到线程局部变量的操作,以确保验证状态不会与其他线程的操作冲突。
延迟验证:
使用maybeVerifyLazily()方法,该方法可能会修改传入的VerificationMode来延迟验证,当某个特定的方法调用时再进行验证。
开始验证:
创建一个MockAwareVerificationMode实例并将其设置到MockingProgress中。
这个实例包含mock对象、实际的验证模式和验证监听器。
结束验证:
返回mock对象。
欢迎大家来到IT世界,在知识的湖畔探索吧!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();}}}
结语
本文由高可用架构转载。技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/105474.html