feat: data-driven.

This commit is contained in:
徐聪
2021-12-30 21:28:05 +08:00
parent 1180511d8f
commit 57816f504b
7 changed files with 141 additions and 57 deletions
+9 -8
View File
@@ -46,15 +46,12 @@ func (b *hrpBoomer) Run(testcases ...ITestCase) {
panic(err) panic(err)
} }
cfg := testcase.Config.ToStruct() cfg := testcase.Config.ToStruct()
parameters := getParameters(testcase.Config) err = initParameterIterator(cfg, "boomer")
if parameters == nil { if err != nil {
parameters = []map[string]interface{}{{}} panic(err)
}
for _, parameter := range parameters {
cfg.Variables = mergeVariables(parameter, cfg.Variables)
task := b.convertBoomerTask(testcase)
taskSlice = append(taskSlice, task)
} }
task := b.convertBoomerTask(testcase)
taskSlice = append(taskSlice, task)
} }
b.Boomer.Run(taskSlice...) b.Boomer.Run(taskSlice...)
} }
@@ -71,6 +68,10 @@ func (b *hrpBoomer) convertBoomerTask(testcase *TestCase) *boomer.Task {
testcaseSuccess := true // flag whole testcase result testcaseSuccess := true // flag whole testcase result
var transactionSuccess = true // flag current transaction result var transactionSuccess = true // flag current transaction result
cfg := testcase.Config.ToStruct()
if it := cfg.ParameterIterator; it.HasNext() {
cfg.Variables = mergeVariables(it.Next(), cfg.Variables)
}
startTime := time.Now() startTime := time.Now()
for index, step := range testcase.TestSteps { for index, step := range testcase.TestSteps {
stepData, err := runner.runStep(index) stepData, err := runner.runStep(index)
+2 -5
View File
@@ -6,14 +6,11 @@
"iOS/10.1", "iOS/10.1",
"iOS/10.2" "iOS/10.2"
], ],
"username1-password1": [
["a", "123"],
["b", "456"]
],
"username-password": "${parameterize(examples/account.csv)}" "username-password": "${parameterize(examples/account.csv)}"
}, },
"parameters_setting": { "parameters_setting": {
"strategy": "random" "strategy": "random",
"iteration": 1
}, },
"variables": { "variables": {
"app_version": "f1" "app_version": "f1"
+1
View File
@@ -5,6 +5,7 @@ config:
username-password: ${parameterize(examples/account.csv)} username-password: ${parameterize(examples/account.csv)}
parameters_setting: parameters_setting:
strategy: random strategy: random
iteration: 1
variables: variables:
app_version: f1 app_version: f1
base_url: "https://postman-echo.com" base_url: "https://postman-echo.com"
+52 -1
View File
@@ -1,5 +1,11 @@
package hrp package hrp
import (
"math/rand"
"strings"
"time"
)
const ( const (
httpGET string = "GET" httpGET string = "GET"
httpHEAD string = "HEAD" httpHEAD string = "HEAD"
@@ -18,11 +24,56 @@ type TConfig struct {
BaseURL string `json:"base_url,omitempty" yaml:"base_url,omitempty"` BaseURL string `json:"base_url,omitempty" yaml:"base_url,omitempty"`
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 map[string]interface{} `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 {
Strategy string `json:"strategy,omitempty" yaml:"strategy,omitempty"`
Iteration int `json:"iteration,omitempty" yaml:"iteration,omitempty"`
}
type paramsType []map[string]interface{}
type Iterator struct {
data paramsType
strategy string
iteration int
index int
}
func (params paramsType) Iterator() *Iterator {
return &Iterator{
data: params,
iteration: len(params),
index: 0,
}
}
func (iter *Iterator) HasNext() bool {
if iter.iteration == -1 {
return true
}
return iter.index < iter.iteration
}
func (iter *Iterator) Next() (value map[string]interface{}) {
iter.index++
if len(iter.data) == 0 {
return map[string]interface{}{}
}
if strings.ToLower(iter.strategy) == "random" {
randSource := rand.New(rand.NewSource(time.Now().Unix()))
randIndex := randSource.Intn(len(iter.data))
value = iter.data[randIndex]
} else {
value = iter.data[iter.index%len(iter.data)]
}
return value
}
// Request represents HTTP request data structure. // Request represents HTTP request data structure.
// This is used for teststep. // This is used for teststep.
type Request struct { type Request struct {
+37 -35
View File
@@ -3,16 +3,13 @@ package hrp
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/rand" "github.com/maja42/goval"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"net/url" "net/url"
"reflect" "reflect"
"regexp" "regexp"
"strings" "strings"
"time"
"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"
) )
@@ -496,19 +493,6 @@ func findallVariables(raw string) variableSet {
return varSet return varSet
} }
func shuffleCartesianProduct(slice []map[string]interface{}) {
if len(slice) == 0 {
return
}
r := rand.New(rand.NewSource(time.Now().Unix()))
for len(slice) > 0 {
n := len(slice)
randIndex := r.Intn(n)
slice[n-1], slice[randIndex] = slice[randIndex], slice[n-1]
slice = slice[:n-1]
}
}
func genCartesianProduct(params [][]map[string]interface{}) []map[string]interface{} { func genCartesianProduct(params [][]map[string]interface{}) []map[string]interface{} {
if len(params) == 0 { if len(params) == 0 {
return nil return nil
@@ -527,19 +511,6 @@ func genCartesianProduct(params [][]map[string]interface{}) []map[string]interfa
return cartesianProduct return cartesianProduct
} }
func getParameters(config IConfig) []map[string]interface{} {
cfg := config.ToStruct()
// parse config parameters
parsedParams, err := parseParameters(cfg.Parameters, cfg.Variables)
if err != nil {
log.Error().Interface("parameters", cfg.Parameters).Err(err).Msg("parse config parameters failed")
}
if cfg.ParametersSetting["strategy"] != nil && strings.ToLower(cfg.ParametersSetting["strategy"].(string)) == "random" {
shuffleCartesianProduct(parsedParams)
}
return parsedParams
}
func parseParameters(parameters map[string]interface{}, variablesMapping map[string]interface{}) ([]map[string]interface{}, error) { func parseParameters(parameters map[string]interface{}, variablesMapping map[string]interface{}) ([]map[string]interface{}, error) {
if len(parameters) == 0 { if len(parameters) == 0 {
return nil, nil return nil, nil
@@ -562,9 +533,9 @@ func parseParameters(parameters map[string]interface{}, variablesMapping map[str
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, got %v")
return nil, errors.New("parsed parameter content should be Slice") return nil, errors.New("parsed parameter content should be Slice")
} }
parameterSlice, err = handleSlice(k, parsedParameterRawValue.Interface()) parameterSlice, err = parseSlice(k, parsedParameterRawValue.Interface())
case reflect.Slice: case reflect.Slice:
parameterSlice, err = handleSlice(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(variables or 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(variables or functions call)")
@@ -577,10 +548,13 @@ func parseParameters(parameters map[string]interface{}, variablesMapping map[str
return genCartesianProduct(parsedParametersSlice), nil return genCartesianProduct(parsedParametersSlice), nil
} }
func handleSlice(parameterName string, parameterContent interface{}) ([]map[string]interface{}, error) { func parseSlice(parameterName string, parameterContent interface{}) ([]map[string]interface{}, error) {
parameterNameSlice := strings.Split(parameterName, "-") parameterNameSlice := strings.Split(parameterName, "-")
var parameterSlice []map[string]interface{} var parameterSlice []map[string]interface{}
parameterContentSlice := reflect.ValueOf(parameterContent) parameterContentSlice := reflect.ValueOf(parameterContent)
if parameterContentSlice.Kind() != reflect.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{})
elem := reflect.ValueOf(parameterContentSlice.Index(i).Interface()) elem := reflect.ValueOf(parameterContentSlice.Index(i).Interface())
@@ -620,3 +594,31 @@ func handleSlice(parameterName string, parameterContent interface{}) ([]map[stri
} }
return parameterSlice, nil return parameterSlice, nil
} }
func initParameterIterator(cfg *TConfig, mode string) (err error) {
var parameters paramsType
parameters, err = parseParameters(cfg.Parameters, cfg.Variables)
cfg.ParameterIterator = parameters.Iterator()
if err != nil {
return err
}
// parse config parameters setting
if cfg.ParametersSetting == nil {
cfg.ParametersSetting = &TParamsConfig{}
}
if len(cfg.ParametersSetting.Strategy) == 0 {
cfg.ParametersSetting.Strategy = "sequential"
} else {
cfg.ParametersSetting.Strategy = strings.ToLower(cfg.ParametersSetting.Strategy)
}
cfg.ParameterIterator.strategy = cfg.ParametersSetting.Strategy
if mode == "boomer" {
cfg.ParametersSetting.Iteration = -1
cfg.ParameterIterator.iteration = cfg.ParametersSetting.Iteration
} else {
if cfg.ParametersSetting.Iteration != 0 {
cfg.ParameterIterator.iteration = cfg.ParametersSetting.Iteration
}
}
return nil
}
+24 -2
View File
@@ -692,7 +692,7 @@ func TestParseParametersError(t *testing.T) {
} }
} }
func TestHandleSlice(t *testing.T) { func TestParseSlice(t *testing.T) {
testData := []struct { testData := []struct {
rawVar1 string rawVar1 string
rawVar2 interface{} rawVar2 interface{}
@@ -724,9 +724,31 @@ func TestHandleSlice(t *testing.T) {
}, },
} }
for _, data := range testData { for _, data := range testData {
value, _ := handleSlice(data.rawVar1, data.rawVar2) value, _ := parseSlice(data.rawVar1, data.rawVar2)
if !assert.Equal(t, data.expect, value) { if !assert.Equal(t, data.expect, value) {
t.Fail() t.Fail()
} }
} }
} }
func TestParseSliceError(t *testing.T) {
testData := []struct {
rawVar1 string
rawVar2 interface{}
}{
{
"app_version",
123,
},
{
"app_version",
"123",
},
}
for _, data := range testData {
_, err := parseSlice(data.rawVar1, data.rawVar2)
if !assert.Error(t, err) {
t.Fail()
}
}
}
+16 -6
View File
@@ -141,13 +141,17 @@ func (r *caseRunner) run() error {
if err := r.parseConfig(config); err != nil { if err := r.parseConfig(config); err != nil {
return err return err
} }
cfg := config.ToStruct()
log.Info().Str("testcase", config.Name()).Msg("run testcase start") log.Info().Str("testcase", config.Name()).Msg("run testcase start")
r.startTime = time.Now() for it := cfg.ParameterIterator; it.HasNext(); {
for index := range r.TestCase.TestSteps { cfg.Variables = mergeVariables(it.Next(), cfg.Variables)
_, err := r.runStep(index) r.startTime = time.Now()
if err != nil { for index := range r.TestCase.TestSteps {
if r.hrpRunner.failfast { _, err := r.runStep(index)
return errors.Wrap(err, "abort running due to failfast setting") if err != nil {
if r.hrpRunner.failfast {
return errors.Wrap(err, "abort running due to failfast setting")
}
} }
} }
} }
@@ -482,6 +486,12 @@ func (r *caseRunner) parseConfig(config IConfig) error {
return err return err
} }
cfg.Variables = parsedVariables cfg.Variables = parsedVariables
// parse config parameters
err = initParameterIterator(cfg, "runner")
if err != nil {
log.Error().Interface("parameters", cfg.Parameters).Err(err).Msg("parse config parameters failed")
return err
}
// parse config name // parse config name
parsedName, err := parseString(cfg.Name, cfg.Variables) parsedName, err := parseString(cfg.Name, cfg.Variables)
if err != nil { if err != nil {