Merge pull request #918 from httprunner/v3

## 3.0.7 (2020-06-03)

**Added**

- feat: make pytest files in chain style
- feat: `hrun` supports run pytest files
- feat: get raw testcase model from pytest file

**Fixed**

- fix: convert jmespath.search result to int/float unintentionally
- fix: referenced testcase should not be run duplicately
- fix: requests.cookies.CookieConflictError, multiple cookies with name
- fix: missing exit code from pytest
- fix: skip invalid testcase/testsuite yaml/json file

**Changed**

- change: `har2case` generate pytest file by default
- docs: update sponsor info
This commit is contained in:
debugtalk
2020-06-03 22:31:02 +08:00
committed by GitHub
44 changed files with 1344 additions and 817 deletions

View File

@@ -24,7 +24,7 @@ jobs:
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install poetry pip install poetry
poetry --version poetry --version
poetry install -vv poetry install -vv -E upload
- name: Test package installation - name: Test package installation
run: | run: |
poetry build poetry build
@@ -36,7 +36,7 @@ jobs:
httprunner har2case -h httprunner har2case -h
- name: Run smoketest - postman echo - name: Run smoketest - postman echo
run: | run: |
hrun -s examples/postman_echo/request_methods poetry run hrun examples/postman_echo/request_methods
- name: Run smoketest - httpbin - name: Run smoketest - httpbin
run: | run: |
hrun -s examples/httpbin/ poetry run hrun examples/httpbin/

View File

@@ -1,5 +1,26 @@
# Release History # Release History
## 3.0.7 (2020-06-03)
**Added**
- feat: make pytest files in chain style
- feat: `hrun` supports run pytest files
- feat: get raw testcase model from pytest file
**Fixed**
- fix: convert jmespath.search result to int/float unintentionally
- fix: referenced testcase should not be run duplicately
- fix: requests.cookies.CookieConflictError, multiple cookies with name
- fix: missing exit code from pytest
- fix: skip invalid testcase/testsuite yaml/json file
**Changed**
- change: `har2case` generate pytest file by default
- docs: update sponsor info
## 3.0.6 (2020-05-29) ## 3.0.6 (2020-05-29)
**Added** **Added**

View File

@@ -0,0 +1 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!

View File

@@ -1,105 +1,76 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/httpbin/basic.yml # FROM: examples/httpbin/basic.yml
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseBasic(HttpRunner): class TestCaseBasic(HttpRunner):
config = TConfig( config = Config("basic test with httpbin").base_url("https://httpbin.org/")
**{
"name": "basic test with httpbin",
"base_url": "https://httpbin.org/",
"path": "examples/httpbin/basic_test.py",
"variables": {},
}
)
teststeps = [ teststeps = [
TStep( Step(
**{ RunRequest("headers")
"name": "headers", .get("/headers")
"request": {"url": "/headers", "method": "GET"}, .validate()
"validate": [ .assert_equal("status_code", 200)
{"eq": ["status_code", 200]}, .assert_equal("body.headers.Host", "httpbin.org")
{"eq": ["body.headers.Host", "httpbin.org"]},
],
}
), ),
TStep( Step(
**{ RunRequest("user-agent")
"name": "user-agent", .get("/user-agent")
"request": {"url": "/user-agent", "method": "GET"}, .validate()
"validate": [ .assert_equal("status_code", 200)
{"eq": ["status_code", 200]}, .assert_startswith('body."user-agent"', "python-requests")
{"startswith": ['body."user-agent"', "python-requests"]},
],
}
), ),
TStep( Step(
**{ RunRequest("get without params")
"name": "get without params", .get("/get")
"request": {"url": "/get", "method": "GET"}, .validate()
"validate": [{"eq": ["status_code", 200]}, {"eq": ["body.args", {}]}], .assert_equal("status_code", 200)
} .assert_equal("body.args", {})
), ),
TStep( Step(
**{ RunRequest("get with params in url")
"name": "get with params in url", .get("/get?a=1&b=2")
"request": {"url": "/get?a=1&b=2", "method": "GET"}, .validate()
"validate": [ .assert_equal("status_code", 200)
{"eq": ["status_code", 200]}, .assert_equal("body.args", {"a": "1", "b": "2"})
{"eq": ["body.args", {"a": "1", "b": "2"}]},
],
}
), ),
TStep( Step(
**{ RunRequest("get with params in params field")
"name": "get with params in params field", .get("/get")
"request": {"url": "/get", "params": {"a": 1, "b": 2}, "method": "GET"}, .with_params(**{"a": 1, "b": 2})
"validate": [ .validate()
{"eq": ["status_code", 200]}, .assert_equal("status_code", 200)
{"eq": ["body.args", {"a": "1", "b": "2"}]}, .assert_equal("body.args", {"a": "1", "b": "2"})
],
}
), ),
TStep( Step(
**{ RunRequest("set cookie")
"name": "set cookie", .get("/cookies/set?name=value")
"request": {"url": "/cookies/set?name=value", "method": "GET"}, .validate()
"validate": [ .assert_equal("status_code", 200)
{"eq": ["status_code", 200]}, .assert_equal("body.cookies.name", "value")
{"eq": ["body.cookies.name", "value"]},
],
}
), ),
TStep( Step(
**{ RunRequest("extract cookie")
"name": "extract cookie", .get("/cookies")
"request": {"url": "/cookies", "method": "GET"}, .validate()
"validate": [ .assert_equal("status_code", 200)
{"eq": ["status_code", 200]}, .assert_equal("body.cookies.name", "value")
{"eq": ["body.cookies.name", "value"]},
],
}
), ),
TStep( Step(
**{ RunRequest("post data")
"name": "post data", .post("/post")
"request": { .with_headers(**{"Content-Type": "application/json"})
"url": "/post", .with_data("abc")
"method": "POST", .validate()
"headers": {"Content-Type": "application/json"}, .assert_equal("status_code", 200)
"data": "abc",
},
"validate": [{"eq": ["status_code", 200]}],
}
), ),
TStep( Step(
**{ RunRequest("validate body length")
"name": "validate body length", .get("/spec.json")
"request": {"url": "/spec.json", "method": "GET"}, .validate()
"validate": [{"len_eq": ["body", 9]}], .assert_length_equal("body", 9)
}
), ),
] ]

View File

@@ -1,48 +1,27 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/httpbin/hooks.yml # FROM: examples/httpbin/hooks.yml
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseHooks(HttpRunner): class TestCaseHooks(HttpRunner):
config = TConfig( config = Config("basic test with httpbin").base_url("${get_httpbin_server()}")
**{
"name": "basic test with httpbin",
"base_url": "${get_httpbin_server()}",
"setup_hooks": ["${hook_print(setup)}"],
"teardown_hooks": ["${hook_print(teardown)}"],
"path": "examples/httpbin/hooks_test.py",
"variables": {},
}
)
teststeps = [ teststeps = [
TStep( Step(
**{ RunRequest("headers")
"name": "headers", .with_variables(**{"a": 123})
"variables": {"a": 123}, .get("/headers")
"request": {"url": "/headers", "method": "GET"}, .validate()
"setup_hooks": [ .assert_equal("status_code", 200)
"${setup_hook_add_kwargs($request)}", .assert_contained_by("body.headers.Host", "${get_httpbin_server()}")
"${setup_hook_remove_kwargs($request)}",
],
"teardown_hooks": ["${teardown_hook_sleep_N_secs($response, 1)}"],
"validate": [
{"eq": ["status_code", 200]},
{"contained_by": ["body.headers.Host", "${get_httpbin_server()}"]},
],
}
), ),
TStep( Step(
**{ RunRequest("alter response")
"name": "alter response", .get("/headers")
"request": {"url": "/headers", "method": "GET"}, .validate()
"teardown_hooks": ["${alter_response($response)}"], .assert_equal("status_code", 200)
"validate": [ .assert_equal("body.headers.Host", "httpbin.org")
{"eq": ["status_code", 200]},
{"eq": ["body.headers.Host", "httpbin.org"]},
],
}
), ),
] ]

View File

@@ -1,47 +1,36 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/httpbin/load_image.yml # FROM: examples/httpbin/load_image.yml
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseLoadImage(HttpRunner): class TestCaseLoadImage(HttpRunner):
config = TConfig( config = Config("load images").base_url("${get_httpbin_server()}")
**{
"name": "load images",
"base_url": "${get_httpbin_server()}",
"path": "examples/httpbin/load_image_test.py",
"variables": {},
}
)
teststeps = [ teststeps = [
TStep( Step(
**{ RunRequest("get png image")
"name": "get png image", .get("/image/png")
"request": {"url": "/image/png", "method": "GET"}, .validate()
"validate": [{"eq": ["status_code", 200]}], .assert_equal("status_code", 200)
}
), ),
TStep( Step(
**{ RunRequest("get jpeg image")
"name": "get jpeg image", .get("/image/jpeg")
"request": {"url": "/image/jpeg", "method": "GET"}, .validate()
"validate": [{"eq": ["status_code", 200]}], .assert_equal("status_code", 200)
}
), ),
TStep( Step(
**{ RunRequest("get webp image")
"name": "get webp image", .get("/image/webp")
"request": {"url": "/image/webp", "method": "GET"}, .validate()
"validate": [{"eq": ["status_code", 200]}], .assert_equal("status_code", 200)
}
), ),
TStep( Step(
**{ RunRequest("get svg image")
"name": "get svg image", .get("/image/svg")
"request": {"url": "/image/svg", "method": "GET"}, .validate()
"validate": [{"eq": ["status_code", 200]}], .assert_equal("status_code", 200)
}
), ),
] ]

View File

@@ -1,54 +1,35 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/httpbin/upload.yml # FROM: examples/httpbin/upload.yml
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseUpload(HttpRunner): class TestCaseUpload(HttpRunner):
config = TConfig( config = Config("test upload file with httpbin").base_url("${get_httpbin_server()}")
**{
"name": "test upload file with httpbin",
"base_url": "${get_httpbin_server()}",
"path": "examples/httpbin/upload_test.py",
"variables": {},
}
)
teststeps = [ teststeps = [
TStep( Step(
**{ RunRequest("upload file")
"name": "upload file", .with_variables(
"variables": { **{
"file_path": "test.env", "file_path": "test.env",
"m_encoder": "${multipart_encoder(file=$file_path)}", "m_encoder": "${multipart_encoder(file=$file_path)}",
}, }
"request": { )
"url": "/post", .post("/post")
"method": "POST", .with_headers(**{"Content-Type": "${multipart_content_type($m_encoder)}"})
"headers": { .with_data("$m_encoder")
"Content-Type": "${multipart_content_type($m_encoder)}" .validate()
}, .assert_equal("status_code", 200)
"data": "$m_encoder", .assert_startswith("body.files.file", "UserName=test")
},
"validate": [
{"eq": ["status_code", 200]},
{"startswith": ["body.files.file", "UserName=test"]},
],
}
), ),
TStep( Step(
**{ RunRequest("upload file with keyword")
"name": "upload file with keyword", .post("/post")
"request": { .upload(**{"file": "test.env"})
"url": "/post", .validate()
"method": "POST", .assert_equal("status_code", 200)
"upload": {"file": "test.env"}, .assert_startswith("body.files.file", "UserName=test")
},
"validate": [
{"eq": ["status_code", 200]},
{"startswith": ["body.files.file", "UserName=test"]},
],
}
), ),
] ]

View File

@@ -13,8 +13,8 @@ teststeps:
method: GET method: GET
validate: validate:
- eq: ["status_code", 200] - eq: ["status_code", 200]
- eq: ["body.args.a", 1] - eq: ["body.args.a", "1"]
- eq: ["body.args.b", 2] - eq: ["body.args.b", "2"]
validate_script: validate_script:
- "assert status_code == 200" - "assert status_code == 200"

View File

