接口测试 接口自动化之参数动态生成替换

孤独一派 · 2020年07月04日 · 最后由 Thirty-Thirty 回复于 2020年07月23日 · 899 次阅读

接口自动化框架设计之时间类型参数动态生成

如题,在接口测试过程中,某些接口提交参数可能是不同格式的当前时间、或者未来时间,如下图

这个接口参数需要提交当前格林威治时间

该接口参数需要提交当前标准时间、n 天后的标准时间

接口自动化框架的设计中,为了保证接口用例能重复运行,需要动态生成提交的时间类型参数,具体设计如下:

第一步:设计一个日期时间工具类 CalendarUtil,用于生成各种需要提交的时间格式:

public class CalendarUtil {

    /**
     * 获取指定格式的当前标准时间
     * 
     * @param format
     * @return
     */
    public static String getCurrentGeneralTime(String format) {
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        Date date = new Date();
        return sdf.format(date);
    }

    /**
     * 获取n天前或n天后指定格式的标准时间
     * 
     * @param format
     * @return
     */
    public static String getGeneralTimeAfter(int date,String format) {
        Calendar c = Calendar.getInstance();
        c.add(Calendar.DATE, date);
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        return sdf.format(c.getTime());
    }

    /**
     * 获取当前格林威治时间
     * 
     * @return
     */
    public static String getCurrentGreenTime() {
        Calendar c = new GregorianCalendar();
        c.add(Calendar.HOUR_OF_DAY, -8);
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.000'Z'");
        return df.format(c.getTime());
    }


    /**
     * 获取date天前或date天后的起始格林时间
     * 
     * @param date
     * @return
     */
    public static String getGreenTimeAfterDate(int date) {
        Calendar c = new GregorianCalendar();
        c.add(Calendar.DATE, date-1);
        c.set(Calendar.HOUR_OF_DAY, 16);
        c.set(Calendar.MINUTE, 00);
        c.set(Calendar.SECOND, 00);
        c.set(Calendar.MILLISECOND, 000);
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.000'Z'");
        return df.format(c.getTime());
    }

    /**
     * 获取当前时间戳
     * 
     * @return
     */
    public static Long getCurrentTimeStamp() {
        Date date = new Date();
        return date.getTime();
    }


    /**
     * 获取date天前或date天后的时间戳
     * 
     * @param date
     * @return
     */
    public static Long getTimeStampAfterDate(int date) {
        Calendar c = Calendar.getInstance();
        c.add(Calendar.DATE, date);
        c.add(Calendar.HOUR_OF_DAY, 00);
        c.add(Calendar.MINUTE, 00);
        c.add(Calendar.SECOND, 00);
        c.add(Calendar.MILLISECOND, 000);
        return c.getTime().getTime();
    }
}

第二步:获取用例数据后检测提交参数中是否包含对应的关键字,然后用生成的方法进行替换提交值,如下:

/**
     * 替换请求参数中的${xxx}
     * 
     * @param requestParams
     * @return
     */
    public String replaceRequestParams(String requestParams) {

        // 请求参数不为空且包含替换标识
        if (StringUtils.isNotEmpty(requestParams) && requestParams.contains("${")) {
            String regex = "\\$\\{(.*?)}";
            Pattern pattern = Pattern.compile(regex);
            Matcher matcher = pattern.matcher(requestParams);
            while (matcher.find()) {
                String finder = matcher.group(1);
                String result = finder.trim();
                if (result.equals("currentGreenTime")) {
                    // 替换成当前格林时间
                    requestParams = requestParams.replaceAll("\\$\\{" + finder + "}",
                            CalendarUtil.getCurrentGreenTime());
                } else if (result.contains("greenTimeAfterDate(")) {
                    // 替换成n天前或者n天后的格林威治时间
                    int date = Integer.parseInt(result.substring(result.indexOf("(") + 1, result.indexOf("")).trim());
                    finder = finder.replaceAll("\\(", "\\\\(").replaceAll("\\)", "\\\\)");
                    requestParams = requestParams.replaceAll("\\$\\{" + finder + "}",
                            CalendarUtil.getGreenTimeAfterDate(date));
                } else if (result.contains("currentTimeStamp")) {
                    // 替换成当前时间戳
                    requestParams = requestParams.replaceAll("\\$\\{" + finder + "}",
                            CalendarUtil.getCurrentTimeStamp().toString());
                } else if (result.contains("timeStampAfterDate(")) {
                    // 替换成n天前或n天后的时间戳
                    int date = Integer.parseInt(result.substring(result.indexOf("(") + 1, result.indexOf("")).trim());
                    finder = finder.replaceAll("\\(", "\\\\(").replaceAll("\\)", "\\\\)");
                    requestParams = requestParams.replaceAll("\\$\\{" + finder + "}",
                            CalendarUtil.getTimeStampAfterDate(date).toString());
                } else if (result.contains("currentGeneralTime")) {
                    // 替换生成的指定格式的当前标准时间
                    String regex2 = "currentGeneralTime,(.*?)";
                    Pattern p = Pattern.compile(regex2);
                    Matcher m = p.matcher(result);
                    while (m.find()) {
                        String format = m.group(1);
                        requestParams = requestParams.replaceAll("\\$\\{" + finder + "}",
                                CalendarUtil.getCurrentGeneralTime(format));
                    }
                } else if (result.contains("generalTimeAfter")) {
                    // 替换生成n天前或n天后指定格式的标准时间
                    int date = Integer.parseInt(result.substring(result.indexOf("(")+1, result.indexOf(")")));
                    finder = finder.replaceAll("\\(", "\\\\(").replaceAll("\\)", "\\\\)");
                    String format2 = result.substring(result.indexOf(",")+1);
                    requestParams = requestParams.replaceAll("\\$\\{" + finder + "}",
                            CalendarUtil.getCurrentGeneralTime(format2));
                } else {
                    Object replacement;
                    // 判断map里有没有存储对应的变量
                    if (map.containsKey(result)) {
                        replacement = map.get(result);
                        // 如果有存变量,但值是空,当空字符串处理
                        if (null == replacement) {
                            replacement = "";
                        }
                    } else {
                        // 跳过用例执行,抛出skipException
                        throw new SkipException(result + "不存在,替换失败");
                    }
                    requestParams = requestParams.replaceAll("\\${" + finder + "}", replacement.toString());
                }
            }
        }
        return requestParams;
    }

