mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-02 22:39:42 +08:00
feat: data-driven.
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
16
models.go
16
models.go
@@ -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]
|
||||||
|
|||||||
35
parser.go
35
parser.go
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user