diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 12328fae..d07d72e0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,13 @@ # Release History +## 3.0.3 (2020-05-17) + +**Fixed** + +- fix: compatibility with testcase file path includes dots, space and minus sign +- fix: testcase generator, validate content.xxx => body.xxx +- fix: scaffold for v3 + ## 3.0.2 (2020-05-16) **Added** diff --git a/examples/httpbin/basic_test.py b/examples/httpbin/basic_test.py index 26862a25..79634dbb 100644 --- a/examples/httpbin/basic_test.py +++ b/examples/httpbin/basic_test.py @@ -1,4 +1,5 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! +# FROM: examples/httpbin/basic.yml from httprunner import HttpRunner, TConfig, TStep diff --git a/examples/httpbin/hooks_test.py b/examples/httpbin/hooks_test.py index b10d450d..84281f9d 100644 --- a/examples/httpbin/hooks_test.py +++ b/examples/httpbin/hooks_test.py @@ -1,4 +1,5 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! +# FROM: examples/httpbin/hooks.yml from httprunner import HttpRunner, TConfig, TStep diff --git a/examples/httpbin/load_image_test.py b/examples/httpbin/load_image_test.py index 6a99f1db..b23759e6 100644 --- a/examples/httpbin/load_image_test.py +++ b/examples/httpbin/load_image_test.py @@ -1,4 +1,5 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! +# FROM: examples/httpbin/load_image.yml from httprunner import HttpRunner, TConfig, TStep diff --git a/examples/httpbin/upload_test.py b/examples/httpbin/upload_test.py index ca418a4e..bef8c0fa 100644 --- a/examples/httpbin/upload_test.py +++ b/examples/httpbin/upload_test.py @@ -1,4 +1,5 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! +# FROM: examples/httpbin/upload.yml from httprunner import HttpRunner, TConfig, TStep diff --git a/examples/httpbin/validate_test.py b/examples/httpbin/validate_test.py index e66796dd..ec88e0a1 100644 --- a/examples/httpbin/validate_test.py +++ b/examples/httpbin/validate_test.py @@ -1,4 +1,5 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! +# FROM: examples/httpbin/validate.yml from httprunner import HttpRunner, TConfig, TStep diff --git a/examples/postman_echo/request_methods/hardcode_test.py b/examples/postman_echo/request_methods/hardcode_test.py index bfbe315b..e60254c4 100644 --- a/examples/postman_echo/request_methods/hardcode_test.py +++ b/examples/postman_echo/request_methods/hardcode_test.py @@ -1,4 +1,5 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! +# FROM: examples/postman_echo/request_methods/hardcode.yml from httprunner import HttpRunner, TConfig, TStep diff --git a/examples/postman_echo/request_methods/request_with_functions_test.py b/examples/postman_echo/request_methods/request_with_functions_test.py index ebe586dd..1933c03a 100644 --- a/examples/postman_echo/request_methods/request_with_functions_test.py +++ b/examples/postman_echo/request_methods/request_with_functions_test.py @@ -1,4 +1,5 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! +# FROM: examples/postman_echo/request_methods/request_with_functions.yml from httprunner import HttpRunner, TConfig, TStep diff --git a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py index 40a59f8f..55b3db9c 100644 --- a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py @@ -1,4 +1,5 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! +# FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml from httprunner import HttpRunner, TConfig, TStep diff --git a/examples/postman_echo/request_methods/request_with_variables_test.py b/examples/postman_echo/request_methods/request_with_variables_test.py index 4edb4932..12dade62 100644 --- a/examples/postman_echo/request_methods/request_with_variables_test.py +++ b/examples/postman_echo/request_methods/request_with_variables_test.py @@ -1,4 +1,5 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! +# FROM: examples/postman_echo/request_methods/request_with_variables.yml from httprunner import HttpRunner, TConfig, TStep diff --git a/examples/postman_echo/request_methods/validate_with_functions_test.py b/examples/postman_echo/request_methods/validate_with_functions_test.py index 160c1390..a4561d96 100644 --- a/examples/postman_echo/request_methods/validate_with_functions_test.py +++ b/examples/postman_echo/request_methods/validate_with_functions_test.py @@ -1,4 +1,5 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! +# FROM: examples/postman_echo/request_methods/validate_with_functions.yml from httprunner import HttpRunner, TConfig, TStep diff --git a/examples/postman_echo/request_methods/validate_with_variables_test.py b/examples/postman_echo/request_methods/validate_with_variables_test.py index 2bb44eb0..5fe266b9 100644 --- a/examples/postman_echo/request_methods/validate_with_variables_test.py +++ b/examples/postman_echo/request_methods/validate_with_variables_test.py @@ -1,4 +1,5 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! +# FROM: examples/postman_echo/request_methods/validate_with_variables.yml from httprunner import HttpRunner, TConfig, TStep diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 37b5c190..2b0bf6b7 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "3.0.2" +__version__ = "3.0.3" __description__ = "One-stop solution for HTTP(S) testing." from httprunner.runner import HttpRunner diff --git a/httprunner/cli.py b/httprunner/cli.py index f577e77a..34d9d0ff 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -25,7 +25,7 @@ def main_run(extra_args): continue elif os.path.isfile(item): # replace YAML/JSON file path with generated python file - extra_args[index] = convert_testcase_path(item) + extra_args[index], _ = convert_testcase_path(item) tests_path_list.append(item) diff --git a/httprunner/ext/har2case/core.py b/httprunner/ext/har2case/core.py index 51864d6e..f7683eac 100644 --- a/httprunner/ext/har2case/core.py +++ b/httprunner/ext/har2case/core.py @@ -261,9 +261,7 @@ class HarParser(object): if isinstance(value, (dict, list)): continue - teststep_dict["validate"].append( - {"eq": ["content.{}".format(key), value]} - ) + teststep_dict["validate"].append({"eq": ["body.{}".format(key), value]}) def _prepare_teststep(self, entry_json): """ extract info from entry dict and make teststep @@ -299,7 +297,7 @@ class HarParser(object): def _prepare_config(self): """ prepare config block. """ - return {"name": "testcase description", "variables": {}} + return {"name": "testcase description", "variables": {}, "verify": False} def _prepare_teststeps(self): """ make teststep list. diff --git a/httprunner/ext/har2case/core_test.py b/httprunner/ext/har2case/core_test.py index 25fa60c4..22ec06e4 100644 --- a/httprunner/ext/har2case/core_test.py +++ b/httprunner/ext/har2case/core_test.py @@ -22,9 +22,9 @@ class TestHar(TestUtils): for validator in teststep_dict["validate"] } self.assertEqual(validators_mapping["status_code"], 200) - self.assertEqual(validators_mapping["content.IsSuccess"], True) - self.assertEqual(validators_mapping["content.Code"], 200) - self.assertEqual(validators_mapping["content.Message"], None) + self.assertEqual(validators_mapping["body.IsSuccess"], True) + self.assertEqual(validators_mapping["body.Code"], 200) + self.assertEqual(validators_mapping["body.Message"], None) def test_prepare_teststeps(self): teststeps = self.har_parser._prepare_teststeps() diff --git a/httprunner/ext/make/__init__.py b/httprunner/ext/make/__init__.py index bebbc762..7da40807 100644 --- a/httprunner/ext/make/__init__.py +++ b/httprunner/ext/make/__init__.py @@ -1,6 +1,6 @@ import os import subprocess -from typing import Union, Text, List +from typing import Union, Text, List, Tuple import jinja2 from loguru import logger @@ -10,6 +10,7 @@ from httprunner.exceptions import TestCaseFormatError from httprunner.loader import load_testcase_file, load_folder_files __TMPL__ = """# NOTICE: Generated By HttpRunner. DO'NOT EDIT! +# FROM: {{ testcase_path }} from httprunner import HttpRunner, TConfig, TStep @@ -28,6 +29,28 @@ if __name__ == "__main__": """ +def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]: + """convert single YAML/JSON testcase path to python file""" + if os.path.isdir(testcase_path): + # folder does not need to convert + return testcase_path, "" + + raw_file_name, file_suffix = os.path.splitext(os.path.basename(testcase_path)) + + file_suffix = file_suffix.lower() + if file_suffix not in [".json", ".yml", ".yaml"]: + raise exceptions.ParamsError("") + + file_name = raw_file_name.replace(" ", "_").replace(".", "_").replace("-", "_") + testcase_dir = os.path.dirname(testcase_path) + testcase_python_path = os.path.join(testcase_dir, f"{file_name}_test.py") + + # convert title case, e.g. request_with_variables => RequestWithVariables + name_in_title_case = file_name.title().replace("_", "") + + return testcase_python_path, name_in_title_case + + def make_testcase(testcase_path: str) -> Union[str, None]: logger.info(f"start to make testcase: {testcase_path}") try: @@ -37,16 +60,12 @@ def make_testcase(testcase_path: str) -> Union[str, None]: template = jinja2.Template(__TMPL__) - raw_file_name, _ = os.path.splitext(os.path.basename(testcase_path)) - # convert title case, e.g. request_with_variables => RequestWithVariables - name_in_title_case = raw_file_name.title().replace("_", "") - - testcase_dir = os.path.dirname(testcase_path) - testcase_python_path = os.path.join(testcase_dir, f"{raw_file_name}_test.py") + testcase_python_path, name_in_title_case = convert_testcase_path(testcase_path) config = testcase["config"] config["path"] = testcase_python_path data = { + "testcase_path": testcase_path, "class_name": f"TestCase{name_in_title_case}", "config": config, "teststeps": testcase["teststeps"], @@ -60,26 +79,10 @@ def make_testcase(testcase_path: str) -> Union[str, None]: return testcase_python_path -def convert_testcase_path(testcase_path: Text) -> Text: - """convert single YAML/JSON testcase path to python file""" - if os.path.isdir(testcase_path): - # folder does not need to convert - return testcase_path - - file_suffix = os.path.splitext(testcase_path)[1].lower() - if file_suffix == ".json": - return testcase_path.replace(".json", "_test.py") - elif file_suffix == ".yaml": - return testcase_path.replace(".yaml", "_test.py") - elif file_suffix == ".yml": - return testcase_path.replace(".yml", "_test.py") - else: - raise exceptions.ParamsError("") - - def format_with_black(tests_path: Text): logger.info("format testcases with black ...") - tests_path = convert_testcase_path(tests_path) + tests_path, _ = convert_testcase_path(tests_path) + try: subprocess.run(["black", tests_path]) except subprocess.CalledProcessError as ex: @@ -119,7 +122,7 @@ def init_make_parser(subparsers): """ make testcases: parse command line options and run commands. """ parser = subparsers.add_parser( - "make", help="Convert YAML/JSON testcases to Python unittests.", + "make", help="Convert YAML/JSON testcases to pytest cases.", ) parser.add_argument( "testcase_path", nargs="*", help="Specify YAML/JSON testcase file/folder path" diff --git a/httprunner/ext/make/make_test.py b/httprunner/ext/make/make_test.py index 14458bf0..44e309a6 100644 --- a/httprunner/ext/make/make_test.py +++ b/httprunner/ext/make/make_test.py @@ -1,5 +1,5 @@ import unittest -from httprunner.ext.make import make_testcase, main_make +from httprunner.ext.make import make_testcase, main_make, convert_testcase_path class TestLoader(unittest.TestCase): @@ -18,3 +18,45 @@ class TestLoader(unittest.TestCase): "examples/postman_echo/request_methods/request_with_functions_test.py", testcase_python_list, ) + + def test_convert_testcase_path(self): + self.assertEqual( + convert_testcase_path("mubu.login.yml")[0], + "mubu_login_test.py" + ) + self.assertEqual( + convert_testcase_path("/path/to/mubu.login.yml")[0], + "/path/to/mubu_login_test.py" + ) + self.assertEqual( + convert_testcase_path("/path/to 2/mubu.login.yml")[0], + "/path/to 2/mubu_login_test.py" + ) + self.assertEqual( + convert_testcase_path("/path/to 2/mubu.login.yml")[1], + "MubuLogin" + ) + self.assertEqual( + convert_testcase_path("mubu login.yml")[0], + "mubu_login_test.py" + ) + self.assertEqual( + convert_testcase_path("/path/to 2/mubu login.yml")[1], + "MubuLogin" + ) + self.assertEqual( + convert_testcase_path("/path/to 2/mubu-login.yml")[0], + "/path/to 2/mubu_login_test.py" + ) + self.assertEqual( + convert_testcase_path("/path/to 2/mubu-login.yml")[1], + "MubuLogin" + ) + self.assertEqual( + convert_testcase_path("/path/to 2/幕布login.yml")[0], + "/path/to 2/幕布login_test.py" + ) + self.assertEqual( + convert_testcase_path("/path/to/幕布login.yml")[1], + "幕布Login" + ) diff --git a/httprunner/ext/scaffold/__init__.py b/httprunner/ext/scaffold/__init__.py index 5204156e..9fe48471 100644 --- a/httprunner/ext/scaffold/__init__.py +++ b/httprunner/ext/scaffold/__init__.py @@ -37,71 +37,84 @@ def create_scaffold(project_name): msg = f"created file: {path}" logger.info(msg) - demo_api_content = """ -name: demo api -variables: - var1: value1 - var2: value2 -request: - url: /api/path/$var1 - method: POST - headers: - Content-Type: "application/json" - json: - key: $var2 -validate: - - eq: ["status_code", 200] -""" - demo_testcase_content = """ + demo_testcase_request_content = """ config: - name: "demo testcase" + name: "request methods testcase with functions" variables: - device_sn: "ABC" - username: ${ENV(USERNAME)} - password: ${ENV(PASSWORD)} - base_url: "http://127.0.0.1:5000" + foo1: session_bar1 + base_url: "https://postman-echo.com" + verify: False teststeps: - - name: demo step 1 - api: path/to/api1.yml + name: get with params variables: - user_agent: 'iOS/10.3' - device_sn: $device_sn + foo1: bar1 + foo2: session_bar2 + 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()} extract: - token: content.token + session_foo2: "body.args.foo2" validate: - eq: ["status_code", 200] + - eq: ["body.args.foo1", "session_bar1"] + - eq: ["body.args.sum_v", 3] + - eq: ["body.args.foo2", "session_bar2"] - - name: demo step 2 - api: path/to/api2.yml + name: post raw text variables: - token: $token + foo1: "hello world" + foo3: "$session_foo2" + request: + method: POST + url: /post + headers: + User-Agent: HttpRunner/${get_httprunner_version()} + Content-Type: "text/plain" + data: "This is expected to be sent back as part of response body: $foo1-$foo3." + validate: + - eq: ["status_code", 200] + - eq: ["body.data", "This is expected to be sent back as part of response body: session_bar1-session_bar2."] """ - demo_testsuite_content = """ + demo_testcase_with_ref_content = """ config: - name: "demo testsuite" + name: "request methods testcase: reference testcase" variables: - device_sn: "XYZ" - base_url: "http://127.0.0.1:5000" + foo1: session_bar1 + base_url: "https://postman-echo.com" + verify: False -testcases: +teststeps: - - name: call demo_testcase with data 1 - testcase: path/to/demo_testcase.yml + name: request with referenced testcase variables: - device_sn: $device_sn -- - name: call demo_testcase with data 2 - testcase: path/to/demo_testcase.yml - variables: - device_sn: $device_sn + foo1: override_bar1 + # NOTICE: relative testcase path based on debugtalk.py + testcase: testcases/demo_testcase_request.yml """ ignore_content = "\n".join( [".env", "reports/*", "__pycache__/*", "*.pyc", ".python-version", "logs/*"] ) - demo_debugtalk_content = """ -import time + demo_debugtalk_content = """import time + +from httprunner import __version__ + + +def get_httprunner_version(): + return __version__ + + +def sum_two(m, n): + return m + n + def sleep(n_secs): time.sleep(n_secs) @@ -109,18 +122,17 @@ def sleep(n_secs): demo_env_content = "\n".join(["USERNAME=leolee", "PASSWORD=123456"]) create_folder(project_name) - create_folder(os.path.join(project_name, "api")) + create_folder(os.path.join(project_name, "har")) create_folder(os.path.join(project_name, "testcases")) - create_folder(os.path.join(project_name, "testsuites")) create_folder(os.path.join(project_name, "reports")) - create_file(os.path.join(project_name, "api", "demo_api.yml"), demo_api_content) + create_file( - os.path.join(project_name, "testcases", "demo_testcase.yml"), - demo_testcase_content, + os.path.join(project_name, "testcases", "demo_testcase_request.yml"), + demo_testcase_request_content, ) create_file( - os.path.join(project_name, "testsuites", "demo_testsuite.yml"), - demo_testsuite_content, + os.path.join(project_name, "testcases", "demo_testcase_ref.yml"), + demo_testcase_with_ref_content, ) create_file(os.path.join(project_name, "debugtalk.py"), demo_debugtalk_content) create_file(os.path.join(project_name, ".env"), demo_env_content) diff --git a/httprunner/ext/scaffold/scaffold_test.py b/httprunner/ext/scaffold/scaffold_test.py index 9c559ae1..b85c1d48 100644 --- a/httprunner/ext/scaffold/scaffold_test.py +++ b/httprunner/ext/scaffold/scaffold_test.py @@ -1,5 +1,6 @@ import os import shutil +import subprocess import unittest from httprunner.ext.scaffold import create_scaffold @@ -9,10 +10,16 @@ class TestUtils(unittest.TestCase): def test_create_scaffold(self): project_name = "projectABC" create_scaffold(project_name) - self.assertTrue(os.path.isdir(os.path.join(project_name, "api"))) + self.assertTrue(os.path.isdir(os.path.join(project_name, "har"))) self.assertTrue(os.path.isdir(os.path.join(project_name, "testcases"))) - self.assertTrue(os.path.isdir(os.path.join(project_name, "testsuites"))) self.assertTrue(os.path.isdir(os.path.join(project_name, "reports"))) self.assertTrue(os.path.isfile(os.path.join(project_name, "debugtalk.py"))) self.assertTrue(os.path.isfile(os.path.join(project_name, ".env"))) - shutil.rmtree(project_name) + + # run demo testcases + try: + subprocess.check_call(["hrun", project_name]) + except subprocess.SubprocessError: + raise + finally: + shutil.rmtree(project_name) diff --git a/pyproject.toml b/pyproject.toml index bc4657d9..ce3b3c40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "3.0.2" +version = "3.0.3" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md"