@@ -1,43 +1,28 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/httpbin/validate.yml # FROM: examples/httpbin/validate.yml
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseValidate(HttpRunner): class TestCaseValidate(HttpRunner):
config = TConfig( config = Config("basic test with httpbin").base_url("http://httpbin.org/")
**{
"name": "basic test with httpbin",
"base_url": "http://httpbin.org/",
"path": "examples/httpbin/validate_test.py",
"variables": {},
}
)
teststeps = [ teststeps = [
TStep( Step(
**{ RunRequest("validate response with json path")
"name": "validate response with json path", .get("/get")
"request": {"url": "/get", "params": {"a": 1, "b": 2}, "method": "GET"}, .with_params(**{"a": 1, "b": 2})
"validate": [ .validate()
{"eq": ["status_code", 200]}, .assert_equal("status_code", 200)
{"eq": ["body.args.a", 1]}, .assert_equal("body.args.a", "1")
{"eq": ["body.args.b", 2]}, .assert_equal("body.args.b", "2")
],
"validate_script": ["assert status_code == 200"],
}
), ),
TStep( Step(
**{ RunRequest("validate response with python script")
"name": "validate response with python script", .get("/get")
"request": {"url": "/get", "params": {"a": 1, "b": 2}, "method": "GET"}, .with_params(**{"a": 1, "b": 2})
"validate": [{"eq": ["status_code", 200]}], .validate()
"validate_script": [ .assert_equal("status_code", 200)
"assert status_code == 201",
"a = response_json.get('args').get('a')",
"assert a == '1'",
],
}
), ),
] ]

View File

@@ -0,0 +1,34 @@
config:
name: "set & delete cookies."
base_url: "https://postman-echo.com"
verify: False
export: ["cookie_foo1", "cookie_foo3"]
teststeps:
-
name: set cookie foo1 & foo2 & foo3
request:
method: GET
url: /cookies/set
params:
foo1: bar1
foo2: bar2
headers:
User-Agent: HttpRunner/${get_httprunner_version()}
extract:
cookie_foo1: $.cookies.foo1
validate:
- eq: ["status_code", 200]
- eq: ["cookies.foo1", "bar1"]
-
name: delete cookie foo2
request:
method: GET
url: /cookies/delete?foo2
headers:
User-Agent: HttpRunner/${get_httprunner_version()}
validate:
- eq: ["status_code", 200]
- ne: ["$.cookies.foo1", "$foo1"]
- eq: ["$.cookies.foo1", "$cookie_foo1"]
- eq: ["$.cookies.foo3", "$cookie_foo3"]

View File

@@ -0,0 +1,54 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# FROM: examples/postman_echo/cookie_manipulation/hardcode.yml
from httprunner import HttpRunner, TConfig, TStep
class TestCaseHardcode(HttpRunner):
config = TConfig(
**{
"name": "set & delete cookies.",
"base_url": "https://postman-echo.com",
"verify": False,
"export": ["cookie_foo1", "cookie_foo3"],
"path": "examples/postman_echo/cookie_manipulation/hardcode_test.py",
}
)
teststeps = [
TStep(
**{
"name": "set cookie foo1 & foo2 & foo3",
"request": {
"method": "GET",
"url": "/cookies/set",
"params": {"foo1": "bar1", "foo2": "bar2"},
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"extract": {"cookie_foo1": "$.cookies.foo1"},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["cookies.foo1", "bar1"]},
],
}
),
TStep(
**{
"name": "delete cookie foo2",
"request": {
"method": "GET",
"url": "/cookies/delete?foo2",
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"validate": [
{"eq": ["status_code", 200]},
{"ne": ["$.cookies.foo1", "$foo1"]},
{"eq": ["$.cookies.foo1", "$cookie_foo1"]},
{"eq": ["$.cookies.foo3", "$cookie_foo3"]},
],
}
),
]
if __name__ == "__main__":
TestCaseHardcode().test_start()

View File

@@ -0,0 +1,62 @@
import unittest
import requests
from httprunner.runner import HttpRunner
from httprunner.schema import TConfig, TStep
class TestCaseSetDeleteCookies(unittest.TestCase):
config = TConfig(
**{
"name": "set & delete cookies.",
"base_url": "https://postman-echo.com",
"variables": {"foo1": "bar1", "foo2": "bar2"},
"verify": False,
"export": ["cookie_foo1", "cookie_foo3"],
}
)
teststeps = [
TStep(
**{
"name": "set cookie foo1 & foo2 & foo3",
"variables": {"foo3": "bar3"},
"request": {
"method": "GET",
"url": "/cookies/set",
"params": {"foo1": "bar111", "foo2": "$foo2", "foo3": "$foo3"},
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"extract": {
"cookie_foo1": "$.cookies.foo1",
"cookie_foo3": "$.cookies.foo3",
},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["$.cookies.foo3", "$foo3"]},
],
}
),
TStep(
**{
"name": "delete cookie foo2",
"request": {
"method": "GET",
"url": "/cookies/delete?foo2",
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"validate": [
{"eq": ["status_code", 200]},
{"ne": ["$.cookies.foo1", "$foo1"]},
{"eq": ["$.cookies.foo1", "$cookie_foo1"]},
{"eq": ["$.cookies.foo3", "$cookie_foo3"]},
],
}
),
]
def test_start(self):
s = requests.Session()
HttpRunner(self.config, self.teststeps, session=s).with_variables(
foo1="bar123", foo2="bar22"
).run()

View File

