前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >动态模型之动态增减【FunTester测试框架】

动态模型之动态增减【FunTester测试框架】

作者头像
FunTester
发布2021-11-19 14:20:48
3280
发布2021-11-19 14:20:48
举报
文章被收录于专栏:FunTesterFunTester

思路

首先要抛弃原有的模型结构,将每个多线程任务都当做一个可管理对象,需要有一个中断方法,然后有一个全局的运行状态的管理类,包含一些基础添加,删除,终止单个多线程任务的能力。

通过一个外部因子触发不同的管理类方法:比如增加用例,然后从任务池中随机(后期会选择某一任务)克隆一个任务,重新放到任务池中。

本地版本的FunTester测试框架将键盘输入当做外部因子,分布式服务化FunTester测试框架将接口请求当做外部因子,本次演示本地版本。

运行过程如下:

  • 由部分少量任务构成基础压力,开始执行
  • 启动额外线程处理外部因子传递的参数
  • 通过外部因子控制多线程任务池增减或者终止

改造

多线程任务类

首先对多线程任务基础类进行改造,我重新写了一个com.funtester.base.constaint.ThreadBase的子类com.funtester.base.constaint.FunThread,专门用于创建动态模型任务。这里改造分两种:1.是增加终止属性com.funtester.base.constaint.FunThread#BREAK_KEY和对应方法com.funtester.base.constaint.FunThread#interrupt;2.是简化了com.funtester.base.constaint.FunThread#run方法,改造成为一个会一直运行的方法,避免克隆中出现终止现象。

代码语言:javascript
复制
package com.funtester.base.constaint;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.Vector;

public abstract class FunThread<F> extends ThreadBase {

    private static final long serialVersionUID = 7878297575504772944L;

    private static final Logger logger = LogManager.getLogger();

    /**
     * 统一管理所有存活线程
     */
    private static Vector<FunThread> threads = new Vector<>();

    /**
     * 单线程中断开关,用于动态调整并发压力,默认值false
     */
    private boolean BREAK_KEY = false;

    public FunThread(F f, String name) {
        this.isTimesMode = true;
        this.threadName = name;
        this.limit = Integer.MAX_VALUE;
        this.f = f;
    }

    protected FunThread() {
        super();
    }


    @Override
    public void run() {
        before();
        while (!BREAK_KEY) {
            try {
                doing();
            } catch (Exception e) {
                logger.warn("执行任务失败!", e);
            }
        }
    }

    /**
     * 运行待测方法的之前的准备
     */
    public void before() {
    }

    /**
     * 动态模型正常不会结束
     */
    protected void after() {
    }


