通用技术 请教下有没有在 slb 或者 nginx 层面实施 mock 的开源框架,最好是 java 的

Vanessa · 2024年11月13日 · 最后由 藕片 回复于 2024年11月20日 · 6133 次阅读

公司用的 nacos 做服务管理,还是 1.4 的老版本,同时还有一些老项目没接 nacos 直接走 http 请求,测试目前有一套自研的 mock 工具,但是对代码有一些侵入,而且有些项目存在依赖不兼容问题。
所以现在想一步到位在 ng 层面做 mock 解决所有问题,请教下各位大佬有没有开源的 mock 框架,或者有没有其他好的解决方案。

顺便就分享下现用的 mock 工具,原理就是在被 mock 类上加注解,然后通过反射替换返回值或者请求到 mock 服务上。
主要实现 2 个功能:

  1. 根据配置中心的配置直接返回被 mock 方法返回值
  2. 根据环境将请求转发到对应环境的 mock 服务,然后在 mock 服务里面处理逻辑。

领导觉得太 low 了让在 ng 层实现,所以希望有能在 ng 层实现上面俩功能的工具,求大佬们推荐。

@Aspect
public class ConfMockAspect {
    @Autowired
    SpelExcetor spelExcetor;
    private final static Logger logger = LoggerFactory.getLogger(ConfMockAspect.class);
    private static String mock_system_test_url = "http://xxxx";
    private static String mock_system_dev_url = "http://xxxx";

    @Pointcut("@within(com.mock.ConfMock)")
    private void aspect() {}

    @Around("aspect()")
    public Object around(JoinPoint joinPoint) throws Throwable {
        Object target = joinPoint.getTarget();
        Class<?> targetClass = target.getClass();
        Class<?>[] interfaces = targetClass.getInterfaces();
        String sName = targetClass.getSimpleName();
        MethodSignature sig = (MethodSignature)joinPoint.getSignature();
        Method method = sig.getMethod();
        String methodName = method.getName();
        Type type = method.getGenericReturnType();
        String key = "ConfMock." + sName + "." + methodName;
        // 走mock系统
        String sysKey1 = "system.ConfMock." + sName;
        String sysKey2 = "system.ConfMock." + sName + "." + methodName;
        String value = null;
        if (LocalCacheRepository.getInstance().get(sysKey1) != null
            || LocalCacheRepository.getInstance().get(sysKey2) != null) {
            String url = null;
            if (PropertyConstant.CONF_PROD_DEV.equals(AppInstance.getEnv())) {
                url = mock_system_dev_url + String.format("/api?className=%s&method=%s&paramClass=%s",
                    interfaces[0].getName(), methodName, method.getParameterTypes()[0].getName());
            } else if (PropertyConstant.CONF_PROD_TEST.equals(AppInstance.getEnv())){
                url = mock_system_test_url + String.format("/api?className=%s&method=%s&paramClass=%s",
                    interfaces[0].getName(), methodName, method.getParameterTypes()[0].getName());
            }  else {
                url = mock_system_uat_url + String.format("/api?className=%s&method=%s&paramClass=%s",
                    interfaces[0].getName(), methodName, method.getParameterTypes()[0].getName());
            }
            String param = JSON.toJSONString(joinPoint.getArgs()[0], new LengthFilter());
            value = HttpUtil.doPost(param, new HashMap<>(), url);
            if (value != null) {
                return JSON.parseObject(value, type);
            }
        } else {
            value = getValue(joinPoint, key);
            if (value != null) {
                Object result = parseReturn(value, type, joinPoint.getArgs());
                return result;
            }
        }
        return ((ProceedingJoinPoint)joinPoint).proceed();
    }

    private String getValue(JoinPoint joinPoint, String key) {
        try {
            Properties properties = LocalCacheRepository.getInstance().getProperties();
            Set<Object> set = properties.keySet();
            for (Object srt : set) {
                String keyStr = srt.toString();
                if (keyStr.startsWith(key)) {
                    String s = org.apache.commons.lang3.StringUtils.removeStart(keyStr, key);
                    if (!StringUtils.isEmpty(s)) {
                        ExpressionParser parser = new SpelExpressionParser();
                        StandardEvaluationContext context = new StandardEvaluationContext();
                        context.setRootObject(joinPoint.getArgs());
                        Boolean result =
                            (Boolean)parser.parseExpression(org.apache.commons.lang3.StringUtils.removeStart(s, "."))
                                .getValue(context);
                        if (result) {
                            return properties.getProperty(keyStr);
                        }
                    }
                }
            }
            if (properties.containsKey(key)) {
                return properties.getProperty(key);
            }
        } catch (Exception e) {
            logger.warn("解析key失败", e);
        }
        return null;
    }

    private Object parseReturn(String value, Type type, Object[] args) {
        if (String.class.getTypeName().equals(type.getTypeName())) {
            return value;
        }
        Object object = JSON.parse(value);
        if (object instanceof JSONArray) {
            JSONArray array = (JSONArray)object;
            parseValue(array, args);
            return array.toJavaObject(type);
        } else if (object instanceof JSONObject) {
            JSONObject jsonObject = (JSONObject)object;
            parseValue(jsonObject, args);
            return jsonObject.toJavaObject(type);
        }
        return object;
    }

