After the foreshadowing of plan 1 and Plan 2, plan 3 is also ready to come out, this is respectfully.

First of all, we will review the idea and implementation of the previous two schemes as usual:

  • Use Case Scheme of Distributed Performance Testing Framework (PART 1)
  • Functional Verification of Distributed Performance Testing Framework Based on Docker
  • Fixed QPS model at high QPS
  • Use Case Scheme of Distributed Performance Testing Framework (PART II)
  • Functional Verification of Distributed Performance Testing Framework Based on Docker

Script-based pressure test scenarios

The performance test script is based on the FunTester performance test framework. In the previous scheme 2, we need to write the use case into the basic JAR package and then call it through reflection. The flexibility is that the main variables of the use case can be parameterized, but the rigidity is that the overall design of the use case has been completed, so the parameterization ability is limited. If use case scenarios need to be added, this approach will not help, and sometimes even a small change in use case will cause the use case to fail.

Here we need a more flexible use case form and run form. Run the test scripts directly with the service. The scripts here are divided into Groovy scripts and Java scripts. Thanks to Groovy’s powerful compatibility, it is OK to treat Java scripts as Groovy scripts in most cases.

Here, users need to edit the script locally, upload the script content to the server, and the performance test service can run the script directly to get the test results. I’ve ruled out file uploading here because it’s too cumbersome, managing files makes things more complicated, and scripts are not very long and can be stored in a database or on Git, which is what the NGrinder does.

These use cases do not need to be deployed and can be run directly. It is convenient for storage, management, and modification. At present, it is compatible with all scenarios of plan 1 and Plan 2, and applies to almost all HTTP protocol interface tests, including more use case scenarios, including single-link, multi-link, and full-link tests. Interfaces that need to be signed on every request are also good choices. This is basically the FunTester testing framework’s ultimate solution to HTTP protocol interface testing.

PS: IN the future, I will strengthen the support of protocols such as Dubbo, Socket, Redis and MySQL. In addition, I will support the parameter transfer function between multiple scripts.

To realize the Demo

Here I wrote a test class, achieve no reference method, the base type parameters method, the String object type, String array type parameter performance of the four methods of reflection Demo function based on com. Funtester. Frame. The execute. ExecuteSource class, The main function of this class is to execute the methods in the JAR package. I’ll leave the code for this class at the end, but you can also read the original article to see the latest code in the repository.

Here is the code for the test class, which is actually a simple single-interface test script:


import com.funtester.config.Constant
import com.funtester.frame.execute.Concurrent
import com.funtester.frame.thread.RequestThreadTimes
import com.funtester.httpclient.ClientManage
import com.funtester.httpclient.FunLibrary
import com.funtester.utils.ArgsUtil
import org.apache.http.client.methods.HttpGet

class Share extends FunLibrary{

    public static void main(String[] args) {
        ClientManage.init(10.5.0, EMPTY, 0);
        def util = new ArgsUtil(args)
        int thread = util.getIntOrdefault(0.20);
        int times = util.getIntOrdefault(1.100);
        String url = "http://localhost:12345/m";
        HttpGet get = getHttpGet(url);
        Constant.RUNUP_TIME = 0;
        RequestThreadTimes task = new RequestThreadTimes(get, times);
        new Concurrent(task, thread, "Local Fixed QPS Test").start(); testOver(); }}Copy the code

Here’s how to do it:

        String s = RWUtil.readTxtByString("/Users/oker/IdeaProjects/funtester/src/test/groovy/com/funtest/groovytest/Share.groovy");
        ExecuteGroovy.executeScript(s);
Copy the code

If you want to parameterize your script, as I did with thread count and request count and soft start time, you’ll need to write a new method, because direct scripts including reflection do not recognize String[] parameters effectively.

You need to add a method like this:

    public static void test(String params) {
        main(params.split(COMMA))
    }

Copy the code