接口用例设计中,动态参数用 ${关键字}形式

这样就完成了接口框架最重要的一部分:参数动态生成替换的设计

共收到 11 条回复 时间 点赞

我们的 excel 目前就是这样设计的

居然还可以在参数文件里传参啊 张姿势了🍻

自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程。
自动化测试工程师需要把精力投放在这种转化上。
示例代码喧宾夺主,违背了自动化测试的初衷。

Thirty-Thirty 回复

照你这样说 直接用 jemeter、postman 就能实现了以人为驱动的测试行为转化为机器执行了,还需要重复造轮子干嘛!拜托搞清楚自动化框架要解决的问题再来评价,而且我示例代码只是我自动化框架封装中碰到的一个小问题而已,我真没搞清楚你所谓的主是什么?

孤独一派 回复

你为什么要自研框架呢?postman 的功能还不够丰富吗,缺少哪些你需要的功能呢?

Thirty-Thirty 回复

postman、jemeter 功能很丰富,我们框架封装其实也是去参考这种成熟的工具,那为什么还要去封装框架,重复造轮子,像你说的把精力集中放在提高自动化能效上?我认同去提高自动化能效是最终的目的,但是框架的设计、搭建的过程也将是直接影响最终能效的一个过程。为什么现在公司很少用工具去组织接口自动化,而要自己封装框架,我想每个公司需求不尽相同,必然是有工具无法解决的一部分问题,比如拿我公司来说,50 多个分布式子系统,最终报告的展示,结算模块要针对金额做个性化断言,不同的子系统需要打通统一登录鉴权,持续集成发送报告邮件等等,这些都是工具做不到的。我们现在又从一套成熟的自动化框架演变成了开发好了接口自动化平台,这不是多此一举,而是自动化实践一步步演变的过程。个人就自己工作得来的一点浅显的看法,不喜勿喷。

孤独一派 回复

目前多数公司虽然都在自己封装框架,但投入的精力只占二分或更少,然后基于自研框架开发测试脚本,投入的精力占八分或更多。然而我认为正确的做法应该是 “八分框架,二分脚本”,这点我们的观点是一致的,因为我能体会你们在开发/封装框架方面做的工作。但这些都要基于需求存在为前提。
结算模块要针对金额做个性化断言,这里可以说的详细些吗?postman 具体做不到什么呢?
postman + newman + jenkins + HTML Publisher Plugin(或 jenkins 自带的 JUnit test result report) + Email Extension Plugin,解决不了你所列的问题吗?

孤独一派 回复

我也带领团队开发过自动化测试平台,让使用者〇编码实现功能测试 (基于 GUI)、接口测试和性能测试。

Thirty-Thirty 回复

不是说它做不到,只是没有自己封装那么灵活,或者说用你说的那一套来做比我自己封装成本更高。可能需要改造,因为需要契合我项目的自动化测试需求。更重要的是我基于 java + httpclient + poi + maven + maven-surefire-plugin + jenkins + HTML Publisher Plugin + Email Extension Plugin 来封装有一套完整的方案。这是一个技术选型的问题。

孤独一派 回复

postman 提供丰富的断言方法,足以覆盖绝大多数的测试需求。
你能否举例说明你们需要的个性化断言呢?

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