mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 23:51:25 +08:00
docs: update dev instruct
This commit is contained in:
+226
-34
@@ -34,8 +34,13 @@ type IStep interface {
|
|||||||
- [thinktime](step_thinktime.go):思考时间,按照配置的逻辑进行等待
|
- [thinktime](step_thinktime.go):思考时间,按照配置的逻辑进行等待
|
||||||
- [transaction](step_transaction.go):事务机制,用于压测
|
- [transaction](step_transaction.go):事务机制,用于压测
|
||||||
- [rendezvous](step_rendezvous.go):集合点机制,用于压测
|
- [rendezvous](step_rendezvous.go):集合点机制,用于压测
|
||||||
- [mobile_UI](step_mobile_ui.go):移动端 UI 自动化
|
- [websocket](step_websocket.go):WebSocket 通信
|
||||||
|
- [android](step_ui.go):Android UI 自动化
|
||||||
|
- [ios](step_ui.go):iOS UI 自动化
|
||||||
|
- [harmony](step_ui.go):Harmony UI 自动化
|
||||||
|
- [browser](step_ui.go):浏览器 UI 自动化
|
||||||
- [shell](step_shell.go):执行 shell 命令
|
- [shell](step_shell.go):执行 shell 命令
|
||||||
|
- [function](step_function.go):自定义函数调用
|
||||||
|
|
||||||
基于该机制,我们可以扩展支持新的协议类型,例如 HTTP2/WebSocket/RPC 等;同时也可以支持新的测试类型,例如 UI 自动化。甚至我们还可以在一个测试用例中混合调用多种不同的 Step 类型,例如实现 HTTP/RPC/UI 混合场景。
|
基于该机制,我们可以扩展支持新的协议类型,例如 HTTP2/WebSocket/RPC 等;同时也可以支持新的测试类型,例如 UI 自动化。甚至我们还可以在一个测试用例中混合调用多种不同的 Step 类型,例如实现 HTTP/RPC/UI 混合场景。
|
||||||
|
|
||||||
@@ -43,28 +48,44 @@ type IStep interface {
|
|||||||
|
|
||||||
### 整体控制器 HRPRunner
|
### 整体控制器 HRPRunner
|
||||||
|
|
||||||
执行接口测试时,会初始化一个 `HRPRunner`,用于控制测试的执行策略。
|
执行测试时,会初始化一个 `HRPRunner`,用于控制测试的执行策略。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type HRPRunner struct {
|
type HRPRunner struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
failfast bool
|
failfast bool
|
||||||
requestsLogOn bool
|
httpStatOn bool
|
||||||
pluginLogOn bool
|
requestsLogOn bool
|
||||||
saveTests bool
|
pluginLogOn bool
|
||||||
genHTMLReport bool
|
venv string
|
||||||
client *http.Client
|
saveTests bool
|
||||||
|
genHTMLReport bool
|
||||||
|
httpClient *http.Client
|
||||||
|
http2Client *http.Client
|
||||||
|
wsDialer *websocket.Dialer
|
||||||
|
caseTimeoutTimer *time.Timer // case timeout timer
|
||||||
|
interruptSignal chan os.Signal // interrupt signal channel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HRPRunner) Run(testcases ...ITestCase) error
|
func (r *HRPRunner) Run(testcases ...ITestCase) error
|
||||||
func (r *HRPRunner) NewCaseRunner(testcase TestCase) (*CaseRunner, error)
|
func NewCaseRunner(testcase TestCase, hrpRunner *HRPRunner) (*CaseRunner, error)
|
||||||
```
|
```
|
||||||
|
|
||||||
重点关注两个方法:
|
重点关注的方法:
|
||||||
|
|
||||||
- Run:测试执行的主入口,支持运行一个或多个测试用例
|
- Run:测试执行的主入口,支持运行一个或多个测试用例
|
||||||
- NewCaseRunner:针对给定的测试用例初始化一个 CaseRunner
|
- NewCaseRunner:针对给定的测试用例初始化一个 CaseRunner
|
||||||
|
|
||||||
|
HRPRunner 支持多种配置选项:
|
||||||
|
- SetFailfast:配置是否在步骤失败时立即停止
|
||||||
|
- SetRequestsLogOn:开启请求响应详细日志
|
||||||
|
- SetHTTPStatOn:开启 HTTP 延迟统计
|
||||||
|
- SetPluginLogOn:开启插件日志
|
||||||
|
- SetProxyUrl:配置代理 URL,用于抓包调试
|
||||||
|
- SetRequestTimeout:配置全局请求超时
|
||||||
|
- SetCaseTimeout:配置测试用例超时
|
||||||
|
- GenHTMLReport:生成 HTML 测试报告
|
||||||
|
|
||||||
### 用例执行器 CaseRunner
|
### 用例执行器 CaseRunner
|
||||||
|
|
||||||
针对每个测试用例,采用 CaseRunner 存储其公共信息,包括 plugin/parser
|
针对每个测试用例,采用 CaseRunner 存储其公共信息,包括 plugin/parser
|
||||||
@@ -96,49 +117,220 @@ type SessionRunner struct {
|
|||||||
|
|
||||||
sessionVariables map[string]interface{} // testcase execution session variables
|
sessionVariables map[string]interface{} // testcase execution session variables
|
||||||
summary *TestCaseSummary // record test case summary
|
summary *TestCaseSummary // record test case summary
|
||||||
|
|
||||||
|
// transactions stores transaction timing info.
|
||||||
|
// key is transaction name, value is map of transaction type and time, e.g. start time and end time.
|
||||||
|
transactions map[string]map[TransactionType]time.Time
|
||||||
|
|
||||||
|
// websocket session
|
||||||
|
ws *wsSession
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SessionRunner) Start(givenVars map[string]interface{}) (summary *TestCaseSummary, err error)
|
func (r *SessionRunner) Start(givenVars map[string]interface{}) (summary *TestCaseSummary, err error)
|
||||||
|
func (r *SessionRunner) RunStep(step IStep) (stepResult *StepResult, err error)
|
||||||
|
func (r *SessionRunner) ParseStep(step IStep) error
|
||||||
```
|
```
|
||||||
|
|
||||||
重点关注一个方法:
|
重点关注的方法:
|
||||||
|
|
||||||
- Start:启动执行用例,依次执行所有测试步骤
|
- Start:启动执行用例,依次执行所有测试步骤
|
||||||
|
- RunStep:执行单个测试步骤,支持循环执行
|
||||||
|
- ParseStep:解析步骤配置,包括变量替换和验证器解析
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (r *SessionRunner) Start(givenVars map[string]interface{}) (summary *TestCaseSummary, err error) {
|
func (r *SessionRunner) Start(givenVars map[string]interface{}) (summary *TestCaseSummary, err error) {
|
||||||
...
|
// report GA event
|
||||||
r.resetSession()
|
sdk.SendGA4Event("hrp_session_runner_start", nil)
|
||||||
|
|
||||||
|
config := r.caseRunner.TestCase.Config.Get()
|
||||||
|
log.Info().Str("testcase", config.Name).Msg("run testcase start")
|
||||||
|
|
||||||
|
// update config variables with given variables
|
||||||
r.InitWithParameters(givenVars)
|
r.InitWithParameters(givenVars)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
// release session resources
|
||||||
|
r.ReleaseResources()
|
||||||
|
|
||||||
summary = r.summary
|
summary = r.summary
|
||||||
}
|
summary.Name = config.Name
|
||||||
|
summary.Time.Duration = time.Since(summary.Time.StartAt).Seconds()
|
||||||
|
// ... handle export variables and logs
|
||||||
|
}()
|
||||||
|
|
||||||
// run step in sequential order
|
// run step in sequential order
|
||||||
for _, step := range r.testCase.TestSteps {
|
for _, step := range r.caseRunner.TestSteps {
|
||||||
// parse step
|
select {
|
||||||
err = r.parseStepStruct(step)
|
case <-r.caseRunner.hrpRunner.caseTimeoutTimer.C:
|
||||||
|
log.Warn().Msg("timeout in session runner")
|
||||||
|
return summary, errors.Wrap(code.TimeoutError, "session runner timeout")
|
||||||
|
case <-r.caseRunner.hrpRunner.interruptSignal:
|
||||||
|
log.Warn().Msg("interrupted in session runner")
|
||||||
|
return summary, errors.Wrap(code.InterruptError, "session runner interrupted")
|
||||||
|
default:
|
||||||
|
_, err := r.RunStep(step)
|
||||||
|
if err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// interrupted or timeout, abort running
|
||||||
|
if errors.Is(err, code.InterruptError) || errors.Is(err, code.TimeoutError) {
|
||||||
|
return summary, err
|
||||||
|
}
|
||||||
|
|
||||||
// run step
|
// check if failfast
|
||||||
stepResult, err := step.Run(r)
|
if r.caseRunner.hrpRunner.failfast {
|
||||||
|
return summary, errors.Wrap(err, "abort running due to failfast setting")
|
||||||
// update summary
|
}
|
||||||
r.summary.Records = append(r.summary.Records, stepResult)
|
|
||||||
|
|
||||||
// update extracted variables
|
|
||||||
for k, v := range stepResult.ExportVars {
|
|
||||||
r.sessionVariables[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if failfast
|
|
||||||
if err != nil && r.caseRunner.hrpRunner.failfast {
|
|
||||||
return errors.Wrap(err, "abort running due to failfast setting")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
...
|
|
||||||
|
log.Info().Str("testcase", config.Name).Msg("run testcase end")
|
||||||
|
return summary, nil
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
在主流程中,SessionRunner 并不需要关注 step 的具体类型,统一都是调用 `step.Run(r)`,具体实现逻辑都在对应 step 的 `Run(*SessionRunner)` 方法中。
|
在主流程中,SessionRunner 并不需要关注 step 的具体类型,统一都是调用 `r.RunStep(step)`,具体实现逻辑都在对应 step 的 `Run(*SessionRunner)` 方法中。
|
||||||
|
|
||||||
|
## 新增特性
|
||||||
|
|
||||||
|
### 1. 超时和中断处理
|
||||||
|
|
||||||
|
v5 版本增加了完善的超时和中断处理机制:
|
||||||
|
- 支持测试用例级别的超时控制
|
||||||
|
- 支持优雅的中断处理(SIGTERM, SIGINT)
|
||||||
|
- 在执行过程中实时检查超时和中断信号
|
||||||
|
|
||||||
|
### 2. 多平台 UI 自动化
|
||||||
|
|
||||||
|
统一的 UI 自动化接口,支持多个平台:
|
||||||
|
- **Android**:基于 ADB 和 UIAutomator2
|
||||||
|
- **iOS**:基于 WebDriverAgent (WDA)
|
||||||
|
- **Harmony**:基于 HDC (Harmony Device Connector)
|
||||||
|
- **Browser**:基于 WebDriver 协议
|
||||||
|
|
||||||
|
### 3. AI 集成
|
||||||
|
|
||||||
|
集成了大模型能力:
|
||||||
|
- 支持 AI 驱动的 UI 操作
|
||||||
|
- 通过 MCP (Model Context Protocol) 与大模型通信
|
||||||
|
- 支持自然语言描述的测试步骤
|
||||||
|
|
||||||
|
### 4. 增强的步骤配置
|
||||||
|
|
||||||
|
步骤配置支持更多选项:
|
||||||
|
```go
|
||||||
|
type StepConfig struct {
|
||||||
|
StepName string `json:"name" yaml:"name"` // required
|
||||||
|
Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"`
|
||||||
|
SetupHooks []string `json:"setup_hooks,omitempty" yaml:"setup_hooks,omitempty"`
|
||||||
|
TeardownHooks []string `json:"teardown_hooks,omitempty" yaml:"teardown_hooks,omitempty"`
|
||||||
|
Extract map[string]string `json:"extract,omitempty" yaml:"extract,omitempty"`
|
||||||
|
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
|
||||||
|
StepExport []string `json:"export,omitempty" yaml:"export,omitempty"`
|
||||||
|
Loops *types.IntOrString `json:"loops,omitempty" yaml:"loops,omitempty"`
|
||||||
|
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 协议支持扩展
|
||||||
|
|
||||||
|
除了 HTTP/HTTPS,还支持:
|
||||||
|
- HTTP/2 协议
|
||||||
|
- WebSocket 通信
|
||||||
|
- 自定义函数调用
|
||||||
|
|
||||||
|
### 6. 资源管理
|
||||||
|
|
||||||
|
增强的资源管理机制:
|
||||||
|
- 自动释放会话资源
|
||||||
|
- UI 驱动器缓存管理
|
||||||
|
- 日志收集和聚合
|
||||||
|
|
||||||
|
## UI 自动化步骤示例
|
||||||
|
|
||||||
|
### StepMobile 结构
|
||||||
|
|
||||||
|
UI 自动化步骤统一使用 `StepMobile` 结构:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type StepMobile struct {
|
||||||
|
StepConfig
|
||||||
|
Mobile *MobileUI `json:"mobile,omitempty" yaml:"mobile,omitempty"`
|
||||||
|
Android *MobileUI `json:"android,omitempty" yaml:"android,omitempty"`
|
||||||
|
Harmony *MobileUI `json:"harmony,omitempty" yaml:"harmony,omitempty"`
|
||||||
|
IOS *MobileUI `json:"ios,omitempty" yaml:"ios,omitempty"`
|
||||||
|
Browser *MobileUI `json:"browser,omitempty" yaml:"browser,omitempty"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 常用 UI 操作方法
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 基础操作
|
||||||
|
func (s *StepMobile) TapXY(x, y float64, opts ...option.ActionOption) *StepMobile
|
||||||
|
func (s *StepMobile) TapByOCR(ocrText string, opts ...option.ActionOption) *StepMobile
|
||||||
|
func (s *StepMobile) TapByCV(imagePath string, opts ...option.ActionOption) *StepMobile
|
||||||
|
func (s *StepMobile) AIAction(prompt string, opts ...option.ActionOption) *StepMobile
|
||||||
|
|
||||||
|
// 应用管理
|
||||||
|
func (s *StepMobile) AppLaunch(bundleId string) *StepMobile
|
||||||
|
func (s *StepMobile) AppTerminate(bundleId string) *StepMobile
|
||||||
|
func (s *StepMobile) InstallApp(path string) *StepMobile
|
||||||
|
|
||||||
|
// 滑动操作
|
||||||
|
func (s *StepMobile) Swipe(sx, sy, ex, ey float64, opts ...option.ActionOption) *StepMobile
|
||||||
|
func (s *StepMobile) SwipeUp(opts ...option.ActionOption) *StepMobile
|
||||||
|
func (s *StepMobile) SwipeDown(opts ...option.ActionOption) *StepMobile
|
||||||
|
|
||||||
|
// 输入操作
|
||||||
|
func (s *StepMobile) Input(text string, opts ...option.ActionOption) *StepMobile
|
||||||
|
|
||||||
|
// 等待操作
|
||||||
|
func (s *StepMobile) Sleep(nSeconds float64, startTime ...time.Time) *StepMobile
|
||||||
|
func (s *StepMobile) SleepRandom(params ...float64) *StepMobile
|
||||||
|
|
||||||
|
// 验证操作
|
||||||
|
func (s *StepMobile) Validate() *StepMobileUIValidation
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI 验证方法
|
||||||
|
|
||||||
|
```go
|
||||||
|
// OCR 文本验证
|
||||||
|
func (s *StepMobileUIValidation) AssertOCRExists(expectedText string, msg ...string) *StepMobileUIValidation
|
||||||
|
func (s *StepMobileUIValidation) AssertOCRNotExists(expectedText string, msg ...string) *StepMobileUIValidation
|
||||||
|
|
||||||
|
// 图像验证
|
||||||
|
func (s *StepMobileUIValidation) AssertImageExists(expectedImagePath string, msg ...string) *StepMobileUIValidation
|
||||||
|
func (s *StepMobileUIValidation) AssertImageNotExists(expectedImagePath string, msg ...string) *StepMobileUIValidation
|
||||||
|
|
||||||
|
// AI 验证
|
||||||
|
func (s *StepMobileUIValidation) AssertAI(prompt string, msg ...string) *StepMobileUIValidation
|
||||||
|
|
||||||
|
// 应用状态验证
|
||||||
|
func (s *StepMobileUIValidation) AssertAppInForeground(packageName string, msg ...string) *StepMobileUIValidation
|
||||||
|
func (s *StepMobileUIValidation) AssertAppNotInForeground(packageName string, msg ...string) *StepMobileUIValidation
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发建议
|
||||||
|
|
||||||
|
### 1. 添加新的步骤类型
|
||||||
|
|
||||||
|
要添加新的步骤类型,需要:
|
||||||
|
1. 在 `step.go` 中定义新的 `StepType` 常量
|
||||||
|
2. 创建实现 `IStep` 接口的结构体
|
||||||
|
3. 在 `testcase.go` 的 `loadISteps` 方法中添加对应的处理逻辑
|
||||||
|
|
||||||
|
### 2. 扩展 UI 平台支持
|
||||||
|
|
||||||
|
要支持新的 UI 平台:
|
||||||
|
1. 在 `uixt/` 目录下实现对应的驱动器
|
||||||
|
2. 在 `StepMobile` 中添加新的平台字段
|
||||||
|
3. 在 `obj()` 方法中添加对应的处理逻辑
|
||||||
|
|
||||||
|
### 3. 调试技巧
|
||||||
|
|
||||||
|
- 使用 `SetRequestsLogOn()` 开启详细的请求日志
|
||||||
|
- 使用 `SetPluginLogOn()` 开启插件日志
|
||||||
|
- 使用 `SetProxyUrl()` 配置代理进行抓包分析
|
||||||
|
- 查看生成的 HTML 报告了解执行详情
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v5.0.0-beta-2505271528
|
v5.0.0-beta-2505271534
|
||||||
|
|||||||
Reference in New Issue
Block a user