diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5c522f56..3f986f07 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -11,6 +11,7 @@ - change: integrate [sentry sdk][sentry sdk] for panic reporting and analysis - change: lock funplugin version when creating scaffold project - fix: call referenced api/testcase with relative path +- refactor: redesign `IStep` to make step extensible to support implementing new protocols and test types **python version** diff --git a/docs/dev.md b/docs/dev.md new file mode 100644 index 00000000..e0d63e7a --- /dev/null +++ b/docs/dev.md @@ -0,0 +1,95 @@ + +## 核心数据结构 + +HttpRunner 以 `TestCase` 为核心,将任意测试场景抽象为有序步骤的集合。 + +```go +type TestCase struct { + Config *TConfig + TestSteps []IStep +} +``` + +其中,测试步骤 `IStep` 采用了 `go interface` 的设计理念,支持进行任意拓展;步骤内容统一在 `Run` 方法中进行实现。 + +```go +type IStep interface { + Name() string + Type() StepType + Struct() *TStep + Run(*SessionRunner) (*StepResult, error) +} +``` + +我们只需遵循 `IStep` 的接口定义,即可实现各种类型的测试步骤类型。当前已支持的步骤类型包括: + +- request:发起单次 HTTP 请求 +- api:引用执行其它 API 文件 +- testcase:引用执行其它测试用例文件 +- thinktime:思考时间,按照配置的逻辑进行等待 +- transaction:事务机制,用于压测 +- rendezvous:集合点机制,用于压测 + +基于该机制,我们可以扩展支持新的协议类型,例如 HTTP2/WebSocket/RPC 等;同时也可以支持新的测试类型,例如 UI 自动化。甚至我们还可以在一个测试用例中混合调用多种不同的 Step 类型,例如实现 HTTP/RPC/UI 混合场景。 + +## 运行主流程 + +### 整体控制器 HRPRunner + +执行接口测试时,会初始化一个 `HRPRunner`,用于控制测试的执行策略。 + +```go +type HRPRunner struct { + t *testing.T + failfast bool + requestsLogOn bool + pluginLogOn bool + saveTests bool + genHTMLReport bool + client *http.Client +} + +func (r *HRPRunner) Run(testcases ...ITestCase) error +func (r *HRPRunner) NewSessionRunner(testcase *TestCase) *SessionRunner +``` + +重点关注两个方法: + +- Run:测试执行的主入口,支持运行一个或多个测试用例 +- NewSessionRunner:针对给定的测试用例初始化一个 SessionRunner + +### 用例执行器 SessionRunner + +测试用例的具体执行都由 `SessionRunner` 完成,每个 TestCase 对应一个实例,在该实例中除了包含测试用例自身内容外,还会包含测试过程的 session 数据和最终测试结果 summary。 + +```go +type SessionRunner struct { + testCase *TestCase + hrpRunner *HRPRunner + parser *Parser + sessionVariables map[string]interface{} + transactions map[string]map[transactionType]time.Time + startTime time.Time // record start time of the testcase + summary *TestCaseSummary // record test case summary +} +``` + +重点关注一个方法: + +- Start:启动执行用例,依次执行所有测试步骤 + +```go +func (r *SessionRunner) Start() error { + ... + // run step in sequential order + for _, step := range r.testCase.TestSteps { + _, err := step.Run(r) + if err != nil && r.hrpRunner.failfast { + return errors.Wrap(err, "abort running due to failfast setting") + } + } + ... +} +``` + +在主流程中,SessionRunner 并不需要关注 step 的具体类型,统一都是调用 `step.Run(r)`,具体实现逻辑都在对应 step 的 `Run(*SessionRunner)` 方法中。 diff --git a/hrp/boomer.go b/hrp/boomer.go index f55617bd..1761169c 100644 --- a/hrp/boomer.go +++ b/hrp/boomer.go @@ -147,7 +147,7 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend if stepResult.StepType == stepTypeTransaction { // transaction // FIXME: support nested transactions - if step.ToStruct().Transaction.Type == transactionEnd { // only record when transaction ends + if step.Struct().Transaction.Type == transactionEnd { // only record when transaction ends b.RecordTransaction(stepResult.Name, transactionSuccess, stepResult.Elapsed, 0) transactionSuccess = true // reset flag for next transaction } diff --git a/hrp/step.go b/hrp/step.go index be47096d..9733843b 100644 --- a/hrp/step.go +++ b/hrp/step.go @@ -47,6 +47,6 @@ type TStep struct { type IStep interface { Name() string Type() StepType - ToStruct() *TStep + Struct() *TStep Run(*SessionRunner) (*StepResult, error) } diff --git a/hrp/step_api.go b/hrp/step_api.go index c275cea8..0bd8cead 100644 --- a/hrp/step_api.go +++ b/hrp/step_api.go @@ -88,7 +88,7 @@ func (s *StepAPIWithOptionalArgs) Type() StepType { return stepTypeAPI } -func (s *StepAPIWithOptionalArgs) ToStruct() *TStep { +func (s *StepAPIWithOptionalArgs) Struct() *TStep { return s.step } diff --git a/hrp/step_rendezvous.go b/hrp/step_rendezvous.go index 088a11b3..8dd57cdd 100644 --- a/hrp/step_rendezvous.go +++ b/hrp/step_rendezvous.go @@ -24,7 +24,7 @@ func (s *StepRendezvous) Type() StepType { return stepTypeRendezvous } -func (s *StepRendezvous) ToStruct() *TStep { +func (s *StepRendezvous) Struct() *TStep { return s.step } diff --git a/hrp/step_request.go b/hrp/step_request.go index 0f9c8d69..372bd50e 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -703,7 +703,7 @@ func (s *StepRequestWithOptionalArgs) Type() StepType { return StepType(fmt.Sprintf("request-%v", s.step.Request.Method)) } -func (s *StepRequestWithOptionalArgs) ToStruct() *TStep { +func (s *StepRequestWithOptionalArgs) Struct() *TStep { return s.step } @@ -737,7 +737,7 @@ func (s *StepRequestExtraction) Type() StepType { return StepType(fmt.Sprintf("request-%v", s.step.Request.Method)) } -func (s *StepRequestExtraction) ToStruct() *TStep { +func (s *StepRequestExtraction) Struct() *TStep { return s.step } @@ -761,7 +761,7 @@ func (s *StepRequestValidation) Type() StepType { return StepType(fmt.Sprintf("request-%v", s.step.Request.Method)) } -func (s *StepRequestValidation) ToStruct() *TStep { +func (s *StepRequestValidation) Struct() *TStep { return s.step } diff --git a/hrp/step_testcase.go b/hrp/step_testcase.go index 56b485c7..bd69eb56 100644 --- a/hrp/step_testcase.go +++ b/hrp/step_testcase.go @@ -39,7 +39,7 @@ func (s *StepTestCaseWithOptionalArgs) Type() StepType { return stepTypeTestCase } -func (s *StepTestCaseWithOptionalArgs) ToStruct() *TStep { +func (s *StepTestCaseWithOptionalArgs) Struct() *TStep { return s.step } diff --git a/hrp/step_thinktime.go b/hrp/step_thinktime.go index 8e90398d..3e54f7ee 100644 --- a/hrp/step_thinktime.go +++ b/hrp/step_thinktime.go @@ -24,7 +24,7 @@ func (s *StepThinkTime) Type() StepType { return stepTypeThinkTime } -func (s *StepThinkTime) ToStruct() *TStep { +func (s *StepThinkTime) Struct() *TStep { return s.step } diff --git a/hrp/step_transaction.go b/hrp/step_transaction.go index 20ebb67e..184be64f 100644 --- a/hrp/step_transaction.go +++ b/hrp/step_transaction.go @@ -35,7 +35,7 @@ func (s *StepTransaction) Type() StepType { return stepTypeTransaction } -func (s *StepTransaction) ToStruct() *TStep { +func (s *StepTransaction) Struct() *TStep { return s.step } diff --git a/hrp/testcase.go b/hrp/testcase.go index 733d8241..79922338 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -39,7 +39,7 @@ func (tc *TestCase) ToTCase() *TCase { Config: tc.Config, } for _, step := range tc.TestSteps { - tCase.TestSteps = append(tCase.TestSteps, step.ToStruct()) + tCase.TestSteps = append(tCase.TestSteps, step.Struct()) } return tCase }