mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-13 08:09:45 +08:00
15
.travis.yml
15
.travis.yml
@@ -2,19 +2,18 @@ sudo: false
|
||||
language: python
|
||||
python:
|
||||
- 2.7
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
matrix:
|
||||
include:
|
||||
- python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
dist: xenial # Required for Python 3.7
|
||||
sudo: true # Required for Python 3.7
|
||||
install:
|
||||
- pip install pipenv --upgrade
|
||||
- pipenv install --dev --skip-lock
|
||||
- pip install poetry
|
||||
- poetry install -vvv
|
||||
script:
|
||||
- pipenv run python setup.py install
|
||||
- pipenv run coverage run --source=httprunner -m unittest discover
|
||||
- poetry build
|
||||
- poetry run coverage run --source=httprunner -m unittest discover
|
||||
after_success:
|
||||
- pipenv run coveralls
|
||||
- poetry run coveralls
|
||||
65
CHANGELOG.md
65
CHANGELOG.md
@@ -1,27 +1,41 @@
|
||||
# Release History
|
||||
|
||||
## 2.2.3
|
||||
## 2.2.4 (2019-07-13)
|
||||
|
||||
**Bugfixes**
|
||||
**Changed**
|
||||
|
||||
- replace pipenv & setup.py with poetry
|
||||
- drop support for Python 3.4 as it was EOL on 2019-03-16
|
||||
- relocate debugging scripts, move from main-debug.py to httprunner.cli
|
||||
|
||||
**Fixed**
|
||||
|
||||
- fix #574: delete unnecessary code
|
||||
- fix #551: raise if times is not digit
|
||||
- fix #572: tests_def_mapping["testcases"] typo error
|
||||
|
||||
## 2.2.3 (2019-06-30)
|
||||
|
||||
**Fixed**
|
||||
|
||||
- fix yaml FullLoader AttributeError when PyYAML version < 5.1
|
||||
|
||||
## 2.2.2 (2019-06-26)
|
||||
|
||||
**Features**
|
||||
**Changed**
|
||||
|
||||
- `extract` is used to replace `output` when passing former teststep's (as a testcase) export value to next teststep
|
||||
- `export` is used to replace `output` in testcase config
|
||||
|
||||
## 2.2.1 (2019-06-25)
|
||||
|
||||
**Features**
|
||||
**Added**
|
||||
|
||||
- add demo api/testcase/testsuite to new created scaffold project
|
||||
- update default `.gitignore` of new created scaffold project
|
||||
- add demo content to `debugtalk.py`/`.env` of new created scaffold project
|
||||
|
||||
**Bugfixes**
|
||||
**Fixed**
|
||||
|
||||
- fix extend with testcase reference in format version 2
|
||||
- fix ImportError when locustio is not installed
|
||||
@@ -29,31 +43,31 @@
|
||||
|
||||
## 2.2.0 (2019-06-24)
|
||||
|
||||
**Features**
|
||||
**Added**
|
||||
|
||||
- support testcase/testsuite in format version 2
|
||||
|
||||
**Bugfixes**
|
||||
**Fixed**
|
||||
|
||||
- add wheel in dev packages
|
||||
- fix exception when teststep name reference former extracted variable
|
||||
|
||||
## 2.1.3 (2019-04-24)
|
||||
|
||||
**Bugfixes**
|
||||
**Fixed**
|
||||
|
||||
- replace eval mechanism with builtins to prevent security vulnerabilities
|
||||
- ImportError for builtins in Python2.7
|
||||
|
||||
## 2.1.2 (2019-04-17)
|
||||
|
||||
**Features**
|
||||
**Added**
|
||||
|
||||
- support new variable notation ${var}
|
||||
- use \$\$ to escape \$ notation
|
||||
- add Python 3.7 for travis CI
|
||||
|
||||
**Bugfixes**
|
||||
**Fixed**
|
||||
|
||||
- match duplicate variable/function in single raw string
|
||||
- escape '{' and '}' notation in raw string
|
||||
@@ -62,7 +76,7 @@
|
||||
|
||||
## 2.1.1 (2019-04-11)
|
||||
|
||||
**Features**
|
||||
**Changed**
|
||||
|
||||
refactor upload files mechanism with [requests-toolbelt](https://toolbelt.readthedocs.io/en/latest/user.html#multipart-form-data-encoder):
|
||||
|
||||
@@ -71,41 +85,41 @@ refactor upload files mechanism with [requests-toolbelt](https://toolbelt.readth
|
||||
|
||||
## 2.1.0 (2019-04-10)
|
||||
|
||||
**Features**
|
||||
**Added**
|
||||
|
||||
- implement json dump Python objects when save tests
|
||||
- implement lazy parser
|
||||
- remove project_mapping from parse_tests result
|
||||
|
||||
**Bugfixes**
|
||||
**Fixed**
|
||||
|
||||
- reference output variables
|
||||
- pass output variables between testcases
|
||||
|
||||
## 2.0.6 (2019-03-18)
|
||||
|
||||
**Features**
|
||||
**Added**
|
||||
|
||||
- create .gitignore file when initializing new project
|
||||
|
||||
**Bugfixes**
|
||||
**Fixed**
|
||||
|
||||
- fix CSV relative path detection
|
||||
- fix current validators displaying the former one when they are empty
|
||||
|
||||
## 2.0.5 (2019-03-04)
|
||||
|
||||
**Features**
|
||||
**Added**
|
||||
|
||||
- implement method to get variables and output
|
||||
|
||||
**Bugfixes**
|
||||
**Fixed**
|
||||
|
||||
- fix xss in response json
|
||||
|
||||
## 2.0.4 (2019-02-28)
|
||||
|
||||
**Bugfixes**
|
||||
**Fixed**
|
||||
|
||||
- fix verify priority with nested testcase
|
||||
- fix function in config variables called multiple times
|
||||
@@ -113,7 +127,7 @@ refactor upload files mechanism with [requests-toolbelt](https://toolbelt.readth
|
||||
|
||||
## 2.0.3 (2019-02-24)
|
||||
|
||||
**Bugfixes**
|
||||
**Fixed**
|
||||
|
||||
- fix verify priority: teststep > config
|
||||
- fix Chinese charactor in log_file encoding error in Windows
|
||||
@@ -121,12 +135,12 @@ refactor upload files mechanism with [requests-toolbelt](https://toolbelt.readth
|
||||
|
||||
## 2.0.2 (2019-01-21)
|
||||
|
||||
**Bugfixes**
|
||||
**Fixed**
|
||||
|
||||
- each teststeps in one testcase share the same session
|
||||
- fix duplicate API definition output
|
||||
|
||||
**Improvements**
|
||||
**Changed**
|
||||
|
||||
- display result from hook functions in DEBUG level log
|
||||
- change log level of "Variables & Output" to INFO
|
||||
@@ -135,15 +149,20 @@ refactor upload files mechanism with [requests-toolbelt](https://toolbelt.readth
|
||||
|
||||
## 2.0.1 (2019-01-18)
|
||||
|
||||
**Bugfixes**
|
||||
**Fixed**
|
||||
|
||||
- override current teststep variables with former testcase output variables
|
||||
- make compatible with testcase name is empty
|
||||
- Fixed compatibility when testcase name is empty
|
||||
- skip undefined variable when parsing string content
|
||||
|
||||
**Changed**
|
||||
|
||||
- add back request method in report
|
||||
|
||||
## 2.0.0 (2019-01-01)
|
||||
|
||||
**Changed**
|
||||
|
||||
- Massive Refactor and Simplification
|
||||
- Redesign testcase structure
|
||||
- Module pipline
|
||||
|
||||
25
Pipfile
25
Pipfile
@@ -1,25 +0,0 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
requests = "*"
|
||||
pyyaml = "*"
|
||||
jinja2 = "*"
|
||||
har2case = "*"
|
||||
colorama = "*"
|
||||
colorlog = "*"
|
||||
requests-toolbelt = "*"
|
||||
filetype = "*"
|
||||
|
||||
[dev-packages]
|
||||
Flask = "<1.0.0"
|
||||
coverage = "*"
|
||||
coveralls = "*"
|
||||
twine = "*"
|
||||
contextlib2 = "*"
|
||||
locustio = "*"
|
||||
wheel = "*"
|
||||
|
||||
[scripts]
|
||||
@@ -1 +0,0 @@
|
||||
httprunner.org
|
||||
20
docs/FAQ.md
20
docs/FAQ.md
@@ -1,20 +0,0 @@
|
||||
## Unable to install PyUnitReport dependency library automatically
|
||||
|
||||
If there is something goes wrong in installation like below.
|
||||
|
||||
```text
|
||||
Downloading/unpacking PyUnitReport (from HttpRunner)
|
||||
Could not find any downloads that satisfy the requirement PyUnitReport (from HttpRunner)
|
||||
```
|
||||
|
||||
You could install `PyUnitReport` manully at first.
|
||||
|
||||
```bash
|
||||
$ pip install git+https://github.com/debugtalk/PyUnitReport.git#egg=PyUnitReport
|
||||
```
|
||||
|
||||
And then everything will be OK when you reinstall `HttpRunner`.
|
||||
|
||||
```bash
|
||||
$ pip install git+https://github.com/debugtalk/HttpRunner.git#egg=HttpRunner
|
||||
```
|
||||
@@ -1,96 +0,0 @@
|
||||
## Installation
|
||||
|
||||
`HttpRunner` is available on [`PyPI`][PyPI] and can be installed through pip or easy_install.
|
||||
|
||||
```bash
|
||||
$ pip install HttpRunner
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
$ easy_install HttpRunner
|
||||
```
|
||||
|
||||
If you want to keep up with the latest version, you can install with github repository url.
|
||||
|
||||
```bash
|
||||
$ pip install git+https://github.com/HttpRunner/HttpRunner.git#egg=HttpRunner
|
||||
```
|
||||
|
||||
## Upgrade
|
||||
|
||||
If you have installed `HttpRunner` before and want to upgrade to the latest version, you can use the `-U` option.
|
||||
|
||||
This option works on each installation method described above.
|
||||
|
||||
```bash
|
||||
$ pip install -U HttpRunner
|
||||
$ easy_install -U HttpRunner
|
||||
$ pip install -U git+https://github.com/HttpRunner/HttpRunner.git#egg=HttpRunner
|
||||
```
|
||||
|
||||
## Check Installation
|
||||
|
||||
When HttpRunner is installed, a **httprunner** (**hrun** for short) command should be available in your shell (if you're not using
|
||||
virtualenv—which you should—make sure your python script directory is on your path).
|
||||
|
||||
To see `HttpRunner` version:
|
||||
|
||||
```bash
|
||||
$ httprunner -V # same as: hrun -V
|
||||
HttpRunner version: 0.8.1b
|
||||
PyUnitReport version: 0.1.3b
|
||||
```
|
||||
|
||||
To see available options, run:
|
||||
|
||||
```bash
|
||||
$ httprunner -h # same as: hrun -h
|
||||
usage: main-debug.py [-h] [-V] [--no-html-report]
|
||||
[--html-report-name HTML_REPORT_NAME]
|
||||
[--html-report-template HTML_REPORT_TEMPLATE]
|
||||
[--log-level LOG_LEVEL] [--log-file LOG_FILE]
|
||||
[--dot-env-path DOT_ENV_PATH] [--failfast]
|
||||
[--startproject STARTPROJECT]
|
||||
[--validate [VALIDATE [VALIDATE ...]]]
|
||||
[--prettify [PRETTIFY [PRETTIFY ...]]]
|
||||
[testcase_paths [testcase_paths ...]]
|
||||
|
||||
One-stop solution for HTTP(S) testing.
|
||||
|
||||
positional arguments:
|
||||
testcase_paths testcase file path
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-V, --version show version
|
||||
--no-html-report do not generate html report.
|
||||
--html-report-name HTML_REPORT_NAME
|
||||
specify html report name, only effective when
|
||||
generating html report.
|
||||
--html-report-template HTML_REPORT_TEMPLATE
|
||||
specify html report template path.
|
||||
--log-level LOG_LEVEL
|
||||
Specify logging level, default is INFO.
|
||||
--log-file LOG_FILE Write logs to specified file path.
|
||||
--dot-env-path DOT_ENV_PATH
|
||||
Specify .env file path, which is useful for keeping
|
||||
sensitive data.
|
||||
--failfast Stop the test run on the first error or failure.
|
||||
--startproject STARTPROJECT
|
||||
Specify new project name.
|
||||
--validate [VALIDATE [VALIDATE ...]]
|
||||
Validate JSON testcase format.
|
||||
--prettify [PRETTIFY [PRETTIFY ...]]
|
||||
Prettify JSON testcase format.
|
||||
```
|
||||
|
||||
## Supported Python Versions
|
||||
|
||||
HttpRunner supports Python 2.7, 3.4, 3.5, and 3.6. And we strongly recommend you to use `Python 3.6`.
|
||||
|
||||
`HttpRunner` has been tested on `macOS`, `Linux` and `Windows` platforms.
|
||||
|
||||
|
||||
[PyPI]: https://pypi.python.org/pypi
|
||||
@@ -1,28 +0,0 @@
|
||||
# Introduction
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
Take full reuse of Python's existing powerful libraries: [`Requests`][requests], [`unittest`][unittest] and [`Locust`][Locust]. And achieve the goal of API automation test, production environment monitoring, and API performance test, with a concise and elegant manner.
|
||||
|
||||
## Key Features
|
||||
|
||||
- Inherit all powerful features of [`Requests`][requests], just have fun to handle HTTP in human way.
|
||||
- Define testcases in YAML or JSON format in concise and elegant manner.
|
||||
- Supports `function`/`variable`/`extract`/`validate` mechanisms to create full test scenarios.
|
||||
- With `debugtalk.py` plugin, module functions can be auto-discovered in recursive upward directories.
|
||||
- Testcases can be run in diverse ways, with single testcase, multiple testcases, or entire project folder.
|
||||
- Test report is concise and clear, with detailed log records. See [`PyUnitReport`][PyUnitReport].
|
||||
- With reuse of [`Locust`][Locust], you can run performance test without extra work.
|
||||
- CLI command supported, perfect combination with [Jenkins][Jenkins].
|
||||
|
||||
## Learn more
|
||||
|
||||
You can read this [blog][HttpRunner-blog] to learn more about the background and initial thoughts of `HttpRunner`.
|
||||
|
||||
|
||||
[requests]: http://docs.python-requests.org/en/master/
|
||||
[unittest]: https://docs.python.org/3/library/unittest.html
|
||||
[Locust]: http://locust.io/
|
||||
[PyUnitReport]: https://github.com/HttpRunner/PyUnitReport
|
||||
[Jenkins]: https://jenkins.io/index.html
|
||||
[HttpRunner-blog]: http://debugtalk.com/post/ApiTestEngine-api-test-best-practice/
|
||||
@@ -1,47 +0,0 @@
|
||||
|
||||
## 版本号(Version)
|
||||
|
||||
从 2.0 版本开始,HttpRunner 开始使用 [`Semantic Versioning`][SemVer] 版本号机制。该机制由 GitHub 联合创始人 Tom Preston-Werner 编写,当前被广泛采用,遵循该机制也可以更好地与开源生态统一,避免出现 “dependency hell” 的情况。
|
||||
|
||||
具体地,HttpRunner 将采用 `MAJOR.MINOR.PATCH` 的版本号机制。
|
||||
|
||||
- MAJOR: 重大版本升级并出现前后版本不兼容时加 1
|
||||
- MINOR: 大版本内新增功能并且保持版本内兼容性时加 1
|
||||
- PATCH: 功能迭代过程中进行问题修复(bugfix)时加 1
|
||||
|
||||
当然,在实际迭代开发过程中,肯定也不会每次提交(commit)都对 PATCH 加 1;在遵循如上主体原则的前提下,也会根据需要,在版本号后面添加先行版本号(-alpha/beta/rc)或版本编译元数据(+20190101)作为延伸。
|
||||
|
||||
## 分支策略
|
||||
|
||||
HttpRunner 的开发分支策略采用 GitHub Flow。
|
||||
|
||||

|
||||
|
||||
## 提交信息(Commit Message)
|
||||
|
||||
代码提交的注释信息遵循如下格式规范:
|
||||
|
||||
```xml
|
||||
<type>(<scope>): <subject>
|
||||
<BLANK LINE>
|
||||
<body>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
- **type**【必填】,大致分类如下:
|
||||
- feat:新功能(feature)
|
||||
- fix:修补 bug
|
||||
- docs:文档(documentation)
|
||||
- style: 格式(不影响代码运行的变动)
|
||||
- perf:性能提升
|
||||
- refactor:重构(即不是新增功能,也不是修改 bug 的代码变动)
|
||||
- test:增加测试
|
||||
- build:构建过程或辅助工具的变动
|
||||
- **subject**【必填】,对提交内容的简要概述
|
||||
- scope【可选项】,用于说明 commit 影响的范围,视项目而定,一般建议写对应具体模块
|
||||
- body【可选项】,对提交内容的详细描述
|
||||
- footer【可选项】,一般为BREAKING CHANGE或关联的issue等内容 详见参考文档
|
||||
|
||||
|
||||
[SemVer]: https://semver.org/
|
||||
@@ -1,23 +0,0 @@
|
||||
## Development
|
||||
|
||||
To develop or debug `HttpRunner`, you shall clone source code first.
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/HttpRunner/HttpRunner.git
|
||||
```
|
||||
|
||||
Then install all dependencies:
|
||||
|
||||
```bash
|
||||
$ pip install -r requirements-dev.txt
|
||||
```
|
||||
|
||||
Now you can use `main-debug.py` as debugging entrances.
|
||||
|
||||
```bash
|
||||
# debug hrun
|
||||
$ python main-debug.py hrun -h
|
||||
|
||||
# debug locusts
|
||||
$ python main-debug.py locusts -h
|
||||
```
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 112 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 106 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB |
@@ -1,3 +0,0 @@
|
||||
Welcome to HttpRunner's documentation!
|
||||
|
||||
点击此处查看[中文使用说明文档](http://cn.httprunner.org)。
|
||||
@@ -1,45 +0,0 @@
|
||||
|
||||
## Load Test
|
||||
|
||||
With reuse of [`Locust`][Locust], you can run performance test without extra work.
|
||||
|
||||
```bash
|
||||
$ locusts -V
|
||||
[2017-08-26 23:45:42,246] bogon/INFO/stdout: Locust 0.8a2
|
||||
[2017-08-26 23:45:42,246] bogon/INFO/stdout:
|
||||
```
|
||||
|
||||
For full usage, you can run `locusts -h` to see help, and you will find that it is the same with `locust -h`.
|
||||
|
||||
The only difference is the `-f` argument. If you specify `-f` with a Python locustfile, it will be the same as `locust`, while if you specify `-f` with a `YAML/JSON` testcase file, it will convert to Python locustfile first and then pass to `locust`.
|
||||
|
||||
```bash
|
||||
$ locusts -f examples/first-testcase.yml
|
||||
[2017-08-18 17:20:43,915] Leos-MacBook-Air.local/INFO/locust.main: Starting web monitor at *:8089
|
||||
[2017-08-18 17:20:43,918] Leos-MacBook-Air.local/INFO/locust.main: Starting Locust 0.8a2
|
||||
```
|
||||
|
||||
In this case, you can reuse all features of [`Locust`][Locust].
|
||||
|
||||
That’s not all about it. With the argument `--processes`, you can even start locust with master and specified number of slaves (default to cpu cores number) at one time, which means you can leverage all cpus of your machine.
|
||||
|
||||
```bash
|
||||
$ locusts -f examples/first-testcase.yml --processes 4
|
||||
[2017-08-26 23:51:47,071] bogon/INFO/locust.main: Starting web monitor at *:8089
|
||||
[2017-08-26 23:51:47,075] bogon/INFO/locust.main: Starting Locust 0.8a2
|
||||
[2017-08-26 23:51:47,078] bogon/INFO/locust.main: Starting Locust 0.8a2
|
||||
[2017-08-26 23:51:47,080] bogon/INFO/locust.main: Starting Locust 0.8a2
|
||||
[2017-08-26 23:51:47,083] bogon/INFO/locust.main: Starting Locust 0.8a2
|
||||
[2017-08-26 23:51:47,084] bogon/INFO/locust.runners: Client 'bogon_656e0af8e968a8533d379dd252422ad3' reported as ready. Currently 1 clients ready to swarm.
|
||||
[2017-08-26 23:51:47,085] bogon/INFO/locust.runners: Client 'bogon_09f73850252ee4ec739ed77d3c4c6dba' reported as ready. Currently 2 clients ready to swarm.
|
||||
[2017-08-26 23:51:47,084] bogon/INFO/locust.main: Starting Locust 0.8a2
|
||||
[2017-08-26 23:51:47,085] bogon/INFO/locust.runners: Client 'bogon_869f7ed671b1a9952b56610f01e2006f' reported as ready. Currently 3 clients ready to swarm.
|
||||
[2017-08-26 23:51:47,085] bogon/INFO/locust.runners: Client 'bogon_80a804cda36b80fac17b57fd2d5e7cdb' reported as ready. Currently 4 clients ready to swarm.
|
||||
```
|
||||
|
||||

|
||||
|
||||
Enjoy!
|
||||
|
||||
|
||||
[Locust]: http://locust.io/
|
||||
@@ -1,336 +0,0 @@
|
||||
# QuickStart
|
||||
|
||||
## Introduction to Sample Interface Service
|
||||
|
||||
Along with this project, I devised a sample interface service, and you can use it to familiarize how to play with `HttpRunner`.
|
||||
|
||||
This sample service mainly has two parts:
|
||||
|
||||
- Authorization, each request of other APIs should sign with some header fields and get token first.
|
||||
- RESTful APIs for user management, you can do CRUD manipulation on users.
|
||||
|
||||
As you see, it is very similar to the mainstream production systems. Therefore once you are familiar with handling this demo service, you can master most test scenarios in your project.
|
||||
|
||||
## Launch Sample Interface Service
|
||||
|
||||
The demo service is a flask server, we can launch it in this way.
|
||||
|
||||
```text
|
||||
$ export FLASK_APP=tests/api_server.py
|
||||
$ flask run
|
||||
* Serving Flask app "tests.api_server"
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
Now the sample interface service is running, and we can move on to the next step.
|
||||
|
||||
## Capture HTTP request and response
|
||||
|
||||
Before we write testcases, we should know the details of the API. It is a good choice to use a web debugging proxy tool like `Charles Proxy` to capture the HTTP traffic.
|
||||
|
||||
For example, the image below illustrates getting token from the sample service first, and then creating one user successfully.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
After thorough understanding of the APIs, we can now begin to write testcases.
|
||||
|
||||
## Write the first test case
|
||||
|
||||
Open your favorite text editor and you can write test cases like this.
|
||||
|
||||
```yaml
|
||||
- test:
|
||||
name: get token
|
||||
request:
|
||||
url: http://127.0.0.1:5000/api/get-token
|
||||
method: POST
|
||||
headers:
|
||||
user_agent: iOS/10.3
|
||||
device_sn: 9TN6O2Bn1vzfybF
|
||||
os_platform: ios
|
||||
app_version: 2.8.6
|
||||
json:
|
||||
sign: 19067cf712265eb5426db8d3664026c1ccea02b9
|
||||
|
||||
- test:
|
||||
name: create user which does not exist
|
||||
request:
|
||||
url: http://127.0.0.1:5000/api/users/1000
|
||||
method: POST
|
||||
headers:
|
||||
device_sn: 9TN6O2Bn1vzfybF
|
||||
token: F8prvGryC5beBr4g
|
||||
json:
|
||||
name: "user1"
|
||||
password: "123456"
|
||||
validate:
|
||||
- {"check": "status_code", "comparator": "eq", "expect": 201}
|
||||
- {"check": "content.success", "comparator": "eq", "expect": true}
|
||||
```
|
||||
|
||||
As you see, each API request is described in a `test` block. And in the `request` field, it describes the detail of HTTP request, includes url, method, headers and data, which are in line with the captured traffic.
|
||||
|
||||
You may wonder why we use the `json` field other than `data`. That's because the post data is in `JSON` format, when we use `json` to indicate the post data, we do not have to specify `Content-Type` to be `application/json` in request headers or dump data before request.
|
||||
|
||||
Have you recalled some familiar scenes?
|
||||
|
||||
Yes! That's what we did in [`requests.request`](requests.request)! Since `HttpRunner` takes full reuse of [`Requests`][requests], it inherits all powerful features of [`Requests`][requests], and we can handle HTTP request as the way we do before.
|
||||
|
||||
## Run test cases
|
||||
|
||||
Suppose the test case file is named as [`quickstart-demo-rev-0.yml`][quickstart-demo-rev-0] and is located in `examples` folder, then we can run it in this way.
|
||||
|
||||
```text
|
||||
ate examples/demo-rev-0.yml
|
||||
Running tests...
|
||||
----------------------------------------------------------------------
|
||||
get token ... INFO:root: Start to POST http://127.0.0.1:5000/api/get-token
|
||||
INFO:root: status_code: 200, response_time: 48 ms, response_length: 46 bytes
|
||||
OK (0.049669)s
|
||||
create user which does not exist ... INFO:root: Start to POST http://127.0.0.1:5000/api/users/1000
|
||||
ERROR:root: Failed to POST http://127.0.0.1:5000/api/users/1000! exception msg: 403 Client Error: FORBIDDEN for url: http://127.0.0.1:5000/api/users/1000
|
||||
ERROR (0.006471)s
|
||||
----------------------------------------------------------------------
|
||||
Ran 2 tests in 0.056s
|
||||
|
||||
FAILED
|
||||
(Errors=1)
|
||||
```
|
||||
|
||||
Oops! The second test case failed with 403 status code.
|
||||
|
||||
That is because we request with the same data as we captured in `Charles Proxy`, while the `token` is generated dynamically, thus the recorded data can not be be used twice directly.
|
||||
|
||||
## Optimize test case: correlation
|
||||
|
||||
To fix this problem, we should correlate `token` field in the second API test case, which is also called `correlation`.
|
||||
|
||||
```yaml
|
||||
- test:
|
||||
name: get token
|
||||
request:
|
||||
url: http://127.0.0.1:5000/api/get-token
|
||||
method: POST
|
||||
headers:
|
||||
user_agent: iOS/10.3
|
||||
device_sn: 9TN6O2Bn1vzfybF
|
||||
os_platform: ios
|
||||
app_version: 2.8.6
|
||||
json:
|
||||
sign: 19067cf712265eb5426db8d3664026c1ccea02b9
|
||||
extract:
|
||||
- token: content.token
|
||||
validate:
|
||||
- {"check": "status_code", "comparator": "eq", "expect": 200}
|
||||
- {"check": "content.token", "comparator": "len_eq", "expect": 16}
|
||||
|
||||
- test:
|
||||
name: create user which does not exist
|
||||
request:
|
||||
url: http://127.0.0.1:5000/api/users/1000
|
||||
method: POST
|
||||
headers:
|
||||
device_sn: 9TN6O2Bn1vzfybF
|
||||
token: $token
|
||||
json:
|
||||
name: "user1"
|
||||
password: "123456"
|
||||
validate:
|
||||
- {"check": "status_code", "comparator": "eq", "expect": 201}
|
||||
- {"check": "content.success", "comparator": "eq", "expect": true}
|
||||
```
|
||||
|
||||
As you see, the `token` field is no longer hardcoded, instead it is extracted from the first API request with `extract` mechanism. In the meanwhile, it is assigned to `token` variable, which can be referenced by the subsequent API requests.
|
||||
|
||||
Now we save the test cases to [`quickstart-demo-rev-1.yml`][quickstart-demo-rev-1] and rerun it, and we will find that both API requests to be successful.
|
||||
|
||||
## Optimize test case: parameterization
|
||||
|
||||
Let's look back to our test set `quickstart-demo-rev-1.yml`, and we can see the `device_sn` field is still hardcoded. This may be quite different from the actual scenarios.
|
||||
|
||||
In actual scenarios, each user's `device_sn` is different, so we should parameterize the request parameters, which is also called `parameterization`. In the meanwhile, the `sign` field is calculated with other header fields, thus it may change significantly if any header field changes slightly.
|
||||
|
||||
However, the test cases are only `YAML` documents, it is impossible to generate parameters dynamically in such text. Fortunately, we can combine `Python` scripts with `YAML/JSON` test cases in `HttpRunner`.
|
||||
|
||||
To achieve this goal, we can utilize `debugtalk.py` plugin and `variables` mechanisms.
|
||||
|
||||
To be specific, we can create a Python file (`examples/debugtalk.py`) and implement the related algorithm in it. The `debugtalk.py` file can not only be located beside `YAML/JSON` testcase file, but also can be in any upward recursive folder. Since we want `debugtalk.py` to be importable, we should put a `__init__.py` in its folder to make it as a Python module.
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
import hmac
|
||||
import random
|
||||
import string
|
||||
|
||||
SECRET_KEY = "DebugTalk"
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
And then, we can revise our demo test case and reference the functions. Suppose the revised file named [`quickstart-demo-rev-2.yml`][quickstart-demo-rev-2].
|
||||
|
||||
```yaml
|
||||
- test:
|
||||
name: get token
|
||||
variables:
|
||||
- user_agent: 'iOS/10.3'
|
||||
- device_sn: ${gen_random_string(15)}
|
||||
- os_platform: 'ios'
|
||||
- app_version: '2.8.6'
|
||||
request:
|
||||
url: http://127.0.0.1:5000/api/get-token
|
||||
method: POST
|
||||
headers:
|
||||
user_agent: $user_agent
|
||||
device_sn: $device_sn
|
||||
os_platform: $os_platform
|
||||
app_version: $app_version
|
||||
json:
|
||||
sign: ${get_sign($user_agent, $device_sn, $os_platform, $app_version)}
|
||||
extract:
|
||||
- token: content.token
|
||||
validate:
|
||||
- {"check": "status_code", "comparator": "eq", "expect": 200}
|
||||
- {"check": "content.token", "comparator": "len_eq", "expect": 16}
|
||||
|
||||
- test:
|
||||
name: create user which does not exist
|
||||
request:
|
||||
url: http://127.0.0.1:5000/api/users/1000
|
||||
method: POST
|
||||
headers:
|
||||
device_sn: $device_sn
|
||||
token: $token
|
||||
json:
|
||||
name: "user1"
|
||||
password: "123456"
|
||||
validate:
|
||||
- {"check": "status_code", "comparator": "eq", "expect": 201}
|
||||
- {"check": "content.success", "comparator": "eq", "expect": true}
|
||||
```
|
||||
|
||||
In this revised test case, `variable reference` and `function invoke` mechanisms are both used.
|
||||
|
||||
To make fields like `device_sn` can be used more than once, we bind values to variables in `variables` block. When we bind variables, we can not only bind exact value to a variable name, but also can call a function and bind the evaluated value to it.
|
||||
|
||||
When we want to reference a variable in the test case, we can do this with a escape character `$`. For example, `$user_agent` will not be taken as a normal string, and `HttpRunner` will consider it as a variable named `user_agent`, search and return its binding value.
|
||||
|
||||
When we want to reference a function, we shall use another escape character `${}`. Any content in `${}` will be considered as function calling, so we should guarantee that we call functions in the right way. At the same time, variables can also be referenced as parameters of function.
|
||||
|
||||
## Optimize test case: overall config block
|
||||
|
||||
There is still one issue unsolved.
|
||||
|
||||
The `device_sn` field is defined in the first API test case, thus it may be impossible to reference it in other test cases. Context separation is a well-designed mechanism, and we should obey this good practice.
|
||||
|
||||
To handle this case, overall `config` block is supported in `HttpRunner`. If we define variables or import functions in `config` block, these variables and functions will become global and can be referenced in the whole test set.
|
||||
|
||||
```yaml
|
||||
# examples/quickstart-demo-rev-3.yml
|
||||
- config:
|
||||
name: "smoketest for CRUD users."
|
||||
variables:
|
||||
- device_sn: ${gen_random_string(15)}
|
||||
request:
|
||||
base_url: http://127.0.0.1:5000
|
||||
headers:
|
||||
device_sn: $device_sn
|
||||
|
||||
- test:
|
||||
name: get token
|
||||
variables:
|
||||
- user_agent: 'iOS/10.3'
|
||||
- os_platform: 'ios'
|
||||
- app_version: '2.8.6'
|
||||
request:
|
||||
url: /api/get-token
|
||||
method: POST
|
||||
headers:
|
||||
user_agent: $user_agent
|
||||
os_platform: $os_platform
|
||||
app_version: $app_version
|
||||
json:
|
||||
sign: ${get_sign($user_agent, $device_sn, $os_platform, $app_version)}
|
||||
extract:
|
||||
- token: content.token
|
||||
validate:
|
||||
- {"check": "status_code", "comparator": "eq", "expect": 200}
|
||||
- {"check": "content.token", "comparator": "len_eq", "expect": 16}
|
||||
|
||||
- test:
|
||||
name: create user which does not exist
|
||||
request:
|
||||
url: /api/users/1000
|
||||
method: POST
|
||||
headers:
|
||||
token: $token
|
||||
json:
|
||||
name: "user1"
|
||||
password: "123456"
|
||||
validate:
|
||||
- {"check": "status_code", "comparator": "eq", "expect": 201}
|
||||
- {"check": "content.success", "comparator": "eq", "expect": true}
|
||||
```
|
||||
|
||||
As you see, we define variables in `config` block. Also, we can set `base_url` in `config` block, thereby we can specify relative path in each API request url. Besides, we can also set common fields in `config` `request`, such as `device_sn` in headers.
|
||||
|
||||
Until now, the test cases are finished and each detail is handled properly.
|
||||
|
||||
## Run test cases and generate report
|
||||
|
||||
Finally, let's run test set [`quickstart-demo-rev-3.yml`][quickstart-demo-rev-3] once more.
|
||||
|
||||
```text
|
||||
$ ate examples/quickstart-demo-rev-4.yml
|
||||
Running tests...
|
||||
----------------------------------------------------------------------
|
||||
get token ... INFO:root: Start to POST http://127.0.0.1:5000/api/get-token
|
||||
INFO:root: status_code: 200, response_time: 33 ms, response_length: 46 bytes
|
||||
OK (0.037027)s
|
||||
create user which does not exist ... INFO:root: Start to POST http://127.0.0.1:5000/api/users/1000
|
||||
INFO:root: status_code: 201, response_time: 15 ms, response_length: 54 bytes
|
||||
OK (0.016414)s
|
||||
----------------------------------------------------------------------
|
||||
Ran 2 tests in 0.054s
|
||||
OK
|
||||
|
||||
Generating HTML reports...
|
||||
Template is not specified, load default template instead.
|
||||
Reports generated: /Users/Leo/MyProjects/HttpRunner/reports/quickstart-demo-rev-0/2017-08-01-16-51-51.html
|
||||
```
|
||||
|
||||
Great! The test case runs successfully and generates a `HTML` test report.
|
||||
|
||||

|
||||
|
||||
## Further more
|
||||
|
||||
This is just a starting point, see the `advanced guide` for the advanced features.
|
||||
|
||||
- templating
|
||||
- [`data extraction and validation`][extraction-and-validation]
|
||||
- [`comparator`][comparator]
|
||||
|
||||
[requests]: http://docs.python-requests.org/en/master/
|
||||
[requests.request]: http://docs.python-requests.org/en/master/api/#requests.request
|
||||
[comparator]: write-testcases.md#comparator
|
||||
[extraction-and-validation]: write-testcases.md#extraction-and-validation
|
||||
[quickstart-demo-rev-0]: ../examples/quickstart-demo-rev-0.yml
|
||||
[quickstart-demo-rev-1]: ../examples/quickstart-demo-rev-1.yml
|
||||
[quickstart-demo-rev-2]: ../examples/quickstart-demo-rev-2.yml
|
||||
[quickstart-demo-rev-3]: ../examples/quickstart-demo-rev-3.yml
|
||||
@@ -1,34 +0,0 @@
|
||||
## Run testcases
|
||||
|
||||
`HttpRunner` can run testcases in diverse ways.
|
||||
|
||||
You can run single testcase by specifying testcase file path.
|
||||
|
||||
```text
|
||||
$ httprunner filepath/testcase.yml
|
||||
```
|
||||
|
||||
You can also run several testcases by specifying multiple testcase file paths.
|
||||
|
||||
```text
|
||||
$ httprunner filepath1/testcase1.yml filepath2/testcase2.yml
|
||||
```
|
||||
|
||||
If you want to run testcases of a whole project, you can achieve this goal by specifying the project folder path.
|
||||
|
||||
```text
|
||||
$ httprunner testcases_folder_path
|
||||
```
|
||||
|
||||
When you do continuous integration test or production environment monitoring with `Jenkins`, you may need to send test result notification. For instance, you can send email with mailgun service as below.
|
||||
|
||||
```text
|
||||
$ httprunner filepath/testcase.yml --report-name ${BUILD_NUMBER} \
|
||||
--mailgun-smtp-username "qa@debugtalk.com" \
|
||||
--mailgun-smtp-password "12345678" \
|
||||
--email-sender excited@samples.mailgun.org \
|
||||
--email-recepients ${MAIL_RECEPIENTS} \
|
||||
--jenkins-job-name ${JOB_NAME} \
|
||||
--jenkins-job-url ${JOB_URL} \
|
||||
--jenkins-build-number ${BUILD_NUMBER}
|
||||
```
|
||||
@@ -1,142 +0,0 @@
|
||||
It is recommended to write testcases in `YAML` format.
|
||||
|
||||
## demo
|
||||
|
||||
Here is a testcase example of typical scenario: get `token` at the beginning, and each subsequent requests should take the `token` in the headers.
|
||||
|
||||
```yaml
|
||||
- config:
|
||||
name: "create user testcases."
|
||||
variables:
|
||||
- user_agent: 'iOS/10.3'
|
||||
- device_sn: ${gen_random_string(15)}
|
||||
- os_platform: 'ios'
|
||||
- app_version: '2.8.6'
|
||||
request:
|
||||
base_url: "http://127.0.0.1:5000"
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
device_sn: $device_sn
|
||||
|
||||
- test:
|
||||
name: get token
|
||||
request:
|
||||
url: /api/get-token
|
||||
method: POST
|
||||
headers:
|
||||
user_agent: $user_agent
|
||||
device_sn: $device_sn
|
||||
os_platform: $os_platform
|
||||
app_version: $app_version
|
||||
json:
|
||||
sign: ${get_sign($user_agent, $device_sn, $os_platform, $app_version)}
|
||||
extract:
|
||||
- token: content.token
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- len_eq: ["content.token", 16]
|
||||
|
||||
- test:
|
||||
name: create user which does not exist
|
||||
request:
|
||||
url: /api/users/1000
|
||||
method: POST
|
||||
headers:
|
||||
token: $token
|
||||
json:
|
||||
name: "user1"
|
||||
password: "123456"
|
||||
validate:
|
||||
- eq: ["status_code", 201]
|
||||
- eq: ["content.success", true]
|
||||
```
|
||||
|
||||
Function invoke is supported in `YAML/JSON` format testcases, such as `gen_random_string` and `get_sign` above. This mechanism relies on the `debugtak.py` hot plugin, with which we can define functions in `debugtak.py` file, and then functions can be auto discovered and invoked in runtime.
|
||||
|
||||
For detailed regulations of writing testcases, you can read the [`quickstart`](quickstart.md) documents.
|
||||
|
||||
|
||||
## Comparator
|
||||
|
||||
`HttpRunner` currently supports the following comparators.
|
||||
|
||||
| comparator | Description | A(check), B(expect) | examples |
|
||||
| -- | -- | -- | -- |
|
||||
| `eq`, `==` | value is equal | A == B | 9 eq 9 |
|
||||
| `lt` | less than | A < B | 7 lt 8 |
|
||||
| `le` | less than or equals | A <= B | 7 le 8, 8 le 8 |
|
||||
| `gt` | greater than | A > B | 8 gt 7 |
|
||||
| `ge` | greater than or equals | A >= B | 8 ge 7, 8 ge 8 |
|
||||
| `ne` | not equals | A != B | 6 ne 9 |
|
||||
| `str_eq` | string equals | str(A) == str(B) | 123 str_eq '123' |
|
||||
| `len_eq`, `count_eq` | length or count equals | len(A) == B | 'abc' len_eq 3, [1,2] len_eq 2 |
|
||||
| `len_gt`, `count_gt` | length greater than | len(A) > B | 'abc' len_gt 2, [1,2,3] len_gt 2 |
|
||||
| `len_ge`, `count_ge` | length greater than or equals | len(A) >= B | 'abc' len_ge 3, [1,2,3] len_gt 3 |
|
||||
| `len_lt`, `count_lt` | length less than | len(A) < B | 'abc' len_lt 4, [1,2,3] len_lt 4 |
|
||||
| `len_le`, `count_le` | length less than or equals | len(A) <= B | 'abc' len_le 3, [1,2,3] len_le 3 |
|
||||
| `contains` | contains | [1, 2] contains 1 | 'abc' contains 'a', [1,2,3] len_lt 4 |
|
||||
| `contained_by` | contained by | A in B | 'a' contained_by 'abc', 1 contained_by [1,2] |
|
||||
| `type_match` | A is instance of B | isinstance(A, B) | 123 type_match 'int' |
|
||||
| `regex_match` | regex matches | re.match(B, A) | 'abcdef' regex 'a\w+d' |
|
||||
| `startswith` | starts with | A.startswith(B) is True | 'abc' startswith 'ab' |
|
||||
| `endswith` | ends with | A.endswith(B) is True | 'abc' endswith 'bc' |
|
||||
|
||||
|
||||
## Extraction and Validation
|
||||
|
||||
Suppose we get the following HTTP response.
|
||||
|
||||
```javascript
|
||||
// status code: 200
|
||||
|
||||
// response headers
|
||||
{
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
// response body content
|
||||
{
|
||||
"success": False,
|
||||
"person": {
|
||||
"name": {
|
||||
"first_name": "Leo",
|
||||
"last_name": "Lee",
|
||||
},
|
||||
"age": 29,
|
||||
"cities": ["Guangzhou", "Shenzhen"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In `extract` and `validate`, we can do chain operation to extract data field in HTTP response.
|
||||
|
||||
For instance, if we want to get `Content-Type` in response headers, then we can specify `headers.content-type`; if we want to get `first_name` in response content, we can specify `content.person.name.first_name`.
|
||||
|
||||
There might be slight difference on list, cos we can use index to locate list item. For example, `Guangzhou` in response content can be specified by `content.person.cities.0`.
|
||||
|
||||
```javascript
|
||||
// get status code
|
||||
status_code
|
||||
|
||||
// get headers field
|
||||
headers.content-type
|
||||
|
||||
// get content field
|
||||
body.success
|
||||
content.success
|
||||
text.success
|
||||
content.person.name.first_name
|
||||
content.person.cities.1
|
||||
```
|
||||
|
||||
```yaml
|
||||
extract:
|
||||
- content_type: headers.content-type
|
||||
- first_name: content.person.name.first_name
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["headers.content-type", "application/json"]
|
||||
- gt: ["headers.content-length", 40]
|
||||
- eq: ["content.success", true]
|
||||
- len_eq: ["content.token", 16]
|
||||
```
|
||||
@@ -1,9 +0,0 @@
|
||||
__title__ = 'HttpRunner'
|
||||
__description__ = 'One-stop solution for HTTP(S) testing.'
|
||||
__url__ = 'https://github.com/HttpRunner/HttpRunner'
|
||||
__version__ = '2.2.3'
|
||||
__author__ = 'debugtalk'
|
||||
__author_email__ = 'mail@debugtalk.com'
|
||||
__license__ = 'Apache-2.0'
|
||||
__copyright__ = 'Copyright 2017 debugtalk'
|
||||
__cake__ = u'\u2728 \U0001f370 \u2728'
|
||||
@@ -0,0 +1,4 @@
|
||||
__version__ = "2.2.4"
|
||||
__description__ = "One-stop solution for HTTP(S) testing."
|
||||
|
||||
__all__ = ["__version__", "__description__"]
|
||||
|
||||
@@ -85,7 +85,14 @@ class HttpRunner(object):
|
||||
|
||||
tests = testcase.get("teststeps", [])
|
||||
for index, test_dict in enumerate(tests):
|
||||
for times_index in range(int(test_dict.get("times", 1))):
|
||||
times = test_dict.get("times", 1)
|
||||
try:
|
||||
times = int(times)
|
||||
except ValueError:
|
||||
raise exceptions.ParamsError(
|
||||
"times should be digit, given: {}".format(times))
|
||||
|
||||
for times_index in range(times):
|
||||
# suppose one testcase should not have more than 9999 steps,
|
||||
# and one step should not run more than 999 times.
|
||||
test_method_name = 'test_{:04}_{:03}'.format(index, times_index)
|
||||
|
||||
@@ -5,7 +5,7 @@ def main_hrun():
|
||||
"""
|
||||
import argparse
|
||||
from httprunner import logger
|
||||
from httprunner.__about__ import __description__, __version__
|
||||
from httprunner import __description__, __version__
|
||||
from httprunner.api import HttpRunner
|
||||
from httprunner.compat import is_py2
|
||||
from httprunner.validator import validate_json_file
|
||||
@@ -176,3 +176,27 @@ def main_locust():
|
||||
locusts.run_locusts_with_processes(sys.argv, processes_count)
|
||||
else:
|
||||
locusts.start_locust_main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
""" debugging mode
|
||||
"""
|
||||
import sys
|
||||
if len(sys.argv) == 0:
|
||||
exit(0)
|
||||
|
||||
cmd = sys.argv.pop(1)
|
||||
|
||||
if cmd in ["hrun", "httprunner", "ate"]:
|
||||
main_hrun()
|
||||
elif cmd in ["locust", "locusts"]:
|
||||
main_locust()
|
||||
else:
|
||||
from httprunner.logger import color_print
|
||||
color_print("Miss debugging type.", "RED")
|
||||
example = "\n".join([
|
||||
"e.g.",
|
||||
"python main-debug.py hrun /path/to/testcase_file",
|
||||
"python main-debug.py locusts -f /path/to/testcase_file"
|
||||
])
|
||||
color_print(example, "yellow")
|
||||
|
||||
@@ -351,9 +351,9 @@ def __extend_with_testcase_ref(raw_testinfo):
|
||||
raise exceptions.FileFormatError(
|
||||
"Invalid format testcase: {}".format(testcase_path))
|
||||
|
||||
tests_def_mapping[testcase_path] = testcase_dict
|
||||
tests_def_mapping["testcases"][testcase_path] = testcase_dict
|
||||
else:
|
||||
testcase_dict = tests_def_mapping[testcase_path]
|
||||
testcase_dict = tests_def_mapping["testcases"][testcase_path]
|
||||
|
||||
raw_testinfo["testcase_def"] = testcase_dict
|
||||
|
||||
|
||||
@@ -482,7 +482,6 @@ class LazyString(object):
|
||||
# search function like ${func($a, $b)}
|
||||
func_match = function_regex_compile.match(raw_string, match_start_position)
|
||||
if func_match:
|
||||
function_meta = parse_function_params(func_match.group(1))
|
||||
function_meta = {
|
||||
"func_name": func_match.group(1)
|
||||
}
|
||||
|
||||
@@ -10,8 +10,7 @@ from collections import Iterable
|
||||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
from httprunner import loader, logger
|
||||
from httprunner.__about__ import __version__
|
||||
from httprunner import __version__, loader, logger
|
||||
from httprunner.compat import basestring, bytes, json, numeric_types
|
||||
from jinja2 import Template, escape
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import sys
|
||||
|
||||
from httprunner.cli import main_hrun, main_locust
|
||||
from httprunner.logger import color_print
|
||||
|
||||
cmd = sys.argv.pop(1)
|
||||
|
||||
if cmd in ["hrun", "httprunner", "ate"]:
|
||||
main_hrun()
|
||||
elif cmd in ["locust", "locusts"]:
|
||||
main_locust()
|
||||
else:
|
||||
color_print("Miss debugging type.", "RED")
|
||||
example = "\n".join([
|
||||
"e.g.",
|
||||
"python main-debug.py hrun /path/to/testcase_file",
|
||||
"python main-debug.py locusts -f /path/to/testcase_file"
|
||||
])
|
||||
color_print(example, "yellow")
|
||||
15
mkdocs.yml
15
mkdocs.yml
@@ -1,15 +0,0 @@
|
||||
site_name: HttpRunner Docs
|
||||
pages:
|
||||
- index.md
|
||||
- Introduction.md
|
||||
- Installation.md
|
||||
- quickstart.md
|
||||
- write-testcases.md
|
||||
- run-testcases.md
|
||||
- load-test.md
|
||||
- development.md
|
||||
- FAQ.md
|
||||
theme: material
|
||||
google_analytics:
|
||||
- 'UA-114587036-1'
|
||||
- 'auto'
|
||||
344
poetry.lock
generated
Normal file
344
poetry.lock
generated
Normal file
@@ -0,0 +1,344 @@
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP"
|
||||
marker = "python_version < \"3\" and extra == \"secure\""
|
||||
name = "asn1crypto"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.24.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
name = "certifi"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2019.6.16"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
marker = "python_version < \"3\" and extra == \"secure\""
|
||||
name = "cffi"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.12.3"
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Universal encoding detector for Python 2 and 3"
|
||||
name = "chardet"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "3.0.4"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Composable command line interface toolkit"
|
||||
name = "click"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "7.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Cross-platform colored terminal text."
|
||||
name = "colorama"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "0.4.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Log formatting with colors!"
|
||||
name = "colorlog"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "4.0.2"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = "*"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Backports and enhancements for the contextlib module"
|
||||
name = "contextlib2"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.5.5"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Code coverage measurement for Python"
|
||||
name = "coverage"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
|
||||
version = "4.5.3"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Show coverage stats online via coveralls.io"
|
||||
name = "coveralls"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.8.1"
|
||||
|
||||
[package.dependencies]
|
||||
coverage = ">=3.6,<5.0"
|
||||
docopt = ">=0.6.1"
|
||||
requests = ">=1.0.0"
|
||||
|
||||
[package.dependencies.urllib3]
|
||||
python = "<3"
|
||||
version = ">=1.21.1,<1.25"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
marker = "python_version < \"3\" and extra == \"secure\""
|
||||
name = "cryptography"
|
||||
optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
||||
version = "2.7"
|
||||
|
||||
[package.dependencies]
|
||||
asn1crypto = ">=0.21.0"
|
||||
cffi = ">=1.8,<1.11.3 || >1.11.3"
|
||||
six = ">=1.4.1"
|
||||
|
||||
[package.dependencies.enum34]
|
||||
python = "<3"
|
||||
version = "*"
|
||||
|
||||
[package.dependencies.ipaddress]
|
||||
python = "<3"
|
||||
version = "*"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Pythonic argument parser, that will make you smile"
|
||||
name = "docopt"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.6.2"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4"
|
||||
marker = "python_version < \"3\" and extra == \"secure\""
|
||||
name = "enum34"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.1.6"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Infer file type and MIME type of any file/buffer. No external dependencies."
|
||||
name = "filetype"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.0.5"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "A microframework based on Werkzeug, Jinja2 and good intentions"
|
||||
name = "flask"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.12.4"
|
||||
|
||||
[package.dependencies]
|
||||
Jinja2 = ">=2.4"
|
||||
Werkzeug = ">=0.7"
|
||||
click = ">=2.0"
|
||||
itsdangerous = ">=0.21"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Clean single-source support for Python 3 and 2"
|
||||
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
|
||||
name = "future"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
version = "0.17.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Convert HAR(HTTP Archive) to YAML/JSON testcases for HttpRunner."
|
||||
name = "har2case"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
|
||||
version = "0.3.1"
|
||||
|
||||
[package.dependencies]
|
||||
PyYAML = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
name = "idna"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "2.8"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "IPv4/IPv6 manipulation library"
|
||||
marker = "python_version < \"3\" and extra == \"secure\""
|
||||
name = "ipaddress"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.0.22"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Various helpers to pass data to untrusted environments and back."
|
||||
name = "itsdangerous"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "1.1.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A small but fast and easy to use stand-alone template engine written in pure python."
|
||||
name = "jinja2"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2.10.1"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=0.23"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
name = "markupsafe"
|
||||
optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
||||
version = "1.1.1"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "C parser in Python"
|
||||
marker = "python_version < \"3\" and extra == \"secure\""
|
||||
name = "pycparser"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "2.19"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python wrapper module around the OpenSSL library"
|
||||
marker = "python_version < \"3\" and extra == \"secure\""
|
||||
name = "pyopenssl"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "19.0.0"
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = ">=2.3"
|
||||
six = ">=1.5.2"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "YAML parser and emitter for Python"
|
||||
name = "pyyaml"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "5.1.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python HTTP for Humans."
|
||||
name = "requests"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "2.22.0"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
chardet = ">=3.0.2,<3.1.0"
|
||||
idna = ">=2.5,<2.9"
|
||||
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A utility belt for advanced users of python-requests"
|
||||
name = "requests-toolbelt"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.8.0"
|
||||
|
||||
[package.dependencies]
|
||||
requests = ">=2.0.1,<3.0.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
marker = "python_version < \"3\" and extra == \"secure\""
|
||||
name = "six"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
|
||||
version = "1.12.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
name = "urllib3"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
|
||||
version = "1.24.3"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
cryptography = ">=1.3.4"
|
||||
idna = ">=2.0.0"
|
||||
ipaddress = "*"
|
||||
pyOpenSSL = ">=0.14"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
name = "werkzeug"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "0.15.5"
|
||||
|
||||
[metadata]
|
||||
content-hash = "f65a674bd12f46ac9b4a8375fb8ee051bcaaf88dd6b8d793d7cd6aae54e81186"
|
||||
python-versions = "~2.7 || ^3.5"
|
||||
|
||||
[metadata.hashes]
|
||||
asn1crypto = ["2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", "9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"]
|
||||
certifi = ["046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", "945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"]
|
||||
cffi = ["041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774", "046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d", "066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90", "066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b", "2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63", "300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45", "34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25", "46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3", "4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b", "4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647", "4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016", "50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4", "55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb", "5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753", "59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7", "73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9", "a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f", "a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8", "a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f", "a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc", "ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42", "b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3", "d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909", "d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45", "dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d", "e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512", "e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff", "ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201"]
|
||||
chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
|
||||
click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
|
||||
colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"]
|
||||
colorlog = ["3cf31b25cbc8f86ec01fef582ef3b840950dea414084ed19ab922c8b493f9b42", "450f52ea2a2b6ebb308f034ea9a9b15cea51e65650593dca1da3eb792e4e4981"]
|
||||
contextlib2 = ["509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48", "f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00"]
|
||||
coverage = ["0c5fe441b9cfdab64719f24e9684502a59432df7570521563d7b1aff27ac755f", "2b412abc4c7d6e019ce7c27cbc229783035eef6d5401695dccba80f481be4eb3", "3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9", "39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74", "3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390", "42692db854d13c6c5e9541b6ffe0fe921fe16c9c446358d642ccae1462582d3b", "465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8", "48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe", "4ec30ade438d1711562f3786bea33a9da6107414aed60a5daa974d50a8c2c351", "5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf", "5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e", "6899797ac384b239ce1926f3cb86ffc19996f6fa3a1efbb23cb49e0c12d8c18c", "68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741", "6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09", "7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd", "7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034", "839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420", "8e679d1bde5e2de4a909efb071f14b472a678b788904440779d2c449c0355b27", "8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c", "932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab", "93f965415cc51604f571e491f280cff0f5be35895b4eb5e55b47ae90c02a497b", "988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba", "998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e", "9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609", "9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2", "a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49", "a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b", "a9abc8c480e103dc05d9b332c6cc9fb1586330356fc14f1aa9c0ca5745097d19", "aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d", "bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce", "bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9", "c22ab9f96cbaff05c6a84e20ec856383d27eae09e511d3e6ac4479489195861d", "c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4", "c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773", "c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723", "ca58eba39c68010d7e87a823f22a081b5290e3e3c64714aac3c91481d8b34d22", "df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c", "f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f", "f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1", "f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260", "fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a"]
|
||||
coveralls = ["d3d49234bffd41e91b241a69f0ebb9f64d7f0515711a76134d53d4647e7eb509", "dafabcff87425fa2ab3122dee21229afbb4d6692cfdacc6bb895f7dfa8b2c849"]
|
||||
cryptography = ["24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c", "25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643", "3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216", "41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799", "5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a", "5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9", "72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc", "7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8", "961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53", "96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1", "ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609", "b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292", "cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e", "e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6", "f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed", "f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d"]
|
||||
docopt = ["49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"]
|
||||
enum34 = ["2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", "644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", "6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", "8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"]
|
||||
filetype = ["17a3b885f19034da29640b083d767e0f13c2dcb5dcc267945c8b6e5a5a9013c7", "4967124d982a71700d94a08c49c4926423500e79382a92070f5ab248d44fe461"]
|
||||
flask = ["2ea22336f6d388b4b242bc3abf8a01244a8aa3e236e7407469ef78c16ba355dd", "6c02dbaa5a9ef790d8219bdced392e2d549c10cd5a5ba4b6aa65126b2271af29"]
|
||||
future = ["67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"]
|
||||
har2case = ["84d3a5cc9fbb16e45372e7e880a936c59bbe8e9b66bad81927769e64f608e2af", "8f159ec7cba82ec4282f46af4a9dac89f65e62796521b2426d3c89c3c9fd8579"]
|
||||
idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"]
|
||||
ipaddress = ["64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794", "b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c"]
|
||||
itsdangerous = ["321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"]
|
||||
jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"]
|
||||
markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"]
|
||||
pycparser = ["a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"]
|
||||
pyopenssl = ["aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200", "c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6"]
|
||||
pyyaml = ["57acc1d8533cbe51f6662a55434f0dbecfa2b9eaf115bede8f6fd00115a0c0d3", "588c94b3d16b76cfed8e0be54932e5729cc185caffaa5a451e7ad2f7ed8b4043", "68c8dd247f29f9a0d09375c9c6b8fdc64b60810ebf07ba4cdd64ceee3a58c7b7", "70d9818f1c9cd5c48bb87804f2efc8692f1023dac7f1a1a5c61d454043c1d265", "86a93cccd50f8c125286e637328ff4eef108400dd7089b46a7be3445eecfa391", "a0f329125a926876f647c9fa0ef32801587a12328b4a3c741270464e3e4fa778", "a3c252ab0fa1bb0d5a3f6449a4826732f3eb6c0270925548cac342bc9b22c225", "b4bb4d3f5e232425e25dda21c070ce05168a786ac9eda43768ab7f3ac2770955", "cd0618c5ba5bda5f4039b9398bb7fb6a317bb8298218c3de25c47c4740e4b95e", "ceacb9e5f8474dcf45b940578591c7f3d960e82f926c707788a570b51ba59190", "fe6a88094b64132c4bb3b631412e90032e8cfe9745a58370462240b8cb7553cd"]
|
||||
requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"]
|
||||
requests-toolbelt = ["42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237", "f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"]
|
||||
six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]
|
||||
urllib3 = ["2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", "a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb"]
|
||||
werkzeug = ["87ae4e5b5366da2347eb3116c0e6c681a0e939a33b2805e2c0cbd282664932c4", "a13b74dd3c45f758d4ebdb224be8f1ab8ef58b3c0ffc1783a8c7d9f4f50227e6"]
|
||||
48
pyproject.toml
Normal file
48
pyproject.toml
Normal file
@@ -0,0 +1,48 @@
|
||||
[tool.poetry]
|
||||
name = "httprunner"
|
||||
version = "2.2.4"
|
||||
description = "One-stop solution for HTTP(S) testing."
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
authors = ["debugtalk <debugtalk@gmail.com>"]
|
||||
|
||||
homepage = "https://github.com/HttpRunner/HttpRunner"
|
||||
repository = "https://github.com/HttpRunner/HttpRunner"
|
||||
documentation = "https://cn.httprunner.org"
|
||||
|
||||
keywords = ["HTTP", "api", "test", "requests", "locustio"]
|
||||
|
||||
classifiers = [
|
||||
"Topic :: Software Development :: Build Tools",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules"
|
||||
]
|
||||
|
||||
include = ["CHANGELOG.md", "httprunner/templates/*"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "~2.7 || ^3.5"
|
||||
requests = "^2.14"
|
||||
requests-toolbelt = "^0.8.0"
|
||||
pyyaml = "^5.1"
|
||||
jinja2 = "^2.10"
|
||||
har2case = "^0.3.1"
|
||||
colorama = "^0.4.1"
|
||||
colorlog = "^4.0"
|
||||
filetype = "^1.0"
|
||||
future = { version = "^0.17.1", python = "~2.7" }
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
flask = "<1.0.0"
|
||||
coverage = "^4.5"
|
||||
coveralls = "^1.8"
|
||||
contextlib2 = "^0.5.5"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
hrun = "httprunner.cli:main_hrun"
|
||||
ate = "httprunner.cli:main_hrun"
|
||||
httprunner = "httprunner.cli:main_hrun"
|
||||
locusts = "httprunner.cli:main_locust"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=0.12"]
|
||||
build-backend = "poetry.masonry.api"
|
||||
114
setup.py
114
setup.py
@@ -1,114 +0,0 @@
|
||||
#encoding: utf-8
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from shutil import rmtree
|
||||
|
||||
from setuptools import Command, find_packages, setup
|
||||
|
||||
about = {}
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
with io.open(os.path.join(here, 'httprunner', '__about__.py'), encoding='utf-8') as f:
|
||||
exec(f.read(), about)
|
||||
|
||||
with io.open("README.md", encoding='utf-8') as f:
|
||||
long_description = f.read()
|
||||
|
||||
install_requires = [
|
||||
"requests",
|
||||
"PyYAML",
|
||||
"Jinja2",
|
||||
"har2case",
|
||||
"colorama",
|
||||
"colorlog",
|
||||
"requests_toolbelt",
|
||||
"filetype"
|
||||
]
|
||||
|
||||
# Python 2.x?
|
||||
is_py2 = (sys.version_info[0] == 2)
|
||||
if is_py2:
|
||||
install_requires.append("future")
|
||||
|
||||
class UploadCommand(Command):
|
||||
""" Build and publish this package.
|
||||
Support setup.py upload. Copied from requests_html.
|
||||
"""
|
||||
|
||||
user_options = []
|
||||
|
||||
@staticmethod
|
||||
def status(s):
|
||||
"""Prints things in green color."""
|
||||
print("\033[0;32m{0}\033[0m".format(s))
|
||||
|
||||
def initialize_options(self):
|
||||
""" override
|
||||
"""
|
||||
pass
|
||||
|
||||
def finalize_options(self):
|
||||
""" override
|
||||
"""
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.status('Removing previous builds…')
|
||||
rmtree(os.path.join(here, 'dist'))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
self.status('Building Source and Wheel (universal) distribution…')
|
||||
os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))
|
||||
|
||||
self.status('Uploading the package to PyPi via Twine…')
|
||||
os.system('twine upload dist/*')
|
||||
|
||||
self.status('Publishing git tags…')
|
||||
os.system('git tag v{0}'.format(about['__version__']))
|
||||
os.system('git push --tags')
|
||||
|
||||
sys.exit()
|
||||
|
||||
|
||||
setup(
|
||||
name=about['__title__'],
|
||||
version=about['__version__'],
|
||||
description=about['__description__'],
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
author=about['__author__'],
|
||||
author_email=about['__author_email__'],
|
||||
url=about['__url__'],
|
||||
license=about['__license__'],
|
||||
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4',
|
||||
packages=find_packages(exclude=["examples", "tests", "tests.*"]),
|
||||
package_data={
|
||||
'': ["README.md"],
|
||||
'httprunner': ["templates/*"],
|
||||
},
|
||||
keywords='HTTP api test requests locust',
|
||||
install_requires=install_requires,
|
||||
extras_require={},
|
||||
classifiers=[
|
||||
"Development Status :: 3 - Alpha",
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7'
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'ate=httprunner.cli:main_hrun',
|
||||
'httprunner=httprunner.cli:main_hrun',
|
||||
'hrun=httprunner.cli:main_hrun',
|
||||
'locusts=httprunner.cli:main_locust'
|
||||
]
|
||||
},
|
||||
# $ setup.py upload support.
|
||||
cmdclass={
|
||||
'upload': UploadCommand
|
||||
}
|
||||
)
|
||||
@@ -3,7 +3,7 @@ name: 302 redirect
|
||||
request:
|
||||
url: https://httpbin.org/redirect-to
|
||||
params:
|
||||
url: https://debugtalk.com
|
||||
url: https://github.com
|
||||
status_code: 302
|
||||
method: GET
|
||||
verify: False
|
||||
|
||||
@@ -4,7 +4,7 @@ import shutil
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from httprunner import loader, parser
|
||||
from httprunner import exceptions, loader, parser
|
||||
from httprunner.api import HttpRunner, prepare_locust_tests
|
||||
from tests.api_server import HTTPBIN_SERVER
|
||||
from tests.base import ApiServerUnittest
|
||||
@@ -80,6 +80,39 @@ class TestHttpRunner(ApiServerUnittest):
|
||||
self.assertEqual(self.runner.summary["stat"]["testcases"]["total"], 1)
|
||||
self.assertEqual(self.runner.summary["stat"]["teststeps"]["total"], 10)
|
||||
|
||||
def test_text_run_times_invalid(self):
|
||||
testcases = [
|
||||
{
|
||||
"config": {
|
||||
'name': "post data",
|
||||
'variables': []
|
||||
},
|
||||
"teststeps": [
|
||||
{
|
||||
"name": "post data",
|
||||
"times": "1.5",
|
||||
"request": {
|
||||
"url": "{}/post".format(HTTPBIN_SERVER),
|
||||
"method": "POST",
|
||||
"headers": {
|
||||
"User-Agent": "python-requests/2.18.4",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"data": "abc"
|
||||
},
|
||||
"validate": [
|
||||
{"eq": ["status_code", 200]}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
tests_mapping = {
|
||||
"testcases": testcases
|
||||
}
|
||||
with self.assertRaises(exceptions.ParamsError):
|
||||
self.runner.run_tests(tests_mapping)
|
||||
|
||||
def test_text_skip(self):
|
||||
self.runner.run(self.testcase_cli_path)
|
||||
self.assertEqual(self.runner.summary["stat"]["teststeps"]["skipped"], 4)
|
||||
@@ -391,7 +424,7 @@ class TestHttpRunner(ApiServerUnittest):
|
||||
req_resp_data = summary["details"][0]["records"][0]["meta_datas"]["data"]
|
||||
self.assertEqual(len(req_resp_data), 2)
|
||||
self.assertIn(
|
||||
"url=https%3A%2F%2Fdebugtalk.com",
|
||||
"url=https%3A%2F%2Fgithub.com",
|
||||
req_resp_data[0]["request"]["url"]
|
||||
)
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ class TestHttpClient(ApiServerUnittest):
|
||||
self.assertEqual(resp.request._cookies["b"], "2")
|
||||
|
||||
def test_request_redirect(self):
|
||||
url = "{}/redirect-to?url=https%3A%2F%2Fdebugtalk.com&status_code=302".format(HTTPBIN_SERVER)
|
||||
url = "{}/redirect-to?url=https%3A%2F%2Fgithub.com&status_code=302".format(HTTPBIN_SERVER)
|
||||
headers = {"accept: text/html"}
|
||||
cookies = {
|
||||
"a": "1",
|
||||
@@ -81,6 +81,6 @@ class TestHttpClient(ApiServerUnittest):
|
||||
self.assertEqual(raw_request._cookies["a"], "1")
|
||||
self.assertEqual(raw_request._cookies["b"], "2")
|
||||
redirect_request = resp.request
|
||||
self.assertEqual(redirect_request.url, "https://debugtalk.com")
|
||||
self.assertEqual(redirect_request.url, "https://github.com")
|
||||
self.assertEqual(redirect_request._cookies["a"], "1")
|
||||
self.assertEqual(redirect_request._cookies["b"], "2")
|
||||
|
||||
@@ -944,7 +944,7 @@ class TestParser(unittest.TestCase):
|
||||
'name': '',
|
||||
"base_url": "$host",
|
||||
'variables': {
|
||||
"host": "https://debugtalk.com"
|
||||
"host": "https://github.com"
|
||||
},
|
||||
"verify": False
|
||||
},
|
||||
@@ -971,7 +971,7 @@ class TestParser(unittest.TestCase):
|
||||
'name': '',
|
||||
"base_url": "$host1",
|
||||
'variables': {
|
||||
"host1": "https://debugtalk.com"
|
||||
"host1": "https://github.com"
|
||||
}
|
||||
},
|
||||
"teststeps": [
|
||||
@@ -1000,7 +1000,7 @@ class TestParser(unittest.TestCase):
|
||||
'name': '',
|
||||
"base_url": "$host1",
|
||||
'variables': {
|
||||
"host1": "https://debugtalk.com"
|
||||
"host1": "https://github.com"
|
||||
}
|
||||
},
|
||||
"teststeps": [
|
||||
@@ -1035,7 +1035,7 @@ class TestParser(unittest.TestCase):
|
||||
'name': '',
|
||||
"base_url": "$host1",
|
||||
'variables': {
|
||||
"host1": "https://debugtalk.com",
|
||||
"host1": "https://github.com",
|
||||
"var_a": "${gen_random_string(5)}",
|
||||
"var_b": "$var_a"
|
||||
}
|
||||
@@ -1087,7 +1087,7 @@ class TestParser(unittest.TestCase):
|
||||
'name': '',
|
||||
"base_url": "$host1",
|
||||
'variables': {
|
||||
"host1": "https://debugtalk.com"
|
||||
"host1": "https://github.com"
|
||||
}
|
||||
},
|
||||
"teststeps": [
|
||||
@@ -1123,7 +1123,7 @@ class TestParser(unittest.TestCase):
|
||||
'name': '',
|
||||
"base_url": "$host",
|
||||
'variables': {
|
||||
"host": "https://debugtalk.com"
|
||||
"host": "https://github.com"
|
||||
},
|
||||
"verify": False
|
||||
},
|
||||
@@ -1277,7 +1277,7 @@ class TestParser(unittest.TestCase):
|
||||
loader.load_project_tests(os.path.join(os.getcwd(), "tests"))
|
||||
raw_testinfo = {
|
||||
"name": "get token",
|
||||
"base_url": "https://debugtalk.com",
|
||||
"base_url": "https://github.com",
|
||||
"api": "api/get_token.yml",
|
||||
}
|
||||
api_def_dict = loader.load_teststep(raw_testinfo)
|
||||
@@ -1301,7 +1301,7 @@ class TestParser(unittest.TestCase):
|
||||
}
|
||||
|
||||
parser._extend_with_api(test_block, api_def_dict)
|
||||
self.assertEqual(test_block["base_url"], "https://debugtalk.com")
|
||||
self.assertEqual(test_block["base_url"], "https://github.com")
|
||||
self.assertEqual(test_block["name"], "override block")
|
||||
self.assertEqual({'var': 123}, test_block["variables"])
|
||||
self.assertIn({'check': 'status_code', 'expect': 201, 'comparator': 'equals'}, test_block["validate"])
|
||||
|
||||
Reference in New Issue
Block a user