Files
httprunner/docs/quickstart.md
2019-11-01 23:28:50 +08:00

625 lines
26 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
本文将通过一个简单的示例来展示 HttpRunner 的核心功能使用方法。
## 案例介绍
该案例作为被测服务,主要有两类接口:
- 权限校验,获取 token
- 支持 CRUD 操作的 RESTful APIs所有接口的请求头域中都必须包含有效的 token
案例的实现形式为 flask 应用服务([api_server.py](data/api_server.py)),启动方式如下:
```text
$ export FLASK_APP=docs/data/api_server.py
$ export FLASK_ENV=development
$ flask run
* Serving Flask app "docs/data/api_server.py" (lazy loading)
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 989-476-348
```
服务启动成功后,我们就可以开始对其进行测试了。
## 测试准备
### 抓包分析
在开始测试之前,我们需要先了解接口的请求和响应细节,而最佳的方式就是采用 `Charles Proxy` 或者 `Fiddler` 这类网络抓包工具进行抓包分析。
例如,在本案例中,我们先进行权限校验,然后成功创建一个用户,对应的网络抓包内容如下图所示:
![](./images/demo-quickstart-http-1.jpg)
![](./images/demo-quickstart-http-2.jpg)
通过抓包我们可以看到具体的接口信息包括请求的URL、Method、headers、参数和响应内容等内容基于这些信息我们就可以开始编写测试用例了。
### 生成测试用例
为了简化测试用例的编写工作HttpRunner 实现了测试用例生成的功能。
首先,需要将抓取得到的数据包导出为 HAR 格式的文件,假设导出的文件名称为 [demo-quickstart.har](data/demo-quickstart.har)。
然后,在命令行终端中运行如下命令,即可将 demo-quickstart.har 转换为 HttpRunner 的测试用例文件。
```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
```
使用 `har2case` 转换脚本时默认转换为 JSON 格式,加上 `-2y` 参数后转换为 YAML 格式。两种格式完全等价YAML 格式更简洁JSON 格式支持的工具更丰富,大家可根据个人喜好进行选择。关于 [har2case][har2case] 的详细使用说明,请查看[《录制生成测试用例》](/prepare/record/)。
经过转换,在源 demo-quickstart.har 文件的同级目录下生成了相同文件名称的 YAML 格式测试用例文件 [demo-quickstart.yml](data/demo-quickstart.yml),其内容如下:
```yaml
- config:
name: testcase description
variables: {}
- test:
name: /api/get-token
request:
headers:
Content-Type: application/json
User-Agent: python-requests/2.18.4
app_version: 2.8.6
device_sn: FwgRiO7CNA50DSU
os_platform: ios
json:
sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98
method: POST
url: http://127.0.0.1:5000/api/get-token
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:
headers:
Content-Type: application/json
User-Agent: python-requests/2.18.4
device_sn: FwgRiO7CNA50DSU
token: baNLX1zhFYP11Seb
json:
name: user1
password: '123456'
method: POST
url: http://127.0.0.1:5000/api/users/1000
validate:
- eq: [status_code, 201]
- eq: [headers.Content-Type, application/json]
- eq: [content.success, true]
- eq: [content.msg, user created successfully.]
```
现在我们只需要知道如下几点:
- 每个 YAML/JSON 文件对应一个测试用例testcase
- 每个测试用例为一个`list of dict`结构其中可能包含全局配置项config和若干个测试步骤test
- `config` 为全局配置项,作用域为整个测试用例
- `test` 对应单个测试步骤,作用域仅限于本身
如上便是 HttpRunner 测试用例的基本结构。
关于测试用例的更多内容,请查看[《测试用例结构描述》](/prepare/testcase-structure/)。
### 首次运行测试用例
测试用例就绪后,我们可以开始调试运行了。
为了演示测试用例文件的迭代优化过程,我们先将 demo-quickstart.json 重命名为 [demo-quickstart-0.json](data/demo-quickstart-0.json)(对应的 YAML 格式:[demo-quickstart-0.yml](data/demo-quickstart-0.yml))。
运行测试用例的命令为`hrun`,后面直接指定测试用例文件的路径即可。
```text
$ hrun docs/data/demo-quickstart-0.yml
INFO Start to run testcase: testcase description
/api/get-token
INFO POST http://127.0.0.1:5000/api/get-token
INFO status_code: 200, response_time(ms): 9.26 ms, response_length: 46 bytes
ERROR validate: content.token equals baNLX1zhFYP11Seb(str) ==> fail
tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str)
ERROR ******************************** DETAILED REQUEST & RESPONSE ********************************
====== request details ======
url: http://127.0.0.1:5000/api/get-token
method: POST
headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'app_version': '2.8.6', 'device_sn': 'FwgRiO7CNA50DSU', 'os_platform': 'ios'}
json: {'sign': '9c0c7e51c91ae963c833a4ccbab8d683c4a90c98'}
verify: True
====== response details ======
status_code: 200
headers: {'Content-Type': 'application/json', 'Content-Length': '46', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:43:55 GMT'}
body: '{"success": true, "token": "tXGuSQgOCVXcltkz"}'
F
/api/users/1000
INFO POST http://127.0.0.1:5000/api/users/1000
ERROR 403 Client Error: FORBIDDEN for url: http://127.0.0.1:5000/api/users/1000
ERROR validate: status_code equals 201(int) ==> fail
403(int) equals 201(int)
ERROR validate: content.success equals True(bool) ==> fail
False(bool) equals True(bool)
ERROR validate: content.msg equals user created successfully.(str) ==> fail
Authorization failed!(str) equals user created successfully.(str)
ERROR ******************************** DETAILED REQUEST & RESPONSE ********************************
====== request details ======
url: http://127.0.0.1:5000/api/users/1000
method: POST
headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'device_sn': 'FwgRiO7CNA50DSU', 'token': 'baNLX1zhFYP11Seb'}
json: {'name': 'user1', 'password': '123456'}
verify: True
====== response details ======
status_code: 403
headers: {'Content-Type': 'application/json', 'Content-Length': '50', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:43:55 GMT'}
body: '{"success": false, "msg": "Authorization failed!"}'
F
======================================================================
FAIL: test_0000_000 (httprunner.api.TestSequense)
/api/get-token
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 54, in test
test_runner.run_test(test_dict)
httprunner.exceptions.ValidationFailure: validate: content.token equals baNLX1zhFYP11Seb(str) ==> fail
tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 56, in test
self.fail(str(ex))
AssertionError: validate: content.token equals baNLX1zhFYP11Seb(str) ==> fail
tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str)
======================================================================
FAIL: test_0001_000 (httprunner.api.TestSequense)
/api/users/1000
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 54, in test
test_runner.run_test(test_dict)
httprunner.exceptions.ValidationFailure: validate: status_code equals 201(int) ==> fail
403(int) equals 201(int)
validate: content.success equals True(bool) ==> fail
False(bool) equals True(bool)
validate: content.msg equals user created successfully.(str) ==> fail
Authorization failed!(str) equals user created successfully.(str)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 56, in test
self.fail(str(ex))
AssertionError: validate: status_code equals 201(int) ==> fail
403(int) equals 201(int)
validate: content.success equals True(bool) ==> fail
False(bool) equals True(bool)
validate: content.msg equals user created successfully.(str) ==> fail
Authorization failed!(str) equals user created successfully.(str)
----------------------------------------------------------------------
Ran 2 tests in 0.026s
FAILED (failures=2)
INFO Start to render Html report ...
INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548513835.html
```
非常不幸,两个接口的测试用例均运行失败了。
## 优化测试用例
从两个测试步骤的报错信息和堆栈信息Traceback可以看出第一个步骤失败的原因是获取的 token 与预期值不一致第二个步骤失败的原因是请求权限校验失败403
接下来我们将逐步进行进行优化。
### 调整校验器
默认情况下,[har2case][har2case] 生成用例时,若 HTTP 请求的响应内容为 JSON 格式,则会将第一层级中的所有`key-value`转换为 validator。
例如上面的第一个测试步骤,生成的 validator 为:
```json
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["headers.Content-Type", "application/json"]},
{"eq": ["content.success", true]},
{"eq": ["content.token", "baNLX1zhFYP11Seb"]}
]
```
运行测试用例时,就会对上面的各个项进行校验。
问题在于,请求`/api/get-token`接口时,每次生成的 token 都会是不同的,因此将生成的 token 作为校验项的话,校验自然就无法通过了。
正确的做法是,在测试步骤的 validate 中应该去掉这类动态变化的值。
去除该项后,将用例另存为 [demo-quickstart-1.json](data/demo-quickstart-1.json)(对应的 YAML 格式:[demo-quickstart-1.yml](data/demo-quickstart-1.yml))。
再次运行测试用例,运行结果如下:
```text
$ hrun docs/data/demo-quickstart-1.yml
INFO Start to run testcase: testcase description
/api/get-token
INFO POST http://127.0.0.1:5000/api/get-token
INFO status_code: 200, response_time(ms): 6.61 ms, response_length: 46 bytes
.
/api/users/1000
INFO POST http://127.0.0.1:5000/api/users/1000
ERROR 403 Client Error: FORBIDDEN for url: http://127.0.0.1:5000/api/users/1000
ERROR validate: status_code equals 201(int) ==> fail
403(int) equals 201(int)
ERROR validate: content.success equals True(bool) ==> fail
False(bool) equals True(bool)
ERROR validate: content.msg equals user created successfully.(str) ==> fail
Authorization failed!(str) equals user created successfully.(str)
ERROR ******************************** DETAILED REQUEST & RESPONSE ********************************
====== request details ======
url: http://127.0.0.1:5000/api/users/1000
method: POST
headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'device_sn': 'FwgRiO7CNA50DSU', 'token': 'baNLX1zhFYP11Seb'}
json: {'name': 'user1', 'password': '123456'}
verify: True
====== response details ======
status_code: 403
headers: {'Content-Type': 'application/json', 'Content-Length': '50', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:45:34 GMT'}
body: '{"success": false, "msg": "Authorization failed!"}'
F
======================================================================
FAIL: test_0001_000 (httprunner.api.TestSequense)
/api/users/1000
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 54, in test
test_runner.run_test(test_dict)
httprunner.exceptions.ValidationFailure: validate: status_code equals 201(int) ==> fail
403(int) equals 201(int)
validate: content.success equals True(bool) ==> fail
False(bool) equals True(bool)
validate: content.msg equals user created successfully.(str) ==> fail
Authorization failed!(str) equals user created successfully.(str)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 56, in test
self.fail(str(ex))
AssertionError: validate: status_code equals 201(int) ==> fail
403(int) equals 201(int)
validate: content.success equals True(bool) ==> fail
False(bool) equals True(bool)
validate: content.msg equals user created successfully.(str) ==> fail
Authorization failed!(str) equals user created successfully.(str)
----------------------------------------------------------------------
Ran 2 tests in 0.018s
FAILED (failures=1)
INFO Start to render Html report ...
INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548513934.html
```
经过修改第一个测试步骤已经运行成功了第二个步骤仍然运行失败403还是因为权限校验的原因。
### 参数关联
我们继续查看 [demo-quickstart-1.json](data/demo-quickstart-1.json),会发现第二个测试步骤的请求 headers 中的 token 仍然是硬编码的,即抓包时获取到的值。在我们再次运行测试用例时,这个 token 已经失效了,所以会出现 403 权限校验失败的问题。
正确的做法是,我们应该在每次运行测试用例的时候,先动态获取到第一个测试步骤中的 token然后在后续测试步骤的请求中使用前面获取到的 token。
在 HttpRunner 中,支持参数提取(`extract`)和参数引用的功能(`$var`)。
在测试步骤test若需要从响应结果中提取参数则可使用 `extract` 关键字。extract 的列表中可指定一个或多个需要提取的参数。
在提取参数时,当 HTTP 的请求响应结果为 JSON 格式,则可以采用`.`运算符的方式,逐级往下获取到参数值;响应结果的整体内容引用方式为 content 或者 body。
例如,第一个接口`/api/get-token`的响应结果为:
```json
{"success": true, "token": "ZQkYhbaQ6q8UFFNE"}
```
那么要获取到 token 参数,就可以使用 content.token 的方式;具体的写法如下:
```json
"extract": [
{"token": "content.token"}
]
```
其中token 作为提取后的参数名称,可以在后续使用 `$token` 进行引用。
```json
"headers": {
"device_sn": "FwgRiO7CNA50DSU",
"token": "$token",
"Content-Type": "application/json"
}
```
修改后的测试用例另存为 [demo-quickstart-2.json](data/demo-quickstart-2.json)(对应的 YAML 格式:[demo-quickstart-2.yml](data/demo-quickstart-2.yml))。
再次运行测试用例,运行结果如下:
```text
$ hrun docs/data/demo-quickstart-2.yml
INFO Start to run testcase: testcase description
/api/get-token
INFO POST http://127.0.0.1:5000/api/get-token
INFO status_code: 200, response_time(ms): 8.32 ms, response_length: 46 bytes
.
/api/users/1000
INFO POST http://127.0.0.1:5000/api/users/1000
INFO status_code: 201, response_time(ms): 3.02 ms, response_length: 54 bytes
.
----------------------------------------------------------------------
Ran 2 tests in 0.019s
OK
INFO Start to render Html report ...
INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548514191.html
```
经过修改,第二个测试步骤也运行成功了。
### base_url
虽然测试步骤运行都成功了,但是仍然有继续优化的地方。
继续查看 [demo-quickstart-2.json](data/demo-quickstart-2.json),我们会发现在每个测试步骤的 URL 中都采用的是完整的描述host+path但大多数情况下同一个用例中的 host 都是相同的,区别仅在于 path 部分。
因此我们可以将各个测试步骤test URL 的 `base_url` 抽取出来放到全局配置模块config在测试步骤中的 URL 只保留 PATH 部分。
```yaml
- config:
name: testcase description
base_url: http://127.0.0.1:5000
- test:
name: get token
request:
url: /api/get-token
```
调整后的测试用例另存为 [demo-quickstart-3.json](data/demo-quickstart-3.json)(对应的 YAML 格式:[demo-quickstart-3.yml](data/demo-quickstart-3.yml))。
重启 flask 应用服务后再次运行测试用例,所有的测试步骤仍然运行成功。
### 变量的申明和引用
继续查看 [demo-quickstart-3.json](data/demo-quickstart-3.json),我们会发现测试用例中存在较多硬编码的参数,例如 app_version、device_sn、os_platform、user_id 等。
大多数情况下,我们可以不用修改这些硬编码的参数,测试用例也能正常运行。但是为了更好地维护测试用例,例如同一个参数值在测试步骤中出现多次,那么比较好的做法是,将这些参数定义为变量,然后在需要参数的地方进行引用。
在 HttpRunner 中,支持变量申明(`variables`)和引用(`$var`)的机制。在 config 和 test 中均可以通过 `variables` 关键字定义变量,然后在测试步骤中可以通过 `$ + 变量名称` 的方式引用变量。区别在于,在 config 中定义的变量为全局的整个测试用例testcase的所有地方均可以引用在 test 中定义的变量作用域仅局限于当前测试步骤teststep
对上述各个测试步骤中硬编码的参数进行变量申明和引用调整后,新的测试用例另存为 [demo-quickstart-4.json](data/demo-quickstart-4.json)(对应的 YAML 格式:[demo-quickstart-4.yml](data/demo-quickstart-4.yml))。
重启 flask 应用服务后再次运行测试用例,所有的测试步骤仍然运行成功。
### 抽取公共变量
查看 [demo-quickstart-4.json](data/demo-quickstart-4.json) 可以看出,两个测试步骤中都定义了 device_sn。针对这类公共的参数我们可以将其统一定义在 config 的 variables 中,在测试步骤中就不用再重复定义。
```yaml
- config:
name: testcase description
base_url: http://127.0.0.1:5000
variables:
device_sn: FwgRiO7CNA50DSU
```
调整后的测试用例见 [demo-quickstart-5.json](data/demo-quickstart-5.json)(对应的 YAML 格式:[demo-quickstart-5.yml](data/demo-quickstart-5.yml))。
### 实现动态运算逻辑
在 [demo-quickstart-5.yml](data/demo-quickstart-5.yml) 中,参数 device_sn 代表的是设备的 SN 编码,虽然采用硬编码的方式暂时不影响测试用例的运行,但这与真实的用户场景不大相符。
假设 device_sn 的格式为 15 长度的字符串,那么我们就可以在每次运行测试用例的时候,针对 device_sn 生成一个 15 位长度的随机字符串。与此同时sign 字段是根据 headers 中的各个字段拼接后生成得到的 MD5 值,因此在 device_sn 变动后sign 也应该重新进行计算,否则就会再次出现签名校验失败的问题。
然而HttpRunner 的测试用例都是采用 YAML/JSON 格式进行描述的,在文本格式中如何执行代码运算呢?
HttpRunner 的实现方式为,支持热加载的插件机制(`debugtalk.py`),可以在 YAML/JSON 中调用 Python 函数。
具体地做法,我们可以在测试用例文件的同级或其父级目录中创建一个 debugtalk.py 文件,然后在其中定义相关的函数和变量。
例如,针对 device_sn 的随机字符串生成功能,我们可以定义一个 gen_random_string 函数;针对 sign 的签名算法,我们可以定义一个 get_sign 函数。
```python
import hashlib
import hmac
import random
import string
SECRET_KEY = "DebugTalk"
def gen_random_string(str_len):
random_char_list = []
for _ in range(str_len):
random_char = random.choice(string.ascii_letters + string.digits)
random_char_list.append(random_char)
random_string = ''.join(random_char_list)
return random_string
def get_sign(*args):
content = ''.join(args).encode('ascii')
sign_key = SECRET_KEY.encode('ascii')
sign = hmac.new(sign_key, content, hashlib.sha1).hexdigest()
return sign
```
然后,我们在 YAML/JSON 测试用例文件中,就可以对定义的函数进行调用,对定义的变量进行引用了。引用变量的方式仍然与前面讲的一样,采用`$ + 变量名称`的方式;调用函数的方式为`${func($var)}`
例如,生成 15 位长度的随机字符串并赋值给 device_sn 的代码为:
```json
"variables": [
{"device_sn": "${gen_random_string(15)}"}
]
```
使用 $user_agent、$device_sn、$os_platform、$app_version 根据签名算法生成 sign 值的代码为:
```json
"json": {
"sign": "${get_sign($user_agent, $device_sn, $os_platform, $app_version)}"
}
```
对测试用例进行上述调整后,另存为 [demo-quickstart-6.json](data/demo-quickstart-6.json)(对应的 YAML 格式:[demo-quickstart-6.yml](data/demo-quickstart-6.yml))。
重启 flask 应用服务后再次运行测试用例,所有的测试步骤仍然运行成功。
### 参数化数据驱动
> 请确保你使用的 HttpRunner 版本号不低于 2.0.0
在 [demo-quickstart-6.yml](data/demo-quickstart-6.yml) 中user_id 仍然是写死的值,假如我们需要创建 user_id 为 10011004 的用户,那我们只能不断地去修改 user_id然后运行测试用例重复操作 4 次?或者我们在测试用例文件中将创建用户的 test 复制 4 份,然后在每一份里面分别使用不同的 user_id
很显然,不管是采用上述哪种方式,都会很繁琐,并且也无法应对灵活多变的测试需求。
针对这类需求HttpRunner 支持参数化数据驱动的功能。
在 HttpRunner 中,若要采用数据驱动的方式来运行测试用例,需要创建一个文件,对测试用例进行引用,并使用 `parameters` 关键字定义参数并指定数据源取值方式。
例如,我们需要在创建用户的接口中对 user_id 进行参数化,参数化列表为 10011004并且取值方式为顺序取值那么最简单的描述方式就是直接指定参数列表。具体的编写方式为新建一个测试场景文件 [demo-quickstart-7.yml](data/demo-quickstart-7.yml)(对应的 JSON 格式:[demo-quickstart-7.json](data/demo-quickstart-7.json)),内容如下所示:
```yaml
config:
name: testcase description
testcases:
create user:
testcase: demo-quickstart-6.yml
parameters:
user_id: [1001, 1002, 1003, 1004]
```
仅需如上配置,针对 user_id 的参数化数据驱动就完成了。
重启 flask 应用服务后再次运行测试用例,测试用例运行情况如下所示:
<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。
关于参数化数据驱动,这里只描述了最简单的场景和使用方式,如需了解更多,请进一步阅读[《数据驱动使用手册》](/prepare/parameters/)。
[har2case]: https://github.com/HttpRunner/har2case
## 查看测试报告
在每次使用 hrun 命令运行测试用例后,均会生成一份 HTML 格式的测试报告。报告文件位于 reports 目录下,文件名称为测试用例的开始运行时间。
例如,在运行完 [demo-quickstart-1.json](data/demo-quickstart-1.json) 后,将生成如下形式的测试报告:
![](./images/report-demo-quickstart-1-overview.jpg)
![](./images/report-demo-quickstart-1-log2.jpg)
![](./images/report-demo-quickstart-1-traceback.jpg)
关于测试报告的详细内容,请查看[《测试报告》](/run-tests/report/)部分。
## 总结
到此为止HttpRunner 的核心功能就介绍完了,掌握本文中的功能特性,足以帮助你应对日常项目工作中至少 80% 的自动化测试需求。
当然HttpRunner 不止于此,如需挖掘 HttpRunner 的更多特性,实现更复杂场景的自动化测试需求,可继续阅读后续文档。