1.开发自己的测试框架

引言

        目前基于java的开发人员的测试框架很多,但基于测试人员的测试框架并不多,我研究了一下,都并不是让我很满意,如使用jmeter,jmeter虽然支持 beanshell,但脚本是 java 语言编写,java 语言本身代码量就比较多,没有python语言简捷,jmeter好处还是有的,jmeter 的界面非常友好,对于不太会写代码的测试人员来说,是不错的选择,jmeter项目及接口管理都比较方便,但我还认为有一些不好的地方,就是 jmeter开发文件需要上传到 git上,同事再从 git 上下载下来,再在本地运行,而这些文件是什么类型,不同的操作系统,不同的 jmeter 版本是否有兼容性问题,不得而知了,但仅从 jmeter 不支持python语法这一点就让我直接放弃使用,让我想到另外一个办法,直接使用 python语言编写测试脚本。但也带来一个问题,接口的管理不如 jmeter 直观。一个后台项目就需建一个python测试项目与之对应,如果后台项目很多,测试项目也会跟着增多,项目管理不方便,如果在测试过程中只想测试部分脚本,python编写的需要修改 python源代码才行,不像 jmeter有启用禁用按钮,因此急需一个既像 jmeter 使用一样方便,又支持 python 语法的测试框架,怎么办,那我们开发一个吧。
        本篇博客主要讲测试框架的使用。下一篇博客再来解析源码。
        可能会有人说,你吹牛逼吧,吹不吹牛逼,我们先看了再说吧。
        为了让测试小伙伴只做纯脚本编写,因此本项目是一个web 项目。后端是我开发的,前端是我们公司前端开发小伙伴庄老板开发。话不多说,来看看框架如何使用。

测试框架搭建

        后端项目需要注意包的引入。在 lz_test 项目resources目录下use_package,这个目录下放着两个包,到时候导入到你们公司的私服就可以了,导入私服方法如下

  1. 导入私服
    在这里插入图片描述

  2. 从私服中拷贝
    在这里插入图片描述

  3. 引入到项目
    在这里插入图片描述
    前端项目
    在这里插入图片描述
    前端项目可能需要解决一些包的引入问题,到时候你根据实际情况install 相关包即可,本次用到的4个项目git 地址放到文章的最后。感兴趣的可以下载下来自行研究。

