mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-13 17:29:56 +08:00
doc: add docs to repo
This commit is contained in:
112
docs/prepare/dot-env.md
Normal file
112
docs/prepare/dot-env.md
Normal file
@@ -0,0 +1,112 @@
|
||||
|
||||
## 环境变量的作用
|
||||
|
||||
在自动化测试中,有时需要借助环境变量实现某些特定的目的,常见的场景包括:
|
||||
|
||||
- 切换测试环境
|
||||
- 切换测试配置
|
||||
- 存储敏感数据(从[信息安全](/prepare/security/)的角度出发)
|
||||
|
||||
## 设置环境变量
|
||||
|
||||
### 在终端中预设环境变量
|
||||
|
||||
使用环境变量之前,需要先在系统中设置环境变量名称和值,传统的方式为使用 export 命令(Windows系统中使用 set 命令):
|
||||
|
||||
```bash
|
||||
$ export UserName=admin
|
||||
$ echo $UserName
|
||||
admin
|
||||
$ export Password=123456
|
||||
$ echo $Password
|
||||
123456
|
||||
```
|
||||
|
||||
然后,在程序中就可以对系统中的环境变量进行读取。
|
||||
|
||||
```bash
|
||||
$ python
|
||||
>>> import os
|
||||
>>> os.environ["UserName"]
|
||||
'admin'
|
||||
```
|
||||
|
||||
### 通过 .env 文件设置环境变量
|
||||
|
||||
除了这种方式,HttpRunner 还借鉴了 pipenv [加载 `.env` 的方式][pipenv_load_env]。
|
||||
|
||||
默认情况下,在自动化测试项目的根目录中,创建 `.env` 文件,并将敏感数据信息放置到其中,存储采用 `name=value` 的格式:
|
||||
|
||||
```bash
|
||||
$ cat .env
|
||||
UserName=admin
|
||||
Password=123456
|
||||
PROJECT_KEY=ABCDEFGH
|
||||
```
|
||||
|
||||
同时,`.env` 文件不应该添加到代码仓库中,建议将 `.env` 加入到 `.gitignore` 中。
|
||||
|
||||
HttpRunner 运行时,会自动将 `.env` 文件中的内容加载到运行时(RunTime)的环境变量中,然后在运行时中就可以对环境变量进行读取了。
|
||||
|
||||
若需加载不位于自动化项目根目录中的 `.env`,或者其它名称的 `.env` 文件(例如 `production.env`),可以采用 `--dot-env-path` 参数指定文件路径:
|
||||
|
||||
```bash
|
||||
$ hrun /path/to/testcase.yml --dot-env-path /path/to/.env --log-level debug
|
||||
INFO Loading environment variables from /path/to/.env
|
||||
DEBUG Loaded variable: UserName
|
||||
DEBUG Loaded variable: Password
|
||||
DEBUG Loaded variable: PROJECT_KEY
|
||||
...
|
||||
```
|
||||
|
||||
## 引用环境变量
|
||||
|
||||
在 HttpRunner 中内置了函数 `environ`(简称 `ENV`),可用于在 YAML/JSON 脚本中直接引用环境变量。
|
||||
|
||||
```yaml
|
||||
- test:
|
||||
name: login
|
||||
request:
|
||||
url: http://host/api/login
|
||||
method: POST
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
json:
|
||||
username: ${ENV(UserName)}
|
||||
password: ${ENV(Password)}
|
||||
validate:
|
||||
- eq: [status_code, 200]
|
||||
```
|
||||
|
||||
若还需对读取的环境变量做进一步处理,则可以在 `debugtalk.py` 通过 Python 内置的函数 `os.environ` 对环境变量进行引用,然后再实现处理逻辑。
|
||||
|
||||
例如,若发起请求的密码需要先与密钥进行拼接并生成 MD5,那么就可以在 `debugtalk.py` 文件中实现如下函数:
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
def get_encrypt_password():
|
||||
raw_passwd = os.environ["Password"]
|
||||
PROJECT_KEY = os.environ["PROJECT_KEY"])
|
||||
password = (raw_passwd + PROJECT_KEY).encode('ascii')
|
||||
return hmac.new(password, hashlib.sha1).hexdigest()
|
||||
```
|
||||
|
||||
然后,在 YAML/JSON 格式的测试用例中,就可以通过`${func()}`的方式引用环境变量的值了。
|
||||
|
||||
```yaml
|
||||
- test:
|
||||
name: login
|
||||
request:
|
||||
url: http://host/api/login
|
||||
method: POST
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
json:
|
||||
username: ${ENV(UserName)}
|
||||
password: ${get_encrypt_password()}
|
||||
validate:
|
||||
- eq: [status_code, 200]
|
||||
```
|
||||
|
||||
[pipenv_load_env]: https://docs.pipenv.org/advanced/#automatic-loading-of-env
|
||||
581
docs/prepare/parameters.md
Normal file
581
docs/prepare/parameters.md
Normal file
@@ -0,0 +1,581 @@
|
||||
## 介绍
|
||||
|
||||
在自动化测试中,经常会遇到如下场景:
|
||||
|
||||
- 测试搜索功能,只有一个搜索输入框,但有 10 种不同类型的搜索关键字;
|
||||
- 测试账号登录功能,需要输入用户名和密码,按照等价类划分后有 20 种组合情况。
|
||||
|
||||
这里只是随意找了两个典型的例子,相信大家都有遇到过很多类似的场景。总结下来,就是在我们的自动化测试脚本中存在参数,并且我们需要采用不同的参数去运行。
|
||||
|
||||
经过概括,参数基本上分为两种类型:
|
||||
|
||||
- 单个独立参数:例如前面的第一种场景,我们只需要变换搜索关键字这一个参数
|
||||
- 多个具有关联性的参数:例如前面的第二种场景,我们需要变换用户名和密码两个参数,并且这两个参数需要关联组合
|
||||
|
||||
然后,对于参数而言,我们可能具有一个参数列表,在脚本运行时需要按照不同的规则去取值,例如顺序取值、随机取值、循环取值等等。
|
||||
|
||||
这就是典型的参数化和数据驱动。
|
||||
|
||||
如需了解 HttpRunner 参数化数据驱动机制的实现原理和技术细节,可前往阅读[《HttpRunner 实现参数化数据驱动机制》](http://debugtalk.com/post/httprunner-data-driven/)。
|
||||
|
||||
## 测试用例集(testsuite)准备
|
||||
|
||||
从 2.0.0 版本开始,HttpRunner 不再支持在测试用例文件中进行参数化配置;参数化的功能需要在 testsuite 中实现。变更的目的是让测试用例(testcase)的概念更纯粹,关于测试用例和测试用例集的概念定义,详见[《测试用例组织》](/prepare/parameters/)。
|
||||
|
||||
参数化机制需要在测试用例集(testsuite)中实现。如需实现数据驱动机制,需要创建一个 testsuite,在 testsuite 中引用测试用例,并定义参数化配置。
|
||||
|
||||
测试用例集(testsuite)的格式如下所示:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
name: testsuite description
|
||||
|
||||
testcases:
|
||||
testcase1_name:
|
||||
testcase: /path/to/testcase1
|
||||
|
||||
testcase2_name:
|
||||
testcase: /path/to/testcase2
|
||||
```
|
||||
|
||||
需要注意的是,testsuite 和 testcase 的格式存在较大区别,详见[《测试用例组织》](/prepare/testcase-structure/)。
|
||||
|
||||
|
||||
## 参数配置概述
|
||||
|
||||
如需对某测试用例(testcase)实现参数化数据驱动,需要使用 `parameters` 关键字,定义参数名称并指定数据源取值方式。
|
||||
|
||||
参数名称的定义分为两种情况:
|
||||
|
||||
- 独立参数单独进行定义;
|
||||
- 多个参数具有关联性的参数需要将其定义在一起,采用短横线(`-`)进行连接。
|
||||
|
||||
数据源指定支持三种方式:
|
||||
|
||||
- 在 YAML/JSON 中直接指定参数列表:该种方式最为简单易用,适合参数列表比较小的情况
|
||||
- 通过内置的 parameterize(可简写为P)函数引用 CSV 文件:该种方式需要准备 CSV 数据文件,适合数据量比较大的情况
|
||||
- 调用 debugtalk.py 中自定义的函数生成参数列表:该种方式最为灵活,可通过自定义 Python 函数实现任意场景的数据驱动机制,当需要动态生成参数列表时也需要选择该种方式
|
||||
|
||||
三种方式可根据实际项目需求进行灵活选择,同时支持多种方式的组合使用。假如测试用例中定义了多个参数,那么测试用例在运行时会对参数进行笛卡尔积组合,覆盖所有参数组合情况。
|
||||
|
||||
使用方式概览如下:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
name: "demo"
|
||||
|
||||
testcases:
|
||||
testcase1_name:
|
||||
testcase: /path/to/testcase1
|
||||
parameters:
|
||||
user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
|
||||
user_id: ${P(user_id.csv)}
|
||||
username-password: ${get_account(10)}
|
||||
```
|
||||
|
||||
|
||||
## 参数配置详解
|
||||
|
||||
将参数名称定义和数据源指定方式进行组合,共有 6 种形式。现分别针对每一类情况进行详细说明。
|
||||
|
||||
### 独立参数 & 直接指定参数列表
|
||||
|
||||
对于参数列表比较小的情况,最简单的方式是直接在 YAML/JSON 中指定参数列表内容。
|
||||
|
||||
例如,对于独立参数 `user_id`,参数列表为 `[1001, 1002, 1003, 1004]`,那么就可以按照如下方式进行配置:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
name: testcase description
|
||||
|
||||
testcases:
|
||||
create user:
|
||||
testcase: demo-quickstart-6.yml
|
||||
parameters:
|
||||
user_id: [1001, 1002, 1003, 1004]
|
||||
```
|
||||
|
||||
进行该配置后,测试用例在运行时就会对 user_id 实现数据驱动,即分别使用 `[1001, 1002, 1003, 1004]` 四个值运行测试用例。
|
||||
|
||||
<details>
|
||||
<summary>点击查看运行日志</summary>
|
||||
|
||||
```text
|
||||
$ hrun docs/data/demo-quickstart-7.json
|
||||
INFO Start to run testcase: create user 1001
|
||||
/api/get-token
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 8.95 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
/api/users/1001
|
||||
INFO POST http://127.0.0.1:5000/api/users/1001
|
||||
INFO status_code: 201, response_time(ms): 3.02 ms, response_length: 54 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 2 tests in 0.021s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: create user 1002
|
||||
/api/get-token
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 2.78 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
/api/users/1002
|
||||
INFO POST http://127.0.0.1:5000/api/users/1002
|
||||
INFO status_code: 201, response_time(ms): 2.84 ms, response_length: 54 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 2 tests in 0.007s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: create user 1003
|
||||
/api/get-token
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 2.92 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
/api/users/1003
|
||||
INFO POST http://127.0.0.1:5000/api/users/1003
|
||||
INFO status_code: 201, response_time(ms): 5.56 ms, response_length: 54 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 2 tests in 0.011s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: create user 1004
|
||||
/api/get-token
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 5.25 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
/api/users/1004
|
||||
INFO POST http://127.0.0.1:5000/api/users/1004
|
||||
INFO status_code: 201, response_time(ms): 7.02 ms, response_length: 54 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 2 tests in 0.016s
|
||||
|
||||
OK
|
||||
INFO Start to render Html report ...
|
||||
INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548518757.html
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
可以看出,测试用例总共运行了 4 次,并且每次运行时都是采用的不同 user_id。
|
||||
|
||||
### 关联参数 & 直接指定参数列表
|
||||
|
||||
对于具有关联性的多个参数,例如 username 和 password,那么就可以按照如下方式进行配置:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
name: "demo"
|
||||
|
||||
testcases:
|
||||
testcase1_name:
|
||||
testcase: /path/to/testcase1
|
||||
parameters:
|
||||
username-password:
|
||||
- ["user1", "111111"]
|
||||
- ["user2", "222222"]
|
||||
- ["user3", "333333"]
|
||||
```
|
||||
|
||||
进行该配置后,测试用例在运行时就会对 username 和 password 实现数据驱动,即分别使用 `{"username": "user1", "password": "111111"}`、`{"username": "user2", "password": "222222"}`、`{"username": "user3", "password": "333333"}` 运行 3 次测试,并且保证参数值总是成对使用。
|
||||
|
||||
### 独立参数 & 引用 CSV 文件
|
||||
|
||||
对于已有参数列表,并且数据量比较大的情况,比较适合的方式是将参数列表值存储在 CSV 数据文件中。
|
||||
|
||||
对于 CSV 数据文件,需要遵循如下几项约定的规则:
|
||||
|
||||
- CSV 文件中的第一行必须为参数名称,从第二行开始为参数值,每个(组)值占一行;
|
||||
- 若同一个 CSV 文件中具有多个参数,则参数名称和数值的间隔符需实用英文逗号;
|
||||
- 在 YAML/JSON 文件引用 CSV 文件时,文件路径为基于项目根目录(debugtalk.py 所在路径)的相对路径。
|
||||
|
||||
例如,user_id 的参数取值范围为 1001~2000,那么我们就可以创建 user_id.csv,并且在文件中按照如下形式进行描述。
|
||||
|
||||
```csv
|
||||
user_id
|
||||
1001
|
||||
1002
|
||||
...
|
||||
1999
|
||||
2000
|
||||
```
|
||||
|
||||
然后在 YAML/JSON 测试用例文件中,就可以通过内置的 `parameterize`(可简写为 `P`)函数引用 CSV 文件。
|
||||
|
||||
假设项目的根目录下有 data 文件夹,user_id.csv 位于其中,那么 user_id.csv 的引用描述如下:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
name: "demo"
|
||||
|
||||
testcases:
|
||||
testcase1_name:
|
||||
testcase: /path/to/testcase1
|
||||
parameters:
|
||||
user_id: ${P(data/user_id.csv)}
|
||||
```
|
||||
|
||||
即 `P` 函数的参数(CSV 文件路径)是相对于项目根目录的相对路径。当然,这里也可以使用 CSV 文件在系统中的绝对路径,不过这样的话在项目路径变动时就会出现问题,因此推荐使用相对路径的形式。
|
||||
|
||||
### 关联参数 & 引用 CSV 文件
|
||||
|
||||
对于具有关联性的多个参数,例如 username 和 password,那么就可以创建 [account.csv](/data/account.csv),并在文件中按照如下形式进行描述。
|
||||
|
||||
```csv
|
||||
username,password
|
||||
test1,111111
|
||||
test2,222222
|
||||
test3,333333
|
||||
```
|
||||
|
||||
然后在 YAML/JSON 测试用例文件中,就可以通过内置的 `parameterize`(可简写为 `P`)函数引用 CSV 文件。
|
||||
|
||||
假设项目的根目录下有 data 文件夹,account.csv 位于其中,那么 account.csv 的引用描述如下:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
name: "demo"
|
||||
|
||||
testcases:
|
||||
testcase1_name:
|
||||
testcase: /path/to/testcase1
|
||||
parameters:
|
||||
username-password: ${P(data/account.csv)}
|
||||
```
|
||||
|
||||
需要说明的是,在 parameters 中指定的参数名称必须与 CSV 文件中第一行的参数名称一致,顺序可以不一致,参数个数也可以不一致。
|
||||
|
||||
例如,在 [account.csv](/data/account.csv) 文件中可以包含多个参数,username、password、phone、age:
|
||||
|
||||
```csv
|
||||
username,password,phone,age
|
||||
test1,111111,18600000001,21
|
||||
test2,222222,18600000002,22
|
||||
test3,333333,18600000003,23
|
||||
```
|
||||
|
||||
而在 YAML/JSON 测试用例文件中指定参数时,可以只使用部分参数,并且参数顺序无需与 CSV 文件中参数名称的顺序一致。
|
||||
|
||||
```yaml
|
||||
config:
|
||||
name: "demo"
|
||||
|
||||
testcases:
|
||||
testcase1_name:
|
||||
testcase: /path/to/testcase1
|
||||
parameters:
|
||||
phone-username: ${P(account.csv)}
|
||||
```
|
||||
|
||||
### 独立参数 & 引用自定义函数
|
||||
|
||||
对于没有现成参数列表,或者需要更灵活的方式动态生成参数的情况,可以通过在 debugtalk.py 中自定义函数生成参数列表,并在 YAML/JSON 引用自定义函数的方式。
|
||||
|
||||
例如,若需对 user_id 进行参数化数据驱动,参数取值范围为 1001~1004,那么就可以在 debugtalk.py 中定义一个函数,返回参数列表。
|
||||
|
||||
```python
|
||||
def get_user_id():
|
||||
return [
|
||||
{"user_id": 1001},
|
||||
{"user_id": 1002},
|
||||
{"user_id": 1003},
|
||||
{"user_id": 1004}
|
||||
]
|
||||
```
|
||||
|
||||
然后,在 YAML/JSON 的 parameters 中就可以通过调用自定义函数的形式来指定数据源。
|
||||
|
||||
```yaml
|
||||
config:
|
||||
name: "demo"
|
||||
|
||||
testcases:
|
||||
testcase1_name:
|
||||
testcase: /path/to/testcase1
|
||||
parameters:
|
||||
user_id: ${get_user_id()}
|
||||
```
|
||||
|
||||
另外,通过函数的传参机制,还可以实现更灵活的参数生成功能,在调用函数时指定需要生成的参数个数。
|
||||
|
||||
### 关联参数 & 引用自定义函数
|
||||
|
||||
对于具有关联性的多个参数,实现方式也类似。
|
||||
|
||||
例如,在 debugtalk.py 中定义函数 get_account,生成指定数量的账号密码参数列表。
|
||||
|
||||
```python
|
||||
def get_account(num):
|
||||
accounts = []
|
||||
for index in range(1, num+1):
|
||||
accounts.append(
|
||||
{"username": "user%s" % index, "password": str(index) * 6},
|
||||
)
|
||||
|
||||
return accounts
|
||||
```
|
||||
|
||||
那么在 YAML/JSON 的 parameters 中就可以调用自定义函数生成指定数量的参数列表。
|
||||
|
||||
```yaml
|
||||
config:
|
||||
name: "demo"
|
||||
|
||||
testcases:
|
||||
testcase1_name:
|
||||
testcase: /path/to/testcase1
|
||||
parameters:
|
||||
username-password: ${get_account(10)}
|
||||
```
|
||||
|
||||
> 需要注意的是,在自定义函数中,生成的参数列表必须为 `list of dict` 的数据结构,该设计主要是为了与 CSV 文件的处理机制保持一致。
|
||||
|
||||
|
||||
## 参数化运行
|
||||
|
||||
完成以上参数定义和数据源准备工作之后,参数化运行与普通测试用例的运行完全一致。
|
||||
|
||||
采用 hrun 命令运行自动化测试:
|
||||
|
||||
```bash
|
||||
$ hrun tests/data/demo_parameters.yml
|
||||
```
|
||||
|
||||
采用 locusts 命令运行性能测试:
|
||||
|
||||
```bash
|
||||
$ locusts -f tests/data/demo_parameters.yml
|
||||
```
|
||||
|
||||
区别在于,自动化测试时遍历一遍后会终止执行,性能测试时每个并发用户都会循环遍历所有参数。
|
||||
|
||||
## 案例演示
|
||||
|
||||
假设我们有一个获取 token 的[测试用例](/data/demo-testcase-get-token.yml)。
|
||||
|
||||
<details>
|
||||
<summary>点击查看 YAML 测试用例</summary>
|
||||
```yaml
|
||||
- config:
|
||||
name: get token
|
||||
base_url: http://127.0.0.1:5000
|
||||
variables:
|
||||
device_sn: ${gen_random_string(15)}
|
||||
os_platform: 'ios'
|
||||
app_version: '2.8.6'
|
||||
|
||||
- test:
|
||||
name: get token with $device_sn, $os_platform, $app_version
|
||||
request:
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
User-Agent: python-requests/2.18.4
|
||||
app_version: $app_version
|
||||
device_sn: $device_sn
|
||||
os_platform: $os_platform
|
||||
json:
|
||||
sign: ${get_sign($device_sn, $os_platform, $app_version)}
|
||||
method: POST
|
||||
url: /api/get-token
|
||||
extract:
|
||||
token: content.token
|
||||
validate:
|
||||
- eq: [status_code, 200]
|
||||
- eq: [headers.Content-Type, application/json]
|
||||
- eq: [content.success, true]
|
||||
```
|
||||
</details>
|
||||
|
||||
如果我们需要使用 device_sn、app_version 和 os_platform 这三个参数来进行参数化数据驱动,那么就可以创建一个 [testsuite](/data/demo-parameters-get-token.yml),并且进行参数化配置。
|
||||
|
||||
```yaml
|
||||
config:
|
||||
name: get token with parameters
|
||||
|
||||
testcases:
|
||||
get token with $user_agent, $app_version, $os_platform:
|
||||
testcase: demo-testcase-get-token.yml
|
||||
parameters:
|
||||
user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
|
||||
app_version: ${P(app_version.csv)}
|
||||
os_platform: ${get_os_platform()}
|
||||
```
|
||||
|
||||
其中,`user_agent` 使用了直接指定参数列表的形式。
|
||||
|
||||
[app_version](/data/app_version.csv) 通过 CSV 文件进行参数配置,对应的文件内容为:
|
||||
|
||||
```csv
|
||||
app_version
|
||||
2.8.5
|
||||
2.8.6
|
||||
```
|
||||
|
||||
os_platform 使用自定义函数的形式生成参数列表,对应的函数内容为:
|
||||
|
||||
```python
|
||||
def get_os_platform():
|
||||
return [
|
||||
{"os_platform": "ios"},
|
||||
{"os_platform": "android"}
|
||||
]
|
||||
```
|
||||
|
||||
那么,经过笛卡尔积组合,应该总共有 `3*2*2=12` 种参数组合情况。
|
||||
|
||||
|
||||
<details>
|
||||
<summary>点击查看完整运行日志</summary>
|
||||
```text
|
||||
$ hrun docs/data/demo-parameters-get-token.yml
|
||||
INFO Start to run testcase: get token with iOS/10.1, 2.8.5, ios
|
||||
get token with PBJda7SXM2ReWlu, ios, 2.8.5
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 10.66 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.026s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: get token with iOS/10.1, 2.8.5, android
|
||||
get token with PBJda7SXM2ReWlu, android, 2.8.5
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 3.03 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.004s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: get token with iOS/10.1, 2.8.6, ios
|
||||
get token with PBJda7SXM2ReWlu, ios, 2.8.6
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 10.76 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.012s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: get token with iOS/10.1, 2.8.6, android
|
||||
get token with PBJda7SXM2ReWlu, android, 2.8.6
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 4.49 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.006s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: get token with iOS/10.2, 2.8.5, ios
|
||||
get token with PBJda7SXM2ReWlu, ios, 2.8.5
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 4.39 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.006s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: get token with iOS/10.2, 2.8.5, android
|
||||
get token with PBJda7SXM2ReWlu, android, 2.8.5
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 4.04 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.005s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: get token with iOS/10.2, 2.8.6, ios
|
||||
get token with PBJda7SXM2ReWlu, ios, 2.8.6
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 3.44 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.004s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: get token with iOS/10.2, 2.8.6, android
|
||||
get token with PBJda7SXM2ReWlu, android, 2.8.6
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 4.03 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.005s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: get token with iOS/10.3, 2.8.5, ios
|
||||
get token with PBJda7SXM2ReWlu, ios, 2.8.5
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 5.14 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.008s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: get token with iOS/10.3, 2.8.5, android
|
||||
get token with PBJda7SXM2ReWlu, android, 2.8.5
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 7.62 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.010s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: get token with iOS/10.3, 2.8.6, ios
|
||||
get token with PBJda7SXM2ReWlu, ios, 2.8.6
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 4.88 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.006s
|
||||
|
||||
OK
|
||||
INFO Start to run testcase: get token with iOS/10.3, 2.8.6, android
|
||||
get token with PBJda7SXM2ReWlu, android, 2.8.6
|
||||
INFO POST http://127.0.0.1:5000/api/get-token
|
||||
INFO status_code: 200, response_time(ms): 5.41 ms, response_length: 46 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.008s
|
||||
|
||||
OK
|
||||
INFO Start to render Html report ...
|
||||
INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1551950193.html
|
||||
```
|
||||
</details>
|
||||
30
docs/prepare/project-structure.md
Normal file
30
docs/prepare/project-structure.md
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
## 文件类型说明
|
||||
|
||||
在 HttpRunner 自动化测试项目中,主要存在如下几类文件:
|
||||
|
||||
- `YAML/JSON`(必须):测试用例文件,存储接口测试相关信息
|
||||
- `debugtalk.py`(可选):存储项目中逻辑运算辅助函数
|
||||
- 该文件存在时,将作为项目根目录定位标记,其所在目录即被视为项目工程根目录
|
||||
- 该文件不存在时,运行测试的所在路径(`CWD`)将被视为项目工程根目录
|
||||
- 测试用例文件中的相对路径(例如`.csv`)均需基于项目工程根目录
|
||||
- 运行测试后,测试报告文件夹(`reports`)会生成在项目工程根目录
|
||||
- `.env`(可选):存储项目环境变量,通常用于存储项目敏感信息
|
||||
- `.csv`(可选):项目数据文件,用于进行数据驱动
|
||||
- `reports`:默认生成测试报告的存储文件夹
|
||||
|
||||
## 项目文件结构
|
||||
|
||||
对于接口数比较少,或者测试场景比较简单的项目,组织测试用例时无需分层。在此种情况下,项目文件的目录结构没有任何要求,在项目中只需要一堆 `YAML/JSON` 文件即可,每一个文件单独对应一条测试用例;根据需要,项目中可能还会有 `debugtalk.py`、`.env`等文件。
|
||||
|
||||
推荐的项目文件目录结构示例如下:
|
||||
|
||||
```bash
|
||||
$ tree demo -a
|
||||
demo
|
||||
├── .env
|
||||
├── debugtalk.py
|
||||
├── reports
|
||||
├── testcase1.yml
|
||||
└── testcase2.json
|
||||
```
|
||||
38
docs/prepare/record.md
Normal file
38
docs/prepare/record.md
Normal file
@@ -0,0 +1,38 @@
|
||||
|
||||
为了简化测试用例的编写工作,HttpRunner 实现了测试用例生成的功能,对应的转换工具为一个独立的项目:[har2case][har2case]。
|
||||
|
||||
简单来说,就是当前主流的抓包工具和浏览器都支持将抓取得到的数据包导出为标准通用的 HAR 格式(HTTP Archive),然后 HttpRunner 实现了将 HAR 格式的数据包转换为`YAML/JSON`格式的测试用例文件的功能。
|
||||
|
||||
## 获取 HAR 数据包
|
||||
|
||||
在转换生成测试用例之前,需要先将抓取得到的数据包导出为 HAR 格式的文件。在`Charles Proxy`中的操作方式为,选中需要转换的接口(可多选或全选),点击右键,在悬浮的菜单目录中点击【Export...】,格式选择`HTTP Archive(.har)`后保存即可;假设我们保存的文件名称为 demo.har。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 转换生成测试用例
|
||||
|
||||
然后,在命令行终端中运行 har2case 命令,即可将 demo.har 转换为 HttpRunner 的测试用例文件。
|
||||
|
||||
使用 `har2case` 转换脚本时默认转换为 JSON 格式。
|
||||
|
||||
```bash
|
||||
$ har2case docs/data/demo-quickstart.har
|
||||
INFO:root:Start to generate testcase.
|
||||
INFO:root:dump testcase to JSON format.
|
||||
INFO:root:Generate JSON testcase successfully: docs/data/demo-quickstart.json
|
||||
```
|
||||
|
||||
加上 `-2y`/`--to-yml` 参数后转换为 YAML 格式。
|
||||
|
||||
```bash
|
||||
$ har2case docs/data/demo-quickstart.har -2y
|
||||
INFO:root:Start to generate testcase.
|
||||
INFO:root:dump testcase to YAML format.
|
||||
INFO:root:Generate YAML testcase successfully: docs/data/demo-quickstart.yml
|
||||
```
|
||||
|
||||
两种格式完全等价,YAML 格式更简洁,JSON 格式支持的工具更丰富,大家可根据个人喜好进行选择。
|
||||
|
||||
[har2case]: https://github.com/HttpRunner/har2case
|
||||
145
docs/prepare/request-hook.md
Normal file
145
docs/prepare/request-hook.md
Normal file
@@ -0,0 +1,145 @@
|
||||
## 概述
|
||||
|
||||
HttpRunner 从 `1.4.5` 版本开始实现了全新的 hook 机制,可以在请求前和请求后调用钩子函数。
|
||||
|
||||
## 调用 hook 函数
|
||||
|
||||
hook 机制分为两个层级:
|
||||
|
||||
- 测试用例层面(testcase)
|
||||
- 测试步骤层面(teststep)
|
||||
|
||||
### 测试用例层面(testcase)
|
||||
|
||||
在 YAML/JSON 测试用例的 `config` 中新增关键字 `setup_hooks` 和 `teardown_hooks`。
|
||||
|
||||
- setup_hooks: 在整个用例开始执行前触发 hook 函数,主要用于准备工作。
|
||||
- teardown_hooks: 在整个用例结束执行后触发 hook 函数,主要用于测试后的清理工作。
|
||||
|
||||
```yaml
|
||||
- config:
|
||||
name: basic test with httpbin
|
||||
request:
|
||||
base_url: http://127.0.0.1:3458/
|
||||
setup_hooks:
|
||||
- ${hook_print(setup)}
|
||||
teardown_hooks:
|
||||
- ${hook_print(teardown)}
|
||||
```
|
||||
|
||||
### 测试步骤层面(teststep)
|
||||
|
||||
在 YAML/JSON 测试步骤的 `test` 中新增关键字 `setup_hooks` 和 `teardown_hooks`。
|
||||
|
||||
- setup_hooks: 在 HTTP 请求发送前执行 hook 函数,主要用于准备工作;也可以实现对请求的 request 内容进行预处理。
|
||||
- teardown_hooks: 在 HTTP 请求发送后执行 hook 函数,主要用于测试后的清理工作;也可以实现对响应的 response 进行修改,例如进行加解密等处理。
|
||||
|
||||
```json
|
||||
"test": {
|
||||
"name": "get token with $user_agent, $os_platform, $app_version",
|
||||
"request": {
|
||||
"url": "/api/get-token",
|
||||
"method": "POST",
|
||||
"headers": {
|
||||
"app_version": "$app_version",
|
||||
"os_platform": "$os_platform",
|
||||
"user_agent": "$user_agent"
|
||||
},
|
||||
"json": {
|
||||
"sign": "${get_sign($user_agent, $device_sn, $os_platform, $app_version)}"
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{"eq": ["status_code", 200]}
|
||||
],
|
||||
"setup_hooks": [
|
||||
"${setup_hook_prepare_kwargs($request)}",
|
||||
"${setup_hook_httpntlmauth($request)}"
|
||||
],
|
||||
"teardown_hooks": [
|
||||
"${teardown_hook_sleep_N_secs($response, 2)}"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 编写 hook 函数
|
||||
|
||||
hook 函数的定义放置在项目的 `debugtalk.py` 中,在 YAML/JSON 中调用 hook 函数仍然是采用 `${func($a, $b)}` 的形式。
|
||||
|
||||
对于测试用例层面的 hook 函数,与 YAML/JSON 中自定义的函数完全相同,可通过自定义参数传参的形式来实现灵活应用。
|
||||
|
||||
```python
|
||||
def hook_print(msg):
|
||||
print(msg)
|
||||
```
|
||||
|
||||
对于单个测试用例层面的 hook 函数,除了可传入自定义参数外,还可以传入与当前测试用例相关的信息,包括请求的 `$request` 和响应的 `$response`,用于实现更复杂场景的灵活应用。
|
||||
|
||||
### setup_hooks
|
||||
|
||||
在测试步骤层面的 setup_hooks 函数中,除了可传入自定义参数外,还可以传入 `$request`,该参数对应着当前测试步骤 request 的全部内容。因为 request 是可变参数类型(dict),因此该函数参数为引用传递,当我们需要对请求参数进行预处理时尤其有用。
|
||||
|
||||
e.g.
|
||||
|
||||
```python
|
||||
def setup_hook_prepare_kwargs(request):
|
||||
if request["method"] == "POST":
|
||||
content_type = request.get("headers", {}).get("content-type")
|
||||
if content_type and "data" in request:
|
||||
# if request content-type is application/json, request data should be dumped
|
||||
if content_type.startswith("application/json") and isinstance(request["data"], (dict, list)):
|
||||
request["data"] = json.dumps(request["data"])
|
||||
|
||||
if isinstance(request["data"], str):
|
||||
request["data"] = request["data"].encode('utf-8')
|
||||
|
||||
def setup_hook_httpntlmauth(request):
|
||||
if "httpntlmauth" in request:
|
||||
from requests_ntlm import HttpNtlmAuth
|
||||
auth_account = request.pop("httpntlmauth")
|
||||
request["auth"] = HttpNtlmAuth(
|
||||
auth_account["username"], auth_account["password"])
|
||||
```
|
||||
|
||||
通过上述的 `setup_hook_prepare_kwargs` 函数,可以实现根据请求方法和请求的 Content-Type 来对请求的 data 进行加工处理;通过 `setup_hook_httpntlmauth` 函数,可以实现 HttpNtlmAuth 权限授权。
|
||||
|
||||
### teardown_hooks
|
||||
|
||||
在测试步骤层面的 teardown_hooks 函数中,除了可传入自定义参数外,还可以传入 `$response`,该参数对应着当前请求的响应实例(requests.Response)。
|
||||
|
||||
e.g.
|
||||
|
||||
```python
|
||||
def teardown_hook_sleep_N_secs(response, n_secs):
|
||||
""" sleep n seconds after request
|
||||
"""
|
||||
if response.status_code == 200:
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
time.sleep(n_secs)
|
||||
```
|
||||
|
||||
通过上述的 `teardown_hook_sleep_N_secs` 函数,可以根据接口响应的状态码来进行不同时间的延迟等待。
|
||||
|
||||
另外,在 teardown_hooks 函数中还可以对 response 进行修改。当我们需要先对响应内容进行处理(例如加解密、参数运算),再进行参数提取(extract)和校验(validate)时尤其有用。
|
||||
|
||||
例如在下面的测试步骤中,在执行测试后,通过 teardown_hooks 函数将响应结果的状态码和 headers 进行了修改,然后再进行了校验。
|
||||
|
||||
```yaml
|
||||
- test:
|
||||
name: alter response
|
||||
request:
|
||||
url: /headers
|
||||
method: GET
|
||||
teardown_hooks:
|
||||
- ${alter_response($response)}
|
||||
validate:
|
||||
- eq: ["status_code", 500]
|
||||
- eq: ["headers.content-type", "html/text"]
|
||||
```
|
||||
|
||||
```python
|
||||
def alter_response(response):
|
||||
response.status_code = 500
|
||||
response.headers["Content-Type"] = "html/text"
|
||||
```
|
||||
19
docs/prepare/security.md
Normal file
19
docs/prepare/security.md
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
## 背景
|
||||
|
||||
很多时候项目代码在运行时需要使用到账号、密码、key等敏感数据信息,但是从信息安全的角度考虑,我们是不能将这些敏感数据提交到代码仓库的,主要原因有两个:
|
||||
|
||||
- 加强权限管控:参与项目的开发人员可能会有很多,大家都有读取代码仓库的权限,但是像 key 这类极度敏感的信息不应该所有人都有权限获取;
|
||||
- 减少代码泄漏的危害性:假如代码出现泄漏,敏感数据信息不应该也同时泄漏。
|
||||
|
||||
## 解决方案
|
||||
|
||||
那代码部署到服务器或 Jenkins 执行机后,运行时要使用到这些敏感数据信息,该怎么操作呢?
|
||||
|
||||
推荐的操作方式为:
|
||||
|
||||
- 对服务器进行权限管控,只有运维人员(或者核心开发人员)才有登录服务器的权限;
|
||||
- 运维人员(或者核心开发人员):在运行的机器上将敏感数据设置到系统的环境变量中;
|
||||
- 普通开发人员:只需要知道敏感信息的变量名称,在代码中通过读取环境变量的方式获取敏感数据。
|
||||
|
||||
存储敏感数据(设置环境变量)和使用敏感数据(引用环境变量)的具体方法,可参考[环境变量](/prepare/dot-env/)使用说明文档。
|
||||
272
docs/prepare/testcase-layer.md
Normal file
272
docs/prepare/testcase-layer.md
Normal file
@@ -0,0 +1,272 @@
|
||||
|
||||
|
||||
## 测试用例分层模型
|
||||
|
||||
在自动化测试领域,自动化测试用例的可维护性是极其重要的因素,直接关系到自动化测试能否持续有效地在项目中开展。
|
||||
|
||||
概括来说,测试用例分层机制的核心是将接口定义、测试步骤、测试用例、测试场景进行分离,单独进行描述和维护,从而尽可能地减少自动化测试用例的维护成本。
|
||||
|
||||
逻辑关系图如下所示:
|
||||
|
||||

|
||||
|
||||
同时,强调如下几点核心概念:
|
||||
|
||||
- 测试用例(testcase)应该是完整且独立的,每条测试用例应该是都可以独立运行的
|
||||
- 测试用例是测试步骤(teststep)的 `有序` 集合,每一个测试步骤对应一个 API 的请求描述
|
||||
- 测试用例集(testsuite)是测试用例的 `无序` 集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系,那就需要在测试用例中完成依赖的处理
|
||||
|
||||
如果对于上述第三点感觉难以理解,不妨看下上图中的示例:
|
||||
|
||||
- testcase1 依赖于 testcase2,那么就可以在测试步骤(teststep12)中对 testcase2 进行引用,然后 testcase1 就是完整且可独立运行的;
|
||||
- 在 testsuite 中,testcase1 与 testcase2 相互独立,运行顺序就不再有先后依赖关系了。
|
||||
|
||||
## 分层描述详解
|
||||
|
||||
理解了测试用例分层模型,接下来我们再来看下在分层模型下,接口、测试用例、测试用例集的描述形式。
|
||||
|
||||
### 接口定义(API)
|
||||
|
||||
为了更好地对接口描述进行管理,推荐使用独立的文件对接口描述进行存储,即每个文件对应一个接口描述。
|
||||
|
||||
接口定义描述的主要内容包括:**name**、variables、**request**、base_url、validate 等,形式如下:
|
||||
|
||||
```yaml
|
||||
name: get headers
|
||||
base_url: http://httpbin.org
|
||||
variables:
|
||||
expected_status_code: 200
|
||||
request:
|
||||
url: /headers
|
||||
method: GET
|
||||
validate:
|
||||
- eq: ["status_code", $expected_status_code]
|
||||
- eq: [content.headers.Host, "httpbin.org"]
|
||||
```
|
||||
|
||||
其中,name 和 request 部分是必须的,request 中的描述形式与 [requests.request](http://docs.python-requests.org/en/master/api/) 完全相同。
|
||||
|
||||
另外,API 描述需要尽量保持完整,做到可以单独运行。如果在接口描述中存在变量引用的情况,可在 variables 中对参数进行定义。通过这种方式,可以很好地实现单个接口的调试。
|
||||
|
||||
```bash
|
||||
$ hrun api/get_headers.yml
|
||||
INFO Start to run testcase: get headers
|
||||
headers
|
||||
INFO GET http://httpbin.org/headers
|
||||
INFO status_code: 200, response_time(ms): 477.32 ms, response_length: 157 bytes
|
||||
|
||||
.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.478s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
### 测试用例(testcase)
|
||||
|
||||
#### 引用接口定义
|
||||
|
||||
有了接口的定义描述后,我们编写测试场景时就可以直接引用接口定义了。
|
||||
|
||||
在测试步骤(teststep)中,可通过 `api` 字段引用接口定义,引用方式为对应 API 文件的路径,绝对路径或相对路径均可。推荐使用相对路径,路径基准为项目根目录,即 `debugtalk.py` 所在的目录路径。
|
||||
|
||||
```yaml
|
||||
- config:
|
||||
name: "setup and reset all."
|
||||
variables:
|
||||
user_agent: 'iOS/10.3'
|
||||
device_sn: "TESTCASE_SETUP_XXX"
|
||||
os_platform: 'ios'
|
||||
app_version: '2.8.6'
|
||||
base_url: "http://127.0.0.1:5000"
|
||||
verify: False
|
||||
output:
|
||||
- session_token
|
||||
|
||||
- test:
|
||||
name: get token (setup)
|
||||
api: api/get_token.yml
|
||||
variables:
|
||||
user_agent: 'iOS/10.3'
|
||||
device_sn: $device_sn
|
||||
os_platform: 'ios'
|
||||
app_version: '2.8.6'
|
||||
extract:
|
||||
- session_token: content.token
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- len_eq: ["content.token", 16]
|
||||
|
||||
- test:
|
||||
name: reset all users
|
||||
api: api/reset_all.yml
|
||||
variables:
|
||||
token: $session_token
|
||||
```
|
||||
|
||||
若需要控制或改变接口定义中的参数值,可在测试步骤中指定 variables 参数,覆盖 API 中的 variables 实现。
|
||||
|
||||
同样地,在测试步骤中定义 validate 后,也会与 API 中的 validate 合并覆盖。因此推荐的做法是,在 API 定义中的 validate 只描述最基本的校验项,例如 status_code,对于与业务逻辑相关的更多校验项,在测试步骤的 validate 中进行描述。
|
||||
|
||||
#### 引用测试用例
|
||||
|
||||
在测试用例的测试步骤中,除了可以引用接口定义,还可以引用其它测试用例。通过这种方式,可以在避免重复描述的同时,解决测试用例的依赖关系,从而保证每个测试用例都是独立可运行的。
|
||||
|
||||
在测试步骤(teststep)中,可通过 `testcase` 字段引用其它测试用例,引用方式为对应测试用例文件的路径,绝对路径或相对路径均可。推荐使用相对路径,路径基准为项目根目录,即 `debugtalk.py` 所在的目录路径。
|
||||
|
||||
例如,在上面的测试用例("setup and reset all.")中,实现了对获取 token 功能的测试;同时,在很多其它功能中都会依赖于获取 token 的功能,如果将该功能的测试步骤脚本拷贝到其它功能的测试用例中,那么就会存在大量重复,当需要对该部分进行修改时就需要修改所有地方,显然不便于维护。
|
||||
|
||||
比较好的做法是,在其它功能的测试用例(如创建用户)中,引用获取 token 功能的测试用例(testcases/setup.yml)作为一个测试步骤,从而创建用户("create user and check result.")这个测试用例也变得独立可运行了。
|
||||
|
||||
```yaml
|
||||
- config:
|
||||
name: "create user and check result."
|
||||
id: create_user
|
||||
base_url: "http://127.0.0.1:5000"
|
||||
variables:
|
||||
uid: 9001
|
||||
device_sn: "TESTCASE_CREATE_XXX"
|
||||
output:
|
||||
- session_token
|
||||
|
||||
- test:
|
||||
name: setup and reset all (override) for $device_sn.
|
||||
testcase: testcases/setup.yml
|
||||
output:
|
||||
- session_token
|
||||
|
||||
- test:
|
||||
name: create user and check result.
|
||||
variables:
|
||||
token: $session_token
|
||||
testcase: testcases/deps/check_and_create.yml
|
||||
```
|
||||
|
||||
### 测试用例集(testsuite)
|
||||
|
||||
当测试用例数量比较多以后,为了方便管理和实现批量运行,通常需要使用测试用例集来对测试用例进行组织。
|
||||
|
||||
在前文的测试用例分层模型中也强调了,测试用例集(testsuite)是测试用例的 `无序` 集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系,那就需要在测试用例中完成依赖的处理。
|
||||
|
||||
因为是 `无序` 集合,因此测试用例集的描述形式会与测试用例有些不同,在每个测试用例集文件中,第一层级存在两类字段:
|
||||
|
||||
- config: 测试用例集的总体配置参数
|
||||
- testcases: 值为字典结构(无序),key 为测试用例的名称,value 为测试用例的内容;在引用测试用例时也可以指定 variables,实现对引用测试用例中 variables 的覆盖。
|
||||
|
||||
#### 非参数化场景
|
||||
|
||||
```yaml
|
||||
config:
|
||||
name: create users with uid
|
||||
variables:
|
||||
device_sn: ${gen_random_string(15)}
|
||||
var_a: ${gen_random_string(5)}
|
||||
var_b: $var_a
|
||||
base_url: "http://127.0.0.1:5000"
|
||||
|
||||
testcases:
|
||||
create user 1000 and check result.:
|
||||
testcase: testcases/create_user.yml
|
||||
variables:
|
||||
uid: 1000
|
||||
var_c: ${gen_random_string(5)}
|
||||
var_d: $var_c
|
||||
|
||||
create user 1001 and check result.:
|
||||
testcase: testcases/create_user.yml
|
||||
variables:
|
||||
uid: 1001
|
||||
var_c: ${gen_random_string(5)}
|
||||
var_d: $var_c
|
||||
```
|
||||
|
||||
#### 参数化场景(parameters)
|
||||
|
||||
对于参数化场景,可通过 parameters 实现,描述形式如下所示。
|
||||
|
||||
```yaml
|
||||
config:
|
||||
name: create users with parameters
|
||||
variables:
|
||||
device_sn: ${gen_random_string(15)}
|
||||
base_url: "http://127.0.0.1:5000"
|
||||
|
||||
testcases:
|
||||
create user $uid and check result for $device_sn.:
|
||||
testcase: testcases/create_user.yml
|
||||
variables:
|
||||
uid: 1000
|
||||
device_sn: TESTSUITE_XXX
|
||||
parameters:
|
||||
uid: [101, 102, 103]
|
||||
device_sn: [TESTSUITE_X1, TESTSUITE_X2]
|
||||
```
|
||||
|
||||
参数化后,parameters 中的变量将采用笛卡尔积组合形成参数列表,依次覆盖 variables 中的参数,驱动测试用例的运行。
|
||||
|
||||
## 文件目录结构管理 && 脚手架工具
|
||||
|
||||
在对测试用例文件进行组织管理时,对于文件的存储位置均没有要求和限制,在引用时只需要指定对应的文件路径即可。但从约定大于配置的角度,最好是按照推荐的文件夹名称进行存储管理,并可通过子目录实现项目模块分类管理。
|
||||
|
||||
推荐的方式汇总如下:
|
||||
|
||||
- `debugtalk.py` 放置在项目根目录下,假设为 `PRJ_ROOT_DIR`
|
||||
- `.env` 放置在项目根目录下,路径为 `PRJ_ROOT_DIR/.env`
|
||||
- 接口定义(API)放置在 `PRJ_ROOT_DIR/api/` 目录下
|
||||
- 测试用例(testcase)放置在 `PRJ_ROOT_DIR/testcases/` 目录下
|
||||
- 测试用例集(testsuite)文件必须放置在 `PRJ_ROOT_DIR/testsuites/` 目录下
|
||||
- data 文件夹:存储参数化文件,或者项目依赖的文件,路径为 `PRJ_ROOT_DIR/data/`
|
||||
- reports 文件夹:存储 HTML 测试报告,生成路径为 `PRJ_ROOT_DIR/reports/`
|
||||
|
||||
目录结构如下所示:
|
||||
|
||||
```bash
|
||||
$ tree tests
|
||||
tests
|
||||
├── .env
|
||||
├── data
|
||||
│ ├── app_version.csv
|
||||
│ └── account.csv
|
||||
├── api
|
||||
│ ├── create_user.yml
|
||||
│ ├── get_headers.yml
|
||||
│ ├── get_token.yml
|
||||
│ ├── get_user.yml
|
||||
│ └── reset_all.yml
|
||||
├── debugtalk.py
|
||||
├── testcases
|
||||
│ ├── create_user.yml
|
||||
│ ├── deps
|
||||
│ │ └── check_and_create.yml
|
||||
│ └── setup.yml
|
||||
└── testsuites
|
||||
├── create_users.yml
|
||||
└── create_users_with_parameters.yml
|
||||
```
|
||||
|
||||
**项目脚手架**
|
||||
|
||||
同时,在 `HttpRunner` 中实现了一个脚手架工具,可以快速创建项目的目录结构。该想法来源于 `Django` 的 `django-admin.py startproject project_name`。
|
||||
|
||||
使用方式也与 `Django` 类似,只需要通过 `--startproject` 指定新项目的名称即可。
|
||||
|
||||
```bash
|
||||
$ hrun --startproject demo
|
||||
Start to create new project: demo
|
||||
CWD: /Users/debugtalk/MyProjects/examples
|
||||
|
||||
created folder: demo
|
||||
created folder: demo/api
|
||||
created folder: demo/testcases
|
||||
created folder: demo/testsuites
|
||||
created folder: demo/reports
|
||||
created file: demo/debugtalk.py
|
||||
created file: demo/.env
|
||||
```
|
||||
|
||||
|
||||
## 相关参考
|
||||
|
||||
- [《HttpRunner 的测试用例分层机制(已过期)》](/post/HttpRunner-testcase-layer)
|
||||
- 测试用例分层详细示例:[HttpRunner/tests](https://github.com/HttpRunner/HttpRunner/tree/master/tests)
|
||||
178
docs/prepare/testcase-structure.md
Normal file
178
docs/prepare/testcase-structure.md
Normal file
@@ -0,0 +1,178 @@
|
||||
|
||||
## YAML & JSON
|
||||
|
||||
HttpRunner 的测试用例支持两种文件格式:YAML 和 JSON。
|
||||
|
||||
JSON 和 YAML 格式的测试用例完全等价,包含的信息内容也完全相同。
|
||||
|
||||
- 对于新手来说,推荐使用 JSON 格式,虽然描述形式上稍显累赘,但是不容易出错(大多编辑器都具有 JSON 格式的检测功能);同时,HttpRunner 也内置了 JSON 格式正确性检测和样式美化功能,详情可查看[《Validate & Prettify》](/testcase//validate-pretty.md)。
|
||||
- 对于熟悉 YAML 格式的人来说,编写维护 YAML 格式的测试用例会更简洁,但前提是要保证 YAML 格式没有语法错误。
|
||||
|
||||
对于两种格式的展示差异,可以对比查看 [demo-quickstart-6.json](/data/demo-quickstart-6.json) 和 [demo-quickstart-6.yml](/data/demo-quickstart-6.yml) 获取初步的印象。
|
||||
|
||||
后面为了更清晰的描述,统一采用 JSON 格式作为示例。
|
||||
|
||||
## 测试用例结构
|
||||
|
||||

|
||||
|
||||
在 HttpRunner 中,测试用例组织主要基于三个概念:
|
||||
|
||||
- 测试用例集(testsuite):对应一个文件夹,包含单个或多个测试用例(`YAML/JSON`)文件
|
||||
- 测试用例(testcase):对应一个 `YAML/JSON` 文件,包含单个或多个测试步骤
|
||||
- 测试步骤(teststep):对应 `YAML/JSON` 文件中的一个 `test`,描述单次接口测试的全部内容,包括发起接口请求、解析响应结果、校验结果等
|
||||
|
||||
对于单个 `YAML/JSON` 文件来说,数据存储结构为 `list of dict` 的形式,其中可能包含一个全局配置项(config)和若干个测试步骤(test)。
|
||||
|
||||
具体地:
|
||||
|
||||
- config:作为整个测试用例的全局配置项
|
||||
- test:对应单个测试步骤(teststep),测试用例存在顺序关系,运行时将从前往后依次运行各个测试步骤
|
||||
|
||||
|
||||
对应的 JSON 格式如下所示:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"config": {...}
|
||||
},
|
||||
{
|
||||
"test": {...}
|
||||
},
|
||||
{
|
||||
"test": {...}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 变量空间(context)作用域
|
||||
|
||||
在测试用例内部,HttpRunner 划分了两层变量空间作用域(context)。
|
||||
|
||||
- config:作为整个测试用例的全局配置项,作用域为整个测试用例;
|
||||
- test:测试步骤的变量空间(context)会继承或覆盖 config 中定义的内容;
|
||||
- 若某变量在 config 中定义了,在某 test 中没有定义,则该 test 会继承该变量
|
||||
- 若某变量在 config 和某 test 中都定义了,则该 test 中使用自己定义的变量值
|
||||
- 各个测试步骤(test)的变量空间相互独立,互不影响;
|
||||
- 如需在多个测试步骤(test)中传递参数值,则需要使用 extract 关键字,并且只能从前往后传递
|
||||
|
||||
|
||||
|
||||
|
||||
## config
|
||||
|
||||
```json
|
||||
"config": {
|
||||
"name": "testcase description",
|
||||
"parameters": [
|
||||
{"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]},
|
||||
{"app_version": "${P(app_version.csv)}"},
|
||||
{"os_platform": "${get_os_platform()}"}
|
||||
],
|
||||
"variables": [
|
||||
{"user_agent": "iOS/10.3"},
|
||||
{"device_sn": "${gen_random_string(15)}"},
|
||||
{"os_platform": "ios"}
|
||||
],
|
||||
"request": {
|
||||
"base_url": "http://127.0.0.1:5000",
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
"device_sn": "$device_sn"
|
||||
}
|
||||
},
|
||||
"output": [
|
||||
"token"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Key | required? | format | descrption
|
||||
--- | --------- | ------ | ----------
|
||||
name | Yes | string | 测试用例的名称,在测试报告中将作为标题
|
||||
variables | No | list of dict | 定义的全局变量,作用域为整个用例
|
||||
parameters | No | list of dict | 全局参数,用于实现数据化驱动,作用域为整个用例
|
||||
request | No | dict | request 的公共参数,作用域为整个用例;常用参数包括 base_url 和 headers
|
||||
|
||||
**request**
|
||||
|
||||
Key | required? | format | descrption
|
||||
---------|----------|---------|---------
|
||||
base_url | No | string | 测试用例请求 URL 的公共 host,指定该参数后,test 中的 url 可以只描述 path 部分
|
||||
headers | No | dict | request 中 headers 的公共参数,作用域为整个用例
|
||||
output | No | list | 整个用例输出的参数列表,可输出的参数包括公共的 variable 和 extract 的参数; 在 log-level 为 debug 模式下,会在 terminal 中打印出参数内容
|
||||
|
||||
## test
|
||||
|
||||
```json
|
||||
"test": {
|
||||
"name": "get token with $user_agent, $os_platform, $app_version",
|
||||
"request": {
|
||||
"url": "/api/get-token",
|
||||
"method": "POST",
|
||||
"headers": {
|
||||
"app_version": "$app_version",
|
||||
"os_platform": "$os_platform",
|
||||
"user_agent": "$user_agent"
|
||||
},
|
||||
"json": {
|
||||
"sign": "${get_sign($user_agent, $device_sn, $os_platform, $app_version)}"
|
||||
},
|
||||
"extract": [
|
||||
{"token": "content.token"}
|
||||
],
|
||||
"validate": [
|
||||
{"eq": ["status_code", 200]},
|
||||
{"eq": ["headers.Content-Type", "application/json"]},
|
||||
{"eq": ["content.success", true]}
|
||||
],
|
||||
"setup_hooks": [],
|
||||
"teardown_hooks": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Key | required? | format | descrption
|
||||
--- | --------- | ------ | ----------
|
||||
name | Yes | string | 测试步骤的名称,在测试报告中将作为测试步骤的名称
|
||||
request | Yes | dict | HTTP 请求的详细内容;可用参数详见 [python-requests][1] 官方文档
|
||||
variables | No | list of dict | 测试步骤中定义的变量,作用域为当前测试步骤
|
||||
extract | No | list | 从当前 HTTP 请求的响应结果中提取参数,并保存到参数变量中(例如`token`),后续测试用例可通过`$token`的形式进行引用
|
||||
validate | No | list | 测试用例中定义的结果校验项,作用域为当前测试用例,用于实现对当前测试用例运行结果的校验
|
||||
setup_hooks | No | list | 在 HTTP 请求发送前执行 hook 函数,主要用于准备工作
|
||||
teardown_hooks | No | list | 在 HTTP 请求发送后执行 hook 函数,主要用户测试后的清理工作
|
||||
|
||||
**extract**
|
||||
|
||||
支持多种提取方式:
|
||||
|
||||
- 响应结果为 JSON 结构,可采用`.`运算符的方式,例如`headers.Content-Type`、`content.success`;
|
||||
- 响应结果为 text/html 结构,可采用正则表达式的方式,例如`blog-motto\">(.*)</h2>`
|
||||
- 详情可阅读[《ApiTestEngine,不再局限于API的测试》][2]
|
||||
|
||||
**validate**
|
||||
|
||||
支持两种格式:
|
||||
|
||||
- `{"comparator_name": [check_item, expect_value]}`
|
||||
- `{"check": check_item, "comparator": comparator_name, "expect": expect_value}`
|
||||
|
||||
**hooks**
|
||||
|
||||
setup_hooks 函数放置于 debugtalk.py 中,并且必须包含三个参数:
|
||||
|
||||
- method: 请求方法,e.g. GET, POST, PUT
|
||||
- url: 请求 URL
|
||||
- kwargs: request 的参数字典
|
||||
|
||||
teardown_hooks 函数放置于 debugtalk.py 中,并且必须包含一个参数:
|
||||
|
||||
- resp_obj: requests.Response 实例
|
||||
|
||||
关于 `setup_hooks` 和 `teardown_hooks` 的更多内容,请参考[《hook 机制》][3]。
|
||||
|
||||
|
||||
[1]: http://docs.python-requests.org/en/master/api/#main-interface
|
||||
[2]: http://debugtalk.com/post/apitestengine-not-only-about-json-api/
|
||||
[3]: ../../prepare/request-hook/
|
||||
74
docs/prepare/validate-pretty.md
Normal file
74
docs/prepare/validate-pretty.md
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
HttpRunner 从 `1.3.1` 版本开始,支持对 JSON 格式测试用例的内容进行格式正确性检测和样式美化功能。
|
||||
|
||||
## JSON 格式正确性检测
|
||||
|
||||
若需对 JSON 格式用例文件的内容进行正确性检测,可使用 `--validate` 参数。
|
||||
|
||||
可指定单个 JSON 用例文件路径。
|
||||
|
||||
```bash
|
||||
$ hrun --validate docs/data/demo-quickstart.json
|
||||
Start to validate JSON file: docs/data/demo-quickstart.json
|
||||
OK
|
||||
```
|
||||
|
||||
也可指定多个 JSON 用例文件路径。
|
||||
|
||||
```bash
|
||||
$ hrun --validate docs/data/demo-quickstart.json docs/data/demo-quickstart.yml docs/data/demo-quickstart-0.json
|
||||
Start to validate JSON file: docs/data/demo-quickstart.json
|
||||
OK
|
||||
WARNING Only JSON file format can be validated, skip docs/data/demo-quickstart.yml
|
||||
Start to validate JSON file: docs/data/demo-quickstart-0.json
|
||||
OK
|
||||
```
|
||||
|
||||
如上所示,当传入的文件后缀不是`.json`,HttpRunner 会打印 WARNING 信息,并跳过检测。
|
||||
|
||||
若 JSON 文件格式正确,则打印 OK。
|
||||
|
||||
若 JSON 文件格式存在异常,则打印详细的报错信息,精确到错误在文件中出现的行和列。
|
||||
|
||||
```bash
|
||||
$ hrun --validate docs/data/demo-quickstart.json
|
||||
Start to validate JSON file: docs/data/demo-quickstart.json
|
||||
Expecting ',' delimiter: line 5 column 13 (char 82)
|
||||
```
|
||||
|
||||
## JSON 格式美化
|
||||
|
||||
与 YAML 格式不同,JSON 格式不强制要求缩进和换行,这有点类似于 C 语言和 Python 语言的差异。
|
||||
|
||||
例如,`demo-quickstart.json`文件也可以改写为如下形式。
|
||||
|
||||
```json
|
||||
[{"config": {"name": "testcase description","variables": [],"request": {"base_url": "","headers": {"User-Agent": "python-requests/2.18.4"}}}},{"test": {"name": "/api/get-token","request": {"url": "http://127.0.0.1:5000/api/get-token","headers": {"device_sn": "FwgRiO7CNA50DSU","user_agent": "iOS/10.3","os_platform": "ios","app_version": "2.8.6","Content-Type": "application/json"},"method": "POST","json": {"sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"}},"validate": [{"eq": ["status_code",200]},{"eq": ["headers.Content-Type","application/json"]},{"eq": ["content.success",true]},{"eq": ["content.token","baNLX1zhFYP11Seb"]}]}},{"test": {"name": "/api/users/1000","request": {"url": "http://127.0.0.1:5000/api/users/1000","headers": {"device_sn": "FwgRiO7CNA50DSU","token": "baNLX1zhFYP11Seb","Content-Type": "application/json"},"method": "POST","json": {"name": "user1","password": "123456"}},"validate": [{"eq": ["status_code",201]},{"eq": ["headers.Content-Type","application/json"]},{"eq": ["content.success",true]},{"eq": ["content.msg","user created successfully."]}]}}]
|
||||
```
|
||||
|
||||
虽然上面 JSON 格式的测试用例也能正常执行,但测试用例文件的可读性太差,不利于阅读和维护。
|
||||
|
||||
针对该需求,可使用 `--prettify` 参数对 JSON 格式用例文件进行样式美化。
|
||||
|
||||
可指定单个 JSON 用例文件路径。
|
||||
|
||||
```bash
|
||||
$ hrun --prettify docs/data/demo-quickstart.json
|
||||
Start to prettify JSON file: docs/data/demo-quickstart.json
|
||||
success: docs/data/demo-quickstart.pretty.json
|
||||
```
|
||||
|
||||
也可指定多个 JSON 用例文件路径。
|
||||
|
||||
```bash
|
||||
$ hrun --prettify docs/data/demo-quickstart.json docs/data/demo-quickstart.yml docs/data/demo-quickstart-0.json
|
||||
WARNING Only JSON file format can be prettified, skip: docs/data/demo-quickstart.yml
|
||||
Start to prettify JSON file: docs/data/demo-quickstart.json
|
||||
success: docs/data/demo-quickstart.pretty.json
|
||||
Start to prettify JSON file: docs/data/demo-quickstart-0.json
|
||||
success: docs/data/demo-quickstart-0.pretty.json
|
||||
```
|
||||
|
||||
如上所示,当传入的文件后缀不是`.json`,HttpRunner 会打印 WARNING 信息,并跳过检测。
|
||||
|
||||
若转换成功,则打印美化后的文件路径;若 JSON 文件格式存在异常,则打印详细的报错信息,精确到错误在文件中出现的行和列。
|
||||
Reference in New Issue
Block a user