mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-31 13:29:35 +08:00
docs: add docs
This commit is contained in:
@@ -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 混合场景。
|
||||||
|
|
||||||
|
|||||||
@@ -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
115
httprunner/README.md
Normal 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)
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user