Take you to understand the design of YAML-based interface automation testing framework in ten minutes

When designing an automated testing framework, we often save test data in external files (such as Excel, YAML, CSV) or databases to decouple scripts and data for later maintenance. At present, many automated testing frameworks use Excel or YAML files to directly write test cases, and read them through scripts to drive the execution of automated test codes. As for whether to use Excel or YAML format, everyone has different opinions. For example, using Excel is intuitive to maintain and convenient to modify data. The disadvantage is that it is not easy to compare historical version differences through version control tools such as Git (because it is in binary format); the advantage of YAML It supports complete data format and convenient version control management (text format). The disadvantage is that it is not as intuitive as Excel. Everyone is familiar with the Excel method. This article will take you to understand how to design an automated testing framework based on YAML.

YAML format test case design

Taking interface automation as an example, the basic functional requirements to be realized by writing test cases in YAML:

  1. A YAML file can support the storage requirements of multiple use cases, otherwise thousands of use cases corresponding to thousands of YAML files cannot be managed
  2. The use cases can support single interface test cases and business scenario use cases (combination of multiple interface calls)
  3. The use case needs to include information such as the module it belongs to, use case name, request information, assertion information, extraction response (to implement interface association), etc.

Based on the above requirements, we design a version of the YAML format use case:

- casename: 登录成功
  module: 用户模块
  teststeps:
    - name: 正确用户名、密码进行登录
      request:
        method: POST
        url: /login
        headers:
          Content-Type: application/json
        json:
          username: lemon_auto
          password: lemon123456
          appType: 3
          loginType: 0
      extract:
        token: access_token
      validate:
        - eq: ["status_code", 200]
        - eq: ["nickName", "lemon_auto"]

The casename and module fields are simple. Let's look at teststeps. Why is teststeps an array type?

Because a use case contains one/multiple interface request steps, that is, a TestCase contains multiple teststeps, and each teststep is an interface request.

Request specifies the interface request information, including the interface request method, request address, request header, and request parameters; among them, we need to distinguish between different request parameter types. The above is json parameter passing. If it is a form form and query parameter passing parameters, we all It can be agreed to a similar key-value structure, just change json to formparam and queryparam.

It should be noted that the parameters of the file upload interface will be quite special. Generally speaking, we only need to set the path of the file to be uploaded, so we can design it like this:

- casename: 上传图片
  module: 用户模块
  teststeps:
    - name: 正常上传图片
      request:
        method: POST
        url: /p/file/upload
        headers:
          Content-Type: multipart/form-data
        file: src/test/resources/upload.png
      extract:
        resourcesUrl: resourcesUrl
        filePath: filePath
      validate:
        - eq: [ "status_code", 200 ]

The extract field is the response data field to be extracted and passed to the subsequent interface for use. Generally, we need to support JsonPath expression or regular expression to extract, the corresponding key is the field name to be extracted, and the corresponding value is the field expression to be extracted.

The validate field is the assertion information, which is to verify whether the response result meets expectations. Here we need to support commonly used judgment methods including: equal to, greater than, less than, greater than or equal to, less than or equal to, and use the abbreviation eq instead of equals (equal to) to judge, others are similar: greater than or equal to (ge), less than or equal to (le), less than ( lt), greater than (gt).

The above is a single-interface test case. Let’s look at the writing style of multi-interface series (business scenario) test cases:

ModifyUserProfile.yaml

- casename: 修改用户头像
  module: 用户模块
  teststeps:
    - name: 登录成功
      request:
        method: POST
        url: /login
        json:
          username: lemon_auto
          password: lemon123456
          appType: 3
          loginType: 0
        headers:
          Content-Type: application/json
      extract:
        token: access_token
      validate:
        - eq: ["status_code", 200]
        - eq: ["nickName", "lemon_auto"]

    - name: 进入到个人中心
      request:
        method: GET
        url: /p/user/userInfo
        headers:
          Authorization: ${token}
      validate:
        - eq: ["status_code", 200]

    - name: 上传头像
      request:
        method: POST
        url: /p/file/upload
        headers:
          Authorization: ${token}
          Content-Type: multipart/form-data
        file: src/test/resources/upload.png
      extract:
        resourcesUrl: resourcesUrl
        filePath: filePath
      validate:
        - eq: ["status_code", 200]

The most important thing in multi-interface testing is to be able to support parameter passing. Here we use extract to extract the response field of the interface in the previous interface, and use ${token} to reference it in the interface to be used later. Familiar with the Jmeter interface testing tool Students should be very familiar with this format.

Script to read YAML data

Before reading YAML file data, we first need to understand two concepts: serialization and deserialization

  • The process of converting an object into a byte sequence is called object serialization;
  • The process of restoring a sequence of bytes to an object is called deserialization of the object.

And our process of reading YAML can be called deserialization.

The mainstream programming language can realize the analysis of YAML, and then take the Java language as an example to explain how to read the content of the YAML file:

There are many libraries in Java that can implement YAML serialization and deserialization, including SnakeYaml, Jackson, jYaml, etc., which are similar in use. Take Jackson as an example:

Step 1: Add the coordinates of the library to the Maven POM file

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.10.2</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
    <version>2.10.2</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

Jackson-databind and jackson-dataformat-yaml are used here, where jackson-databind is the main library of Jackson, and jackson-dataformat-yaml is a library that supports the YAML data format. Lombok is also introduced here for later writing entity classes Simplify writing some code:

Lombok can help us simplify some necessary but bloated Java code tools (such as get/set methods). By using the corresponding annotations, the corresponding methods can be automatically generated when compiling the source code.

Step 2: Write YAML entity class

Compare the content of the YAML file, such as the field name (such as "name") and the data type of the field (such as a string), to create a corresponding class to represent the information of the YAML file in Java. The purpose is to be able to save YAML files into Java objects (deserialization).

TestCase entity class:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TestCase {
    private String casename;
    private String module;
    private List<Teststep> teststeps;
}

Teststep entity class:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teststep {
    private String name;
    private Request request;
    private HashMap<String,String> extract;
    private List<Validate> validate;
}

Validate entity class:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Validate {
    private List<Object> eq;
    private List<Object> gt;
    private List<Object> ge;
    private List<Object> lt;
    private List<Object> le;
}

Read the content of the YAML file through Jackson and save it into the TestCase entity class object

public static List<TestCase> loadYaml(String path){
    ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
    List<TestCase> cases = null;
    try {
        cases = objectMapper.readValue(new File(path), new TypeReference<List<TestCase>>() {});
    } catch (IOException e) {
        System.out.println(path+"格式非法,请检查配置");
        e.printStackTrace();
    }
    return cases;
}

Among them, new TypeReference<List<TestCase>>() {} is because there are multiple TestCase use cases in the read YAML file, so we need to define it as a List collection type to receive.

Let's take a look at the effect after reading:

Then you can use the returned testCase to initiate interface requests (for example, through REST-assured), perform interface assertions, extract response fields, and other operations.

Guess you like

Origin blog.csdn.net/a448335587/article/details/129864623