mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-26 10:01:28 +08:00
feat: add parameterization support for test cases and steps with configuration options
This commit is contained in:
195
runner.go
195
runner.go
@@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
@@ -734,6 +735,98 @@ const (
|
||||
RUN_STEP_END = "run step end"
|
||||
)
|
||||
|
||||
// executionTask holds the necessary information for a single step execution.
|
||||
type executionTask struct {
|
||||
stepName string
|
||||
parameters map[string]interface{}
|
||||
}
|
||||
|
||||
// formatParameters formats parameter values into a string for display in step names.
|
||||
// e.g. {"foo": "bar", "age": 18} -> "bar-18"
|
||||
func formatParameters(params map[string]interface{}) string {
|
||||
if len(params) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// sort keys to ensure consistent order
|
||||
keys := make([]string, 0, len(params))
|
||||
for k := range params {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var values []string
|
||||
for _, k := range keys {
|
||||
values = append(values, fmt.Sprintf("%v", params[k]))
|
||||
}
|
||||
return strings.Join(values, "-")
|
||||
}
|
||||
|
||||
// generateExecutionTasks generates a list of execution tasks based on step parameters and loops.
|
||||
func (r *SessionRunner) generateExecutionTasks(step IStep) ([]executionTask, error) {
|
||||
stepConfig := step.Config()
|
||||
stepName := step.Name()
|
||||
|
||||
// determine effective loop times
|
||||
loopTimes := stepConfig.Loops
|
||||
if loopTimes <= 0 {
|
||||
loopTimes = 1 // default to 1 if not set
|
||||
}
|
||||
|
||||
// initialize parameters iterator
|
||||
parametersIterator, err := r.caseRunner.parser.InitParametersIterator(&TConfig{
|
||||
Parameters: stepConfig.Parameters,
|
||||
ParametersSetting: stepConfig.ParametersSetting,
|
||||
Variables: stepConfig.Variables,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to initialize parameters iterator")
|
||||
}
|
||||
|
||||
// collect all parameter combinations first
|
||||
var allParameters []map[string]interface{}
|
||||
if parametersIterator != nil {
|
||||
for parametersIterator.HasNext() {
|
||||
allParameters = append(allParameters, parametersIterator.Next())
|
||||
}
|
||||
}
|
||||
|
||||
// if no parameters are specified, but loop times are set,
|
||||
// we should run the step loopTimes with empty parameters.
|
||||
if len(allParameters) == 0 && loopTimes > 0 {
|
||||
allParameters = append(allParameters, make(map[string]interface{}))
|
||||
}
|
||||
|
||||
// generate execution tasks
|
||||
var tasks []executionTask
|
||||
for loopIndex := 1; loopIndex <= loopTimes; loopIndex++ {
|
||||
for _, params := range allParameters {
|
||||
// determine step name based on parameters and loops
|
||||
currentStepName := stepName
|
||||
hasParameters := len(params) > 0
|
||||
hasLoops := loopTimes > 1
|
||||
|
||||
if hasParameters {
|
||||
paramStr := formatParameters(params)
|
||||
if hasLoops {
|
||||
currentStepName = fmt.Sprintf("%s [loop_%d_params_%s]", stepName, loopIndex, paramStr)
|
||||
} else {
|
||||
currentStepName = fmt.Sprintf("%s [params_%s]", stepName, paramStr)
|
||||
}
|
||||
} else if hasLoops {
|
||||
currentStepName = fmt.Sprintf("%s_loop_%d", stepName, loopIndex)
|
||||
}
|
||||
|
||||
tasks = append(tasks, executionTask{
|
||||
stepName: currentStepName,
|
||||
parameters: params,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
func (r *SessionRunner) RunStep(step IStep) (stepResult *StepResult, err error) {
|
||||
// check for interrupt signal before running step
|
||||
select {
|
||||
@@ -753,56 +846,90 @@ func (r *SessionRunner) RunStep(step IStep) (stepResult *StepResult, err error)
|
||||
|
||||
stepName := step.Name()
|
||||
stepType := string(step.Type())
|
||||
|
||||
log.Info().Str("step", stepName).Str("type", stepType).Msg(RUN_STEP_START)
|
||||
|
||||
// run times of step
|
||||
loopTimes := step.Config().Loops
|
||||
if loopTimes == 0 {
|
||||
loopTimes = 1 // default run once
|
||||
} else if loopTimes > 1 {
|
||||
log.Info().Int("loops", loopTimes).Msg("set multiple loop times")
|
||||
// execute step with parameters iterator
|
||||
tasks, err := r.generateExecutionTasks(step)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate execution tasks")
|
||||
}
|
||||
|
||||
// run step with specified loop times
|
||||
for i := 1; i <= loopTimes; i++ {
|
||||
var loopIndex string
|
||||
if loopTimes > 1 {
|
||||
log.Info().Int("index", i).Msg("start running step in loop")
|
||||
loopIndex = fmt.Sprintf("_loop_%d", i)
|
||||
var stepResults []*StepResult
|
||||
|
||||
// execute with loops as outer iteration
|
||||
for _, task := range tasks {
|
||||
// execute step with merged variables
|
||||
stepResult, err := r.executeStepWithVariables(step, task.stepName, task.parameters)
|
||||
if err != nil {
|
||||
if r.caseRunner.hrpRunner.failfast {
|
||||
return nil, errors.Wrap(err, "execute step failed")
|
||||
}
|
||||
log.Error().Err(err).Str("step", task.stepName).Msg("execute step failed")
|
||||
}
|
||||
|
||||
// run step
|
||||
stepResult, err = step.Run(r)
|
||||
stepResult.Name = stepName + loopIndex
|
||||
stepResults = append(stepResults, stepResult)
|
||||
}
|
||||
|
||||
// add step result to summary
|
||||
r.summary.AddStepResult(stepResult)
|
||||
|
||||
// update extracted variables
|
||||
for k, v := range stepResult.ExportVars {
|
||||
r.sessionVariables[k] = v
|
||||
// return the last step result, or nil if no steps were executed
|
||||
if len(stepResults) > 0 {
|
||||
// add all step results to summary
|
||||
for _, result := range stepResults {
|
||||
r.summary.AddStepResult(result)
|
||||
// update extracted variables from the last result
|
||||
for k, v := range result.ExportVars {
|
||||
r.sessionVariables[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// run step success
|
||||
if err == nil {
|
||||
// log final result
|
||||
lastResult := stepResults[len(stepResults)-1]
|
||||
if lastResult.Success {
|
||||
log.Info().Str("step", stepName).
|
||||
Str("type", stepType).
|
||||
Bool("success", true).
|
||||
Int64("elapsed(ms)", stepResult.Elapsed).
|
||||
Interface("exportVars", stepResult.ExportVars).
|
||||
Int64("elapsed(ms)", lastResult.Elapsed).
|
||||
Interface("exportVars", lastResult.ExportVars).
|
||||
Msg(RUN_STEP_END)
|
||||
} else {
|
||||
log.Error().Str("step", stepName).
|
||||
Str("type", stepType).
|
||||
Bool("success", false).
|
||||
Int64("elapsed(ms)", lastResult.Elapsed).
|
||||
Msg(RUN_STEP_END)
|
||||
continue
|
||||
}
|
||||
// run step failed
|
||||
log.Error().Err(err).Str("step", stepName).
|
||||
Str("type", stepType).
|
||||
Bool("success", false).
|
||||
Int64("elapsed(ms)", stepResult.Elapsed).
|
||||
Msg(RUN_STEP_END)
|
||||
return stepResult, err
|
||||
|
||||
return lastResult, nil
|
||||
}
|
||||
|
||||
return stepResult, nil
|
||||
return nil, errors.New("no steps were executed")
|
||||
}
|
||||
|
||||
// executeStepWithVariables executes a single step with given parameters
|
||||
// parameters will override step variables with the same name
|
||||
func (r *SessionRunner) executeStepWithVariables(step IStep, stepName string, parameters map[string]interface{}) (stepResult *StepResult, err error) {
|
||||
stepConfig := step.Config()
|
||||
|
||||
// backup original variables
|
||||
originalVariables := make(map[string]interface{})
|
||||
for k, v := range stepConfig.Variables {
|
||||
originalVariables[k] = v
|
||||
}
|
||||
|
||||
// merge parameters into step variables
|
||||
// parameters have higher priority than variables
|
||||
for k, v := range parameters {
|
||||
stepConfig.Variables[k] = v
|
||||
}
|
||||
|
||||
// execute step
|
||||
stepResult, err = step.Run(r)
|
||||
stepResult.Name = stepName
|
||||
|
||||
// restore original variables to avoid side effects
|
||||
stepConfig.Variables = originalVariables
|
||||
|
||||
return stepResult, err
|
||||
}
|
||||
|
||||
func (r *SessionRunner) GetSummary() *TestCaseSummary {
|
||||
|
||||
Reference in New Issue
Block a user