Finally, request this method to achieve the purpose of parameterization, execute as follows:

        ExecuteGroovy.executeFileMethod("/Users/oker/IdeaProjects/funtester/src/test/groovy/com/funtest/groovytest/Share.groovy"."test"."20100");
Copy the code

The main consideration here is the possibility of executing the test case manually on the server, so the content of the use case is written in the main method, as well as the syntax of Groovy scripts, which is to write the content directly and execute it independently of classes and methods.

        import com.funtester.config.Constant
        import com.funtester.frame.execute.Concurrent
        import com.funtester.frame.thread.RequestThreadTimes
        import com.funtester.httpclient.ClientManage
        import com.funtester.httpclient.FunLibrary
        import com.funtester.utils.ArgsUtil
        import org.apache.http.client.methods.HttpGet

        ClientManage.init(10.5.0."".0);
        def util = new ArgsUtil(args)
        int thread = util.getIntOrdefault(0.20);
        int times = util.getIntOrdefault(1.100);
        String url = "http://localhost:12345/m";
        HttpGet get = getHttpGet(url);
        Constant.RUNUP_TIME = 0;
        RequestThreadTimes task = new RequestThreadTimes(get, times);
        new Concurrent(task, thread, "Local Fixed QPS Test").start();
Copy the code

One disadvantage of this scheme is that it is difficult to parameterize and requires the combination of groovy.lang.binding, which inevitably makes it more complicated to use, so I have given it up for now. The functionality planned later may not get around this point when passing parameters across scripts.

Use case to create

Because of the use of scripting cases, in fact, the previous scheme 2 use cases can be reused, just do not compile and package the use cases. Below share a single link test case, interested can pass through a single link performance test practice article link design and implementation ideas. Just share the script here, as follows:


import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import com.funtester.base.bean.AbstractBean
import com.funtester.base.constaint.ThreadLimitTimesCount
import com.funtester.frame.execute.Concurrent
import com.funtester.httpclient.ClientManage
import com.funtester.utils.ArgsUtil
import com.okayqa.composer.base.OkayBase
import com.okayqa.composer.function.Mirro
import com.okayqa.composer.function.OKClass

class Login_collect_uncollect extends OkayBase {

    public static void main(String[] args) {
        ClientManage.init(10.5.0."".0)
        def util = new ArgsUtil(args)
        def thread = util.getIntOrdefault(0.30)
        def times = util.getIntOrdefault(1.40)

        def tasks = []

        thread.times {
            tasks << new FunTester(it, times)
        }

        new Concurrent(tasks, "Resource Library 1.4 Login > Query > Favorites > Unfavorites Link Pressure Test").start()

        allOver()
    }

    private static class FunTester extends ThreadLimitTimesCount<Integer> {

        OkayBase base

        def mirro

        def clazz

        FunTester(Integer integer, int times) {
            super(integer, times, null)}@Override
        void before(a) {
            super.before()
            base = getBase(t)
            mirro = new Mirro(base)
            clazz = new OKClass(base)
        }

        @Override
        protected void doing(a) throws Exception {

            def klist = mirro.getKList()
            def karray = klist.getJSONArray("data")
            K ks
            karray.each {
                JSONObject parse = JSON.parse(JSON.toJSONString(it))
                if (ks == null) {
                    def level = parse.getIntValue("node_level")
                    def type = parse.getIntValue("ktype")
                    def id = parse.getIntValue("id")
                    ks = new K(id, type, level)
                }
            }
            JSONObject response = clazz.recommend(ks.id, ks.type, ks.level)
            def minis = []
            int i = 0
            response.getJSONArray("data").each {
                if (i++ < 2) {
                    JSONObject parse = JSON.parse(JSON.toJSONString(it))
                    int value = parse.getIntValue("minicourse_id")
                    minis << value
                }
            }
            clazz.unCollect(random(minis))

            mirro.getMiniCourseListV3(ks.id, ks.type, 0, ks.level)
        }
    }

    private static class K extends AbstractBean {

        int id

        int type

        int level

        K(int id, int type, int level) {
            this.id = id
            this.type = type
            this.level = level
        }
    }

}


