# zhl-api-test

> Test API

Latest version **1.1.17** (published 2021-02-01) · ISC license · 0 weekly downloads

## Install

```sh
npm install zhl-api-test
pnpm add zhl-api-test
yarn add zhl-api-test
bun add zhl-api-test
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.1.17 |
| Published | 2021-02-01 |
| First published | 2020-10-18 |
| Weekly downloads | 0 |
| License | ISC |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 6 |
| Unpacked size | 58.4 KB |
| Known vulnerabilities | 0 (+25 in 1 direct dependencies) |
| Install scripts | no |
| Author | zhuhelong@foxmail.com |
| Maintainers | zhuhelong |

## Links

- npm: https://www.npmjs.com/package/zhl-api-test
- npm.io page: https://npm.io/package/zhl-api-test

## Dependencies (6)

- [qs](https://npm.io/package/qs.md) ^6.9.4
- [axios](https://npm.io/package/axios.md) ^0.20.0
- [lodash](https://npm.io/package/lodash.md) ^4.17.20
- [form-data](https://npm.io/package/form-data.md) ^3.0.0
- [require-all](https://npm.io/package/require-all.md) ^3.0.0
- [tough-cookie](https://npm.io/package/tough-cookie.md) ^4.0.0

## Recent versions

- 1.1.17 (latest) — 2021-02-01
- 1.1.16 — 2021-02-01
- 1.1.15 — 2021-02-01
- 1.1.14 — 2021-01-23
- 1.1.13 — 2021-01-18
- 1.1.12 — 2021-01-18
- 1.1.11 — 2021-01-18
- 1.1.10 — 2021-01-18
- 1.1.9 — 2020-10-27
- 1.1.8 — 2020-10-27
- 1.1.7 — 2020-10-27
- 1.1.6 — 2020-10-27
- 1.1.5 — 2020-10-27
- 1.1.3 — 2020-10-18
- 1.1.2 — 2020-10-18
- … 4 more at https://npm.io/package/zhl-api-test/versions

## README

# 功能

- [x] 尽量简单，不要有复杂的依赖；
- [x] HTTP API 测试；
- [x] 因为 API 之间有依赖关系，所以要能按照依赖关系进行执行；
- [x] 某些 API 需要从新的 Session 开始执行，需要满足可以设定 API 测试线从新的 Session 执行；
- [x] API 可以多次循环测试；
- [x] API 线可以多次循环测试；
- [ ] 方便的开启 debug 功能；

# 使用方法

作者习惯使用 Spring Boot 搭建服务，故使用 Spring Boot 进行说明。

假设 Spring Boot 项目已经搭建好，现在需要开始 API 测试，目录结构如下：

```
spring-boot-project
    |---- ...
    |---- ...
    |---- ...
```

## 导入框架

打开 CMD，cd 到 `spring-boot-project` 工程目录下。

新建 node 项目，如 `api-test`

### 创建目录 api-test

```shell
mkdir api-test
cd api-test
```

### npm 初始化 node 项目

```shell
npm init
```

然后根据提示填写信息，不清楚的话就一路回车。

### 添加依赖库

打开 api-test 目录下 package.json 文件，修改如下：

```json
{
  "name": "api-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  // 添加这几行
  "dependencies": {
    "zhl-api-test": "^1.1.12"
  }
}
```

### 安装依赖

```shell
npm i
```

等待执行完成。

### 导入框架完成

此时目录结构：

```
spring-boot-project
    |---- ...
    |---- ...
    |---- ...
    |---- api-test
         |---- node_modules
              |---- ...
              |---- ...
              |---- ...
         |---- package.json
         |---- package-lock.json
    |---- ...
```

## 添加测试单元

在 api-test 目录中，添加 index.js 文件。文件内容如下：

```javascript
const {StartApiTest} = require('zhl-api-test')

// 正确的请求测试
const TEST_CORRECT_REQUEST = {
    'Hello 测试': {
        dependentItem: [],
        requestConfig: {
            method: 'post',
            url: '/say/hello'
        },
        assert: (data) => {
            console.assert(null != data, "返回数据不为空");
        },
    },
}

// 错误的请求测试
const TEST_WRONG_REQUEST = {
}

const TEST_LIST = Object.assign({}, TEST_CORRECT_REQUEST, TEST_WRONG_REQUEST);

// 进行测试
StartApiTest(TEST_LIST);
```

注：也可在 api-test 中建目录，分模块进行测试。

## 创建自动化测试流程

在 Spring Boot 中，一般是在 src/test 目录中的 `...ApplicationTests` 类中定义测试。

```java
package ...;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