测试框架的使用

  1. 登陆,默认用户名: admin ,密码: admin
    在这里插入图片描述

  2. 根据不同的项目创建不同的测试用例
    在这里插入图片描述

  3. 新建测试框架页面
    在这里插入图片描述

  4. 选择【普通页面】类型,再选择测试接口页面
    在这里插入图片描述

  5. 可以看到在测试页面中有组和接口,可以添加多个组,每个组下可以选择不同的接口。
    在这里插入图片描述

  6. 点击添加组,填写组名称即可
    在这里插入图片描述

  7. 为组添加接口
    在这里插入图片描述

  8. 新增接口
    在这里插入图片描述

  9. 接口编写
    在这里插入图片描述

  10. 脚本大部分语法是 python的,只有小部分是java的
    在这里插入图片描述

  11. 话不多说,直接来测试每一项语法吧,如果用户想添加新的方法,可以直接在 testshell项目的 methods 包下,任意类中添加任意方法,即可直接在脚本中使用。
    在这里插入图片描述
    如下面即将使用 math函数相关方法,java的实现是在MathMethod 这个类中提前定义好方法。即可直接在脚本中使用。
    在这里插入图片描述

  12. math 函数支持,目前主要是对 python 中的 Math.xxx()函数的支持,支持了一部分方法,其他方法,可能觉得没有什么用处,因此就不提供支持了,如果想自己添加扩展方法,直接在 testshell 项目下methods包下的任意类添加你想要的方法即可。
    在这里插入图片描述

  13. map操作支持,map 是关键字,我们不能直接用 map 作为变量名或方法名
    在这里插入图片描述

  14. 空对象及空串处理
    在这里插入图片描述

  15. list 定义及使用
    在这里插入图片描述
    list 按道理和 python 中的 list 的使用方法一样,在脚本语言中不支持元组,因为我觉得元组和 list的使用方式极其相似,因此我觉得元组的使用,可能会增加测试人员的负担,因此不提提供支持。但是 list 中集成的元组的功能,如 a,b = list ,则将 list[0]赋值给a,list[1:]后面所有的值赋值给 b ,如果 list 只有两个元素,则将 a=list[0],b = list[1]。同时 list 遍历也延用了 python 的语法。
    在这里插入图片描述
            小伙伴可以看到在 python 中 list 遍历的好处,如果我既想得到遍历的索引,又想得到遍历的值,在 java中有两种写法。


    java 中的写法
    for(int i = 0 ;i < list.size();i ++){
    item = list.get(i);
    print(i,item)
    }

    int i = 0 ;
    for(item in list ){
    print(i,item)
    i ++
    }
    python 中的写法
    for(i,item in list ){
    print(i,item)
    }

            聪明的小伙伴肯定一眼就看出来,python 比 java 好用,因为在 java 中无论怎样写,都是"鱼和熊掌不可兼得",要么就得到list 中的 item,要么就得到 i,不能同时得到,但是在 python 中就可以直接得到了。接下来我们来看看自定义框架中对于 string 的支持

  16. 对于 string 的支持,对于 string 的支持也是延用 python 的语法,如取倒数第二个字符a = ‘abcdef’, 直接使用 a[-2]即可,如想截取从第2到第4个字符 ,包含第二个字符 ,不包含第4个字符,直接a[2:4]即可,当然字符下标是从0开始的。
    在这里插入图片描述

  17. 三目运算符
    在这里插入图片描述
    三目运算符我使用的是 java 的语法,因为 python 的语法确实有点晕。我们来看看 python 语法。
    在这里插入图片描述
    在这里插入图片描述
    代码中需要这样用,我是 java出身的,觉得这样写确实有点诡异,因此,还是用 java 的三目运算符吧,如 a 大于b ,取 a ,否则取 b , 写成 a > b ? a : b ,更加顺口而已。

  18. 时间处理
    在这里插入图片描述
            上面需要注意的是,time()函数返回的是一个number 类型的毫秒值,date()返回的是 Date 类型,日期转毫秒,直接调用日期的 getTime()方法即可,毫秒值转日期,直接调用 date()方法即可,传入时间的毫秒值。就可以将number类型转化为日期类型。

  19. for 循环语法,在之前的 list 集合使用时,我们己经使用过 for 的一些语法,下面我们继续来探索 for循环语法的使用。
    在这里插入图片描述
            对于 for我一的使用,一部分是延用 java 的语法,一部分是用了 python 的语法,当需要退出多层循环时,可以在最外层 for 循环之上定义一个 label_标签,当内部条件不满足时,可以用 break label_来终止多层循环,而对于list 和 map 的遍历,我觉得还是 python 语法好用。

  20. while循环,及 do while 使用
    在这里插入图片描述可以支持 while(){},while(){ swith() { case : ; default : break ; } } ,do {} while(condition) 语法。这些语法延用 java 语法。可以自己去测试一下。

  21. return 语句使用,没有什么好说的,和 python 一样
    在这里插入图片描述

  22. 异常处理,当异常没有被捕获时,程序终止
    在这里插入图片描述
            当异常被捕获时,即使程序抛出异常,程序也不会被终止,会继续向下执行。在这里插入图片描述
    在这里插入图片描述

  23. 加密方法,当前支持sha256加密,和md5加密
    在这里插入图片描述

  24. 方法参数指定默认值
    在这里插入图片描述

  25. 方法作为方法参数使用,如下图中,max 方法作为findMax方法的第三个参数使用,这是 java 中没有的使用方式,在 python中很常见。
    在这里插入图片描述

  26. 方法内嵌套方法,方法返回值是方法的引用,在外层使用方法引用调用方法,此语法和 python语法一样
    在这里插入图片描述

  27. 强大的柯里化
    在这里插入图片描述

  28. 利用柯里化特性,使用 logger方法打印方法的请求参数及返回参数
    在这里插入图片描述

  29. 内层方法修改外层方法变量,无效
    在这里插入图片描述

  30. 方法局部定义的变量不能修改全局变量
    在这里插入图片描述

  31. 方法局部变量list集合使用
    在这里插入图片描述

  32. 方法参数默认值设置,在下图中可以看到,如果只传一个参数,默认情况下,是填充方法的第一个参数,当然也可以像 foo3一样,你什么都不传。
    在这里插入图片描述

  33. 通过外部传参数的方式覆盖掉方法的局部参数(在list或 map为参数的情况)
    在这里插入图片描述

  34. 继承python语法,* args 和 * kwargs 的使用