@@ -0,0 +1,59 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# FROM: examples/postman_echo/cookie_manipulation/set_delete_cookies.yml
from httprunner import HttpRunner, TConfig, TStep
class TestCaseSetDeleteCookies(HttpRunner):
config = TConfig(
**{
"name": "set & delete cookies.",
"variables": {"foo1": "bar1", "foo2": "bar2"},
"base_url": "https://postman-echo.com",
"verify": False,
"export": ["cookie_foo1", "cookie_foo3"],
"path": "examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py",
}
)
teststeps = [
TStep(
**{
"name": "set cookie foo1 & foo2 & foo3",
"variables": {"foo3": "bar3"},
"request": {
"method": "GET",
"url": "/cookies/set",
"params": {"foo1": "bar111", "foo2": "$foo2", "foo3": "$foo3"},
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"extract": {
"cookie_foo1": "$.cookies.foo1",
"cookie_foo3": "$.cookies.foo3",
},
"validate": [
{"eq": ["status_code", 200]},
{"ne": ["$.cookies.foo3", "$foo3"]},
],
}
),
TStep(
**{
"name": "delete cookie foo2",
"request": {
"method": "GET",
"url": "/cookies/delete?foo2",
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"validate": [
{"eq": ["status_code", 200]},
{"ne": ["$.cookies.foo1", "$foo1"]},
{"eq": ["$.cookies.foo1", "$cookie_foo1"]},
{"eq": ["$.cookies.foo3", "$cookie_foo3"]},
],
}
),
]
if __name__ == "__main__":
TestCaseSetDeleteCookies().test_start()

View File

@@ -2,10 +2,9 @@ import uuid
from typing import List from typing import List
import pytest import pytest
from httprunner import Config, Step
from loguru import logger from loguru import logger
from httprunner.schema import TConfig, TStep
@pytest.fixture(scope="session", autouse=True) @pytest.fixture(scope="session", autouse=True)
def session_fixture(request): def session_fixture(request):
@@ -33,8 +32,8 @@ def session_fixture(request):
@pytest.fixture(scope="function", autouse=True) @pytest.fixture(scope="function", autouse=True)
def testcase_fixture(request): def testcase_fixture(request):
"""setup and teardown each testcase""" """setup and teardown each testcase"""
config: TConfig = request.cls.config config: Config = request.cls.config
teststeps: List[TStep] = request.cls.teststeps teststeps: List[Step] = request.cls.teststeps
logger.debug(f"setup testcase fixture: {config.name} - {request.module.__name__}") logger.debug(f"setup testcase fixture: {config.name} - {request.module.__name__}")

View File

@@ -0,0 +1 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!

View File

@@ -1,87 +1,69 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/request_with_functions.yml # FROM: examples/postman_echo/request_methods/request_with_functions.yml
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithFunctions(HttpRunner): class TestCaseRequestWithFunctions(HttpRunner):
config = TConfig( config = (
**{ Config("request with functions")
"name": "request with functions", .variables(**{"foo1": "session_bar1", "var1": "testsuite_val1"})
"variables": {"foo1": "session_bar1", "var1": "testsuite_val1"}, .base_url("https://postman-echo.com")
"base_url": "https://postman-echo.com", .verify(False)
"verify": False,
"path": "examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py",
}
) )
teststeps = [ teststeps = [
TStep( Step(
**{ RunRequest("get with params")
"name": "get with params", .with_variables(
"variables": { **{"foo1": "bar1", "foo2": "session_bar2", "sum_v": "${sum_two(1, 2)}"}
"foo1": "bar1", )
"foo2": "session_bar2", .get("/get")
"sum_v": "${sum_two(1, 2)}", .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
}, .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"})
"request": { .extract()
"method": "GET", .with_jmespath("body.args.foo2", "session_foo2")
"url": "/get", .validate()
"params": {"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}, .assert_equal("status_code", 200)
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"}, .assert_equal("body.args.foo1", "session_bar1")
}, .assert_equal("body.args.sum_v", "3")
"extract": {"session_foo2": "body.args.foo2"}, .assert_equal("body.args.foo2", "session_bar2")
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.args.foo1", "session_bar1"]},
{"eq": ["body.args.sum_v", 3]},
{"eq": ["body.args.foo2", "session_bar2"]},
],
}
), ),
TStep( Step(
**{ RunRequest("post raw text")
"name": "post raw text", .with_variables(**{"foo1": "hello world", "foo3": "$session_foo2"})
"variables": {"foo1": "hello world", "foo3": "$session_foo2"}, .post("/post")
"request": { .with_headers(
"method": "POST", **{
"url": "/post", "User-Agent": "HttpRunner/${get_httprunner_version()}",
"headers": { "Content-Type": "text/plain",
"User-Agent": "HttpRunner/${get_httprunner_version()}", }
"Content-Type": "text/plain", )
}, .with_data(
"data": "This is expected to be sent back as part of response body: $foo1-$foo3.", "This is expected to be sent back as part of response body: $foo1-$foo3."
}, )
"validate": [ .validate()
{"eq": ["status_code", 200]}, .assert_equal("status_code", 200)
{ .assert_equal(
"eq": [ "body.data",
"body.data", "This is expected to be sent back as part of response body: session_bar1-session_bar2.",
"This is expected to be sent back as part of response body: session_bar1-session_bar2.", )
]
},
],
}
), ),
TStep( Step(
**{ RunRequest("post form data")
"name": "post form data", .with_variables(**{"foo1": "bar1", "foo2": "bar2"})
"variables": {"foo1": "bar1", "foo2": "bar2"}, .post("/post")
"request": { .with_headers(
"method": "POST", **{
"url": "/post", "User-Agent": "HttpRunner/${get_httprunner_version()}",
"headers": { "Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "HttpRunner/${get_httprunner_version()}", }
"Content-Type": "application/x-www-form-urlencoded", )
}, .with_data("foo1=$foo1&foo2=$foo2")
"data": "foo1=$foo1&foo2=$foo2", .validate()
}, .assert_equal("status_code", 200)
"validate": [ .assert_equal("body.form.foo1", "session_bar1")
{"eq": ["status_code", 200]}, .assert_equal("body.form.foo2", "bar2")
{"eq": ["body.form.foo1", "session_bar1"]},
{"eq": ["body.form.foo2", "bar2"]},
],
}
), ),
] ]

View File

@@ -1,4 +1,4 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml # FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml
import os import os
@@ -6,7 +6,7 @@ import sys
sys.path.insert(0, os.getcwd()) sys.path.insert(0, os.getcwd())
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
from examples.postman_echo.request_methods.request_with_functions_test import ( from examples.postman_echo.request_methods.request_with_functions_test import (
TestCaseRequestWithFunctions as RequestWithFunctions, TestCaseRequestWithFunctions as RequestWithFunctions,
@@ -14,23 +14,18 @@ from examples.postman_echo.request_methods.request_with_functions_test import (
class TestCaseRequestWithTestcaseReference(HttpRunner): class TestCaseRequestWithTestcaseReference(HttpRunner):
config = TConfig( config = (
**{ Config("request with referenced testcase")
"name": "request with referenced testcase", .variables(**{"foo1": "session_bar1", "var2": "testsuite_val2"})
"variables": {"foo1": "session_bar1", "var2": "testsuite_val2"}, .base_url("https://postman-echo.com")
"base_url": "https://postman-echo.com", .verify(False)
"verify": False,
"path": "examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py",
}
) )
teststeps = [ teststeps = [
TStep( Step(
**{ RunTestCase("request with functions")
"name": "request with functions", .with_variables(**{"foo1": "override_bar1"})
"variables": {"foo1": "override_bar1"}, .call(RequestWithFunctions)
"testcase": RequestWithFunctions,
}
), ),
] ]

View File

@@ -1,77 +1,57 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/hardcode.yml # FROM: examples/postman_echo/request_methods/hardcode.yml
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseHardcode(HttpRunner): class TestCaseHardcode(HttpRunner):
config = TConfig( config = (
**{ Config("request methods testcase in hardcode")
"name": "request methods testcase in hardcode", .base_url("https://postman-echo.com")
"base_url": "https://postman-echo.com", .verify(False)
"verify": False,
"path": "examples/postman_echo/request_methods/hardcode_test.py",
"variables": {},
}
) )
teststeps = [ teststeps = [
TStep( Step(
**{ RunRequest("get with params")
"name": "get with params", .get("/get")
"request": { .with_params(**{"foo1": "bar1", "foo2": "bar2"})
"method": "GET", .with_headers(**{"User-Agent": "HttpRunner/3.0"})
"url": "/get", .validate()
"params": {"foo1": "bar1", "foo2": "bar2"}, .assert_equal("status_code", 200)
"headers": {"User-Agent": "HttpRunner/3.0"},
},
"validate": [{"eq": ["status_code", 200]}],
}
), ),
TStep( Step(
**{ RunRequest("post raw text")
"name": "post raw text", .post("/post")
"request": { .with_headers(
"method": "POST", **{"User-Agent": "HttpRunner/3.0", "Content-Type": "text/plain"}
"url": "/post", )
"headers": { .with_data("This is expected to be sent back as part of response body.")
"User-Agent": "HttpRunner/3.0", .validate()
"Content-Type": "text/plain", .assert_equal("status_code", 200)
},
"data": "This is expected to be sent back as part of response body.",
},
"validate": [{"eq": ["status_code", 200]}],
}
), ),
TStep( Step(
**{ RunRequest("post form data")
"name": "post form data", .post("/post")
"request": { .with_headers(
"method": "POST", **{
"url": "/post", "User-Agent": "HttpRunner/3.0",
"headers": { "Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "HttpRunner/3.0", }
"Content-Type": "application/x-www-form-urlencoded", )
}, .with_data("foo1=bar1&foo2=bar2")
"data": "foo1=bar1&foo2=bar2", .validate()
}, .assert_equal("status_code", 200)
"validate": [{"eq": ["status_code", 200]}],
}
), ),
TStep( Step(
**{ RunRequest("put request")
"name": "put request", .put("/put")
"request": { .with_headers(
"method": "PUT", **{"User-Agent": "HttpRunner/3.0", "Content-Type": "text/plain"}
"url": "/put", )
"headers": { .with_data("This is expected to be sent back as part of response body.")
"User-Agent": "HttpRunner/3.0", .validate()
"Content-Type": "text/plain", .assert_equal("status_code", 200)
},
"data": "This is expected to be sent back as part of response body.",
},
"validate": [{"eq": ["status_code", 200]}],
}
), ),
] ]

View File

@@ -26,7 +26,7 @@ teststeps:
validate: validate:
- eq: ["status_code", 200] - eq: ["status_code", 200]
- eq: ["body.args.foo1", "session_bar1"] - eq: ["body.args.foo1", "session_bar1"]
- eq: ["body.args.sum_v", 3] - eq: ["body.args.sum_v", "3"]
- eq: ["body.args.foo2", "session_bar2"] - eq: ["body.args.foo2", "session_bar2"]
- -
name: post raw text name: post raw text

View File

@@ -1,87 +1,69 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/request_with_functions.yml # FROM: examples/postman_echo/request_methods/request_with_functions.yml
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithFunctions(HttpRunner): class TestCaseRequestWithFunctions(HttpRunner):
config = TConfig( config = (
**{ Config("request methods testcase with functions")
"name": "request methods testcase with functions", .variables(**{"foo1": "session_bar1"})
"variables": {"foo1": "session_bar1"}, .base_url("https://postman-echo.com")
"base_url": "https://postman-echo.com", .verify(False)
"verify": False,
"path": "examples/postman_echo/request_methods/request_with_functions_test.py",
}
) )
teststeps = [ teststeps = [
TStep( Step(
**{ RunRequest("get with params")
"name": "get with params", .with_variables(
"variables": { **{"foo1": "bar1", "foo2": "session_bar2", "sum_v": "${sum_two(1, 2)}"}
"foo1": "bar1", )
"foo2": "session_bar2", .get("/get")
"sum_v": "${sum_two(1, 2)}", .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
}, .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"})
"request": { .extract()
"method": "GET", .with_jmespath("body.args.foo2", "session_foo2")
"url": "/get", .validate()
"params": {"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}, .assert_equal("status_code", 200)
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"}, .assert_equal("body.args.foo1", "session_bar1")
}, .assert_equal("body.args.sum_v", "3")
"extract": {"session_foo2": "body.args.foo2"}, .assert_equal("body.args.foo2", "session_bar2")
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.args.foo1", "session_bar1"]},
{"eq": ["body.args.sum_v", 3]},
{"eq": ["body.args.foo2", "session_bar2"]},
],
}
), ),
TStep( Step(
**{ RunRequest("post raw text")
"name": "post raw text", .with_variables(**{"foo1": "hello world", "foo3": "$session_foo2"})
"variables": {"foo1": "hello world", "foo3": "$session_foo2"}, .post("/post")
"request": { .with_headers(
"method": "POST", **{
"url": "/post", "User-Agent": "HttpRunner/${get_httprunner_version()}",
"headers": { "Content-Type": "text/plain",
"User-Agent": "HttpRunner/${get_httprunner_version()}", }
"Content-Type": "text/plain", )
}, .with_data(
"data": "This is expected to be sent back as part of response body: $foo1-$foo3.", "This is expected to be sent back as part of response body: $foo1-$foo3."
}, )
"validate": [ .validate()
{"eq": ["status_code", 200]}, .assert_equal("status_code", 200)
{ .assert_equal(
"eq": [ "body.data",
"body.data", "This is expected to be sent back as part of response body: session_bar1-session_bar2.",
"This is expected to be sent back as part of response body: session_bar1-session_bar2.", )
]
},
],
}
), ),
TStep( Step(
**{ RunRequest("post form data")
"name": "post form data", .with_variables(**{"foo1": "bar1", "foo2": "bar2"})
"variables": {"foo1": "bar1", "foo2": "bar2"}, .post("/post")
"request": { .with_headers(
"method": "POST", **{
"url": "/post", "User-Agent": "HttpRunner/${get_httprunner_version()}",
"headers": { "Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "HttpRunner/${get_httprunner_version()}", }
"Content-Type": "application/x-www-form-urlencoded", )
}, .with_data("foo1=$foo1&foo2=$foo2")
"data": "foo1=$foo1&foo2=$foo2", .validate()
}, .assert_equal("status_code", 200)
"validate": [ .assert_equal("body.form.foo1", "session_bar1")
{"eq": ["status_code", 200]}, .assert_equal("body.form.foo2", "bar2")
{"eq": ["body.form.foo1", "session_bar1"]},
{"eq": ["body.form.foo2", "bar2"]},
],
}
), ),
] ]

View File

@@ -1,4 +1,4 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml # FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml
import os import os
@@ -6,7 +6,7 @@ import sys
sys.path.insert(0, os.getcwd()) sys.path.insert(0, os.getcwd())
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
from examples.postman_echo.request_methods.request_with_functions_test import ( from examples.postman_echo.request_methods.request_with_functions_test import (
TestCaseRequestWithFunctions as RequestWithFunctions, TestCaseRequestWithFunctions as RequestWithFunctions,
@@ -14,23 +14,18 @@ from examples.postman_echo.request_methods.request_with_functions_test import (
class TestCaseRequestWithTestcaseReference(HttpRunner): class TestCaseRequestWithTestcaseReference(HttpRunner):
config = TConfig( config = (
**{ Config("request methods testcase: reference testcase")
"name": "request methods testcase: reference testcase", .variables(**{"foo1": "session_bar1"})
"variables": {"foo1": "session_bar1"}, .base_url("https://postman-echo.com")
"base_url": "https://postman-echo.com", .verify(False)
"verify": False,
"path": "examples/postman_echo/request_methods/request_with_testcase_reference_test.py",
}
) )
teststeps = [ teststeps = [
TStep( Step(
**{ RunTestCase("request with functions")
"name": "request with functions", .with_variables(**{"foo1": "override_bar1"})
"variables": {"foo1": "override_bar1"}, .call(RequestWithFunctions)
"testcase": RequestWithFunctions,
}
), ),
] ]

View File

@@ -1,82 +1,63 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/request_with_variables.yml # FROM: examples/postman_echo/request_methods/request_with_variables.yml
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithVariables(HttpRunner): class TestCaseRequestWithVariables(HttpRunner):
config = TConfig( config = (
**{ Config("request methods testcase with variables")
"name": "request methods testcase with variables", .variables(**{"foo1": "session_bar1"})
"variables": {"foo1": "session_bar1"}, .base_url("https://postman-echo.com")
"base_url": "https://postman-echo.com", .verify(False)
"verify": False,
"path": "examples/postman_echo/request_methods/request_with_variables_test.py",
}
) )
teststeps = [ teststeps = [
TStep( Step(
**{ RunRequest("get with params")
"name": "get with params", .with_variables(**{"foo1": "bar1", "foo2": "session_bar2"})
"variables": {"foo1": "bar1", "foo2": "session_bar2"}, .get("/get")
"request": { .with_params(**{"foo1": "$foo1", "foo2": "$foo2"})
"method": "GET", .with_headers(**{"User-Agent": "HttpRunner/3.0"})
"url": "/get", .extract()
"params": {"foo1": "$foo1", "foo2": "$foo2"}, .with_jmespath("body.args.foo2", "session_foo2")
"headers": {"User-Agent": "HttpRunner/3.0"}, .validate()
}, .assert_equal("status_code", 200)
"extract": {"session_foo2": "body.args.foo2"}, .assert_equal("body.args.foo1", "session_bar1")
"validate": [ .assert_equal("body.args.foo2", "session_bar2")
{"eq": ["status_code", 200]},
{"eq": ["body.args.foo1", "session_bar1"]},
{"eq": ["body.args.foo2", "session_bar2"]},
],
}
), ),
TStep( Step(
**{ RunRequest("post raw text")
"name": "post raw text", .with_variables(**{"foo1": "hello world", "foo3": "$session_foo2"})
"variables": {"foo1": "hello world", "foo3": "$session_foo2"}, .post("/post")
"request": { .with_headers(
"method": "POST", **{"User-Agent": "HttpRunner/3.0", "Content-Type": "text/plain"}
"url": "/post", )
"headers": { .with_data(
"User-Agent": "HttpRunner/3.0", "This is expected to be sent back as part of response body: $foo1-$foo3."
"Content-Type": "text/plain", )
}, .validate()
"data": "This is expected to be sent back as part of response body: $foo1-$foo3.", .assert_equal("status_code", 200)
}, .assert_equal(
"validate": [ "body.data",
{"eq": ["status_code", 200]}, "This is expected to be sent back as part of response body: session_bar1-session_bar2.",
{ )
"eq": [
"body.data",
"This is expected to be sent back as part of response body: session_bar1-session_bar2.",
]
},
],
}
), ),
TStep( Step(
**{ RunRequest("post form data")
"name": "post form data", .with_variables(**{"foo1": "bar1", "foo2": "bar2"})
"variables": {"foo1": "bar1", "foo2": "bar2"}, .post("/post")
"request": { .with_headers(
"method": "POST", **{
"url": "/post", "User-Agent": "HttpRunner/3.0",
"headers": { "Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "HttpRunner/3.0", }
"Content-Type": "application/x-www-form-urlencoded", )
}, .with_data("foo1=$foo1&foo2=$foo2")
"data": "foo1=$foo1&foo2=$foo2", .validate()
}, .assert_equal("status_code", 200)
"validate": [ .assert_equal("body.form.foo1", "session_bar1")
{"eq": ["status_code", 200]}, .assert_equal("body.form.foo2", "bar2")
{"eq": ["body.form.foo1", "session_bar1"]},
{"eq": ["body.form.foo2", "bar2"]},
],
}
), ),
] ]

View File

@@ -25,5 +25,5 @@ teststeps:
session_foo2: "body.args.foo2" session_foo2: "body.args.foo2"
validate: validate:
- eq: ["status_code", 200] - eq: ["status_code", 200]
- eq: ["body.args.sum_v", 3] - eq: ["body.args.sum_v", "3"]
- less_than: ["body.args.sum_v", "${sum_two(2, 2)}"] # - less_than: ["body.args.sum_v", "${sum_two(2, 2)}"] TODO

View File

@@ -1,42 +1,31 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/validate_with_functions.yml # FROM: examples/postman_echo/request_methods/validate_with_functions.yml
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseValidateWithFunctions(HttpRunner): class TestCaseValidateWithFunctions(HttpRunner):
config = TConfig( config = (
**{ Config("request methods testcase: validate with functions")
"name": "request methods testcase: validate with functions", .variables(**{"foo1": "session_bar1"})
"variables": {"foo1": "session_bar1"}, .base_url("https://postman-echo.com")
"base_url": "https://postman-echo.com", .verify(False)
"verify": False,
"path": "examples/postman_echo/request_methods/validate_with_functions_test.py",
}
) )
teststeps = [ teststeps = [
TStep( Step(
**{ RunRequest("get with params")
"name": "get with params", .with_variables(
"variables": { **{"foo1": "bar1", "foo2": "session_bar2", "sum_v": "${sum_two(1, 2)}"}
"foo1": "bar1", )
"foo2": "session_bar2", .get("/get")
"sum_v": "${sum_two(1, 2)}", .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
}, .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"})
"request": { .extract()
"method": "GET", .with_jmespath("body.args.foo2", "session_foo2")
"url": "/get", .validate()
"params": {"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}, .assert_equal("status_code", 200)
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"}, .assert_equal("body.args.sum_v", "3")
},
"extract": {"session_foo2": "body.args.foo2"},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.args.sum_v", 3]},
{"less_than": ["body.args.sum_v", "${sum_two(2, 2)}"]},
],
}
), ),
] ]

View File

@@ -1,82 +1,63 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT! # NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/validate_with_variables.yml # FROM: examples/postman_echo/request_methods/validate_with_variables.yml
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseValidateWithVariables(HttpRunner): class TestCaseValidateWithVariables(HttpRunner):
config = TConfig( config = (
**{ Config("request methods testcase: validate with variables")
"name": "request methods testcase: validate with variables", .variables(**{"foo1": "session_bar1"})
"variables": {"foo1": "session_bar1"}, .base_url("https://postman-echo.com")
"base_url": "https://postman-echo.com", .verify(False)
"verify": False,
"path": "examples/postman_echo/request_methods/validate_with_variables_test.py",
}
) )
teststeps = [ teststeps = [
TStep( Step(
**{ RunRequest("get with params")
"name": "get with params", .with_variables(**{"foo1": "bar1", "foo2": "session_bar2"})
"variables": {"foo1": "bar1", "foo2": "session_bar2"}, .get("/get")
"request": { .with_params(**{"foo1": "$foo1", "foo2": "$foo2"})
"method": "GET", .with_headers(**{"User-Agent": "HttpRunner/3.0"})
"url": "/get", .extract()
"params": {"foo1": "$foo1", "foo2": "$foo2"}, .with_jmespath("body.args.foo2", "session_foo2")
"headers": {"User-Agent": "HttpRunner/3.0"}, .validate()
}, .assert_equal("status_code", 200)
"extract": {"session_foo2": "body.args.foo2"}, .assert_equal("body.args.foo1", "$foo1")
"validate": [ .assert_equal("body.args.foo2", "$foo2")
{"eq": ["status_code", 200]},
{"eq": ["body.args.foo1", "$foo1"]},
{"eq": ["body.args.foo2", "$foo2"]},
],
}
), ),
TStep( Step(
**{ RunRequest("post raw text")
"name": "post raw text", .with_variables(**{"foo1": "hello world", "foo3": "$session_foo2"})
"variables": {"foo1": "hello world", "foo3": "$session_foo2"}, .post("/post")
"request": { .with_headers(
"method": "POST", **{"User-Agent": "HttpRunner/3.0", "Content-Type": "text/plain"}
"url": "/post", )
"headers": { .with_data(
"User-Agent": "HttpRunner/3.0", "This is expected to be sent back as part of response body: $foo1-$foo3."
"Content-Type": "text/plain", )
}, .validate()
"data": "This is expected to be sent back as part of response body: $foo1-$foo3.", .assert_equal("status_code", 200)
}, .assert_equal(
"validate": [ "body.data",
{"eq": ["status_code", 200]}, "This is expected to be sent back as part of response body: session_bar1-$foo3.",
{ )
"eq": [
"body.data",
"This is expected to be sent back as part of response body: session_bar1-$foo3.",
]
},
],
}
), ),
TStep( Step(
**{ RunRequest("post form data")
"name": "post form data", .with_variables(**{"foo1": "bar1", "foo2": "bar2"})
"variables": {"foo1": "bar1", "foo2": "bar2"}, .post("/post")
"request": { .with_headers(
"method": "POST", **{
"url": "/post", "User-Agent": "HttpRunner/3.0",
"headers": { "Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "HttpRunner/3.0", }
"Content-Type": "application/x-www-form-urlencoded", )
}, .with_data("foo1=$foo1&foo2=$foo2")
"data": "foo1=$foo1&foo2=$foo2", .validate()
}, .assert_equal("status_code", 200)
"validate": [ .assert_equal("body.form.foo1", "$foo1")
{"eq": ["status_code", 200]}, .assert_equal("body.form.foo2", "$foo2")
{"eq": ["body.form.foo1", "$foo1"]},
{"eq": ["body.form.foo2", "$foo2"]},
],
}
), ),
] ]

View File

@@ -1,8 +1,9 @@
__version__ = "3.0.6" __version__ = "3.0.7"
__description__ = "One-stop solution for HTTP(S) testing." __description__ = "One-stop solution for HTTP(S) testing."
from httprunner.runner import HttpRunner from httprunner.runner import HttpRunner
from httprunner.schema import TConfig, TStep from httprunner.schema import TConfig, TStep
from httprunner.testcase import Config, Step, RunRequest, RunTestCase
__all__ = [ __all__ = [
"__version__", "__version__",
@@ -10,4 +11,8 @@ __all__ = [
"HttpRunner", "HttpRunner",
"TConfig", "TConfig",
"TStep", "TStep",
"Config",
"Step",
"RunRequest",
"RunTestCase",
] ]

View File

@@ -5,27 +5,27 @@ Built-in validate comparators.
import re import re
def equals(check_value, expect_value): def equal(check_value, expect_value):
assert check_value == expect_value assert check_value == expect_value
def less_than(check_value, expect_value):
assert check_value < expect_value
def less_than_or_equals(check_value, expect_value):
assert check_value <= expect_value
def greater_than(check_value, expect_value): def greater_than(check_value, expect_value):
assert check_value > expect_value assert check_value > expect_value
def greater_than_or_equals(check_value, expect_value): def less_than(check_value, expect_value):
assert check_value < expect_value
def greater_or_equals(check_value, expect_value):
assert check_value >= expect_value assert check_value >= expect_value
def not_equals(check_value, expect_value): def less_or_equals(check_value, expect_value):
assert check_value <= expect_value
def not_equal(check_value, expect_value):
assert check_value != expect_value assert check_value != expect_value
@@ -33,34 +33,29 @@ def string_equals(check_value, expect_value):
assert str(check_value) == str(expect_value) assert str(check_value) == str(expect_value)
def length_equals(check_value, expect_value): def length_equal(check_value, expect_value):
assert isinstance(expect_value, int) assert isinstance(expect_value, int)
expect_len = _cast_to_int(expect_value) assert len(check_value) == expect_value
assert len(check_value) == expect_len
def length_greater_than(check_value, expect_value): def length_greater_than(check_value, expect_value):
assert isinstance(expect_value, int) assert isinstance(expect_value, (int, float))
expect_len = _cast_to_int(expect_value) assert len(check_value) > expect_value
assert len(check_value) > expect_len
def length_greater_than_or_equals(check_value, expect_value): def length_greater_or_equals(check_value, expect_value):
assert isinstance(expect_value, int) assert isinstance(expect_value, (int, float))
expect_len = _cast_to_int(expect_value) assert len(check_value) >= expect_value
assert len(check_value) >= expect_len
def length_less_than(check_value, expect_value): def length_less_than(check_value, expect_value):
assert isinstance(expect_value, int) assert isinstance(expect_value, (int, float))
expect_len = _cast_to_int(expect_value) assert len(check_value) < expect_value
assert len(check_value) < expect_len
def length_less_than_or_equals(check_value, expect_value): def length_less_or_equals(check_value, expect_value):
assert isinstance(expect_value, int) assert isinstance(expect_value, (int, float))
expect_len = _cast_to_int(expect_value) assert len(check_value) <= expect_value
assert len(check_value) <= expect_len
def contains(check_value, expect_value): def contains(check_value, expect_value):
@@ -100,10 +95,3 @@ def startswith(check_value, expect_value):
def endswith(check_value, expect_value): def endswith(check_value, expect_value):
assert str(check_value).endswith(str(expect_value)) assert str(check_value).endswith(str(expect_value))
def _cast_to_int(expect_value):
try:
return int(expect_value)
except Exception:
raise AssertionError("%r can't cast to int" % str(expect_value))

View File

@@ -44,7 +44,7 @@ def main_run(extra_args):
sys.exit(1) sys.exit(1)
extra_args_new.extend(testcase_path_list) extra_args_new.extend(testcase_path_list)
pytest.main(extra_args_new) sys.exit(pytest.main(extra_args_new))
def main(): def main():

View File

@@ -41,7 +41,7 @@ def get_req_resp_record(resp_obj: Response) -> ReqRespData:
# record actual request info # record actual request info
request_headers = dict(resp_obj.request.headers) request_headers = dict(resp_obj.request.headers)
request_cookies = dict(resp_obj.request._cookies) request_cookies = resp_obj.request._cookies.get_dict()
request_body = resp_obj.request.body request_body = resp_obj.request.body
try: try:
request_body = json.loads(request_body) request_body = json.loads(request_body)

View File

@@ -265,7 +265,9 @@ def session_fixture(request):
) )
summary["stat"]["teststeps"]["failures"] += 1 summary["stat"]["teststeps"]["failures"] += 1
summary["details"].append(testcase_summary.dict()) testcase_summary_json = testcase_summary.dict()
testcase_summary_json["records"] = testcase_summary_json.pop("step_datas")
summary["details"].append(testcase_summary_json)
summary_path = "{{SUMMARY_PATH_PLACEHOLDER}}" summary_path = "{{SUMMARY_PATH_PLACEHOLDER}}"
summary_dir = os.path.dirname(summary_path) summary_dir = os.path.dirname(summary_path)

View File

@@ -30,7 +30,14 @@ def init_har2case_parser(subparsers):
"--to-yaml", "--to-yaml",
dest="to_yaml", dest="to_yaml",
action="store_true", action="store_true",
help="Convert to YAML format, if not specified, convert to JSON format by default.", help="Convert to YAML format, if not specified, convert to pytest format by default.",
)
parser.add_argument(
"-2j",
"--to-json",
dest="to_json",
action="store_true",
help="Convert to JSON format, if not specified, convert to pytest format by default.",
) )
parser.add_argument( parser.add_argument(
"--filter", "--filter",
@@ -55,7 +62,13 @@ def main_har2case(args):
logger.error(f"HAR file not exists: {har_source_file}") logger.error(f"HAR file not exists: {har_source_file}")
sys.exit(1) sys.exit(1)
output_file_type = "YML" if args.to_yaml else "JSON" if args.to_yaml:
output_file_type = "YAML"
elif args.to_yaml:
output_file_type = "JSON"
else:
output_file_type = "pytest"
HarParser(har_source_file, args.filter, args.exclude).gen_testcase(output_file_type) HarParser(har_source_file, args.filter, args.exclude).gen_testcase(output_file_type)
return 0 return 0

View File

@@ -7,6 +7,7 @@ import urllib.parse as urlparse
from loguru import logger from loguru import logger
from httprunner.ext.har2case import utils from httprunner.ext.har2case import utils
from httprunner.make import make_testcase, format_pytest_with_black
try: try:
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError
@@ -329,17 +330,23 @@ class HarParser(object):
testcase = {"config": config, "teststeps": teststeps} testcase = {"config": config, "teststeps": teststeps}
return testcase return testcase
def gen_testcase(self, file_type="JSON"): def gen_testcase(self, file_type="pytest"):
logger.info(f"Start to generate testcase from {self.har_file_path}") logger.info(f"Start to generate testcase from {self.har_file_path}")
harfile = os.path.splitext(self.har_file_path)[0] harfile = os.path.splitext(self.har_file_path)[0]
output_testcase_file = "{}.{}".format(harfile, file_type.lower())
testcase = self._make_testcase() testcase = self._make_testcase()
logger.debug("prepared testcase: {}".format(testcase)) logger.debug("prepared testcase: {}".format(testcase))
if file_type == "JSON": if file_type == "JSON":
output_testcase_file = f"{harfile}.json"
utils.dump_json(testcase, output_testcase_file) utils.dump_json(testcase, output_testcase_file)
else: elif file_type == "YAML":
output_testcase_file = f"{harfile}.yml"
utils.dump_yaml(testcase, output_testcase_file) utils.dump_yaml(testcase, output_testcase_file)
else:
# default to generate pytest file
testcase["config"]["path"] = self.har_file_path
output_testcase_file = make_testcase(testcase)
format_pytest_with_black(output_testcase_file)
logger.info(f"generated testcase: {output_testcase_file}") logger.info(f"generated testcase: {output_testcase_file}")

View File

@@ -48,7 +48,6 @@ def _load_json_file(json_file: Text) -> Dict:
json_content = json.load(data_file) json_content = json.load(data_file)
except json.JSONDecodeError as ex: except json.JSONDecodeError as ex:
err_msg = f"JSONDecodeError:\nfile: {json_file}\nerror: {ex}" err_msg = f"JSONDecodeError:\nfile: {json_file}\nerror: {ex}"
logger.error(err_msg)
raise exceptions.FileFormatError(err_msg) raise exceptions.FileFormatError(err_msg)
return json_content return json_content
@@ -74,12 +73,11 @@ def load_test_file(test_file: Text) -> Dict:
def load_testcase(testcase: Dict) -> TestCase: def load_testcase(testcase: Dict) -> TestCase:
path = testcase["config"]["path"]
try: try:
# validate with pydantic TestCase model # validate with pydantic TestCase model
testcase_obj = TestCase.parse_obj(testcase) testcase_obj = TestCase.parse_obj(testcase)
except ValidationError as ex: except ValidationError as ex:
err_msg = f"TestCase ValidationError:\nfile: {path}\nerror: {ex}" err_msg = f"TestCase ValidationError:\nerror: {ex}\ncontent: {testcase}"
logger.error(err_msg) logger.error(err_msg)
raise exceptions.TestCaseFormatError(err_msg) raise exceptions.TestCaseFormatError(err_msg)
@@ -89,8 +87,8 @@ def load_testcase(testcase: Dict) -> TestCase:
def load_testcase_file(testcase_file: Text) -> TestCase: def load_testcase_file(testcase_file: Text) -> TestCase:
"""load testcase file and validate with pydantic model""" """load testcase file and validate with pydantic model"""
testcase_content = load_test_file(testcase_file) testcase_content = load_test_file(testcase_file)
testcase_content.setdefault("config", {})["path"] = testcase_file
testcase_obj = load_testcase(testcase_content) testcase_obj = load_testcase(testcase_content)
testcase_obj.config.path = testcase_file
return testcase_obj return testcase_obj
@@ -195,7 +193,7 @@ def load_csv_file(csv_file: Text) -> List[Dict]:
def load_folder_files(folder_path: Text, recursive: bool = True) -> List: def load_folder_files(folder_path: Text, recursive: bool = True) -> List:
""" load folder path, return all files endswith yml/yaml/json in list. """ load folder path, return all files endswith .yml/.yaml/.json/_test.py in list.
Args: Args:
folder_path (str): specified folder path to load folder_path (str): specified folder path to load
@@ -220,7 +218,7 @@ def load_folder_files(folder_path: Text, recursive: bool = True) -> List:
filenames_list = [] filenames_list = []
for filename in filenames: for filename in filenames:
if not filename.endswith((".yml", ".yaml", ".json")): if not filename.lower().endswith((".yml", ".yaml", ".json", "_test.py")):
continue continue
filenames_list.append(filename) filenames_list.append(filename)

View File

@@ -16,13 +16,15 @@ from httprunner.loader import (
load_project_meta, load_project_meta,
) )
from httprunner.parser import parse_data from httprunner.parser import parse_data
from httprunner.response import uniform_validator
""" cache converted pytest files, avoid duplicate making """ cache converted pytest files, avoid duplicate making
""" """
make_files_cache_set: Set = set() make_files_cache_set: Set = set()
pytest_files_set: Set = set()
__TEMPLATE__ = jinja2.Template( __TEMPLATE__ = jinja2.Template(
"""# NOTICE: Generated By HttpRunner. DO NOT EDIT! """# NOTICE: Generated By HttpRunner.
# FROM: {{ testcase_path }} # FROM: {{ testcase_path }}
{% if imports_list %} {% if imports_list %}
import os import os
@@ -30,17 +32,17 @@ import sys
sys.path.insert(0, os.getcwd()) sys.path.insert(0, os.getcwd())
{% endif %} {% endif %}
from httprunner import HttpRunner, TConfig, TStep from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
{% for import_str in imports_list %} {% for import_str in imports_list %}
{{ import_str }} {{ import_str }}
{% endfor %} {% endfor %}
class {{ class_name }}(HttpRunner): class {{ class_name }}(HttpRunner):
config = TConfig(**{{ config }}) config = {{ config_chain_style }}
teststeps = [ teststeps = [
{% for teststep in teststeps %} {% for step_chain_style in teststeps_chain_style %}
TStep(**{{ teststep }}), {{ step_chain_style }},
{% endfor %} {% endfor %}
] ]
@@ -96,7 +98,7 @@ def __ensure_testcase_module(path: Text) -> NoReturn:
return return
with open(init_file, "w", encoding="utf-8") as f: with open(init_file, "w", encoding="utf-8") as f:
f.write("# NOTICE: Generated By HttpRunner. DO NOT EDIT!") f.write("# NOTICE: Generated By HttpRunner. DO NOT EDIT!\n")
def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]: def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]:
@@ -109,7 +111,7 @@ def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]:
raw_file_name, file_suffix = os.path.splitext(os.path.basename(testcase_path)) raw_file_name, file_suffix = os.path.splitext(os.path.basename(testcase_path))
file_suffix = file_suffix.lower() file_suffix = file_suffix.lower()
if file_suffix not in [".json", ".yml", ".yaml"]: if file_suffix not in [".json", ".yml", ".yaml", ".har"]:
raise exceptions.ParamsError( raise exceptions.ParamsError(
"testcase file should have .yaml/.yml/.json suffix" "testcase file should have .yaml/.yml/.json suffix"
) )
@@ -124,7 +126,7 @@ def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]:
return testcase_python_path, name_in_title_case return testcase_python_path, name_in_title_case
def __format_pytest_with_black(python_paths: List[Text]) -> NoReturn: def format_pytest_with_black(*python_paths: Text) -> NoReturn:
logger.info("format pytest cases with black ...") logger.info("format pytest cases with black ...")
try: try:
subprocess.run(["black", *python_paths]) subprocess.run(["black", *python_paths])
@@ -132,7 +134,112 @@ def __format_pytest_with_black(python_paths: List[Text]) -> NoReturn:
logger.error(ex) logger.error(ex)
def __make_testcase(testcase: Dict, dir_path: Text = None) -> NoReturn: def make_config_chain_style(config: Dict) -> Text:
config_chain_style = f'Config("{config["name"]}")'
if config["variables"]:
variables = config["variables"]
config_chain_style += f".variables(**{variables})"
if "base_url" in config:
config_chain_style += f'.base_url("{config["base_url"]}")'
if "verify" in config:
config_chain_style += f'.verify({config["verify"]})'
return config_chain_style
def make_request_chain_style(request: Dict) -> Text:
method = request["method"].lower()
url = request["url"]
request_chain_style = f'.{method}("{url}")'
if "params" in request:
params = request["params"]
request_chain_style += f".with_params(**{params})"
if "headers" in request:
headers = request["headers"]
request_chain_style += f".with_headers(**{headers})"
if "cookies" in request:
cookies = request["cookies"]
request_chain_style += f".with_cookies(**{cookies})"
if "data" in request:
data = request["data"]
if isinstance(data, Text):
data = f'"{data}"'
request_chain_style += f".with_data({data})"
if "timeout" in request:
timeout = request["timeout"]
request_chain_style += f".set_timeout({timeout})"
if "verify" in request:
verify = request["verify"]
request_chain_style += f".set_verify({verify})"
if "allow_redirects" in request:
allow_redirects = request["allow_redirects"]
request_chain_style += f".set_allow_redirects({allow_redirects})"
if "upload" in request:
upload = request["upload"]
request_chain_style += f".upload(**{upload})"
return request_chain_style
def make_teststep_chain_style(teststep: Dict) -> Text:
if teststep.get("request"):
step_info = f'RunRequest("{teststep["name"]}")'
elif teststep.get("testcase"):
step_info = f'RunTestCase("{teststep["name"]}")'
else:
raise exceptions.TestCaseFormatError
if "variables" in teststep:
variables = teststep["variables"]
step_info += f".with_variables(**{variables})"
if teststep.get("request"):
step_info += make_request_chain_style(teststep["request"])
elif teststep.get("testcase"):
testcase = teststep["testcase"]
call_ref_testcase = f".call({testcase})"
step_info += call_ref_testcase
if "extract" in teststep:
step_info += ".extract()"
for extract_name, extract_path in teststep["extract"].items():
step_info += f'.with_jmespath("{extract_path}", "{extract_name}")'
if "validate" in teststep:
step_info += ".validate()"
for v in teststep["validate"]:
validator = uniform_validator(v)
assert_method = validator["assert"]
check = validator["check"]
if '"' in check:
# e.g. body."user-agent" => 'body."user-agent"'
check = f"'{check}'"
else:
check = f'"{check}"'
expect = validator["expect"]
if isinstance(expect, Text):
expect = f'"{expect}"'
step_info += f".assert_{assert_method}({check}, {expect})"
return f"Step({step_info})"
def make_testcase(
testcase: Dict, dir_path: Text = None, ref_flag: bool = False,
) -> Text:
"""convert valid testcase dict to pytest file path""" """convert valid testcase dict to pytest file path"""
# ensure compatibility with testcase format v2 # ensure compatibility with testcase format v2
testcase = ensure_testcase_v3(testcase) testcase = ensure_testcase_v3(testcase)
@@ -151,7 +258,7 @@ def __make_testcase(testcase: Dict, dir_path: Text = None) -> NoReturn:
global make_files_cache_set global make_files_cache_set
if testcase_python_path in make_files_cache_set: if testcase_python_path in make_files_cache_set:
return return testcase_python_path
config = testcase["config"] config = testcase["config"]
config["path"] = __ensure_cwd_relative(testcase_python_path) config["path"] = __ensure_cwd_relative(testcase_python_path)
@@ -174,13 +281,13 @@ def __make_testcase(testcase: Dict, dir_path: Text = None) -> NoReturn:
# make ref testcase pytest file # make ref testcase pytest file
ref_testcase_path = __ensure_absolute(teststep["testcase"]) ref_testcase_path = __ensure_absolute(teststep["testcase"])
__make(ref_testcase_path) __make(ref_testcase_path, ref_flag=True)
# prepare ref testcase class name # prepare ref testcase class name
ref_testcase_python_path, ref_testcase_cls_name = convert_testcase_path( ref_testcase_python_path, ref_testcase_cls_name = convert_testcase_path(
ref_testcase_path ref_testcase_path
) )
teststep["testcase"] = f"CLS_LB({ref_testcase_cls_name})CLS_RB" teststep["testcase"] = ref_testcase_cls_name
# prepare import ref testcase # prepare import ref testcase
ref_testcase_python_path = ref_testcase_python_path[len(os.getcwd()) + 1 :] ref_testcase_python_path = ref_testcase_python_path[len(os.getcwd()) + 1 :]
@@ -193,12 +300,13 @@ def __make_testcase(testcase: Dict, dir_path: Text = None) -> NoReturn:
data = { data = {
"testcase_path": __ensure_cwd_relative(testcase_path), "testcase_path": __ensure_cwd_relative(testcase_path),
"class_name": f"TestCase{testcase_cls_name}", "class_name": f"TestCase{testcase_cls_name}",
"config": config,
"teststeps": teststeps,
"imports_list": imports_list, "imports_list": imports_list,
"config_chain_style": make_config_chain_style(config),
"teststeps_chain_style": [
make_teststep_chain_style(step) for step in teststeps
],
} }
content = __TEMPLATE__.render(data) content = __TEMPLATE__.render(data)
content = content.replace("'CLS_LB(", "").replace(")CLS_RB'", "")
with open(testcase_python_path, "w", encoding="utf-8") as f: with open(testcase_python_path, "w", encoding="utf-8") as f:
f.write(content) f.write(content)
@@ -206,10 +314,14 @@ def __make_testcase(testcase: Dict, dir_path: Text = None) -> NoReturn:
__ensure_testcase_module(testcase_python_path) __ensure_testcase_module(testcase_python_path)
logger.info(f"generated testcase: {testcase_python_path}") logger.info(f"generated testcase: {testcase_python_path}")
make_files_cache_set.add(__ensure_cwd_relative(testcase_python_path))
if not ref_flag:
make_files_cache_set.add(__ensure_cwd_relative(testcase_python_path))
return testcase_python_path
def __make_testsuite(testsuite: Dict) -> NoReturn: def make_testsuite(testsuite: Dict) -> NoReturn:
"""convert valid testsuite dict to pytest folder with testcases""" """convert valid testsuite dict to pytest folder with testcases"""
# validate testsuite format # validate testsuite format
load_testsuite(testsuite) load_testsuite(testsuite)
@@ -254,15 +366,16 @@ def __make_testsuite(testsuite: Dict) -> NoReturn:
testcase_dict["config"]["variables"].update(testsuite_variables) testcase_dict["config"]["variables"].update(testsuite_variables)
# make testcase # make testcase
__make_testcase(testcase_dict, testsuite_dir) make_testcase(testcase_dict, testsuite_dir)
def __make(tests_path: Text) -> NoReturn: def __make(tests_path: Text, ref_flag: bool = False) -> NoReturn:
""" make testcase(s) with testcase/testsuite/folder absolute path """ make testcase(s) with testcase/testsuite/folder absolute path
generated pytest file path will be cached in make_files_cache_set generated pytest file path will be cached in make_files_cache_set
Args: Args:
tests_path: should be in absolute path tests_path: should be in absolute path
ref_flag: flag if referenced test path
""" """
test_files = [] test_files = []
@@ -275,6 +388,10 @@ def __make(tests_path: Text) -> NoReturn:
raise exceptions.TestcaseNotFound(f"Invalid tests path: {tests_path}") raise exceptions.TestcaseNotFound(f"Invalid tests path: {tests_path}")
for test_file in test_files: for test_file in test_files:
if test_file.lower().endswith("_test.py"):
pytest_files_set.add(test_file)
continue
try: try:
test_content = load_test_file(test_file) test_content = load_test_file(test_file)
except (exceptions.FileNotFound, exceptions.FileFormatError) as ex: except (exceptions.FileNotFound, exceptions.FileFormatError) as ex:
@@ -290,34 +407,36 @@ def __make(tests_path: Text) -> NoReturn:
# testcase # testcase
if "teststeps" in test_content: if "teststeps" in test_content:
try: try:
__make_testcase(test_content) make_testcase(test_content, ref_flag=ref_flag)
except exceptions.TestCaseFormatError: except exceptions.TestCaseFormatError:
continue continue
# testsuite # testsuite
elif "testcases" in test_content: elif "testcases" in test_content:
try: try:
__make_testsuite(test_content) make_testsuite(test_content)
except exceptions.TestSuiteFormatError: except exceptions.TestSuiteFormatError:
continue continue
# invalid format # invalid format
else: else:
raise exceptions.FileFormatError( logger.warning(f"skip invalid testcase/testsuite file: {test_file}")
f"test file is neither testcase nor testsuite: {test_file}"
)
def main_make(tests_paths: List[Text]) -> List[Text]: def main_make(tests_paths: List[Text]) -> List[Text]:
if not tests_paths:
return []
for tests_path in tests_paths: for tests_path in tests_paths:
if not os.path.isabs(tests_path): if not os.path.isabs(tests_path):
tests_path = os.path.join(os.getcwd(), tests_path) tests_path = os.path.join(os.getcwd(), tests_path)
__make(tests_path) __make(tests_path)
testcase_path_list = list(make_files_cache_set) pytest_files_set.update(make_files_cache_set)
__format_pytest_with_black(testcase_path_list) pytest_files_list = list(pytest_files_set)
return testcase_path_list format_pytest_with_black(*pytest_files_list)
return pytest_files_list
def init_make_parser(subparsers): def init_make_parser(subparsers):

View File

@@ -12,45 +12,39 @@ from httprunner.schema import VariablesMapping, Validators, FunctionsMapping
def get_uniform_comparator(comparator: Text): def get_uniform_comparator(comparator: Text):
""" convert comparator alias to uniform name """ convert comparator alias to uniform name
""" """
if comparator in ["eq", "equals", "==", "is"]: if comparator in ["eq", "equals", "equal"]:
return "equals" return "equal"
elif comparator in ["lt", "less_than"]: elif comparator in ["lt", "less_than"]:
return "less_than" return "less_than"
elif comparator in ["le", "less_than_or_equals"]: elif comparator in ["le", "less_or_equals"]:
return "less_than_or_equals" return "less_or_equals"
elif comparator in ["gt", "greater_than"]: elif comparator in ["gt", "greater_than"]:
return "greater_than" return "greater_than"
elif comparator in ["ge", "greater_than_or_equals"]: elif comparator in ["ge", "greater_or_equals"]:
return "greater_than_or_equals" return "greater_or_equals"
elif comparator in ["ne", "not_equals"]: elif comparator in ["ne", "not_equal"]:
return "not_equals" return "not_equal"
elif comparator in ["str_eq", "string_equals"]: elif comparator in ["str_eq", "string_equals"]:
return "string_equals" return "string_equals"
elif comparator in ["len_eq", "length_equals", "count_eq"]: elif comparator in ["len_eq", "length_equal"]:
return "length_equals" return "length_equal"
elif comparator in [ elif comparator in [
"len_gt", "len_gt",
"count_gt",
"length_greater_than", "length_greater_than",
"count_greater_than",
]: ]:
return "length_greater_than" return "length_greater_than"
elif comparator in [ elif comparator in [
"len_ge", "len_ge",
"count_ge", "length_greater_or_equals",
"length_greater_than_or_equals",
"count_greater_than_or_equals",
]: ]:
return "length_greater_than_or_equals" return "length_greater_or_equals"
elif comparator in ["len_lt", "count_lt", "length_less_than", "count_less_than"]: elif comparator in ["len_lt", "length_less_than"]:
return "length_less_than" return "length_less_than"
elif comparator in [ elif comparator in [
"len_le", "len_le",
"count_le", "length_less_or_equals",
"length_less_than_or_equals",
"count_less_than_or_equals",
]: ]:
return "length_less_than_or_equals" return "length_less_or_equals"
else: else:
return comparator return comparator
@@ -62,8 +56,8 @@ def uniform_validator(validator):
validator (dict): validator maybe in two formats: validator (dict): validator maybe in two formats:
format1: this is kept for compatibility with the previous versions. format1: this is kept for compatibility with the previous versions.
{"check": "status_code", "assert": "eq", "expect": 201} {"check": "status_code", "comparator": "eq", "expect": 201}
{"check": "$resp_body_success", "assert": "eq", "expect": True} {"check": "$resp_body_success", "comparator": "eq", "expect": True}
format2: recommended new version, {assert: [check_item, expected_value]} format2: recommended new version, {assert: [check_item, expected_value]}
{'eq': ['status_code', 201]} {'eq': ['status_code', 201]}
{'eq': ['$resp_body_success', True]} {'eq': ['$resp_body_success', True]}
@@ -169,11 +163,10 @@ class ResponseObject(object):
check_value = parse_data( check_value = parse_data(
check_item, variables_mapping, functions_mapping check_item, variables_mapping, functions_mapping
) )
check_value = parse_string_value(check_value)
else: else:
check_value = jmespath.search(check_item, self.resp_obj_meta) check_value = jmespath.search(check_item, self.resp_obj_meta)
check_value = parse_string_value(check_value)
# comparator # comparator
assert_method = u_validator["assert"] assert_method = u_validator["assert"]
assert_func = get_mapping_function(assert_method, functions_mapping) assert_func = get_mapping_function(assert_method, functions_mapping)

View File

@@ -2,7 +2,7 @@ import os
import time import time
import uuid import uuid
from datetime import datetime from datetime import datetime
from typing import List, Dict, Text from typing import List, Dict, Text, NoReturn
try: try:
import allure import allure
@@ -20,6 +20,7 @@ from httprunner.ext.uploader import prepare_upload_step
from httprunner.loader import load_project_meta, load_testcase_file from httprunner.loader import load_project_meta, load_testcase_file
from httprunner.parser import build_url, parse_data, parse_variables_mapping from httprunner.parser import build_url, parse_data, parse_variables_mapping
from httprunner.response import ResponseObject from httprunner.response import ResponseObject
from httprunner.testcase import Config, Step
from httprunner.schema import ( from httprunner.schema import (
TConfig, TConfig,
TStep, TStep,
@@ -34,10 +35,12 @@ from httprunner.schema import (
class HttpRunner(object): class HttpRunner(object):
config: TConfig config: Config
teststeps: List[TStep] teststeps: List[Step]
success: bool = True # indicate testcase execution result success: bool = True # indicate testcase execution result
__config: TConfig
__teststeps: List[TStep]
__project_meta: ProjectMeta = None __project_meta: ProjectMeta = None
__case_id: Text = "" __case_id: Text = ""
__step_datas: List[StepData] = None __step_datas: List[StepData] = None
@@ -49,6 +52,19 @@ class HttpRunner(object):
# log # log
__log_path: Text = "" __log_path: Text = ""
def __init_tests__(self) -> NoReturn:
self.__config = self.config.perform()
self.__teststeps = []
for step in self.teststeps:
self.__teststeps.append(step.perform())
@property
def raw_testcase(self) -> TestCase:
if not hasattr(self, "__config"):
self.__init_tests__()
return TestCase(config=self.__config, teststeps=self.__teststeps)
def with_project_meta(self, project_meta: ProjectMeta) -> "HttpRunner": def with_project_meta(self, project_meta: ProjectMeta) -> "HttpRunner":
self.__project_meta = project_meta self.__project_meta = project_meta
return self return self
@@ -65,7 +81,7 @@ class HttpRunner(object):
self.__session_variables = variables self.__session_variables = variables
return self return self
def __run_step_request(self, step: TStep): def __run_step_request(self, step: TStep) -> StepData:
"""run teststep: request""" """run teststep: request"""
step_data = StepData(name=step.name) step_data = StepData(name=step.name)
@@ -84,7 +100,7 @@ class HttpRunner(object):
# prepare arguments # prepare arguments
method = parsed_request_dict.pop("method") method = parsed_request_dict.pop("method")
url_path = parsed_request_dict.pop("url") url_path = parsed_request_dict.pop("url")
url = build_url(self.config.base_url, url_path) url = build_url(self.__config.base_url, url_path)
parsed_request_dict["json"] = parsed_request_dict.pop("req_json", {}) parsed_request_dict["json"] = parsed_request_dict.pop("req_json", {})
# request # request
@@ -142,7 +158,7 @@ class HttpRunner(object):
return step_data return step_data
def __run_step_testcase(self, step): def __run_step_testcase(self, step: TStep) -> StepData:
"""run teststep: referenced testcase""" """run teststep: referenced testcase"""
step_data = StepData(name=step.name) step_data = StepData(name=step.name)
step_variables = step.variables step_variables = step.variables
@@ -183,7 +199,7 @@ class HttpRunner(object):
return step_data return step_data
def __run_step(self, step: TStep): def __run_step(self, step: TStep) -> Dict:
"""run teststep, teststep maybe a request or referenced testcase""" """run teststep, teststep maybe a request or referenced testcase"""
logger.info(f"run step begin: {step.name} >>>>>>") logger.info(f"run step begin: {step.name} >>>>>>")
@@ -200,7 +216,7 @@ class HttpRunner(object):
logger.info(f"run step end: {step.name} <<<<<<\n") logger.info(f"run step end: {step.name} <<<<<<\n")
return step_data.export return step_data.export
def __parse_config(self, config: TConfig): def __parse_config(self, config: TConfig) -> NoReturn:
config.variables.update(self.__session_variables) config.variables.update(self.__session_variables)
config.variables = parse_variables_mapping( config.variables = parse_variables_mapping(
config.variables, self.__project_meta.functions config.variables, self.__project_meta.functions
@@ -212,7 +228,7 @@ class HttpRunner(object):
config.base_url, config.variables, self.__project_meta.functions config.base_url, config.variables, self.__project_meta.functions
) )
def run_testcase(self, testcase: TestCase): def run_testcase(self, testcase: TestCase) -> "HttpRunner":
"""run specified testcase """run specified testcase
Examples: Examples:
@@ -220,21 +236,23 @@ class HttpRunner(object):
>>> HttpRunner().with_project_meta(project_meta).run_testcase(testcase_obj) >>> HttpRunner().with_project_meta(project_meta).run_testcase(testcase_obj)
""" """
self.config = testcase.config self.__config = testcase.config
self.teststeps = testcase.teststeps self.__teststeps = testcase.teststeps
# prepare # prepare
self.__project_meta = self.__project_meta or load_project_meta(self.config.path) self.__project_meta = self.__project_meta or load_project_meta(
self.__parse_config(self.config) self.__config.path
)
self.__parse_config(self.__config)
self.__start_at = time.time() self.__start_at = time.time()
self.__step_datas: List[StepData] = [] self.__step_datas: List[StepData] = []
self.__session = self.__session or HttpSession() self.__session = self.__session or HttpSession()
self.__session_variables = {} self.__session_variables = {}
# run teststeps # run teststeps
for step in self.teststeps: for step in self.__teststeps:
# update with config variables # update with config variables
step.variables.update(self.config.variables) step.variables.update(self.__config.variables)
# update with session variables extracted from pre step # update with session variables extracted from pre step
step.variables.update(self.__session_variables) step.variables.update(self.__session_variables)
# parse variables # parse variables
@@ -267,7 +285,8 @@ class HttpRunner(object):
>>> TestCaseRequestWithFunctions().run() >>> TestCaseRequestWithFunctions().run()
""" """
testcase_obj = TestCase(config=self.config, teststeps=self.teststeps) self.__init_tests__()
testcase_obj = TestCase(config=self.__config, teststeps=self.__teststeps)
return self.run_testcase(testcase_obj) return self.run_testcase(testcase_obj)
def get_step_datas(self) -> List[StepData]: def get_step_datas(self) -> List[StepData]:
@@ -275,7 +294,7 @@ class HttpRunner(object):
def get_export_variables(self) -> Dict: def get_export_variables(self) -> Dict:
export_vars_mapping = {} export_vars_mapping = {}
for var_name in self.config.export: for var_name in self.__config.export:
if var_name not in self.__session_variables: if var_name not in self.__session_variables:
raise ParamsError( raise ParamsError(
f"failed to export variable {var_name} from session variables {self.__session_variables}" f"failed to export variable {var_name} from session variables {self.__session_variables}"
@@ -290,7 +309,7 @@ class HttpRunner(object):
start_at_timestamp = self.__start_at start_at_timestamp = self.__start_at
start_at_iso_format = datetime.utcfromtimestamp(start_at_timestamp).isoformat() start_at_iso_format = datetime.utcfromtimestamp(start_at_timestamp).isoformat()
return TestCaseSummary( return TestCaseSummary(
name=self.config.name, name=self.__config.name,
success=self.success, success=self.success,
case_id=self.__case_id, case_id=self.__case_id,
time=TestCaseTime( time=TestCaseTime(
@@ -299,15 +318,18 @@ class HttpRunner(object):
duration=self.__duration, duration=self.__duration,
), ),
in_out=TestCaseInOut( in_out=TestCaseInOut(
vars=self.config.variables, export=self.get_export_variables() vars=self.__config.variables, export=self.get_export_variables()
), ),
log=self.__log_path, log=self.__log_path,
step_datas=self.__step_datas, step_datas=self.__step_datas,
) )
def test_start(self): def test_start(self) -> "HttpRunner":
"""main entrance, discovered by pytest""" """main entrance, discovered by pytest"""
self.__project_meta = self.__project_meta or load_project_meta(self.config.path) self.__init_tests__()
self.__project_meta = self.__project_meta or load_project_meta(
self.__config.path
)
self.__case_id = self.__case_id or str(uuid.uuid4()) self.__case_id = self.__case_id or str(uuid.uuid4())
self.__log_path = self.__log_path or os.path.join( self.__log_path = self.__log_path or os.path.join(
self.__project_meta.PWD, "logs", f"{self.__case_id}.run.log" self.__project_meta.PWD, "logs", f"{self.__case_id}.run.log"
@@ -315,24 +337,24 @@ class HttpRunner(object):
log_handler = logger.add(self.__log_path, level="DEBUG") log_handler = logger.add(self.__log_path, level="DEBUG")
# parse config name # parse config name
variables = self.config.variables variables = self.__config.variables
variables.update(self.__session_variables) variables.update(self.__session_variables)
self.config.name = parse_data( self.__config.name = parse_data(
self.config.name, variables, self.__project_meta.functions self.__config.name, variables, self.__project_meta.functions
) )
if USE_ALLURE: if USE_ALLURE:
# update allure report meta # update allure report meta
allure.dynamic.title(self.config.name) allure.dynamic.title(self.__config.name)
allure.dynamic.description(f"TestCase ID: {self.__case_id}") allure.dynamic.description(f"TestCase ID: {self.__case_id}")
logger.info( logger.info(
f"Start to run testcase: {self.config.name}, TestCase ID: {self.__case_id}" f"Start to run testcase: {self.__config.name}, TestCase ID: {self.__case_id}"
) )
try: try:
return self.run_testcase( return self.run_testcase(
TestCase(config=self.config, teststeps=self.teststeps) TestCase(config=self.__config, teststeps=self.__teststeps)
) )
finally: finally:
logger.remove(log_handler) logger.remove(log_handler)

View File

@@ -66,7 +66,7 @@ teststeps:
validate: validate:
- eq: ["status_code", 200] - eq: ["status_code", 200]
- eq: ["body.args.foo1", "session_bar1"] - eq: ["body.args.foo1", "session_bar1"]
- eq: ["body.args.sum_v", 3] - eq: ["body.args.sum_v", "3"]
- eq: ["body.args.foo2", "session_bar2"] - eq: ["body.args.foo2", "session_bar2"]
- -
name: post raw text name: post raw text

View File

@@ -29,8 +29,6 @@ class MethodEnum(Text, Enum):
HEAD = "HEAD" HEAD = "HEAD"
OPTIONS = "OPTIONS" OPTIONS = "OPTIONS"
PATCH = "PATCH" PATCH = "PATCH"
CONNECT = "CONNECT"
TRACE = "TRACE"
class TConfig(BaseModel): class TConfig(BaseModel):
@@ -45,17 +43,17 @@ class TConfig(BaseModel):
path: Text = None path: Text = None
class Request(BaseModel): class TRequest(BaseModel):
"""requests.Request model""" """requests.Request model"""
method: MethodEnum = MethodEnum.GET method: MethodEnum
url: Url url: Url
params: Dict[Text, Text] = {} params: Dict[Text, Text] = {}
headers: Headers = {} headers: Headers = {}
req_json: Dict = Field({}, alias="json") req_json: Union[Dict, List] = Field({}, alias="json")
data: Union[Text, Dict[Text, Any]] = "" data: Union[Text, Dict[Text, Any]] = ""
cookies: Cookies = {} cookies: Cookies = {}
timeout: int = 120 timeout: float = 120
allow_redirects: bool = True allow_redirects: bool = True
verify: Verify = False verify: Verify = False
upload: Dict = {} # used for upload files upload: Dict = {} # used for upload files
@@ -63,8 +61,8 @@ class Request(BaseModel):
class TStep(BaseModel): class TStep(BaseModel):
name: Name name: Name
request: Request = None request: Union[TRequest, None] = None
testcase: Union[Text, Callable] = "" testcase: Union[Text, Callable, None] = None
variables: VariablesMapping = {} variables: VariablesMapping = {}
setup_hooks: Hook = [] setup_hooks: Hook = []
teardown_hooks: Hook = [] teardown_hooks: Hook = []

318
httprunner/testcase.py Normal file
View File

@@ -0,0 +1,318 @@
import inspect
from typing import Text, Any, Union, Callable
from httprunner.schema import (
TConfig,
TStep,
TRequest,
MethodEnum,
TestCase,
)
class Config(object):
def __init__(self, name: Text):
self.__name = name
self.__variables = {}
self.__base_url = ""
self.__verify = False
caller_frame = inspect.stack()[1]
self.__path = caller_frame.filename
@property
def name(self):
return self.__name
@property
def path(self):
return self.__path
def variables(self, **variables) -> "Config":
self.__variables.update(variables)
return self
def base_url(self, base_url: Text) -> "Config":
self.__base_url = base_url
return self
def verify(self, verify: bool) -> "Config":
self.__verify = verify
return self
def perform(self) -> TConfig:
return TConfig(
name=self.__name,
base_url=self.__base_url,
verify=self.__verify,
variables=self.__variables,
path=self.__path,
)
class StepValidation(object):
def __init__(self, step: TStep):
self.__t_step = step
def assert_equal(self, jmes_path: Text, expected_value: Any) -> "StepValidation":
self.__t_step.validators.append({"equal": [jmes_path, expected_value]})
return self
def assert_not_equal(
self, jmes_path: Text, expected_value: Any
) -> "StepValidation":
self.__t_step.validators.append({"not_equal": [jmes_path, expected_value]})
return self
def assert_greater_than(
self, jmes_path: Text, expected_value: Union[int, float]
) -> "StepValidation":
self.__t_step.validators.append({"greater_than": [jmes_path, expected_value]})
return self
def assert_less_than(
self, jmes_path: Text, expected_value: Union[int, float]
) -> "StepValidation":
self.__t_step.validators.append({"less_than": [jmes_path, expected_value]})
return self
def assert_greater_or_equals(
self, jmes_path: Text, expected_value: Union[int, float]
) -> "StepValidation":
self.__t_step.validators.append(
{"greater_or_equals": [jmes_path, expected_value]}
)
return self
def assert_less_or_equals(
self, jmes_path: Text, expected_value: Union[int, float]
) -> "StepValidation":
self.__t_step.validators.append({"less_or_equals": [jmes_path, expected_value]})
return self
def assert_length_equal(
self, jmes_path: Text, expected_value: int
) -> "StepValidation":
self.__t_step.validators.append({"length_equal": [jmes_path, expected_value]})
return self
def assert_length_greater_than(
self, jmes_path: Text, expected_value: int
) -> "StepValidation":
self.__t_step.validators.append(
{"length_greater_than": [jmes_path, expected_value]}
)
return self
def assert_length_less_than(
self, jmes_path: Text, expected_value: int
) -> "StepValidation":
self.__t_step.validators.append(
{"length_less_than": [jmes_path, expected_value]}
)
return self
def assert_length_greater_or_equals(
self, jmes_path: Text, expected_value: int
) -> "StepValidation":
self.__t_step.validators.append(
{"length_greater_or_equals": [jmes_path, expected_value]}
)
return self
def assert_length_less_or_equals(
self, jmes_path: Text, expected_value: int
) -> "StepValidation":
self.__t_step.validators.append(
{"length_less_or_equals": [jmes_path, expected_value]}
)
return self
def assert_string_equals(
self, jmes_path: Text, expected_value: int
) -> "StepValidation":
self.__t_step.validators.append({"string_equals": [jmes_path, expected_value]})
return self
def assert_startswith(
self, jmes_path: Text, expected_value: Text
) -> "StepValidation":
self.__t_step.validators.append({"startswith": [jmes_path, expected_value]})
return self
def assert_endswith(
self, jmes_path: Text, expected_value: Text
) -> "StepValidation":
self.__t_step.validators.append({"endswith": [jmes_path, expected_value]})
return self
def assert_regex_match(
self, jmes_path: Text, expected_value: Text
) -> "StepValidation":
self.__t_step.validators.append({"regex_match": [jmes_path, expected_value]})
return self
def assert_contains(self, jmes_path: Text, expected_value: Any) -> "StepValidation":
self.__t_step.validators.append({"contains": [jmes_path, expected_value]})
return self
def assert_contained_by(
self, jmes_path: Text, expected_value: Any
) -> "StepValidation":
self.__t_step.validators.append({"contained_by": [jmes_path, expected_value]})
return self
def assert_type_match(
self, jmes_path: Text, expected_value: Text
) -> "StepValidation":
self.__t_step.validators.append({"type_match": [jmes_path, expected_value]})
return self
def perform(self) -> TStep:
return self.__t_step
class StepExtraction(object):
def __init__(self, step: TStep):
self.__t_step = step
def with_jmespath(self, jmes_path: Text, var_name: Text) -> "StepExtraction":
self.__t_step.extract[var_name] = jmes_path
return self
# def with_regex(self):
# # TODO: extract response html with regex
# pass
#
# def with_jsonpath(self):
# # TODO: extract response json with jsonpath
# pass
def validate(self) -> StepValidation:
return StepValidation(self.__t_step)
def perform(self) -> TStep:
return self.__t_step
class RequestWithOptionalArgs(object):
def __init__(self, step: TStep):
self.__t_step = step
def with_params(self, **params) -> "RequestWithOptionalArgs":
self.__t_step.request.params.update(params)
return self
def with_headers(self, **headers) -> "RequestWithOptionalArgs":
self.__t_step.request.headers.update(headers)
return self
def with_cookies(self, **cookies) -> "RequestWithOptionalArgs":
self.__t_step.request.cookies.update(cookies)
return self
def with_data(self, data) -> "RequestWithOptionalArgs":
self.__t_step.request.data = data
return self
def set_timeout(self, timeout: float) -> "RequestWithOptionalArgs":
self.__t_step.request.timeout = timeout
return self
def set_verify(self, verify: bool) -> "RequestWithOptionalArgs":
self.__t_step.request.verify = verify
return self
def set_allow_redirects(self, allow_redirects: bool) -> "RequestWithOptionalArgs":
self.__t_step.request.allow_redirects = allow_redirects
return self
def upload(self, **file_info) -> "RequestWithOptionalArgs":
self.__t_step.request.upload.update(file_info)
return self
# def hooks(self):
# pass
def extract(self) -> StepExtraction:
return StepExtraction(self.__t_step)
def validate(self) -> StepValidation:
return StepValidation(self.__t_step)
def perform(self) -> TStep:
return self.__t_step
class RunRequest(object):
def __init__(self, name: Text):
self.__t_step = TStep(name=name)
def with_variables(self, **variables) -> "RunRequest":
self.__t_step.variables.update(variables)
return self
def get(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.GET, url=url)
return RequestWithOptionalArgs(self.__t_step)
def post(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.POST, url=url)
return RequestWithOptionalArgs(self.__t_step)
def put(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.PUT, url=url)
return RequestWithOptionalArgs(self.__t_step)
def head(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.HEAD, url=url)
return RequestWithOptionalArgs(self.__t_step)
def delete(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.DELETE, url=url)
return RequestWithOptionalArgs(self.__t_step)
def options(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.OPTIONS, url=url)
return RequestWithOptionalArgs(self.__t_step)
def patch(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.PATCH, url=url)
return RequestWithOptionalArgs(self.__t_step)
class RunTestCase(object):
def __init__(self, name: Text):
self.__t_step = TStep(name=name)
def with_variables(self, **variables) -> "RunTestCase":
self.__t_step.variables.update(variables)
return self
def call(self, testcase: Callable):
self.__t_step.testcase = testcase
return self
def perform(self) -> TStep:
return self.__t_step
class Step(object):
def __init__(
self,
step: Union[
StepValidation, StepExtraction, RequestWithOptionalArgs, RunTestCase
],
):
self.__t_step = step.perform()
@property
def request(self) -> TRequest:
return self.__t_step.request
@property
def testcase(self) -> TestCase:
return self.__t_step.testcase
def perform(self) -> TStep:
return self.__t_step

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "httprunner" name = "httprunner"
version = "3.0.6" version = "3.0.7"
description = "One-stop solution for HTTP(S) testing." description = "One-stop solution for HTTP(S) testing."
license = "Apache-2.0" license = "Apache-2.0"
readme = "README.md" readme = "README.md"

View File

@@ -34,7 +34,7 @@ class TestHar(TestHar2CaseUtils):
self.assertIn("validate", teststeps[0]) self.assertIn("validate", teststeps[0])
def test_gen_testcase_yaml(self): def test_gen_testcase_yaml(self):
yaml_file = os.path.join(os.path.dirname(__file__), "data", "demo.yaml") yaml_file = os.path.join(os.path.dirname(__file__), "data", "demo.yml")
self.har_parser.gen_testcase(file_type="YAML") self.har_parser.gen_testcase(file_type="YAML")
self.assertTrue(os.path.isfile(yaml_file)) self.assertTrue(os.path.isfile(yaml_file))

View File

@@ -1,9 +1,16 @@
import unittest import unittest
from httprunner.make import main_make, convert_testcase_path, make_files_cache_set from httprunner.make import (
main_make,
convert_testcase_path,
make_files_cache_set,
make_config_chain_style,
make_teststep_chain_style,
pytest_files_set,
)
class TestLoader(unittest.TestCase): class TestMake(unittest.TestCase):
def test_make_testcase(self): def test_make_testcase(self):
path = ["examples/postman_echo/request_methods/request_with_variables.yml"] path = ["examples/postman_echo/request_methods/request_with_variables.yml"]
testcase_python_list = main_make(path) testcase_python_list = main_make(path)
@@ -17,8 +24,9 @@ class TestLoader(unittest.TestCase):
"examples/postman_echo/request_methods/request_with_testcase_reference.yml" "examples/postman_echo/request_methods/request_with_testcase_reference.yml"
] ]
make_files_cache_set.clear() make_files_cache_set.clear()
pytest_files_set.clear()
testcase_python_list = main_make(path) testcase_python_list = main_make(path)
self.assertEqual(len(testcase_python_list), 2) self.assertEqual(len(testcase_python_list), 1)
self.assertIn( self.assertIn(
"examples/postman_echo/request_methods/request_with_testcase_reference_test.py", "examples/postman_echo/request_methods/request_with_testcase_reference_test.py",
testcase_python_list, testcase_python_list,
@@ -37,7 +45,7 @@ from examples.postman_echo.request_methods.request_with_functions_test import (
content, content,
) )
self.assertIn( self.assertIn(
'"testcase": RequestWithFunctions,', content, ".call(RequestWithFunctions)", content,
) )
def test_make_testcase_folder(self): def test_make_testcase_folder(self):
@@ -85,8 +93,9 @@ from examples.postman_echo.request_methods.request_with_functions_test import (
def test_make_testsuite(self): def test_make_testsuite(self):
path = ["examples/postman_echo/request_methods/demo_testsuite.yml"] path = ["examples/postman_echo/request_methods/demo_testsuite.yml"]
make_files_cache_set.clear() make_files_cache_set.clear()
pytest_files_set.clear()
testcase_python_list = main_make(path) testcase_python_list = main_make(path)
self.assertEqual(len(testcase_python_list), 3) self.assertEqual(len(testcase_python_list), 2)
self.assertIn( self.assertIn(
"examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py", "examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py",
testcase_python_list, testcase_python_list,
@@ -95,7 +104,42 @@ from examples.postman_echo.request_methods.request_with_functions_test import (
"examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py", "examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py",
testcase_python_list, testcase_python_list,
) )
self.assertIn(
"examples/postman_echo/request_methods/request_with_functions_test.py", def test_make_config_chain_style(self):
testcase_python_list, config = {
"name": "request methods testcase: validate with functions",
"variables": {"foo1": "bar1", "foo2": 22},
"base_url": "https://postman-echo.com",
"verify": False,
"path": "examples/postman_echo/request_methods/validate_with_functions_test.py",
}
self.assertEqual(
make_config_chain_style(config),
"""Config("request methods testcase: validate with functions").variables(**{'foo1': 'bar1', 'foo2': 22}).base_url("https://postman-echo.com").verify(False)""",
)
def test_make_teststep_chain_style(self):
step = {
"name": "get with params",
"variables": {"foo1": "bar1", "foo2": 123, "sum_v": "${sum_two(1, 2)}",},
"request": {
"method": "GET",
"url": "/get",
"params": {"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"},
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"testcase": "CLS_LB(TestCaseDemo)CLS_RB",
"extract": {
"session_foo1": "body.args.foo1",
"session_foo2": "body.args.foo2",
},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.args.sum_v", "3"]},
],
}
teststep_chain_style = make_teststep_chain_style(step)
self.assertEqual(
teststep_chain_style,
"""Step(RunRequest("get with params").with_variables(**{'foo1': 'bar1', 'foo2': 123, 'sum_v': '${sum_two(1, 2)}'}).get("/get").with_params(**{'foo1': '$foo1', 'foo2': '$foo2', 'sum_v': '$sum_v'}).with_headers(**{'User-Agent': 'HttpRunner/${get_httprunner_version()}'}).extract().with_jmespath("body.args.foo1", "session_foo1").with_jmespath("body.args.foo2", "session_foo2").validate().assert_equal("status_code", 200).assert_equal("body.args.sum_v", "3"))""",
) )

View File

@@ -12,33 +12,32 @@ class TestUtils(unittest.TestCase):
self.assertIn("abc", os.environ) self.assertIn("abc", os.environ)
self.assertEqual(os.environ["abc"], "123") self.assertEqual(os.environ["abc"], "123")
def current_validators(self): def test_validators(self):
from httprunner.builtin import comparators from httprunner.builtin import comparators
functions_mapping = loader.load_module_functions(comparators) functions_mapping = loader.load_module_functions(comparators)
functions_mapping["equals"](None, None) functions_mapping["equal"](None, None)
functions_mapping["equals"](1, 1) functions_mapping["equal"](1, 1)
functions_mapping["equals"]("abc", "abc") functions_mapping["equal"]("abc", "abc")
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
functions_mapping["equals"]("123", 123) functions_mapping["equal"]("123", 123)
functions_mapping["less_than"](1, 2) functions_mapping["less_than"](1, 2)
functions_mapping["less_than_or_equals"](2, 2) functions_mapping["less_or_equals"](2, 2)
functions_mapping["greater_than"](2, 1) functions_mapping["greater_than"](2, 1)
functions_mapping["greater_than_or_equals"](2, 2) functions_mapping["greater_or_equals"](2, 2)
functions_mapping["not_equals"](123, "123") functions_mapping["not_equal"](123, "123")
functions_mapping["length_equals"]("123", 3) functions_mapping["length_equal"]("123", 3)
# Because the Numbers in a CSV file are by default treated as strings,
# you need to convert them to Numbers, and we'll test that out here.
functions_mapping["length_equals"]("123", "3")
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
functions_mapping["length_equals"]("123", "abc") functions_mapping["length_equal"]("123", "3")
with self.assertRaises(AssertionError):
functions_mapping["length_equal"]("123", "abc")
functions_mapping["length_greater_than"]("123", 2) functions_mapping["length_greater_than"]("123", 2)
functions_mapping["length_greater_than_or_equals"]("123", 3) functions_mapping["length_greater_or_equals"]("123", 3)
functions_mapping["contains"]("123abc456", "3ab") functions_mapping["contains"]("123abc456", "3ab")
functions_mapping["contains"](["1", "2"], "1") functions_mapping["contains"](["1", "2"], "1")