通用技术 [testNG][Javassist] 借助 javassist 动态生成测试类,以同时执行

bauul · 2019年04月17日 · 最后由 bauul 回复于 2019年06月22日 · 685 次阅读
本帖已被设为精华帖!

缘由

之前有基于 testNG+okHttp 封装了一个接口自动化测试的执行器,将测试用例都放在 json 文件中: [接口测试平台一期] 接口测试用例参数化方案
但是一直都是按顺序执行的,现在想要实现同时执行多个用例,但是因为我的执行器就一个类文件,
如果想要同时执行,基于 testNG 的话,需要多个类,那么问题来了,如何自动生成多个类文件呢?

解决思路(主要是动态代理)

  • cglib
  • javassist

这两者都支持继承实现动态代理,
cglib 在继承注解时,存在一定的问题,自定义注解中未加入@Inherited, 是不会继承该注解的,或者是我没找到解决的方法
javassist 更强大,字节码级别的,可在运行时修改方法,注解参数等
两个坑都踩了,最后还是用 javassist 实现了

实现

引入 javassist

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.24.1-GA</version>
</dependency>

生成类

CtClass cc = pool.makeClass("TestExecutor" + caseSetIndex);
CtClass superClass = pool.getCtClass("com.wz.exec.BaseExecutor");
cc.setSuperclass(superClass);

CtMethod method  = CtNewMethod.make("public void executor() { run();}", cc);
ConstPool methodPool = method.getMethodInfo().getConstPool();
AnnotationsAttribute attribute = new AnnotationsAttribute(methodPool, AnnotationsAttribute.visibleTag);
Annotation annotation = new Annotation("org.testng.annotations.Test", methodPool);
annotation.addMemberValue("invocationCount", new IntegerMemberValue(methodPool, 1));
annotation.addMemberValue("description", new StringMemberValue(apiTestCase.getCaseInfo().getCaseName(), methodPool));
attribute.addAnnotation(annotation);
method.getMethodInfo().addAttribute(attribute);
cc.addMethod(method);

Class executor = cc.toClass();

塞到 testNG 中

# 实际这里我塞进去的是一个列表
testNG.setTestClasses(executor);

更多思考

  1. 因为最近在看《Java 程序性能优化 让你的 Java 程序更快、更稳定》,接触到 javassist,
    但在百度查的时候发现,这个套路在 12 年的时候就有人玩过
    还是自己太无知了

  2. 因为是动态生成的,如果生成了很多的类,会存在内存耗尽的问题,
    所以可以更进一步,思考内存消耗的问题,
    可以自定义 classloader 来加载这些动态生成的类,用完就释放

参考

Book:《Java 程序性能优化 让你的 Java 程序更快、更稳定》
javassist 官网:http://www.javassist.org/tutorial/tutorial2.html#add

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 17 条回复 时间 点赞
思寒_seveniruby 将本帖设为了精华贴 04月20日 20:19

好好的看源码吧。javassist?丢掉。哈

Bensir 回复

这位大神,你在讲什么?

bauul 回复

就是让你看 testng 源码阿~不要用这个哇~大佬

Bensir 回复

噢?说说看,怎么用的?我学习下

但是感觉你只是复制了很多个 BaseExecutor,并没有在基础类上面做扩展,并不是很符合动态代理的用法。如果只是需要同时跑多个是否用线程来实现更好?很久没玩 java 不知道 java 线程通信方便不。

Karaser 回复

嗯,你说的对,在本例中,并没有使用到动态代理的特性,去增加前置、后置、或环绕什么的方法,准确来说是动态生成了一个子类。
多线程来实现是指下面这个参数吗?从注解来看,是跑这个方法多次

public @interface Test {
  /**
   * The size of the thread pool for this method.  The method will be invoked
   * from multiple threads as specified by invocationCount.
   * Note:  this attribute is ignored if invocationCount is not specified
   */
  public int threadPoolSize() default 0;
}
bauul 回复

我确实不熟悉 testNG😅 我的意思是自己起一个线程池去满足你的这个需求~

Karaser 回复

嗯,之前有想过,忘记为啥没去试这条路了😅

bauul 回复

调用源码生成 xml,设并发数,用数据驱动的形式 run 你的测试类或者带有 test 的注解…其他的话就是你框架的设计囖

bauul #11 · 2019年05月04日 Author
Bensir 回复

我试了一下,未达到并行执行的效果,它是串行的,我的测试代码如下:

@DataProvider
public Object[][] getData() {
    Object[][] objects = new Object[3][3];
    for (int i=0; i<objects.length; i++) {
        for (int j=0; j<objects[i].length; j++) {
            objects[i][j] = i + "-" + j;
        }
    }
    return objects;
}

@Test(dataProvider = "getData", threadPoolSize = 4)
public void runData(Object... objects) {
    System.out.println(System.currentTimeMillis());
    System.out.println(Thread.currentThread().getName());
    for (int i=0; i<objects.length; i++) {
        System.out.println(objects[i]);
        ThreadUtils.sleep(100);
    }
    System.out.println();
}
bauul 回复

Hi, 看下你 set Thread 的方法截图。

bauul #13 · 2019年05月06日 Author
Bensir 回复

你是说这个吗?

testNG.setThreadCount(Runtime.getRuntime().availableProcessors() + 1);

没达到效果噢

public class Demo1 {

    @DataProvider
    public Object[][] getData() {
        Object[][] objects = new Object[3][3];
        for (int i=0; i<objects.length; i++) {
            for (int j=0; j<objects[i].length; j++) {
                objects[i][j] = i + "-" + j;
            }
        }

        return objects;
    }

    @Test(dataProvider = "getData", threadPoolSize = 4)
    public void runData(Object... objects) {
        System.out.println(System.currentTimeMillis());
        System.out.println(Thread.currentThread().getName());
        for (int i=0; i<objects.length; i++) {
            System.out.println(objects[i]);
            ThreadUtils.sleep(100);
        }
        System.out.println();
    }

    public static void main(String[] args) {
        TestNG testNG = new TestNG(false);
        testNG.setThreadCount(Runtime.getRuntime().availableProcessors() + 1);
        testNG.setPreserveOrder(true);
        testNG.setTestClasses(new Class[]{Demo1.class});
        testNG.run();
    }
}
bauul [接口测试][TestNG] 基于 testNG 的并发测试 中提及了此贴 05月07日 09:26
bauul 回复

main 方法需要重新写。需要从 suite 开始搞。。大佬就帮你到这。

bauul #16 · 2019年05月07日 Author
Bensir 回复

额,好的吧,感谢大佬

TestNG 本身不就是支持多线程吗?或者换 Junit5,根据 cpu core 自动分解用例,一行配置而已

如果是在平台上的话,可以直接使用异步执行工具

bauul #19 · 2019年06月22日 Author
misszhang 回复

哪个平台,自己开发的平台?其他平台?

simple [精彩盘点] TesterHome 社区 2019 年 度精华帖 中提及了此贴 12月24日 23:00
simple [精彩盘点] TesterHome 社区 2019 年 度精华帖 中提及了此贴 12月24日 23:00
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册