在这里插入图片描述

  1. * args 和 **kwargs应用场景,打印方法的请求参数和返回参数。

在这里插入图片描述
36. 基于注解方式实现,打印方法请求参数返回值
在这里插入图片描述
37. 柯里化的复杂嵌套使用
在这里插入图片描述

  1. 柯里化使用场景,根据方法上配置注解的参数,打印方法是否超时,在下图中可以看到add1方法容忍超时时间为2秒,add2方法容忍超时时间为3秒,在 add1方法内睡眠1秒,add2方法内睡眠3秒,在方法的执行过程中,打印出 add2方法超时。
    在这里插入图片描述

  2. 使用偷天换日的方式替换掉原来方法,根据柯里化方法规则,我们知道src代表的是 b 方法,dest代表的是目标方法 add,根据实际业务逻辑,可以替换掉原方法。
    在这里插入图片描述

  3. 方法柯里化属性本来是没有的,可以后天获得
    在这里插入图片描述

  4. 柯里化复杂使用场景,当柯里化中嵌套柯里化时,为每一层方法打印日志。
    在这里插入图片描述
    执行结果
    在这里插入图片描述

  5. lambda表达式简单使用场景
    在这里插入图片描述

  6. lambda和list结合使用场景
    在这里插入图片描述

  7. lambda表达式作为方法参数使用
    在这里插入图片描述

  8. lambda和map关键字结合使用
    在这里插入图片描述
    map 关键字的语法和 python 的语法是一致的。
    在这里插入图片描述

  9. 脚本最基本的用法 ,+ ,- ,* ,/ , i++,i–,--i,++i,%= ,<<= ,>>>= 等
    在这里插入图片描述
    在这里插入图片描述
    运行结果
    在这里插入图片描述

  10. 线上测试用例, 登陆接口,用户通过登陆获取用户名和密码,获取 token,并定义全局 token,在一个组内,任何其他的接口都可以通过 import来导入 token变量
    在这里插入图片描述

  11. 导入token 变量,并通过 token获取用户信息
    在这里插入图片描述
    获取用户信息脚本
    测试结果
    在这里插入图片描述

  12. 为了不污染变量,可以用 export关键字导出变量,导出的变量只为下一个接口使用,这个什么意思呢?如定义的全局变量token,这个测试组中所有的接口中都可以import 导入token变量,但有时候,变量不需要定义成全局变量,只为当前接的下一个接口使用,而其他接口不需要使用,可以使用 export 关键字来导出变量。话不多说,先来测试一把。
    a)在第一个接口中定义变量 a
    在这里插入图片描述
    b) 在第二个接口中导入变量 a
    在这里插入图片描述
    c)第三个接口中导入变量a
    在这里插入图片描述
    测试结果,在第2个接口中能拿到定义的变量a,在第三个接口中拿不到变量 a
    在这里插入图片描述
    在这里插入图片描述

  13. 批量测试
    在这里插入图片描述
    在这里插入图片描述

  14. 当某个组中的某个接口抛出异常时,后面的接口停止执行
    新增一个异常接口
    在这里插入图片描述
    测试
    在这里插入图片描述

  15. 自定义方法
    在这里插入图片描述
    在第二个接口中引入mysum.tsh文件中的 mysum方法
    在这里插入图片描述
    上述做法的好处,就是不限定测试小伙伴想象,小伙伴可以利用语法开发出好用的公共方法。

  16. 文件冲突解决,新增接口不会出现文件冲突的,只有在编辑文件时,会出现,如,用户A编辑A接口,用户B也编辑 A接口,用户A 修改接口文件提交,用户B 再修改接口提交,这个时候可能会出现冲突,用户 B 解决冲突,再提交代码,此时接口 A 得到的将是 A 用户和 B 用户合并后的代码。
    admin 用户打开gittest12接口 ,新增内容aaaaaaaaaaaaaaaaaa
    在这里插入图片描述
    zc 用户打开gittest12,新增内容 bbbbbbbbbbbbbbbbbbbb
    在这里插入图片描述
    提示代码冲突
    在这里插入图片描述
    解决冲突,点击保存在这里插入图片描述
    编辑的不是同一块代码,代码自动合并情况
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

        git操作代码的源码如下

