网站首页 > 技术文章 正文
在微服务架构中,服务之间的依赖是很常见的事情。在开发过程中都是并行开发的,前端会依赖后端的接口,后端也有可能会依赖其他后端服务的接口。
项目整体提测后是没有问题的,因为大家都开发完了,也会同时部署到测试环境中。但是在开发过程中需要进行单测,单测的时候会依赖其他的服务,这个时候就需要解决这个依赖问题。
前端依赖后端接口
前端依赖后端接口,一般会提前将接口定义好,然后拉上前端同学一起评审。如果没有问题就各自去开发,那么前端同学在自测的时候是需要数据的,这个时候可以采用 Mock 的方式提供数据。
关于 Mock 的工具有很多,我们用的 YAPI,既可以管理接口信息,又可以提供 Mock 功能。
YAPI:https://github.com/ymfe/yapi
后端依赖其他服务接口(Dubbo)
基于预定
如果不想自己 Mock 数据,可以在一开始的时候,跟需要对接的同学约定好。接口定好后先将接口写好,返回固定的数据,发布一下。后面在慢慢开发,这样也能直接调用,并且有假数据返回。
自带 Mock 功能
Dubbo 自带了 Mock 功能,自定义一个 Mock 类,然后在使用的时候指定即可。
接口定义:
public interface UserRemoteService {
ResponseData<UserResponse> getUser(Long userId);
}
Mock 类:
public class CustomUserRemoteServiceMock implements UserRemoteService {
@Override
public ResponseData<UserResponse> getUser(Long userId) {
UserResponse userResponse = new UserResponse();
userResponse.setNickname("尹吉欢");
return Response.ok(userResponse);
}
}
使用:
@Reference(version = DubboConstant.VERSION_V100,mock = "com.cxytiandi.CustomUserRemoteServiceMock")
private UserRemoteService userRemoteService;
自带的 Mock 在单测的时候其实不是很方便,因为我们调用远程服务的代码是在业务代码中,单测都是单独的代码,如果想用 Mock 还得去改动业务代码,加上 mock 的信息。很容易和本地修改的代码一起提交造成问题。
用在服务异常回退的场景还是比较适合的,返回静态数据或者缓存数据等。
包装一个类实现 Mock 功能
定义一个获取远程对象的工厂类,负责获取 Bean 的逻辑,使用者不需要关心内部逻辑。如果@Reference 注入了就返回 Dubbo 代理的 UserRemoteService。如果本地 Spring 中有对应的实现就返回本地的 UserRemoteService。
这样在单测的时候,如果对方还没有提供新服务,就可以用自己在本地建的 Mock 类实现。并且这个 Mock 类可以写在 test 包下。
@Component
public class UserRemoteServiceBeanFactory {
@Reference(version = DubboConstant.VERSION_V100, check=false)
private UserRemoteService userRemoteService;
@Autowired(required = false)
private UserRemoteService beanUserRemoteService;
public UserRemoteService getUserRemoteService() {
if(Objects.nonNull(beanUserRemoteService)){
return beanUserRemoteService;
}
if(Objects.nonNull(userRemoteService)){
return userRemoteService;
}
throw new ApplicationException("can't find UserRemoteService");
}
}
Mocktio Mock
Mockito 就是一个优秀的用于单元测试的 Mock 框架, 地址:https://github.com/mockito/mockito
在单测的时候,可以用 Mockito Mock 出一个远程接口的实现,以及要返回的数据。
@MockBean
private UserRemoteService userRemoteService;
@Before
public void before() {
Mockito.when(userRemoteService.getUser(1L))
.thenAnswer(t -> {
UserResponse userResponse = new UserResponse();
userResponse.setNickname("mock name");
return Response.ok(userResponse);
});
}
上面的方式在单测的时候存在一个问题,虽然 Mock 了一个 Bean,但是业务类中还是用的 Dubbo 的代理类,所以得做一些特殊处理。
比如我们在 UserManagerImpl 中用了 UserRemoteService,那么就可以先获取 UserManagerImpl 的对象,然后在将 Mock 的 Bean set 进去,前提是得加好 setUserRemoteService 的方法。
很多时候我们在注入的时候都不会手动写 set 方法,那你也可以用反射去 set。
UserManagerImpl userManager = applicationContext.getBean(UserManagerImpl.class);
Field field = userManager.getClass().getDeclaredField("userRemoteService");
field.setAccessible(true);
field.set(userManager, userRemoteService);
上面虽然解决了 Mock 的 Bean 可以替换的问题,但是在每个单测中都得手动去替换,这就有点受不了啊。
所以最好是单独封装一个替换的类,让使用者无感知。
@Component
public class MockitoMockDubboBeanProcessor implements BeanPostProcessor {
@Autowired
private ApplicationContext applicationContext;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (StringUtils.isNotBlank(beanName) && !beanName.endsWith("ManagerImpl")) {
return bean;
}
List<Field> fields = FieldUtils.getAllFieldsList(bean.getClass());
for (Field field : fields) {
processField(bean, field);
}
return bean;
}
private void processField(Object bean, Field field) {
if (field.isAnnotationPresent(Reference.class)) {
Map<String, ?> beans = applicationContext.getBeansOfType(field.getType());
Optional<? extends Map.Entry<String, ?>> mockitoMock = beans.entrySet().stream()
.filter(b -> b.getValue().getClass().getName().contains("$MockitoMock#34;)).findFirst();
if (mockitoMock.isPresent()) {
field.setAccessible(true);
try {
field.set(bean, mockitoMock.get().getValue());
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
主要还是对所有的 Bean 进行处理,然后根据你们的规范去做一些过滤。比如我这边只会在 ManagerImpl 结尾的类中去调用远程接口,那么就可以直接根据这个去过滤出来我要处理的类。
然后判断类中的属性是不是加了 Reference 注入,然后替换 Bean 为 MockitoMock 的 Bean。
后端依赖其他服务接口(Feign)
fallback
Feign 整合 Hystrix 可实现 fallback 功能,利用这个也可以实现对方服务没开发好,返回默认数据的功能。跟 Dubbo 的 Mock 类似。
Mocktio Mock
Mocktio 的方式跟上面一致,如果是 Feign 的话会更简单,因为不需要单独对类中的实例进行替换。Feign 的调用对象本来就在 Spring 中管理,Mocktio 直接就可以替换掉。
整合 YAPI
先说下想法吧,实现的话需要二次开发。比如前后端是通过 YAPI 来约定接口,前端在自测的时候也是通过 YAPI 的 Mock 功能获取 Mock 的数据。
如果用 Feign 进行远程调用,说明你们的服务内部通信就是基于 Http 方式。那么是否可以和前端一样,正常的时候走服务调用,单测的时候可以 YAPI 的 Mock 接口呢?这样也就不用自己在单测中去 Mock 数据了。
要做的话基于 Feign 底层扩展下,通过配置来控制,我这里只是给大家提供个思路,感兴趣的可以动手试试,然后投稿下,哈哈。
关于作者:尹吉欢,简单的技术爱好者,《Spring Cloud微服务-全栈技术与案例解析》, 《Spring Cloud微服务 入门 实战与进阶》作者, 公众号猿天地发起人。
猜你喜欢
- 2024-10-17 使用 Spock 编写高效简洁的单元测试
- 2024-10-17 程序员有福了!万字长文带你掌握SpringBoot所提供的测试解决方案
- 2024-10-17 如何使用Spring Boot提供的测试工具和注解。
- 2024-10-17 单元测试实践(Spring-boot+Junbit5+Mockito)
- 2024-10-17 Spring云原生实战指南:8 弹性和可扩展性
- 2024-10-17 SpringBoot 太强了,这些优势你需要了解
- 2024-10-17 如何使用Spring Cloud Contract(如何使用朋友的山姆卡)
- 2024-10-17 Spring Boot 开发离不开这些注解,快来学习啦!
- 2024-10-17 Spring Boot中文参考指南46.3.11、自动配置的Spring WebFlux测试
- 2024-10-17 一台不容错过的Java单元测试代码“永动机”
- 最近发表
- 标签列表
-
- cmd/c (64)
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- sqlset (64)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)