zoukankan      html  css  js  c++  java
  • 基于docker的分布式性能测试框架功能验证(一)

    本文是FunTester测试框架分布式性能测试功能拓展实践,是一种比较粗略的技术验证实践,技术方案采用分布式性能测试框架用例方案设想(一)中所设想场景。

    粗实现方案分成三块:master调度机slave测试机server被测服务

    • master调度机:处理用例、分配任务
    • slave测试机:接受任务、执行用例
    • server被测服务:提供测试接口

    docker镜像

    刚开始学,学了点皮毛,这里只分享几个简单步骤,各位要是有兴趣的话,还是需要一个完成的docker教程的。

    基础镜像

    这里我选择了Groovy:latest版本作为基础镜像,里面是Groovy 3.0.8,各位使用的使用请注意这个版本需要跟自己项目依赖的Groovy版本一致,不然会报错:

    Caused by: groovy.lang.GroovyRuntimeException: Conflicting module versions. Module [groovy-xml is loaded in version 3.0.8 and you are trying to load version 2.5.7
    

    启动容器

    使用命令docker run -it -u 0 --name funtester aed55a7f14d3 /bin/bash启动容器,这里参数-u 0使用root身份登录,不然会使用groovy账户登录,导致权限不足的报错。

    设置网络

    因为我的master调度机放在本机了,所以多了一个设置容器访问本地主机端口的步骤。

    请参考官网文档:

    主机的IP地址正在更改(如果没有网络访问权限,则没有IP地址)。我们建议您连接到特殊的DNS名称host.docker.internal,该名称 解析为主机使用的内部IP地址。这是出于开发目的,不适用于Docker Desktop for Windows以外的生产环境。

    这个功能在安装docker desktop的时候已经默认打开了,所以直接用域名host.docker.internal替换localhost即可访问master调度机服务接口。

    安装vim

    安装vimapt-get update && apt-get install apt-file -y && apt-file update && apt-get install vim

    更新依赖

    这里需要使用docker cp命令将本机打包好的jar包,推送到容器中的Groovy lib目录中。

    更新镜像

    使用命令:docker commit -a "funtester" -m "update groovy" c9596359c1d1 funtester/groovy:v1

    更新脚本

    将写好的脚本推送到容器中,然后启动对应的脚本(下面会分享),就可以执行验证工作了。这里应当使用dockerfile,原谅我才看了两天,dockerfile还不是很熟练,我打算放在Springboot项目中编写dockerfile文件。

    master调度机

    这里我只实现了一种调度功能:就是提供一个接口,该接口返回一个测试用例(尚未封装对象)。提供给slave测试机请求,返回给测试机测试任务(测试用例)。

    听起来这是一个服务了,但是我现在还没开始写Springboot项目,只能用funtester moco server代替了这个功能。对于用例管理等其他功能还没有实现。

    master脚本

    import com.alibaba.fastjson.JSONObject
    import com.funtester.base.bean.Result
    import com.funtester.httpclient.FunLibrary
    import com.funtester.httpclient.FunRequest
    import com.mocofun.moco.MocoServer
    import com.sun.deploy.ui.FancyButton
    import org.apache.http.client.methods.HttpGet
    
    class DcsServer extends MocoServer {
    
        public static void main(String[] args) {
            def server = getServer(12345)
            def res = new JSONObject()
            res.times = 1000
            res.thread = 20
            res.mode = "ftt"
            res.desc = "FunTester分布式测试Demo"
            res.runup = 10
            String url = "http://192.168.80.169:12345/m"
            def get = FunLibrary.getHttpGet(url)
            def request = FunRequest.initFromRequest(get)
            res.request = request
            output(res)
            server.get(urlStartsWith("/m")).response(obRes(Result.success(res)))
    
            def run = run(server)
    
            waitForKey("fun")
    
            run.stop()
    
        }
    }
    
    

    其中http://192.168.80.169:12345/m是被测服务暴露的测试接口,也是用funtester moco server做的,后面也会分享脚本内容。

    这里我根据测试机中方法com.funtester.httpclient.FunRequest#initFromString和一些必要的参数创建了一个JSON格式的接口返回。其中在request赋值的时候,我采用的方式是:

            def request = FunRequest.initFromRequest(get)
            res.request = request
    
    

    能省去不少麻烦,直接把request对象放到响应结果中。

    下面是具体的响应结果:

    ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
    >  {
    >  ① . "code":0,
    >  ① . "data":{
    >  ② . . . "mode":"ftt",
    >  ② . . . "request":{
    >  ③ . . . . . "args":{
    >  ④ . . . . . . . 
    >  ③ . . . . . },
    >  ③ . . . . . "headers":[
    >  ④ . . . . . . . 
    >  ③ . . . . . ],
    >  ③ . . . . . "path":"",
    >  ③ . . . . . "request":{
    >  ④ . . . . . . . "allHeaders":[
    >  ⑤ . . . . . . . . . 
    >  ④ . . . . . . . ],
    >  ④ . . . . . . . "method":"GET",
    >  ④ . . . . . . . "aborted":false,
    >  ④ . . . . . . . "protocolVersion":{
    >  ⑤ . . . . . . . . . "protocol":"HTTP",
    >  ⑤ . . . . . . . . . "major":1,
    >  ⑤ . . . . . . . . . "minor":1
    >  ④ . . . . . . . },
    >  ④ . . . . . . . "requestLine":{
    >  ⑤ . . . . . . . . . "method":"GET",
    >  ⑤ . . . . . . . . . "protocolVersion":{
    >  ⑥ . . . . . . . . . . . "$ref":"$.data.request.request.protocolVersion"
    >  ⑤ . . . . . . . . . },
    >  ⑤ . . . . . . . . . "uri":"http://192.168.80.169:12345/m"
    >  ④ . . . . . . . },
    >  ④ . . . . . . . "params":{
    >  ⑤ . . . . . . . . . "names":[
    >  ⑥ . . . . . . . . . . . 
    >  ⑤ . . . . . . . . . ]
    >  ④ . . . . . . . },
    >  ④ . . . . . . . "uRI":"http://192.168.80.169:12345/m"
    >  ③ . . . . . },
    >  ③ . . . . . "requestType":"GET",
    >  ③ . . . . . "response":{
    >  ④ . . . . . . . "code":-2,
    >  ④ . . . . . . . "FunTester":200,
    >  ④ . . . . . . . "content":"hello funtester!!!"
    >  ③ . . . . . },
    >  ③ . . . . . "host":"",
    >  ③ . . . . . "json":{
    >  ④ . . . . . . . 
    >  ③ . . . . . },
    >  ③ . . . . . "params":{
    >  ④ . . . . . . . 
    >  ③ . . . . . },
    >  ③ . . . . . "uri":"http://192.168.80.169:12345/m"
    >  ② . . . },
    >  ② . . . "times":1000,
    >  ② . . . "thread":20,
    >  ② . . . "runup":10,
    >  ② . . . "desc":"FunTester分布式测试Demo"
    >  ① . },
    >  ① . "success":true
    >  }
    ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~
    

    可以看到在data.request节点中,很多信息都是重复的,这并不影响,因为data.request节点下的key我只会取有用的,这个在设计方案时已经说明了,request信息除了com.funtester.httpclient.FunRequest对象属性外,都是从使用fastjson提供的工具类对org.apache.http.client.methods.HttpRequestBase进行序列化得到的。

    slave测试机

    这个逻辑通过简单的轮询去master调度机提供的接口获取测试任务或者测试用例。然后解析,执行测试用例。

    脚本如下:

    
    import com.funtester.config.Constant
    import com.funtester.frame.execute.Concurrent
    import com.funtester.frame.thread.RequestThreadTimes
    import com.funtester.httpclient.FunLibrary
    import com.funtester.httpclient.FunRequest
    
    class Dcs extends FunLibrary {
    
        public static void main(String[] args) {
            while (true) {
                String url = "http://host.docker.internal:12345/m"
                //请求此接口会返回一个用例,目前没有用对象封装
                //            String url = "http://localhost:12345/m"//本机调试用的
                def get = getHttpGet(url)
                def response = getHttpResponse(get)
                if (response.getInteger("code") == 0) {
                    def data = response.getJSONObject("data")
                    def r = data.getString("request")
                    /*此处以后会简化成一个对象*/
                    def request = FunRequest.initFromString(r).getRequest()
                    //压测模式
                    def times = data.getIntValue("times")
                    //测试终止条件
                    def mode = data.getString("mode")
                    //线程数,这里默认固定线程模式
                    def thread = data.getIntValue("thread")
                    //软启动时间
                    def runup = data.getIntValue("runup")
                    //用例描述
                    def desc = data.getString("desc")
                    if (mode == "ftt") {
                        Constant.RUNUP_TIME = runup
                        def task = new RequestThreadTimes(request, times)
                        def performanceResultBean = new Concurrent(task, thread, desc).start()
                    }
                }
                sleep(5.0)
            }
        }
    }
    
    

    后期应该会把每个不同的响应体中data封装成不同的对象,这样处理数据会比较简单。

    关于测试mode,目前支持了四种(固定线程|固定QPS * 限制请求次数|限制请求时间),之前都分享过了,这里就不多说。

    server被测服务

    同样采用了funtester moco serverr编写,脚本如下:

    import com.mocofun.moco.MocoServer
    
    class TestDemo extends MocoServer{
    
        static void main(String[] args) {
            def log = getServer(12345)
            log.response("hello funtester!!!")
    
    
            def run = run(log)
            waitForKey("fan")
            run.stop()
    
    
        }
    }
    

    这次我没有屏蔽日志,这样比较方便观察slave测试机是请求是否正确,单词请求的日志内容如下:

    
    Request received:
    
    GET /m HTTP/1.1
    Host: 192.168.80.169:12345
    Connection: Keep-Alive
    User-Agent: Apache-HttpClient/4.5.6 (Java/1.8.0_282)
    Accept-Encoding: gzip,deflate
    content-length: 0
    
    Response return:
    
    HTTP/1.1 200
    Content-Length: 18
    Content-Type: text/plain; charset=utf-8
    
    hello funtester!!!
    

    FunTester腾讯云年度作者Boss直聘签约作者GDevOps官方合作媒体,非著名测试开发。

  • 相关阅读:
    003 01 Android 零基础入门 01 Java基础语法 01 Java初识 03 Java程序的执行流程
    002 01 Android 零基础入门 01 Java基础语法 01 Java初识 02 Java简介
    001 01 Android 零基础入门 01 Java基础语法 01 Java初识 01 导学
    001 Android Studio 首次编译执行项目过程中遇到的几个常见问题
    Dora.Interception,为.NET Core度身打造的AOP框架 [2]:以约定的方式定义拦截器
    Dora.Interception,为.NET Core度身打造的AOP框架 [1]:更加简练的编程体验
    监视EntityFramework中的sql流转你需要知道的三种方式Log,SqlServerProfile, EFProfile
    轻量级ORM框架——第二篇:Dapper中的一些复杂操作和inner join应该注意的坑
    轻量级ORM框架——第一篇:Dapper快速学习
    CF888G Xor-MST(异或生成树模板)
  • 原文地址:https://www.cnblogs.com/FunTester/p/14790987.html
Copyright © 2011-2022 走看看