feat: data-driven.

This commit is contained in:
徐聪
2021-12-30 22:31:39 +08:00
parent 57816f504b
commit cfc46b0a63
6 changed files with 39 additions and 30 deletions

View File

@@ -69,7 +69,7 @@ func (b *hrpBoomer) convertBoomerTask(testcase *TestCase) *boomer.Task {
var transactionSuccess = true // flag current transaction result var transactionSuccess = true // flag current transaction result
cfg := testcase.Config.ToStruct() cfg := testcase.Config.ToStruct()
if it := cfg.ParameterIterator; it.HasNext() { if it := cfg.ParametersSetting.Iterator; it.HasNext() {
cfg.Variables = mergeVariables(it.Next(), cfg.Variables) cfg.Variables = mergeVariables(it.Next(), cfg.Variables)
} }
startTime := time.Now() startTime := time.Now()

View File

@@ -4,10 +4,11 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"
) )
func (tc *TCase) Dump2JSON(path string) error { func (tc *TCase) Dump2JSON(path string) error {

View File

@@ -4,13 +4,14 @@ import (
"crypto/md5" "crypto/md5"
"encoding/csv" "encoding/csv"
"encoding/hex" "encoding/hex"
"github.com/rs/zerolog/log"
"io/ioutil" "io/ioutil"
"math" "math"
"math/rand" "math/rand"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/rs/zerolog/log"
) )
var Functions = map[string]interface{}{ var Functions = map[string]interface{}{
@@ -19,8 +20,8 @@ var Functions = map[string]interface{}{
"gen_random_string": genRandomString, // call with one argument "gen_random_string": genRandomString, // call with one argument
"max": math.Max, // call with two arguments "max": math.Max, // call with two arguments
"md5": MD5, "md5": MD5,
"parameterize": LoadFromCSV, "parameterize": loadFromCSV,
"P": LoadFromCSV, "P": loadFromCSV,
} }
func init() { func init() {
@@ -52,7 +53,7 @@ func MD5(str string) string {
return hex.EncodeToString(hasher.Sum(nil)) 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) path, err := filepath.Abs(path)
if err != nil { if err != nil {
log.Error().Str("path", path).Err(err).Msg("convert absolute path failed") log.Error().Str("path", path).Err(err).Msg("convert absolute path failed")

View File

@@ -2,7 +2,6 @@ package hrp
import ( import (
"math/rand" "math/rand"
"strings"
"time" "time"
) )
@@ -25,21 +24,26 @@ type TConfig struct {
Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"` Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"`
Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"`
ParametersSetting *TParamsConfig `json:"parameters_setting,omitempty" yaml:"parameters_setting,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"` Export []string `json:"export,omitempty" yaml:"export,omitempty"`
Weight int `json:"weight,omitempty" yaml:"weight,omitempty"` Weight int `json:"weight,omitempty" yaml:"weight,omitempty"`
} }
type TParamsConfig struct { type TParamsConfig struct {
Strategy string `json:"strategy,omitempty" yaml:"strategy,omitempty"` Strategy string `json:"strategy,omitempty" yaml:"strategy,omitempty"`
Iteration int `json:"iteration,omitempty" yaml:"iteration,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 paramsType []map[string]interface{}
type Iterator struct { type Iterator struct {
data paramsType data paramsType
strategy string strategy string // random, sequential
iteration int iteration int
index int index int
} }
@@ -64,7 +68,7 @@ func (iter *Iterator) Next() (value map[string]interface{}) {
if len(iter.data) == 0 { if len(iter.data) == 0 {
return map[string]interface{}{} return map[string]interface{}{}
} }
if strings.ToLower(iter.strategy) == "random" { if iter.strategy == strategyRandom {
randSource := rand.New(rand.NewSource(time.Now().Unix())) randSource := rand.New(rand.NewSource(time.Now().Unix()))
randIndex := randSource.Intn(len(iter.data)) randIndex := randSource.Intn(len(iter.data))
value = iter.data[randIndex] value = iter.data[randIndex]

View File

@@ -3,14 +3,15 @@ package hrp
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/maja42/goval"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"net/url" "net/url"
"reflect" "reflect"
"regexp" "regexp"
"strings" "strings"
"github.com/maja42/goval"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/httprunner/hrp/internal/builtin" "github.com/httprunner/hrp/internal/builtin"
) )
@@ -522,23 +523,25 @@ func parseParameters(parameters map[string]interface{}, variablesMapping map[str
rawValue := reflect.ValueOf(v) rawValue := reflect.ValueOf(v)
switch rawValue.Kind() { switch rawValue.Kind() {
case reflect.String: case reflect.String:
// e.g. username-password: ${parameterize(examples/account.csv)} -> [{"username": "test1", "password": "111111"}, {"username": "test2", "password": "222222"}]
var parsedParameterContent interface{} var parsedParameterContent interface{}
parsedParameterContent, err = parseData(rawValue.Interface(), variablesMapping) parsedParameterContent, err = parseString(rawValue.String(), variablesMapping)
if err != nil { if err != nil {
log.Error().Interface("parameterContent", rawValue).Msg("[parseParameters] parse parameter content error") log.Error().Interface("parameterContent", rawValue).Msg("[parseParameters] parse parameter content error")
return nil, err return nil, err
} }
parsedParameterRawValue := reflect.ValueOf(parsedParameterContent) parsedParameterRawValue := reflect.ValueOf(parsedParameterContent)
if parsedParameterRawValue.Kind() != reflect.Slice { if parsedParameterRawValue.Kind() != reflect.Slice {
log.Error().Interface("parameterContent", parsedParameterRawValue).Msg("[parseParameters] parsed parameter content should be Slice, got %v") log.Error().Interface("parameterContent", parsedParameterRawValue).Msg("[parseParameters] parsed parameter content should be slice")
return nil, errors.New("parsed parameter content should be Slice") return nil, errors.New("parsed parameter content should be slice")
} }
parameterSlice, err = parseSlice(k, parsedParameterRawValue.Interface()) parameterSlice, err = parseSlice(k, parsedParameterRawValue.Interface())
case reflect.Slice: 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()) parameterSlice, err = parseSlice(k, rawValue.Interface())
default: default:
log.Error().Interface("parameter", parameters).Msg("[parseParameters] 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(variables or functions call)") return nil, errors.New("parameter content should be slice or text(functions call)")
} }
if err != nil { if err != nil {
return nil, err return nil, err
@@ -553,7 +556,7 @@ func parseSlice(parameterName string, parameterContent interface{}) ([]map[strin
var parameterSlice []map[string]interface{} var parameterSlice []map[string]interface{}
parameterContentSlice := reflect.ValueOf(parameterContent) parameterContentSlice := reflect.ValueOf(parameterContent)
if parameterContentSlice.Kind() != reflect.Slice { 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++ { for i := 0; i < parameterContentSlice.Len(); i++ {
parameterMap := make(map[string]interface{}) 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"]] // e.g. "username-password": [["test1", "passwd1"], ["test2", "passwd2"]]
// -> [{"username": "test1", "password": "passwd1"}, {"username": "test2", "password": "passwd2"}] // -> [{"username": "test1", "password": "passwd1"}, {"username": "test2", "password": "passwd2"}]
if len(parameterNameSlice) != elem.Len() { 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") 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") return nil, errors.New("parameter name slice and parameter content slice should have the same length")
} else { } else {
for j := 0; j < elem.Len(); j++ { for j := 0; j < elem.Len(); j++ {
parameterMap[parameterNameSlice[j]] = elem.Index(j).Interface() 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) { func initParameterIterator(cfg *TConfig, mode string) (err error) {
var parameters paramsType var parameters paramsType
parameters, err = parseParameters(cfg.Parameters, cfg.Variables) parameters, err = parseParameters(cfg.Parameters, cfg.Variables)
cfg.ParameterIterator = parameters.Iterator() cfg.ParametersSetting.Iterator = parameters.Iterator()
if err != nil { if err != nil {
return err return err
} }
@@ -607,17 +610,17 @@ func initParameterIterator(cfg *TConfig, mode string) (err error) {
cfg.ParametersSetting = &TParamsConfig{} cfg.ParametersSetting = &TParamsConfig{}
} }
if len(cfg.ParametersSetting.Strategy) == 0 { if len(cfg.ParametersSetting.Strategy) == 0 {
cfg.ParametersSetting.Strategy = "sequential" cfg.ParametersSetting.Strategy = strategySequential
} else { } else {
cfg.ParametersSetting.Strategy = strings.ToLower(cfg.ParametersSetting.Strategy) cfg.ParametersSetting.Strategy = strings.ToLower(cfg.ParametersSetting.Strategy)
} }
cfg.ParameterIterator.strategy = cfg.ParametersSetting.Strategy cfg.ParametersSetting.Iterator.strategy = cfg.ParametersSetting.Strategy
if mode == "boomer" { if mode == "boomer" {
cfg.ParametersSetting.Iteration = -1 cfg.ParametersSetting.Iteration = -1
cfg.ParameterIterator.iteration = cfg.ParametersSetting.Iteration cfg.ParametersSetting.Iterator.iteration = cfg.ParametersSetting.Iteration
} else { } else {
if cfg.ParametersSetting.Iteration != 0 { if cfg.ParametersSetting.Iteration != 0 {
cfg.ParameterIterator.iteration = cfg.ParametersSetting.Iteration cfg.ParametersSetting.Iterator.iteration = cfg.ParametersSetting.Iteration
} }
} }
return nil return nil

View File

@@ -143,7 +143,7 @@ func (r *caseRunner) run() error {
} }
cfg := config.ToStruct() cfg := config.ToStruct()
log.Info().Str("testcase", config.Name()).Msg("run testcase start") 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) cfg.Variables = mergeVariables(it.Next(), cfg.Variables)
r.startTime = time.Now() r.startTime = time.Now()
for index := range r.TestCase.TestSteps { for index := range r.TestCase.TestSteps {