diff --git a/examples/postman_echo/request_methods/conf.py b/examples/postman_echo/request_methods/conf.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/postman_echo/request_methods/hardcode_test.py b/examples/postman_echo/request_methods/hardcode_test.py index c23d1fc1..1b4a535a 100644 --- a/examples/postman_echo/request_methods/hardcode_test.py +++ b/examples/postman_echo/request_methods/hardcode_test.py @@ -1,83 +1,79 @@ +# NOTICE: Generated By HttpRunner. DO'NOT EDIT! +import unittest + from httprunner.runner import TestCaseRunner from httprunner.schema import TestsConfig, TestStep -class TestCaseRequestMethodsHardcode(TestCaseRunner): - config = TestsConfig(**{ - "name": "request methods testcase in hardcode", - "base_url": "https://postman-echo.com", - "verify": False - }) +class TestCaseHardcode(unittest.TestCase): + config = TestsConfig( + **{ + "name": "request methods testcase in hardcode", + "base_url": "https://postman-echo.com", + "verify": False, + "path": "examples/postman_echo/request_methods/hardcode_test.py", + } + ) teststeps = [ - TestStep(**{ - "name": "get with params", - "request": { - "method": "GET", - "url": "/get", - "params": { - "foo1": "bar1", - "foo2": "bar2" + TestStep( + **{ + "name": "get with params", + "request": { + "method": "GET", + "url": "/get", + "params": {"foo1": "bar1", "foo2": "bar2"}, + "headers": {"User-Agent": "HttpRunner/3.0"}, }, - "headers": { - "User-Agent": "HttpRunner/3.0" - } - }, - "extract": { - "server": "headers.Server" - }, - "validate": [ - {"eq": ["status_code", 200]}, - {"eq": ["headers.Server", "nginx"]} - ] - }), - TestStep(**{ - "name": "post raw text", - "request": { - "method": "POST", - "url": "/post", - "data": "This is expected to be sent back as part of response body.", - "headers": { - "User-Agent": "HttpRunner/3.0", - "Content-Type": "text/plain" - } - }, - "validate": [ - {"eq": ["status_code", 200]} - ] - }), - TestStep(**{ - "name": "post form data", - "request": { - "method": "POST", - "url": "/post", - "data": "foo1=bar1&foo2=bar2", - "headers": { - "User-Agent": "HttpRunner/3.0", - "Content-Type": "application/x-www-form-urlencoded" - } - }, - "validate": [ - {"eq": ["status_code", 200]} - ] - }), - TestStep(**{ - "name": "put request", - "request": { - "method": "PUT", - "url": "/put", - "data": "This is expected to be sent back as part of response body.", - "headers": { - "User-Agent": "HttpRunner/3.0", - "Content-Type": "text/plain" - } - }, - "validate": [ - {"eq": ["status_code", 200]} - ] - }) + "validate": [{"eq": ["status_code", 200]}], + } + ), + TestStep( + **{ + "name": "post raw text", + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "HttpRunner/3.0", + "Content-Type": "text/plain", + }, + "data": "This is expected to be sent back as part of response body.", + }, + "validate": [{"eq": ["status_code", 200]}], + } + ), + TestStep( + **{ + "name": "post form data", + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "HttpRunner/3.0", + "Content-Type": "application/x-www-form-urlencoded", + }, + "data": "foo1=bar1&foo2=bar2", + }, + "validate": [{"eq": ["status_code", 200]}], + } + ), + TestStep( + **{ + "name": "put request", + "request": { + "method": "PUT", + "url": "/put", + "headers": { + "User-Agent": "HttpRunner/3.0", + "Content-Type": "text/plain", + }, + "data": "This is expected to be sent back as part of response body.", + }, + "validate": [{"eq": ["status_code", 200]}], + } + ), ] - -if __name__ == '__main__': - TestCaseRequestMethodsHardcode().run() + def test_start(self): + TestCaseRunner(self.config, self.teststeps).run() 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 66c73d66..d45fd718 100644 --- a/examples/postman_echo/request_methods/request_with_functions_test.py +++ b/examples/postman_echo/request_methods/request_with_functions_test.py @@ -1,98 +1,90 @@ +# NOTICE: Generated By HttpRunner. DO'NOT EDIT! +import unittest + from httprunner.runner import TestCaseRunner from httprunner.schema import TestsConfig, TestStep -from examples.postman_echo import debugtalk -class TestCaseRequestMethodsWithFunctions(TestCaseRunner): - config = TestsConfig(**{ - "name": "request methods testcase with functions", - "variables": { - "foo1": "session_bar1" - }, - "functions": { - "get_httprunner_version": debugtalk.get_httprunner_version, - "sum_two": debugtalk.sum_two - }, - "base_url": "https://postman-echo.com", - "verify": False - }) +class TestCaseRequestWithFunctions(unittest.TestCase): + config = TestsConfig( + **{ + "name": "request methods testcase with functions", + "variables": {"foo1": "session_bar1"}, + "base_url": "https://postman-echo.com", + "verify": False, + "path": "examples/postman_echo/request_methods/request_with_functions_test.py", + } + ) teststeps = [ - TestStep(**{ - "name": "get with params", - "variables": { - "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" + TestStep( + **{ + "name": "get with params", + "variables": { + "foo1": "bar1", + "foo2": "session_bar2", + "sum_v": "${sum_two(1, 2)}", }, - "headers": { - "User-Agent": "HttpRunner/${get_httprunner_version()}" - } - }, - "extract": { - "session_foo2": "body.args.foo2" - }, - "validate": [ - {"eq": ["status_code", 200]}, - {"eq": ["body.args.foo1", "session_bar1"]}, - {"eq": ["body.args.foo2", "session_bar2"]}, - {"eq": ["body.args.sum_v", 3]} - ] - }), - TestStep(**{ - "name": "post raw text", - "variables": { - "foo1": "hello world", - "foo3": "$session_foo2" - }, - "request": { - "method": "POST", - "url": "/post", - "data": "This is expected to be sent back as part of response body: $foo1-$foo3.", - "headers": { - "User-Agent": "HttpRunner/${get_httprunner_version()}", - "Content-Type": "text/plain" - } - }, - "validate": [ - {"eq": ["status_code", 200]}, - {"eq": [ - "body.data", - "This is expected to be sent back as part of response body: session_bar1-session_bar2." - ]}, - ] - }), - TestStep(**{ - "name": "post form data", - "variables": { - "foo1": "session_bar1", - "foo2": "bar2" - }, - "request": { - "method": "POST", - "url": "/post", - "data": "foo1=$foo1&foo2=$foo2", - "headers": { - "User-Agent": "HttpRunner/${get_httprunner_version()}", - "Content-Type": "application/x-www-form-urlencoded" - } - }, - "validate": [ - {"eq": ["status_code", 200]}, - {"eq": ["body.form.foo1", "session_bar1"]}, - {"eq": ["body.form.foo2", "bar2"]} - ] - }) + "request": { + "method": "GET", + "url": "/get", + "params": {"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}, + "headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"}, + }, + "extract": {"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"]}, + ], + } + ), + TestStep( + **{ + "name": "post raw text", + "variables": {"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.", + ] + }, + ], + } + ), + TestStep( + **{ + "name": "post form data", + "variables": {"foo1": "bar1", "foo2": "bar2"}, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "HttpRunner/${get_httprunner_version()}", + "Content-Type": "application/x-www-form-urlencoded", + }, + "data": "foo1=$foo1&foo2=$foo2", + }, + "validate": [ + {"eq": ["status_code", 200]}, + {"eq": ["body.form.foo1", "session_bar1"]}, + {"eq": ["body.form.foo2", "bar2"]}, + ], + } + ), ] - -if __name__ == '__main__': - TestCaseRequestMethodsWithFunctions().run() + def test_start(self): + TestCaseRunner(self.config, self.teststeps).run() 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 f33a82b5..7ec814a4 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,34 +1,30 @@ -from examples.postman_echo import debugtalk -from examples.postman_echo.request_methods.validate_with_variables_test \ - import TestCaseRequestMethodsValidateWithVariables +# NOTICE: Generated By HttpRunner. DO'NOT EDIT! +import unittest + from httprunner.runner import TestCaseRunner from httprunner.schema import TestsConfig, TestStep -class TestCaseRequestMethodsRefTestcase(TestCaseRunner): - config = TestsConfig(**{ - "name": "request methods testcase: reference testcase", - "variables": { - "foo1": "session_bar1" - }, - "functions": { - "get_httprunner_version": debugtalk.get_httprunner_version, - "sum_two": debugtalk.sum_two - }, - "base_url": "https://postman-echo.com", - "verify": False - }) +class TestCaseRequestWithTestcaseReference(unittest.TestCase): + config = TestsConfig( + **{ + "name": "request methods testcase: reference testcase", + "variables": {"foo1": "session_bar1"}, + "base_url": "https://postman-echo.com", + "verify": False, + "path": "examples/postman_echo/request_methods/request_with_testcase_reference_test.py", + } + ) teststeps = [ - TestStep(**{ - "name": "get with params", - "variables": { - "foo1": "override_bar1" - }, - "testcase": TestCaseRequestMethodsValidateWithVariables - }) + TestStep( + **{ + "name": "request with variables", + "variables": {"foo1": "override_bar1"}, + "testcase": "request_methods/request_with_variables.yml", + } + ), ] - -if __name__ == '__main__': - TestCaseRequestMethodsRefTestcase().run() + def test_start(self): + TestCaseRunner(self.config, self.teststeps).run() 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 9fdfbcd4..b351e92b 100644 --- a/examples/postman_echo/request_methods/request_with_variables_test.py +++ b/examples/postman_echo/request_methods/request_with_variables_test.py @@ -1,14 +1,18 @@ +# NOTICE: Generated By HttpRunner. DO'NOT EDIT! +import unittest + from httprunner.runner import TestCaseRunner from httprunner.schema import TestsConfig, TestStep -class TestCaseRequestWithVariables(TestCaseRunner): +class TestCaseRequestWithVariables(unittest.TestCase): config = TestsConfig( **{ "name": "request methods testcase with variables", "variables": {"foo1": "session_bar1"}, "base_url": "https://postman-echo.com", "verify": False, + "path": "examples/postman_echo/request_methods/request_with_variables_test.py", } ) @@ -77,6 +81,5 @@ class TestCaseRequestWithVariables(TestCaseRunner): ), ] - -if __name__ == "__main__": - TestCaseRequestWithVariables().run() + def test_start(self): + TestCaseRunner(self.config, self.teststeps).run() 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 a580040c..82b7d454 100644 --- a/examples/postman_echo/request_methods/validate_with_functions_test.py +++ b/examples/postman_echo/request_methods/validate_with_functions_test.py @@ -1,53 +1,45 @@ +# NOTICE: Generated By HttpRunner. DO'NOT EDIT! +import unittest + from httprunner.runner import TestCaseRunner from httprunner.schema import TestsConfig, TestStep -from examples.postman_echo import debugtalk -class TestCaseRequestMethodsValidateWithFunctions(TestCaseRunner): - config = TestsConfig(**{ - "name": "request methods testcase: validate with functions", - "variables": { - "foo1": "session_bar1" - }, - "functions": { - "get_httprunner_version": debugtalk.get_httprunner_version, - "sum_two": debugtalk.sum_two - }, - "base_url": "https://postman-echo.com", - "verify": False - }) +class TestCaseValidateWithFunctions(unittest.TestCase): + config = TestsConfig( + **{ + "name": "request methods testcase: validate with functions", + "variables": {"foo1": "session_bar1"}, + "base_url": "https://postman-echo.com", + "verify": False, + "path": "examples/postman_echo/request_methods/validate_with_functions_test.py", + } + ) teststeps = [ - TestStep(**{ - "name": "get with params", - "variables": { - "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" + TestStep( + **{ + "name": "get with params", + "variables": { + "foo1": "bar1", + "foo2": "session_bar2", + "sum_v": "${sum_two(1, 2)}", }, - "headers": { - "User-Agent": "HttpRunner/${get_httprunner_version()}" - } - }, - "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)}"]} - ] - }) + "request": { + "method": "GET", + "url": "/get", + "params": {"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}, + "headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"}, + }, + "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)}"]}, + ], + } + ), ] - -if __name__ == '__main__': - TestCaseRequestMethodsValidateWithFunctions().run() + def test_start(self): + TestCaseRunner(self.config, self.teststeps).run() 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 6db6c6e0..0293766c 100644 --- a/examples/postman_echo/request_methods/validate_with_variables_test.py +++ b/examples/postman_echo/request_methods/validate_with_variables_test.py @@ -1,99 +1,85 @@ +# NOTICE: Generated By HttpRunner. DO'NOT EDIT! +import unittest + from httprunner.runner import TestCaseRunner from httprunner.schema import TestsConfig, TestStep -class TestCaseRequestMethodsValidateWithVariables(TestCaseRunner): - config = TestsConfig(**{ - "name": "request methods testcase: validate with variables", - "variables": { - "foo1": "session_bar1" - }, - "base_url": "https://postman-echo.com", - "verify": False - }) +class TestCaseValidateWithVariables(unittest.TestCase): + config = TestsConfig( + **{ + "name": "request methods testcase: validate with variables", + "variables": {"foo1": "session_bar1"}, + "base_url": "https://postman-echo.com", + "verify": False, + "path": "examples/postman_echo/request_methods/validate_with_variables_test.py", + } + ) teststeps = [ - TestStep(**{ - "name": "get with params", - "variables": { - "foo1": "bar1", - "foo2": "session_bar2" - }, - "request": { - "method": "GET", - "url": "/get", - "params": { - "foo1": "$foo1", - "foo2": "$foo2" + TestStep( + **{ + "name": "get with params", + "variables": {"foo1": "bar1", "foo2": "session_bar2"}, + "request": { + "method": "GET", + "url": "/get", + "params": {"foo1": "$foo1", "foo2": "$foo2"}, + "headers": {"User-Agent": "HttpRunner/3.0"}, }, - "headers": { - "User-Agent": "HttpRunner/3.0" - } - }, - "extract": { - "session_foo2": "body.args.foo2" - }, - "validate": [ - {"eq": ["status_code", 200]}, - {"eq": ["body.args.foo1", "session_bar1"]}, - {"eq": ["body.args.foo1", "$foo1"]}, - {"eq": ["body.args.foo2", "session_bar2"]}, - {"eq": ["body.args.foo2", "$foo2"]} - ] - }), - TestStep(**{ - "name": "post raw text", - "variables": { - "foo1": "hello world", - "foo3": "$session_foo2" - }, - "request": { - "method": "POST", - "url": "/post", - "data": "This is expected to be sent back as part of response body: $foo1-$foo3.", - "headers": { - "User-Agent": "HttpRunner/3.0", - "Content-Type": "text/plain" - } - }, - "validate": [ - {"eq": ["status_code", 200]}, - {"eq": [ - "body.data", - "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: $foo1-$foo3." - ]}, - ] - }), - TestStep(**{ - "name": "post form data", - "variables": { - "foo1": "session_bar1", - "foo2": "bar2" - }, - "request": { - "method": "POST", - "url": "/post", - "data": "foo1=$foo1&foo2=$foo2", - "headers": { - "User-Agent": "HttpRunner/3.0", - "Content-Type": "application/x-www-form-urlencoded" - } - }, - "validate": [ - {"eq": ["status_code", 200]}, - {"eq": ["body.form.foo1", "session_bar1"]}, - {"eq": ["body.form.foo1", "$foo1"]}, - {"eq": ["body.form.foo2", "bar2"]}, - {"eq": ["body.form.foo2", "$foo2"]} - ] - }) + "extract": {"session_foo2": "body.args.foo2"}, + "validate": [ + {"eq": ["status_code", 200]}, + {"eq": ["body.args.foo1", "$foo1"]}, + {"eq": ["body.args.foo2", "$foo2"]}, + ], + } + ), + TestStep( + **{ + "name": "post raw text", + "variables": {"foo1": "hello world", "foo3": "$session_foo2"}, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "HttpRunner/3.0", + "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-$foo3.", + ] + }, + ], + } + ), + TestStep( + **{ + "name": "post form data", + "variables": {"foo1": "bar1", "foo2": "bar2"}, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "HttpRunner/3.0", + "Content-Type": "application/x-www-form-urlencoded", + }, + "data": "foo1=$foo1&foo2=$foo2", + }, + "validate": [ + {"eq": ["status_code", 200]}, + {"eq": ["body.form.foo1", "$foo1"]}, + {"eq": ["body.form.foo2", "$foo2"]}, + ], + } + ), ] - -if __name__ == '__main__': - runner = TestCaseRequestMethodsValidateWithVariables().run() - print(runner.step_datas) + def test_start(self): + TestCaseRunner(self.config, self.teststeps).run() diff --git a/httprunner/api.py b/httprunner/api.py index 40569576..5f615e44 100644 --- a/httprunner/api.py +++ b/httprunner/api.py @@ -75,7 +75,7 @@ class HttpRunner(object): testcase.config.variables.update(project_meta.variables) testcase.config.functions.update(project_meta.functions) - test_runner = TestCaseRunner().init(testcase) + test_runner = TestCaseRunner(testcase.config, testcase.teststeps) TestSequense = type("TestSequense", (unittest.TestCase,), {}) test_method = _add_test(test_runner) diff --git a/httprunner/make.py b/httprunner/make.py index 0e8d838d..7d7efe12 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -3,14 +3,17 @@ import os import jinja2 from loguru import logger -from httprunner.new_loader import load_testcase_file +from httprunner import exceptions +from httprunner.new_loader import load_testcase_file, load_folder_files + +__TMPL__ = """# NOTICE: Generated By HttpRunner. DO'NOT EDIT! +import unittest -__TMPL__ = """ from httprunner.runner import TestCaseRunner from httprunner.schema import TestsConfig, TestStep -class {{ class_name }}(TestCaseRunner): +class {{ class_name }}(unittest.TestCase): config = TestsConfig(**{{ config }}) teststeps = [ @@ -19,31 +22,50 @@ class {{ class_name }}(TestCaseRunner): {% endfor %} ] + def test_start(self): + TestCaseRunner(self.config, self.teststeps).run() -if __name__ == '__main__': - {{ class_name }}().run() """ -def make_testcase(path: str) -> str: - testcase = load_testcase_file(path) +def make_testcase(testcase_path: str) -> str: + testcase = load_testcase_file(testcase_path) template = jinja2.Template(__TMPL__) - raw_file_name, _ = os.path.splitext(os.path.basename(path)) + 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") + + config = testcase["config"] + config["path"] = testcase_python_path data = { "class_name": f"TestCase{name_in_title_case}", - "config": testcase["config"], + "config": config, "teststeps": testcase["teststeps"], } content = template.render(data) - testcase_dir = os.path.dirname(path) - testcase_python_path = os.path.join(testcase_dir, f"{raw_file_name}_test.py") with open(testcase_python_path, "w") as f: f.write(content) logger.info(f"generated testcase: {testcase_python_path}") return testcase_python_path + + +def make(tests_path: str) -> list: + testcases = [] + if os.path.isdir(tests_path): + files_list = load_folder_files(tests_path) + testcases.extend(files_list) + elif os.path.isfile(tests_path): + testcases.append(tests_path) + else: + raise exceptions.TestcaseNotFound(f"Invalid tests path: {tests_path}") + + return [ + make_testcase(testcase_path) + for testcase_path in testcases + ] diff --git a/httprunner/make_test.py b/httprunner/make_test.py new file mode 100644 index 00000000..9fd9faf7 --- /dev/null +++ b/httprunner/make_test.py @@ -0,0 +1,21 @@ +import unittest +from httprunner.make import make_testcase, make + + +class TestLoader(unittest.TestCase): + + def test_make_testcase(self): + path = "examples/postman_echo/request_methods/request_with_variables.yml" + testcase_python_path = make_testcase(path) + self.assertEqual( + testcase_python_path, + "examples/postman_echo/request_methods/request_with_variables_test.py" + ) + + def test_make_testcase_folder(self): + path = "examples/postman_echo/request_methods/" + testcase_python_list = make(path) + self.assertIn( + "examples/postman_echo/request_methods/request_with_functions_test.py", + testcase_python_list + ) diff --git a/httprunner/new_loader.py b/httprunner/new_loader.py index f7efc1cc..b42b8395 100644 --- a/httprunner/new_loader.py +++ b/httprunner/new_loader.py @@ -1,12 +1,14 @@ +import importlib import io import json import os +import sys import types import yaml from loguru import logger -from httprunner import builtin +from httprunner import builtin, utils from httprunner import exceptions from httprunner.schema import TestCase @@ -64,6 +66,47 @@ def load_testcase_file(testcase_file): return testcase_content +def load_dot_env_file(dot_env_path): + """ load .env file. + + Args: + dot_env_path (str): .env file path + + Returns: + dict: environment variables mapping + + { + "UserName": "debugtalk", + "Password": "123456", + "PROJECT_KEY": "ABCDEFGH" + } + + Raises: + exceptions.FileFormatError: If .env file format is invalid. + + """ + if not os.path.isfile(dot_env_path): + return {} + + logger.info(f"Loading environment variables from {dot_env_path}") + env_variables_mapping = {} + + with io.open(dot_env_path, "r", encoding="utf-8") as fp: + for line in fp: + # maxsplit=1 + if "=" in line: + variable, value = line.split("=", 1) + elif ":" in line: + variable, value = line.split(":", 1) + else: + raise exceptions.FileFormatError(".env format error") + + env_variables_mapping[variable.strip()] = value.strip() + + utils.set_os_environ(env_variables_mapping) + return env_variables_mapping + + def load_folder_files(folder_path, recursive=True): """ load folder path, return all files endswith yml/yaml/json in list. @@ -133,3 +176,169 @@ def load_builtin_functions(): """ load builtin module functions """ return load_module_functions(builtin) + + +def locate_file(start_path, file_name): + """ locate filename and return absolute file path. + searching will be recursive upward until current working directory or system root dir. + + Args: + file_name (str): target locate file name + start_path (str): start locating path, maybe file path or directory path + + Returns: + str: located file path. None if file not found. + + Raises: + exceptions.FileNotFound: If failed to locate file. + + """ + if os.path.isfile(start_path): + start_dir_path = os.path.dirname(start_path) + elif os.path.isdir(start_path): + start_dir_path = start_path + else: + raise exceptions.FileNotFound(f"invalid path: {start_path}") + + file_path = os.path.join(start_dir_path, file_name) + if os.path.isfile(file_path): + return os.path.abspath(file_path) + + # current working directory + if os.path.abspath(start_dir_path) == os.getcwd(): + raise exceptions.FileNotFound(f"{file_name} not found in {start_path}") + + # system root dir + # Windows, e.g. 'E:\\' + # Linux/Darwin, '/' + parent_dir = os.path.dirname(start_dir_path) + if parent_dir == start_dir_path: + raise exceptions.FileNotFound(f"{file_name} not found in {start_path}") + + # locate recursive upward + return locate_file(parent_dir, file_name) + + +def locate_debugtalk_py(start_path): + """ locate debugtalk.py file + + Args: + start_path (str): start locating path, + maybe testcase file path or directory path + + Returns: + str: debugtalk.py file path, None if not found + + """ + try: + # locate debugtalk.py file. + debugtalk_path = locate_file(start_path, "debugtalk.py") + except exceptions.FileNotFound: + debugtalk_path = None + + return debugtalk_path + + +def init_project_working_directory(test_path): + """ this should be called at startup + + run test file: + run_path -> load_cases -> load_project_data -> init_project_working_directory + or run passed in data structure: + run -> init_project_working_directory + + Args: + test_path: specified testfile path + + Returns: + (str, str): debugtalk.py path, project_working_directory + + """ + + def prepare_path(path): + if not os.path.exists(path): + err_msg = f"path not exist: {path}" + logger.error(err_msg) + raise exceptions.FileNotFound(err_msg) + + if not os.path.isabs(path): + path = os.path.join(os.getcwd(), path) + + return path + + test_path = prepare_path(test_path) + + # locate debugtalk.py file + debugtalk_path = locate_debugtalk_py(test_path) + + global project_working_directory + if debugtalk_path: + # The folder contains debugtalk.py will be treated as PWD. + project_working_directory = os.path.dirname(debugtalk_path) + else: + # debugtalk.py not found, use os.getcwd() as PWD. + project_working_directory = os.getcwd() + + # add PWD to sys.path + sys.path.insert(0, project_working_directory) + + return debugtalk_path, project_working_directory + + +def load_debugtalk_functions(): + """ load project debugtalk.py module functions + debugtalk.py should be located in project working directory. + + Returns: + dict: debugtalk module functions mapping + { + "func1_name": func1, + "func2_name": func2 + } + + """ + # load debugtalk.py module + imported_module = importlib.import_module("debugtalk") + return load_module_functions(imported_module) + + +def load_project_data(test_path: str, dot_env_path: str = None): + """ load api, testcases, .env, debugtalk.py functions. + api/testcases folder is relative to project_working_directory + + Args: + test_path (str): test file/folder path, locate pwd from this path. + dot_env_path (str): specified .env file path + + Returns: + dict: project loaded api/testcases definitions, + environments and debugtalk.py functions. + + """ + debugtalk_path, project_working_directory = init_project_working_directory( + test_path + ) + + project_meta = {} + + # load .env file + # NOTICE: + # environment variable maybe loaded in debugtalk.py + # thus .env file should be loaded before loading debugtalk.py + dot_env_path = dot_env_path or os.path.join(project_working_directory, ".env") + project_meta["env"] = load_dot_env_file(dot_env_path) + + if debugtalk_path: + # load debugtalk.py functions + debugtalk_functions = load_debugtalk_functions() + else: + debugtalk_functions = {} + + # locate PWD and load debugtalk.py functions + project_meta["PWD"] = project_working_directory + project_meta["functions"] = debugtalk_functions + project_meta["test_path"] = os.path.abspath(test_path)[ + len(project_working_directory) + 1 : + ] + + return project_meta diff --git a/httprunner/runner.py b/httprunner/runner.py index 828747f4..bbeee279 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -2,38 +2,30 @@ from typing import List, Dict from loguru import logger -from httprunner import utils +from httprunner import utils, exceptions from httprunner.client import HttpSession from httprunner.exceptions import ValidationFailure, ParamsError +from httprunner.new_loader import load_project_data from httprunner.parser import build_url, parse_data, parse_variables_mapping from httprunner.response import ResponseObject from httprunner.schema import ( TestsConfig, TestStep, VariablesMapping, - TestCase, StepData, ) class TestCaseRunner(object): - config: TestsConfig = {} - teststeps: List[TestStep] = [] - session: HttpSession = None - step_datas: List[StepData] = [] - validation_results: Dict = {} - session_variables: Dict = {} - success: bool = True # indicate testcase execution result - - def init(self, testcase: TestCase) -> "TestCaseRunner": - self.config = testcase.config - self.teststeps = testcase.teststeps - return self - - def with_session(self, s: HttpSession) -> "TestCaseRunner": - self.session = s - return self + def __init__(self, config: TestsConfig, teststeps: List[TestStep], session: HttpSession = None): + self.config = config + self.teststeps = teststeps + self.session = session + self.step_datas: List[StepData] = [] + self.validation_results: Dict = {} + self.session_variables: Dict = {} + self.success: bool = True # indicate testcase execution result def with_variables(self, **variables: VariablesMapping) -> "TestCaseRunner": self.config.variables.update(variables) @@ -142,8 +134,14 @@ class TestCaseRunner(object): self.step_datas.append(step_data) return step_data.export - def test_start(self): + def run(self): """main entrance""" + if not self.config.path: + raise exceptions.ParamsError("config path missed!") + + project_data = load_project_data(self.config.path) + self.config.functions = project_data["functions"] + self.step_datas.clear() self.session_variables.clear() for step in self.teststeps: @@ -162,10 +160,6 @@ class TestCaseRunner(object): return self - def run(self): - """main entrance alias for test_start""" - return self.test_start() - def get_export_variables(self): export_vars_mapping = {} for var_name in self.config.export: diff --git a/httprunner/schema.py b/httprunner/schema.py index 58078094..0130f769 100644 --- a/httprunner/schema.py +++ b/httprunner/schema.py @@ -40,6 +40,7 @@ class TestsConfig(BaseModel): setup_hooks: Hook = [] teardown_hooks: Hook = [] export: Export = [] + path: Text = None class Request(BaseModel):