    private void parseValue(JSONArray array, Object rootObject) {
        if (array != null && !array.isEmpty()) {
            for (int i = 0; i < array.size(); i++) {
                Object value = array.get(i);
                if (value != null) {
                    if (value instanceof String) {
                        Object newValue = doneInSpringContext((String)value, rootObject);
                        if (newValue != null) {
                            array.set(i, newValue);
                        }
                    } else if (value instanceof JSONObject) {
                        parseValue((JSONObject)value, rootObject);
                    } else if (value instanceof JSONArray) {
                        parseValue((JSONArray)value, rootObject);
                    }
                }
            }
        }
    }

    private void parseValue(JSONObject object, Object rootObject) {
        Set<String> set = object.keySet();
        if (!set.isEmpty()) {
            for (String key : set) {
                Object value = object.get(key);
                if (value != null) {
                    if (value instanceof String) {
                        if (isSpel((String)value)) {
                            Object newValue = doneInSpringContext(spelValue((String)value), rootObject);
                            if (newValue != null) {
                                object.put(key, newValue);
                            }
                        }
                    } else if (value instanceof JSONObject) {
                        parseValue((JSONObject)value, rootObject);
                    } else if (value instanceof JSONArray) {
                        parseValue((JSONArray)value, rootObject);
                    }
                }
            }
        }
    }

    private static boolean isSpel(String value) {
        // ${}
        if (value != null && value.startsWith("${") && value.endsWith("}")) {
            return true;
        }
        return false;
    }

    public static String spelValue(String value) {
        if (isSpel(value)) {
            value = org.apache.commons.lang3.StringUtils.removeFirst(value, "\\$\\{");
            value = org.apache.commons.lang3.StringUtils.removeEnd(value, "}");
            return value;
        }
        return value;
    }

    private Object doneInSpringContext(String value, Object rootObject) {
        try {
            return spelExcetor.doneInSpringContext(null, rootObject, value);
        } catch (Exception e) {
        }
        return null;
    }
    public static class LengthFilter implements ValueFilter{

        @Override
        public Object process(Object object, String name, Object value) {
            if (value != null && value instanceof String) {
                String var = (String)value;
                if (var.length() > 500) {
                    return var.substring(0, 500);
                }
            }
            return value;
        }

    }
共收到 11 条回复 时间 点赞

1、我点小疑问,站在测试的角度什么情况下需要去在 nginx 层面实施 mock?比如现在提测了某个功能,这个功能前端操作后会调用 A 服务,A 服务会和 B 服务的某个接口获取拿数据然后处理后返回给前端,如果此时说因为 B 服务没有开发完,需要测试去 mockB 服务的接口给前端,此时流程就基本没有跑通,就是算你现在使用 mock 数据把当前流程验证完了,等 B 服务开发完成了,最后你还是要重新把全部流程跑一边,浪费时间;
2、从项目管理风险角度讲,在人力资源充足的情况下,遇到 1 中的情况,测试提早介入能提前发现问题,这个是好事情。这种情况我通常是把项目代码拉到本地,在本地运行进行白盒走查,构造前端 API 请求然后再本地惊醒 debug 断点,本地断点的话你想要什么模拟数据可以设置,最后检查 API 返回的数据;
写在最后: 目前还没有搞过在 nginx 层面实施 mock 1、还不如直接点点点 2、浸入式的东西不能把控有什么风险,项目经理也不会同意你这么搞
如果后续研究到好的方案,请大佬分享一下,谢谢

我理解是 ng 把某些接口转发去自己的 mock 服务就行了?

在 ng 层面做 mock……那就用 nginx 呗,至于怎么用我不知道,但是我觉得你查查资料可以做到😎

fox 回复

哎,我们本来有一个 mock 工具凑活着也能用,就是需要在被 mock 类上加注解,通过反射替换要 mock 类的方法返回值,但是领导觉得太 low 了,让我们想想能不能从 ng 层去拦截处理,所以只能来求助各位大佬了

单独起一个 mock 服务呗,改下 nginx 配置走 mock 服务代理呗,不知道 mitmproxy 能不能满足你的需求

仅楼主可见

具体得看链接架构,然后尽量不要侵入被测的服务代码。从看到标题是 slb 和 nginx,我理解链路是 A 服务 请求到 B 服务,A-->B 加一层代理,即 A 请求到 slb/nginx 后,反向代理到 mock 服务或者 nginx 应答即可。至于采用那种,看具体协议。
mock 服务关键输入输出的控制,有时间可以自研。美团这个文章不错 https://tech.meituan.com/2015/10/19/mock-server-in-action.html

网关应该能解决你的问题

用 wiremock

直接用 wire mock 吧 虽然不开源 但是启服务就能用 JAVA 的 挺方面的

不太懂,是后端开发调试过程中,mock 其他人的服务吗?接触的测试一般不这么用,我们都是 mock 后端接口返回,来测试前端代码。不知道 mock 某个后端的方法或者类对测试有啥意义。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册