Some time ago, I led the team to implement a general pressure measurement platform based on Locust+boomer. The main purpose was to meet various pressure measurement scenarios in our group, such as GRPC, Websocket, WEBRTC, HTTP and other protocols. As our company’s technology stack is dominated by GO, we can easily use GO to write scripts, compile and package them through the company’s deployment platform, and then expand and shrink the pressure cluster horizontally, which can be said to solve all kinds of difficult pressure measurement requirements and have good performance. However, we found that although it was very free to write scripts by ourselves, the use cost was quite high for students who did not know the platform and Go, especially for the first time. Therefore, I began to think about how to simplify the writing and deployment of scripts.

Starting from the HTTP

Taking inspiration from Httprunner and other tools in the company’s Group, I came up with the idea of using JSON to define HTTP compression scenarios and then using GO to parse and execute them. Predictably, the compression performance is not as good as writing code, but if there is an acceptable performance loss in exchange for easier access, A more uniform way of using it would also be great, since we don’t lack for machines. For the HTTP protocol, the following Outlines the capabilities that need to be implemented.

A quick note:

  • Multiple interfaces

    It is well understood that the pressure test needs to meet the requirements of multiple interfaces in a certain proportion at the same time. In some special scenarios, there may be interface dependencies that need to be taken into account.

  • Login state

    The interface of pressure test may have login verification, so you need to bring the login state during pressure test. If you can get through the account platform, it will be much more convenient to automatically generate the login state in batches.

  • A parameterized

    The script needs to provide parameterization capability. You can’t write out all parameters, such as dynamic generation of timestamps, ids, variable length strings, etc. If simple parameter generation is not enough, users can upload their own.

  • check

    Response content validation is an important part of interface testing, as well as in pressure testing scenarios.

Defining the Json structure

Next, define the Json structure to try to meet the requirements described above. HTTP contains only three parts: body, header, and URL, so each interface needs to contain these three fields. Of course, the name is essential. There is also a very important field, which is the validator.

{
    "debug": true."domain": "https://postman-echo.com"."header": {},  
    "declare": [
        "{{ $sessionId := getSid }}"]."init_variables": {
        "roomId": 1001."sessionId": "{{ $sessionId }}"."ids": "{{ $sessionId }}"
    },
    "running_variables": {
        "tid": "{{ getRandomId 5000 }}"
    },
    "func_set": [{"key": "getTest"."method": "GET"."url": "/get? name=gannicus&roomId={{ .roomId }}&age=10&tid={{ .tid }}"."body": "{\"timeout\":10000}"."validator": "{{ and (eq .http_status_code 200) (eq .args.age (10 | toString )) }}"
        },
        {
            "key": "postTest"."method": "POST"."header": {
                "Cookie": "{{ .tid }}"."Content-Type": "application/json"
            },
            "url": "/post? name=gannicus"."body": "{\"timeout\":{{ .tid }}, \"retry\":true}"."validator": "{{ and (eq .http_status_code 200) (eq .data.timeout (.tid | toFloat64 ) ) (eq .data.retry false) }}"}}]Copy the code

Func_set should be easy to understand. Declare, init_variables, running_variables:

  • Declare The declare field is used to declare variables, such as init or running variables, which can be referenced as follows:

    [
    	"{{ $sessionId := getSid }}"."{{ $id := 100100 }}"
    ]
    Copy the code
  • init_variables

    Initialize a variable only once. It can be a constant or retrieved from a template function, such as:

    {
            "roomId": 1001."sessionId": "{{ $sessionId }}"."ids": "{{ now }}"
    }
    Copy the code
  • running_variables

    Runtime variables, parameters are constructed before each request is issued, so constant definitions are not recommended here.

    {
        	"tid": "{{ getRandomId 5000 }}"
    }
    Copy the code

Analytical process

To take advantage of boomer, you need to generate boomer.task, which has the following structure:

type Task struct {
	// The weight is used to distribute goroutines over multiple tasks.
	Weight int
	// Fn is called by the goroutines allocated to this task, in a loop.
	Fn   func(a)
	Name string
}
Copy the code

An anonymous function is defined for each request declared in func_set according to the init and running variables respectively. The function dynamically generates variables, then makes real requests, and finally asserts according to the declared validator of each request. The entire execution process is as follows:

Realize the principle of

There is a template-related library text/template in go’s native library. I directly use the template library to realize this set of parsing logic, including the generation of parameters, template methods and assertions. The syntax of the whole JSON script is based on go’s template library. Interested friends can check:

  • Official Template Library
  • The Go language standard library text/template package is simple

How to assert that

Because the assertion part is very important, I’ll do it alone. As mentioned above, assertions are implemented through templates, so using assertions requires basic template syntax.

The template library has built-in comparison and logical methods so you can use them directly, such as comparing HTTP status codes:

"validator": "{{ eq .http_status_code 200 }}"
Copy the code

Another example is multiple comparisons:

"validator": "{{ and  (eq .http_status_code 200) (eq .data.timeout (.tid | toFloat64 ) ) (eq .data.retry false) }}"
Copy the code

You may also have noticed that there is a toFloat64, which is a template custom function for type conversion.

Also, you can see how easy it is to access variables based on the GO template library, such as.data.timeout above, which corresponds to something like this in the response:

{
	"data": {"timeout": 1000}}Copy the code

This allows us to compare any field in the response JSON.

Write in the last

The performance of template parsing in CPU-intensive scenarios is about 1/3 of that of script compilation. If it is not CPU intensive, it can be up to 1/2. Therefore, it is not optimized for now. Surprisingly, this template parsing can also be extended to write interface tests, similar to Httprunner.

At present, it is only a Demo, which has not been integrated into our pressure measuring platform. It is still very exciting.

Source code address: github.com/bugVanisher…

About the author

My name is Jianhuan, a test developer who is exploring the possibilities of testing.

If you are interested in me, you can find me here:

  • Github address: bugVanisher
  • Personal blog: bugvanisher.cn/

Eager to exchange ideas