From 1b70678bfd3de71ef5f6e57a4b31a6e8f3646617 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 2 Apr 2022 12:12:42 +0800 Subject: [PATCH] docs: add docs --- hrp/README.md | 15 +++--- hrp/session.go | 10 ++-- httprunner/README.md | 115 +++++++++++++++++++++++++++++++++++++++++++ httprunner/runner.py | 24 +++++---- 4 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 httprunner/README.md diff --git a/hrp/README.md b/hrp/README.md index e0d63e7a..31878c5f 100644 --- a/hrp/README.md +++ b/hrp/README.md @@ -1,3 +1,4 @@ +# 代码阅读指南(golang 部分) ## 核心数据结构 @@ -21,14 +22,14 @@ type IStep interface { } ``` -我们只需遵循 `IStep` 的接口定义,即可实现各种类型的测试步骤类型。当前已支持的步骤类型包括: +我们只需遵循 `IStep` 的接口定义,即可实现各种类型的测试步骤类型。当前 hrp 已支持的步骤类型包括: -- request:发起单次 HTTP 请求 -- api:引用执行其它 API 文件 -- testcase:引用执行其它测试用例文件 -- thinktime:思考时间,按照配置的逻辑进行等待 -- transaction:事务机制,用于压测 -- rendezvous:集合点机制,用于压测 +- [request](step_request.go):发起单次 HTTP 请求 +- [api](step_api.go):引用执行其它 API 文件 +- [testcase](step_testcase.go):引用执行其它测试用例文件 +- [thinktime](step_thinktime.go):思考时间,按照配置的逻辑进行等待 +- [transaction](step_transaction.go):事务机制,用于压测 +- [rendezvous](step_rendezvous.go):集合点机制,用于压测 基于该机制,我们可以扩展支持新的协议类型,例如 HTTP2/WebSocket/RPC 等;同时也可以支持新的测试类型,例如 UI 自动化。甚至我们还可以在一个测试用例中混合调用多种不同的 Step 类型,例如实现 HTTP/RPC/UI 混合场景。 diff --git a/hrp/session.go b/hrp/session.go index 78674bf2..51966bb0 100644 --- a/hrp/session.go +++ b/hrp/session.go @@ -83,7 +83,9 @@ func (r *SessionRunner) Start() error { } // update extracted variables - r.updateSession(stepResult.ExportVars) + for k, v := range stepResult.ExportVars { + r.sessionVariables[k] = v + } // update testcase summary r.updateSummary(stepResult) @@ -99,12 +101,6 @@ func (r *SessionRunner) Start() error { return nil } -func (r *SessionRunner) updateSession(vars map[string]interface{}) { - for k, v := range vars { - r.sessionVariables[k] = v - } -} - // updateSummary appends step result to summary func (r *SessionRunner) updateSummary(stepResult *StepResult) { r.summary.Records = append(r.summary.Records, stepResult) diff --git a/httprunner/README.md b/httprunner/README.md new file mode 100644 index 00000000..c6d241b2 --- /dev/null +++ b/httprunner/README.md @@ -0,0 +1,115 @@ +# 代码阅读指南(python 部分) + +## 核心数据结构 + +HttpRunner 以 `TestCase` 为核心,将任意测试场景抽象为有序步骤的集合。 + +```py +class TestCase(BaseModel): + config: TConfig + teststeps: List[TStep] +``` + +针对每种测试步骤,统一继承自 `IStep`,并要求必须至少实现如下 4 个方法;步骤内容统一在 `run` 方法中进行实现。 + +```py +class IStep(object): + + def name(self) -> str: + raise NotImplementedError + + def type(self) -> str: + raise NotImplementedError + + def struct(self) -> TStep: + raise NotImplementedError + + def run(self, runner) -> StepData: + # runner: HttpRunner + raise NotImplementedError +``` + +我们只需遵循 `IStep` 的接口定义,即可实现各种类型的测试步骤类型。当前 python 版本已支持的步骤类型包括: + +- [request](step_request.py):发起单次 HTTP 请求 +- [testcase](step_testcase.py):引用执行其它测试用例文件 + +基于该机制,我们可以扩展支持新的协议类型,例如 HTTP2/WebSocket/RPC 等;同时也可以支持新的测试类型,例如 UI 自动化。甚至我们还可以在一个测试用例中混合调用多种不同的 Step 类型,例如实现 HTTP/RPC/UI 混合场景。 + +## 用例编写 + +## 运行主流程 + +### 整体控制器 pytest + +不同于 golang 版本,python 版本的控制逻辑都基于 `pytest` 的用例发现和执行机制。 + +- 如果是运行 JSON/YAML 格式的用例,hrp 会将用例转换为 pytest 支持的用例格式 +- 如果是要自行编写 pytest 测试用例,需要遵循 HttpRunner 的格式要求 + +### pytest 用例格式要求 + +所有测试用例要求都继承自 `HttpRunner`,然后 + +结构如下所示: + +```py +class TestCaseRequestWithFunctions(HttpRunner): + + config = ( + Config("request methods testcase with functions") + ) + + teststeps = [ + Step( + RunRequest("get with params")... + ), + Step( + RunRequest("post raw text")... + ), + Step( + RunRequest("post form data")... + ), + ] +``` + +完整案例可参考: + +- [request_with_functions_test.py](../examples/postman_echo/request_methods/request_with_functions_test.py):用例中包含了 requests 的情况 +- [request_with_testcase_reference_test.py](../examples/postman_echo/request_methods/request_with_testcase_reference_test.py):用例中包含了引用其它测试用例的情况 + +### 用例执行器 SessionRunner + +测试用例的具体执行都由 `SessionRunner` 完成,每个 TestCase 对应一个实例,在该实例中除了包含测试用例自身内容外,还会包含测试过程的 session 数据和最终测试结果 summary。 + +```py +class SessionRunner(object): + config: Config + teststeps: List[object] # list of Step + ... +``` + +重点关注一个方法: + +- test_start:该方法将被 pytest 发现,作为启动执行入口,依次执行所有测试步骤 + +```go +def test_start(self, param: Dict = None) -> "SessionRunner": + """main entrance, discovered by pytest""" + self.__start_at = time.time() + try: + # run step in sequential order + for step in self.teststeps: + self.__run_step(step) + finally: + logger.info(f"generate testcase log: {self.__log_path}") + + self.__duration = time.time() - self.__start_at +``` + +在主流程中,SessionRunner 并不需要关注 step 的具体类型,统一都是调用 `step.run(self)`,具体实现逻辑都在对应 step 的 `run` 方法中。 + +```py +def run(self, runner: HttpRunner) -> StepData: + return self.__step.run(runner) +``` diff --git a/httprunner/runner.py b/httprunner/runner.py index 24b40d0c..6100692a 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -16,15 +16,14 @@ from loguru import logger from httprunner.client import HttpSession from httprunner.config import Config from httprunner.exceptions import ParamsError -from httprunner.loader import load_project_meta, load_testcase_file -from httprunner.models import (ProjectMeta, StepData, TConfig, TestCase, - TestCaseInOut, TestCaseSummary, TestCaseTime, - VariablesMapping) +from httprunner.loader import load_project_meta +from httprunner.models import (ProjectMeta, StepData, TConfig, TestCaseInOut, + TestCaseSummary, TestCaseTime, VariablesMapping) from httprunner.parser import Parser from httprunner.utils import merge_variables -class HttpRunner(object): +class SessionRunner(object): config: Config teststeps: List[object] # list of Step @@ -63,22 +62,22 @@ class HttpRunner(object): self.session = self.session or HttpSession() self.parser = self.parser or Parser(self.__project_meta.functions) - def with_session(self, session: HttpSession) -> "HttpRunner": + def with_session(self, session: HttpSession) -> "SessionRunner": self.session = session return self def get_config(self) -> TConfig: return self.__config - def with_case_id(self, case_id: Text) -> "HttpRunner": + def with_case_id(self, case_id: Text) -> "SessionRunner": self.case_id = case_id return self - def with_variables(self, variables: VariablesMapping) -> "HttpRunner": + def with_variables(self, variables: VariablesMapping) -> "SessionRunner": self.__session_variables = variables return self - def with_export(self, export: List[Text]) -> "HttpRunner": + def with_export(self, export: List[Text]) -> "SessionRunner": self.__export = export return self @@ -176,7 +175,7 @@ class HttpRunner(object): logger.info(f"run step end: {step.name()} <<<<<<\n") - def test_start(self, param: Dict = None) -> "HttpRunner": + def test_start(self, param: Dict = None) -> "SessionRunner": """main entrance, discovered by pytest""" self.__init() self.__parse_config(param) @@ -202,3 +201,8 @@ class HttpRunner(object): self.__duration = time.time() - self.__start_at return self + + +class HttpRunner(SessionRunner): + # split SessionRunner to keep consistant with golang version + pass