Copy the code

Use case transmission

Upload the use case

In fact, use cases can be treated as String objects, upload use cases, save use cases, edit use cases can also follow the same idea.

Allocate cases

Assigning the use cases is actually assigning the use cases to slave nodes to run. Here I have adopted the NGrinder approach, which does not deal with the custom parameters of the use cases, including but not limited to the number of threads, number of requests, request time, soft start time, etc., but controls the pressure multiplier by specifying the number of nodes to run.

Case execution

This is the way to execute a specific use case in a test task, as described in the Demo beginning with this article. The code for the execution class is as follows:

package com.funtester.frame.execute;

import com.alibaba.fastjson.JSON;
import com.funtester.base.exception.FailException;
import com.funtester.frame.SourceCode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

public class ExecuteSource extends SourceCode {

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

       /** ** execute method * <p> to prevent compilation error, use list </p> **@param params
     */
    public static Object executeMethod(List<String> params) {
        Object[] objects = params.subList(1, params.size()).toArray();
        return executeMethod(params.get(0), objects);
    }

    /** ** execute method * <p> to prevent compilation error, use list </p> **@param params
     */
    public static Object executeMethod(String[] params) {
        return executeMethod(Arrays.asList(params));
    }

    /** * Executes a specific method, providing an internal method call * <p> overloaded method </p> ** Error if the argument is an underlying data type@param path
     * @param paramsTpey
     */
    public static Object executeMethod(String path, Object... paramsTpey) {
        int length = paramsTpey.length;
        if (length % 2= =1) FailException.fail("Wrong number of arguments. Should be even.");
        String className = path.substring(0, path.lastIndexOf("."));
        String methodname = path.substring(className.length() + 1); Class<? > c =null;
        Object object = null;
        try {
            c = Class.forName(className);
            object = c.newInstance();
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            logger.warn("Error creating instance object :{}", className, e);
        }
        Method[] methods = c.getDeclaredMethods();
        for (Method method : methods) {
            if(! method.getName().equalsIgnoreCase(methodname))continue;
            try {
                Class[] classs = new Class[length / 2];
                for (int i = 0; i < paramsTpey.length; i +=2) {
                    classs[i / 2] = Class.forName(paramsTpey[i].toString());// Arguments to the underlying data type here will cause an error, but will not affect the following calls
                }
                method = c.getMethod(method.getName(), classs);
            } catch (NoSuchMethodException | ClassNotFoundException e) {
                logger.warn("Method property handling error!");
            }
            try {
                Object[] ps = new Object[length / 2];
                for (int i = 1; i < paramsTpey.length; i +=2) {
                    String name = paramsTpey[i - 1].toString();
                    String param = paramsTpey[i].toString();
                    Object p = param;
                    if (name.contains("Integer")) {
                        p = Integer.parseInt(param);
                    } else if (name.contains("JSON")) {
                        p = JSON.parseObject(param);
                    }
                    ps[i / 2] = p;
                }
                method.invoke(object, ps);
            } catch (IllegalAccessException | InvocationTargetException e) {
                logger.warn("Reflection execution method failed :{}", path, e);
            }
            break;
        }
        return null; }}Copy the code

I have deleted some temporarily useless methods. If you are interested, you can go to the warehouse and click on the original text to go to the FunTester warehouse.


FunTester.Tencent Cloud Author of the Year,Boss direct hire contract author.Official GDevOps media partner, non-famous test development, welcome to follow.

  • FunTester test framework architecture diagram
  • Arthas Quick start video demo
  • How to become a Full stack automation engineer
  • JsonPath utility class unit test
  • Socket interface fixed QPS performance test practice
  • Automated Testing trends in 2021
  • How to test the probabilistic algorithm P= P (1+0.1*N)
  • Appium 2.0 quick reference
  • There is no data to drive automated tests
  • Selenium parallel testing best practices
  • Selenium Test automation tips
  • Groovy handles headers in JMeter