//需要注意的是每一个用户名都会有一个自己分支,每次都切换到自己的分支,将编辑的代码和 master 代码合并,如果
//冲突,冲突的代码返回给前端,前端解决冲突,即可保存代码,并提交,再合并到 master 中,再提交远程
public R gitOperation(String branch, String directory, String fileName, String code) {
    try {
        // 拉取 master 代码 , 恢复 master 代码
        String currBranch = git.getBranch();     //获取当前本地分支
        if (MASTER.equals(currBranch)) {          // 如果当前分支是 master分支
            git.fetch();
            git.reset(MASTER);
            git.add();
            git.commit("提交修改");//为了保险起见,还是 git -am "提交修改" 一下

            git.checkout(branch);   //切换到用户自己分支
            git.pull();
        } else if (branch.equals(currBranch)) {        //如果当前分支是自己之前所在分支,可能用户正在解决冲突
            //...                                      //什么都不做
        } else {      // 如果是在别人的账户下,可能别人的代码也冲突了,因此需要恢复别人的代码,再切换到自己所在分支
            git.add();
            git.commit("切换到" + branch + "分支,先提交代码");

            git.checkout(branch);
            git.pull();
        }
        git.write(directory, fileName, code); // 写入文件
        git.add(directory + "/" + fileName);  // 提交推送到远程
        git.commit("提交本次修改");
        String status = git.merge(MASTER);    // 合并 master 文件
        if ("Conflicting".equals(status)) {
            String content = git.read(directory, fileName);
            R r = R.error("代码冲突").putData("code", content);
            r.put("code", 300);
            return r;
        }
        git.push(branch); //如果代码没有冲突,将分支代码推送到远程
        git.checkout(MASTER);  //切换到 master分支
        git.pull();
        git.merge(branch);      //合并分支代码
        git.push(MASTER);     //推送master到远程
        return R.ok();
    } catch (Exception e) {
        log.error("git 操作文件异常", e);
    }
    return R.error("git操作异常");
}

        上述代码的意思是什么呢?先来看第一种情况,如用户 A 编辑接口1,先创建并切换到A分支,修改接口1的文件内容,提交代码,再合并 Master 分支代码,如果没有冲突,则推送当前分支代码到远程,切换到 Master 创建,合并 A分支代码,推送Master 分支代码到远程。
        当 A,B 两个用户同时来编辑接口1时,又是怎样的情景呢?A 用户打开接口1,同时 B 用户也打开接口1,A 用户编辑并保存,流程和第一种情况一样,B 用户编辑保存时,Master分支中己经有 A 用户的编辑的代码了,如果 A 用户和 B 用户编辑的同一块代码,先切换到 B 分支,保存B 用户编辑的代码,合并 Master,此时文件冲突,将冲突的代码返回给前端,用户在前端页面解决冲突,再次保存,此时合并成功。

        还有第三种情况,当 A,B 两个用户同时来编辑接口1时,又是怎样的情景呢?A 用户打开接口1,同时 B 用户也打开接口1,A 用户编辑并保存,流程和第一种情况一样,B 用户编辑保存时,Master分支中己经有 A 用户的编辑的代码了,如果 A 用户和 B 用户编辑的不是同一块代码,先切换到 B 分支,保存B 用户编辑的代码,合并 Master,此时文件合并成功。