    private static synchronized boolean checkName(String name) {
        for (FunThread thread : threads) {
            String threadName = thread.threadName;
            if (StringUtils.isAnyBlank(threadName, name) || threadName.equalsIgnoreCase(name)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 拷贝对象方法,用于统计单一对象多线程调用时候的请求数和成功数,对于<T>的复杂情况,需要将T类型也重写clone方法
     *
     * @return
     */
    @Override
    public abstract FunThread clone();

    /**
     * 线程终止,用于动态调节并发压力
     */
    public void interrupt() {
        BREAK_KEY = true;
    }


}

管理功能

管理功能我目前写在了com.funtester.base.constaint.FunThread类中,通过一个java.util.Vector集合存放所有的运行任务,当做一个任务池,增加了添加、删除、查询、终止、克隆的方法。

代码语言:javascript
复制
/**
 * 用于在某些情况下提前终止测试
 */
public static synchronized void stop() {
    threads.forEach(f -> f.interrupt());
    threads.clear();
}

public static synchronized boolean addThread(FunThread base) {
    if (!checkName(base.threadName)) return false;
    return threads.add(base);
}

/**
 * 删除某个任务,或者停止
 *
 * @param base
 */
public static synchronized void remoreThread(FunThread base) {
    base.interrupt();
    threads.remove(base);
}

public static synchronized FunThread find(String name) {
    for (int i = 0; i < threads.size(); i++) {
        FunThread funThread = threads.get(i);
        if (StringUtils.isNoneBlank(funThread.threadName, name) && funThread.threadName.equalsIgnoreCase(name)) {
            return funThread;
        }
    }
    return null;
}

public static synchronized void remoreThread(String name) {
    FunThread funThread = find(name);
    if (funThread == null) remoreThread(funThread);
}

public static synchronized FunThread getRandom() {
    return random(threads);
}

public static synchronized int aliveSize() {
    return threads.size();
}


动态执行类

执行类由于不需要统计数据了,只需要进行任务池管理即可,所以显得非常简单。

代码语言:javascript
复制
package com.funtester.frame.execute;

import com.funtester.base.constaint.FunThread;
import com.funtester.base.interfaces.IFunController;
import com.funtester.config.HttpClientConstant;
import com.funtester.frame.SourceCode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;

/**
 * 动态压测模型的启动类
 */
public class FunConcurrent extends SourceCode {

    private static Logger logger = LogManager.getLogger(FunConcurrent.class);

    /**
     * 任务集
     */
    public List<FunThread> threads = new ArrayList<>();

    /**
     * 线程池
     */
    public static ExecutorService executorService;

    public static IFunController controller;

    /**
     * @param threads 线程组
     */
    public FunConcurrent(List<FunThread> threads) {
        this.threads = threads;
        executorService = ThreadPoolUtil.createCachePool(HttpClientConstant.THREADPOOL_MAX);
    }

    private FunConcurrent() {

    }

    /**
     * 执行多线程任务
     * 默认取list中thread对象,丢入线程池,完成多线程执行,如果没有threadname,name默认采用desc+线程数作为threadname,去除末尾的日期
     */
    public void start() {
        if (controller == null) controller = new FunTester();
        new Thread(controller,"接收器").start();
        threads.forEach(f -> addTask(f));
    }

    public static void addTask(FunThread thread) {
        boolean b = FunThread.addThread(thread);
        logger.info("任务{}添加{}", thread.threadName, b ? "成功" : "失败");
        if (b) executorService.execute(thread);
    }

    public static void addTask() {
        FunThread thread = FunThread.getRandom();
        addTask(thread.clone());
    }

    public static void removeTask(FunThread thread) {
        logger.info("任务{}被终止", thread.threadName);
        FunThread.remoreThread(thread);
    }

    public static void removeTask(String name) {
        logger.info("任务{}被终止", name);
        FunThread.remoreThread(name);
    }

    public static void removeTask() {
        FunThread thread = FunThread.getRandom();
        removeTask(thread);
    }


}

处理外部因子多线程类

这里我实现的比较简单,只实现了加一和减一以及终止,后续会增加批量增减的功能,以及动态从Groovy脚本中引入压测任务,当然这依赖于更精细化的任务池管理。

代码语言:javascript
复制
    private static class FunTester implements IFunController {

        boolean key = true;

        @Override
        public void run() {
            while (key) {
                String input = getInput();
                switch (input) {
                    case "+":
                        add();
                        break;
                    case "-":
                        reduce();
                        break;
                    case "*":
                        over();
                        key = false;
                        break;
                    default:
                        break;
                }
            }
        }

        @Override
        public void add() {
            addTask();
        }

        @Override
        public void reduce() {
            removeTask();
        }

        @Override
        public void over() {
            logger.info("动态结束任务!");
            FunThread.stop();
        }

    }

基本功能已经实现,下面让我们来测试一下吧。

测试

测试脚本

我用了两个任务当做基础任务,然后执行压测,通过键盘输出控制用例增减。视频演示版本在最后面,或者去B站以及视频号关注我,都叫FunTester。

代码语言:javascript
复制
package com.funtest.funthead;

import com.funtester.base.constaint.FunThread;
import com.funtester.frame.SourceCode;
import com.funtester.frame.execute.FunConcurrent;

import java.util.Arrays;

public class Ft extends SourceCode {

    public static void main(String[] args) {
        FunTester e2 = new FunTester("task A");
        FunTester e22 = new FunTester("task B");
        new FunConcurrent(Arrays.asList(e2, e22)).start();
    }

    private static class FunTester extends FunThread {

        public FunTester(String name) {
            super(null, name);
        }

        @Override
        protected void doing() throws Exception {
            sleep(3.0 + getRandomDouble());
            output(threadName + TAB + "任务正在运行!");
        }

        @Override
        public FunThread clone() {
            return new FunTester(this.threadName + "克隆体");
        }

    }

}

控制台输出

以下只展示一些必要的信息,用例必需手动终止或者被外界触发终止。克隆失败的原因是任务名称重复,计划以任务名称作为任务的标志进行管理,所以不能重复。避免重复可以在com.funtest.funthead.Ft.FunTester#clone实现名称赋值随机和唯一性。

代码语言:javascript
复制
INFO-> main 当前用户:oker,工作目录:/Users/oker/IdeaProjects/funtester/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> main 任务task A添加成功
INFO-> main 任务task B添加成功
INFO-> FT-2   task B 任务正在运行!
INFO-> FT-1   task A 任务正在运行!
+
INFO-> 接收器 输入内容:+
INFO-> 接收器 任务task A克隆体添加成功
INFO-> FT-2   task B 任务正在运行!
INFO-> FT-1   task A 任务正在运行!
INFO-> FT-3   task A克隆体 任务正在运行!
+
INFO-> 接收器 输入内容:+
INFO-> 接收器 任务task A克隆体添加失败
INFO-> FT-2   task B 任务正在运行!
INFO-> FT-1   task A 任务正在运行!
INFO-> FT-3   task A克隆体 任务正在运行!
+
INFO-> 接收器 输入内容:+
INFO-> 接收器 任务task B克隆体添加成功
INFO-> FT-3   task A克隆体 任务正在运行!
INFO-> FT-2   task B 任务正在运行!
INFO-> FT-4   task B克隆体 任务正在运行!
INFO-> FT-1   task A 任务正在运行!
_
INFO-> 接收器 输入内容:_
INFO-> FT-3   task A克隆体 任务正在运行!
-
INFO-> 接收器 输入内容:-
INFO-> 接收器 任务task B克隆体被终止
INFO-> FT-4   task B克隆体 任务正在运行!
INFO-> FT-2   task B 任务正在运行!
INFO-> FT-1   task A 任务正在运行!

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-11-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FunTester 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 改造
    • 多线程任务类
      • 管理功能
        • 动态执行类
          • 处理外部因子多线程类
          • 测试
            • 测试脚本
              • 控制台输出
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档