@SpringBootTest(classes = Application.class,
        webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class ApplicationTests {

    public static final String NODE_ACTUATOR =
            "node 可执行文件位置，Windows 中为 node.exe，Linux 中为 node";
    private static final List<String> outputString = new CopyOnWriteArrayList<>();
    @LocalServerPort
    private int port;

    private void printMessage(final InputStream input) {
        new Thread(() -> {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
            String line;
            try {
                while ((line = bufferedReader.readLine()) != null) {
                    outputString.add(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

    @Test
    void apiTest() {
        System.out.println("port = " + port);
        try {
            File workDir = new File("./api-test");
            Process process = Runtime.getRuntime().exec(NODE_ACTUATOR + " index.js http://127.0.0.1:" + port, null, workDir);

            printMessage(process.getErrorStream());
            printMessage(process.getInputStream());
            int exitVal = process.waitFor();

            System.out.println("\n\n===============================================================================\n\n");
            System.out.println("node test finished, Exit code is " + exitVal);
            AtomicBoolean noErrorMessage = new AtomicBoolean(true);
            outputString.forEach(line -> {
                if (null == line) {
                    return;
                }
                String lineLowerCase = line.trim().toLowerCase();
                if (lineLowerCase.contains("error:")
                        || lineLowerCase.contains("assertion failed")) {
                    System.err.println(line);
                    if (noErrorMessage.get()) {
                        noErrorMessage.set(false);
                    }
                } else {
                    System.out.println(line);
                }
            });
            assert exitVal == 0;
            assert noErrorMessage.get();

            System.out.println("\n\n===============================================================================\n\n");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
```

此时，便完成了自动化测试。

# 测试单元规则详解

单个测试：

```javascript
// 这是一条测试
const TestNodeTemplate = {
    // String，测试名称，可为空
    // 在测试组中作为键值定义即可
    name: '',
    
    // Array<String>，必须定义，所依赖的测试，比如，登录前，需要先请求得到验证码，那么登录测试就得依赖于请求验证码测试
    dependentItem: [],
    
    // Object or Function，必须定义，可以是静态的 Object，也可以定义函数，注：如是函数，则每次请求都会调用函数
    // requestConfig 具体内容参照 AxiosRequestConfig，必须要定义的有 url，method
    requestConfig: {},
    
    // Function or Array<Function>，可为空，数据通过函数或函数数组提供
    // 如果是函数数组，则 cycle 值会被设置为 data.length
    data: () => {
    },
    data: [() => {}, () => {}, () => {}, ....]
    
    // 'parallel' or 'serial'，定义测试是并行执行还是串行执行。默认 'serial'
    parallelOrSerial: 'serial',
    
    // Function，必须定义，对返回内容数据的断言函数
    assert: (data, requestConfig) => {
        // 如：
        console.assert(null != data);
    },
    
    // Function，对返回数据的断言函数，默认空函数
    assertResponse: (response, requestConfig) => {
        // 如：
        console.assert(null != response);
    },
    
    // Integer，测试自身循环次数，默认为 1
    // 如果 data 是函数数组，则 cycle 值会被设置为 cycle × data.length
    cycles: 1,
    
    // 循环结束函数。默认为一个返回 true 的函数，
    // 如果该设置了该函数，则 cycle 的值会被重新设置，如果 data 是函数，则 cycle 设置为 1，如果是 函数数组，则设置为 data.length
    // 注：并行执行不支持 requestConfig 函数，且 response 的结果不可预期
    // 有些测试需要判断数据是否符合一定条件，才会结束循环
    loopEndFunction: (response, requestConfig) => {
        return true;
    }
    
    // 延迟执行，默认为 0，不延迟
    delay: 0,
    
    // Integer，测试线循环次数，默认为 1
    // 该定义必须定义在没有任何测试依赖的测试上（即测试线最末尾的测试）
    lineCycles: 1,
    
    // true or false，是否需要在新 Session 中进行该测试，默认 false
    newSession: false,
    
    // Function，回调函数，可做一些请求后的处理工作，默认空函数
    callback: (response, requestConfig) => {
    }
}
```

测试线：

```javascript
const TEST_CORRECT_REQUEST = {
    // 定义单个测试，测试名作为键值
    'Hello 测试': {
        dependentItem: [],
        requestConfig: {
            method: 'post',
            url: '/say/hello'
        },
        assert: (data) => {
            console.assert(null != data, "返回数据不为空");
        },
    },
}
```

# 依赖规则

假设有如下测试依赖：

```
A 依赖 B
B 依赖 C、D、E
C 依赖 F
D、F 依赖 G
E 依赖 H

如图示：
A--->B--->C--->F--->G
     |--->D---------↑ 
     |
     |--->E-------->H
```

则有两条测试线，分别是以 G 和 H 开始的测试线，测试单元执行过程如下：

```
// 即 B 一定先于 A 执行，（C、F）组合与 D 可以并行执行，无执行顺序要求（这里展示的是串行执行过程，所以只是其中一种情况），
// 但 C、D 一定先于 B 执行，F 一定先于 C 执行，当然，最初执行肯定是 G
G --> D --> F --> C --> B --> A

// H 的这条线只需跟着依赖走即可
H --> E --> B --> A
```

原则就是：测试单元的依赖一定被全部执行完成后才会执行测试单元本身。

---
_Source: https://npm.io/package/zhl-api-test · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
