docs: add docs

This commit is contained in:
debugtalk
2022-04-02 12:12:42 +08:00
parent acd85136dd
commit a0963ce413
4 changed files with 140 additions and 24 deletions

View File

@@ -1,3 +1,4 @@
# 代码阅读指南golang 部分)
## 核心数据结构 ## 核心数据结构
@@ -21,14 +22,14 @@ type IStep interface {
} }
``` ```
我们只需遵循 `IStep` 的接口定义,即可实现各种类型的测试步骤类型。当前已支持的步骤类型包括: 我们只需遵循 `IStep` 的接口定义,即可实现各种类型的测试步骤类型。当前 hrp 已支持的步骤类型包括:
- request发起单次 HTTP 请求 - [request](step_request.go):发起单次 HTTP 请求
- api引用执行其它 API 文件 - [api](step_api.go):引用执行其它 API 文件
- testcase引用执行其它测试用例文件 - [testcase](step_testcase.go):引用执行其它测试用例文件
- thinktime思考时间按照配置的逻辑进行等待 - [thinktime](step_thinktime.go):思考时间,按照配置的逻辑进行等待
- transaction事务机制用于压测 - [transaction](step_transaction.go):事务机制,用于压测
- rendezvous集合点机制用于压测 - [rendezvous](step_rendezvous.go):集合点机制,用于压测
基于该机制,我们可以扩展支持新的协议类型,例如 HTTP2/WebSocket/RPC 等;同时也可以支持新的测试类型,例如 UI 自动化。甚至我们还可以在一个测试用例中混合调用多种不同的 Step 类型,例如实现 HTTP/RPC/UI 混合场景。 基于该机制,我们可以扩展支持新的协议类型,例如 HTTP2/WebSocket/RPC 等;同时也可以支持新的测试类型,例如 UI 自动化。甚至我们还可以在一个测试用例中混合调用多种不同的 Step 类型,例如实现 HTTP/RPC/UI 混合场景。

View File

@@ -83,7 +83,9 @@ func (r *SessionRunner) Start() error {
} }
// update extracted variables // update extracted variables
r.updateSession(stepResult.ExportVars) for k, v := range stepResult.ExportVars {
r.sessionVariables[k] = v
}
// update testcase summary // update testcase summary
r.updateSummary(stepResult) r.updateSummary(stepResult)
@@ -99,12 +101,6 @@ func (r *SessionRunner) Start() error {
return nil 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 // updateSummary appends step result to summary
func (r *SessionRunner) updateSummary(stepResult *StepResult) { func (r *SessionRunner) updateSummary(stepResult *StepResult) {
r.summary.Records = append(r.summary.Records, stepResult) r.summary.Records = append(r.summary.Records, stepResult)

115
httprunner/README.md Normal file
View File

@@ -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)
```

View File

@@ -16,15 +16,14 @@ from loguru import logger
from httprunner.client import HttpSession from httprunner.client import HttpSession
from httprunner.config import Config from httprunner.config import Config
from httprunner.exceptions import ParamsError from httprunner.exceptions import ParamsError
from httprunner.loader import load_project_meta, load_testcase_file from httprunner.loader import load_project_meta
from httprunner.models import (ProjectMeta, StepData, TConfig, TestCase, from httprunner.models import (ProjectMeta, StepData, TConfig, TestCaseInOut,
TestCaseInOut, TestCaseSummary, TestCaseTime, TestCaseSummary, TestCaseTime, VariablesMapping)
VariablesMapping)
from httprunner.parser import Parser from httprunner.parser import Parser
from httprunner.utils import merge_variables from httprunner.utils import merge_variables
class HttpRunner(object): class SessionRunner(object):
config: Config config: Config
teststeps: List[object] # list of Step teststeps: List[object] # list of Step
@@ -63,22 +62,22 @@ class HttpRunner(object):
self.session = self.session or HttpSession() self.session = self.session or HttpSession()
self.parser = self.parser or Parser(self.__project_meta.functions) 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 self.session = session
return self return self
def get_config(self) -> TConfig: def get_config(self) -> TConfig:
return self.__config 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 self.case_id = case_id
return self return self
def with_variables(self, variables: VariablesMapping) -> "HttpRunner": def with_variables(self, variables: VariablesMapping) -> "SessionRunner":
self.__session_variables = variables self.__session_variables = variables
return self return self
def with_export(self, export: List[Text]) -> "HttpRunner": def with_export(self, export: List[Text]) -> "SessionRunner":
self.__export = export self.__export = export
return self return self
@@ -176,7 +175,7 @@ class HttpRunner(object):
logger.info(f"run step end: {step.name()} <<<<<<\n") 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""" """main entrance, discovered by pytest"""
self.__init() self.__init()
self.__parse_config(param) self.__parse_config(param)
@@ -202,3 +201,8 @@ class HttpRunner(object):
self.__duration = time.time() - self.__start_at self.__duration = time.time() - self.__start_at
return self return self
class HttpRunner(SessionRunner):
# split SessionRunner to keep consistant with golang version
pass