在使用过程中需要注意的是修改你的 gitlab 的用户名和密码及项目地址
在这里插入图片描述
关键源码解析

@Test
public void test() throws Exception {
    ClassLoader classLoader = TClassUtils.getDefaultClassLoader();
    URL url = classLoader.getResource("code/base.tsh");
    String path = url.getPath();
    int a = path.lastIndexOf("/");
    path = path.substring(0, a);
    ResouceHelp resouceHelp = new ResouceHelp();
    Map<String, Object> init = new LinkedHashMap<>();
    //主要是加载base.tsh 文件中定义的公共方法,v如 http 请求相关的 open 方法和文件上传的 upload方法
    for (File file : TFileUtils.getFiles(path)) {
        String content = TFileUtils.readToStr(file.getPath());
        TTuple1<Map<String, Object>> baseVarible = Utils.run(content, null, null, null, resouceHelp).getData();
        init.putAll(baseVarible.getFirst());
    }
	//global主要是存储全局变量,也就是 global 定义的变量
    Map<String, Object> globals = new LinkedHashMap<>();
    //import 主要是存储在接口代码中 export 关键字导出的变量及值
    Map<String, Object> imports = new LinkedHashMap<>();
	
	//依次执行三个接口
    List<String> files = Arrays.asList(new String[]{
            "/Users/quyixiao/git/java-python/script/yijie/get_verify_code.tsh",
            "/Users/quyixiao/git/java-python/script/yijie/login.tsh",
            "/Users/quyixiao/git/java-python/script/yijie/test.tsh"
    });

    TTuple3<Map<String, Object>, Map<String, Object>, Map<String, Object>> data = null;
    for (String f : files) {
        String content = TFileUtils.readToStr(f);
        //每次将global 定义的变量及export导出的变量及值设置到下一个接口调用的名称空间内
        data = Utils.run(content, init, globals, imports, resouceHelp).getData();
        globals = data.getSecond();
        imports = data.getThird();
    }
}

        test 测试用例需要注意的是 run 方法,关于 run 方法如何实现,我们在下一篇博客中再来深究了。
下一篇博客将对整个脚本解析器源码进行解析,目前因为工作上事情也多,可能写得没有那么及时,不过,是肯定会写出来的,感兴趣的小伙伴请耐心等待。

总结 :

        经过这段时间的学习,我相信大家对测试框架的使用己经有了一定了解,目前这个框架己经开始在公司中推广,虽然有一些小 bug ,但是不影响使用,后面这些 bug 修复好以后,我再来更新代码了。

注意:

        因为只用了两个星期开发这个脚本解析器,目前只对python的部分语法支持,如 python中的类,魔数等,都没有提供支持,但我觉得当前脚本支持语法己经足够使用,将来如果需要其他功能,大家可以给我留言,或者根据公司业务需求,从 github clone代码自行开发。

        本项目中下面两个包可能我随时更新新的代码,还是希望用户自己去下载testshell编译,源码地址文档的最后。

在这里插入图片描述
        本次测试所有的接口脚本都放到了项目的 sql文件下。
在这里插入图片描述

git 地址

脚本解析器源码
https://github.com/quyixiao/testshell.git

mybatis 小插件源码
https://github.com/quyixiao/lz_mybatis_plugin

测试框架源码
https://github.com/quyixiao/lz_test

前端项目地址
https://github.com/quyixiao/lz_test_ui

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值