From bc0a3427ac9211bfd413bb03a9a26bdaa71f0a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Thu, 30 Dec 2021 22:31:39 +0800 Subject: [PATCH] feat: data-driven. --- boomer.go | 2 +- convert.go | 5 +++-- internal/builtin/function.go | 9 +++++---- models.go | 16 ++++++++++------ parser.go | 35 +++++++++++++++++++---------------- runner.go | 2 +- 6 files changed, 39 insertions(+), 30 deletions(-) diff --git a/boomer.go b/boomer.go index 6e973d83..48593fbd 100644 --- a/boomer.go +++ b/boomer.go @@ -69,7 +69,7 @@ func (b *hrpBoomer) convertBoomerTask(testcase *TestCase) *boomer.Task { var transactionSuccess = true // flag current transaction result cfg := testcase.Config.ToStruct() - if it := cfg.ParameterIterator; it.HasNext() { + if it := cfg.ParametersSetting.Iterator; it.HasNext() { cfg.Variables = mergeVariables(it.Next(), cfg.Variables) } startTime := time.Now() diff --git a/convert.go b/convert.go index 7cdedfae..7f1bc830 100644 --- a/convert.go +++ b/convert.go @@ -4,10 +4,11 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/rs/zerolog/log" - "gopkg.in/yaml.v3" "io/ioutil" "path/filepath" + + "github.com/rs/zerolog/log" + "gopkg.in/yaml.v3" ) func (tc *TCase) Dump2JSON(path string) error { diff --git a/internal/builtin/function.go b/internal/builtin/function.go index 8d6fd87e..f4f7aaae 100644 --- a/internal/builtin/function.go +++ b/internal/builtin/function.go @@ -4,13 +4,14 @@ import ( "crypto/md5" "encoding/csv" "encoding/hex" - "github.com/rs/zerolog/log" "io/ioutil" "math" "math/rand" "path/filepath" "strings" "time" + + "github.com/rs/zerolog/log" ) var Functions = map[string]interface{}{ @@ -19,8 +20,8 @@ var Functions = map[string]interface{}{ "gen_random_string": genRandomString, // call with one argument "max": math.Max, // call with two arguments "md5": MD5, - "parameterize": LoadFromCSV, - "P": LoadFromCSV, + "parameterize": loadFromCSV, + "P": loadFromCSV, } func init() { @@ -52,7 +53,7 @@ func MD5(str string) string { return hex.EncodeToString(hasher.Sum(nil)) } -func LoadFromCSV(path string) []map[string]interface{} { +func loadFromCSV(path string) []map[string]interface{} { path, err := filepath.Abs(path) if err != nil { log.Error().Str("path", path).Err(err).Msg("convert absolute path failed") diff --git a/models.go b/models.go index 9ded19c3..0fb93199 100644 --- a/models.go +++ b/models.go @@ -2,7 +2,6 @@ package hrp import ( "math/rand" - "strings" "time" ) @@ -25,21 +24,26 @@ type TConfig struct { Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"` Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` ParametersSetting *TParamsConfig `json:"parameters_setting,omitempty" yaml:"parameters_setting,omitempty"` - ParameterIterator *Iterator `json:"parameterIterator,omitempty" yaml:"parameterIterator,omitempty"` Export []string `json:"export,omitempty" yaml:"export,omitempty"` Weight int `json:"weight,omitempty" yaml:"weight,omitempty"` } type TParamsConfig struct { - Strategy string `json:"strategy,omitempty" yaml:"strategy,omitempty"` - Iteration int `json:"iteration,omitempty" yaml:"iteration,omitempty"` + Strategy string `json:"strategy,omitempty" yaml:"strategy,omitempty"` + Iteration int `json:"iteration,omitempty" yaml:"iteration,omitempty"` + Iterator *Iterator `json:"parameterIterator,omitempty" yaml:"parameterIterator,omitempty"` } +const ( + strategyRandom string = "random" + strategySequential string = "Sequential" +) + type paramsType []map[string]interface{} type Iterator struct { data paramsType - strategy string + strategy string // random, sequential iteration int index int } @@ -64,7 +68,7 @@ func (iter *Iterator) Next() (value map[string]interface{}) { if len(iter.data) == 0 { return map[string]interface{}{} } - if strings.ToLower(iter.strategy) == "random" { + if iter.strategy == strategyRandom { randSource := rand.New(rand.NewSource(time.Now().Unix())) randIndex := randSource.Intn(len(iter.data)) value = iter.data[randIndex] diff --git a/parser.go b/parser.go index 7f34539b..389111a3 100644 --- a/parser.go +++ b/parser.go @@ -3,14 +3,15 @@ package hrp import ( "encoding/json" "fmt" - "github.com/maja42/goval" - "github.com/pkg/errors" - "github.com/rs/zerolog/log" "net/url" "reflect" "regexp" "strings" + "github.com/maja42/goval" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "github.com/httprunner/hrp/internal/builtin" ) @@ -522,23 +523,25 @@ func parseParameters(parameters map[string]interface{}, variablesMapping map[str rawValue := reflect.ValueOf(v) switch rawValue.Kind() { case reflect.String: + // e.g. username-password: ${parameterize(examples/account.csv)} -> [{"username": "test1", "password": "111111"}, {"username": "test2", "password": "222222"}] var parsedParameterContent interface{} - parsedParameterContent, err = parseData(rawValue.Interface(), variablesMapping) + parsedParameterContent, err = parseString(rawValue.String(), variablesMapping) if err != nil { log.Error().Interface("parameterContent", rawValue).Msg("[parseParameters] parse parameter content error") return nil, err } parsedParameterRawValue := reflect.ValueOf(parsedParameterContent) if parsedParameterRawValue.Kind() != reflect.Slice { - log.Error().Interface("parameterContent", parsedParameterRawValue).Msg("[parseParameters] parsed parameter content should be Slice, got %v") - return nil, errors.New("parsed parameter content should be Slice") + log.Error().Interface("parameterContent", parsedParameterRawValue).Msg("[parseParameters] parsed parameter content should be slice") + return nil, errors.New("parsed parameter content should be slice") } parameterSlice, err = parseSlice(k, parsedParameterRawValue.Interface()) case reflect.Slice: + // e.g. user_agent: ["iOS/10.1", "iOS/10.2"] -> [{"user_agent": "iOS/10.1"}, {"user_agent": "iOS/10.2"}] parameterSlice, err = parseSlice(k, rawValue.Interface()) default: - log.Error().Interface("parameter", parameters).Msg("[parseParameters] parameter content should be Slice or Text(variables or functions call)") - return nil, errors.New("parameter content should be Slice or Text(variables or functions call)") + log.Error().Interface("parameter", parameters).Msg("[parseParameters] parameter content should be slice or text(functions call)") + return nil, errors.New("parameter content should be slice or text(functions call)") } if err != nil { return nil, err @@ -553,7 +556,7 @@ func parseSlice(parameterName string, parameterContent interface{}) ([]map[strin var parameterSlice []map[string]interface{} parameterContentSlice := reflect.ValueOf(parameterContent) if parameterContentSlice.Kind() != reflect.Slice { - return nil, errors.New("parameterContent should be Slice") + return nil, errors.New("parameterContent should be slice") } for i := 0; i < parameterContentSlice.Len(); i++ { parameterMap := make(map[string]interface{}) @@ -574,8 +577,8 @@ func parseSlice(parameterName string, parameterContent interface{}) ([]map[strin // e.g. "username-password": [["test1", "passwd1"], ["test2", "passwd2"]] // -> [{"username": "test1", "password": "passwd1"}, {"username": "test2", "password": "passwd2"}] if len(parameterNameSlice) != elem.Len() { - log.Error().Interface("parameterNameSlice", parameterNameSlice).Interface("parameterContent", elem.Interface()).Msg("[parseParameters] parameter name Slice and parameter content Slice should have the same length") - return nil, errors.New("parameter name Slice and parameter cjntent Slice should have the same length") + log.Error().Interface("parameterNameSlice", parameterNameSlice).Interface("parameterContent", elem.Interface()).Msg("[parseParameters] parameter name slice and parameter content slice should have the same length") + return nil, errors.New("parameter name slice and parameter content slice should have the same length") } else { for j := 0; j < elem.Len(); j++ { parameterMap[parameterNameSlice[j]] = elem.Index(j).Interface() @@ -598,7 +601,7 @@ func parseSlice(parameterName string, parameterContent interface{}) ([]map[strin func initParameterIterator(cfg *TConfig, mode string) (err error) { var parameters paramsType parameters, err = parseParameters(cfg.Parameters, cfg.Variables) - cfg.ParameterIterator = parameters.Iterator() + cfg.ParametersSetting.Iterator = parameters.Iterator() if err != nil { return err } @@ -607,17 +610,17 @@ func initParameterIterator(cfg *TConfig, mode string) (err error) { cfg.ParametersSetting = &TParamsConfig{} } if len(cfg.ParametersSetting.Strategy) == 0 { - cfg.ParametersSetting.Strategy = "sequential" + cfg.ParametersSetting.Strategy = strategySequential } else { cfg.ParametersSetting.Strategy = strings.ToLower(cfg.ParametersSetting.Strategy) } - cfg.ParameterIterator.strategy = cfg.ParametersSetting.Strategy + cfg.ParametersSetting.Iterator.strategy = cfg.ParametersSetting.Strategy if mode == "boomer" { cfg.ParametersSetting.Iteration = -1 - cfg.ParameterIterator.iteration = cfg.ParametersSetting.Iteration + cfg.ParametersSetting.Iterator.iteration = cfg.ParametersSetting.Iteration } else { if cfg.ParametersSetting.Iteration != 0 { - cfg.ParameterIterator.iteration = cfg.ParametersSetting.Iteration + cfg.ParametersSetting.Iterator.iteration = cfg.ParametersSetting.Iteration } } return nil diff --git a/runner.go b/runner.go index f568c818..c227064a 100644 --- a/runner.go +++ b/runner.go @@ -143,7 +143,7 @@ func (r *caseRunner) run() error { } cfg := config.ToStruct() log.Info().Str("testcase", config.Name()).Msg("run testcase start") - for it := cfg.ParameterIterator; it.HasNext(); { + for it := cfg.ParametersSetting.Iterator; it.HasNext(); { cfg.Variables = mergeVariables(it.Next(), cfg.Variables) r.startTime = time.Now() for index := range r.TestCase.TestSteps {