From 30449b624a593c546137177ba09c0089ad3bf26f Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 16 Apr 2022 09:14:12 +0800 Subject: [PATCH] change: add unittests for init parameters iterator --- examples/hrp/parameters_test.json | 9 +- examples/hrp/parameters_test.yaml | 7 +- hrp/parameters.go | 33 ++++- hrp/parameters_test.go | 192 +++++++++++++++++++++++++++--- hrp/session.go | 9 +- 5 files changed, 223 insertions(+), 27 deletions(-) diff --git a/examples/hrp/parameters_test.json b/examples/hrp/parameters_test.json index f379f326..84c36531 100644 --- a/examples/hrp/parameters_test.json +++ b/examples/hrp/parameters_test.json @@ -6,18 +6,19 @@ "iOS/10.1", "iOS/10.2" ], - "username-password": "${parameterize(examples/hrp/account.csv)}" + "username-password": "${parameterize($file)}" }, "parameters_setting": { - "strategy": { + "strategies": { "user_agent": "sequential", "username-password": "random" }, - "iteration": 6 + "limit": 6 }, "variables": { "app_version": "v1", - "user_agent": "iOS/10.3" + "user_agent": "iOS/10.3", + "file": "examples/hrp/account.csv" }, "base_url": "https://postman-echo.com", "verify": false diff --git a/examples/hrp/parameters_test.yaml b/examples/hrp/parameters_test.yaml index 7c3ab523..b5d06c71 100644 --- a/examples/hrp/parameters_test.yaml +++ b/examples/hrp/parameters_test.yaml @@ -2,15 +2,16 @@ config: name: "request methods testcase: validate with parameters" parameters: user_agent: [ "iOS/10.1", "iOS/10.2" ] - username-password: ${parameterize(examples/hrp/account.csv)} + username-password: ${parameterize($file)} parameters_setting: - strategy: + strategies: user_agent: "sequential" username-password: "random" - iteration: 6 + limit: 6 variables: app_version: v1 user_agent: iOS/10.3 + file: examples/hrp/account.csv base_url: "https://postman-echo.com" verify: False diff --git a/hrp/parameters.go b/hrp/parameters.go index a42f87eb..9b263696 100644 --- a/hrp/parameters.go +++ b/hrp/parameters.go @@ -42,6 +42,9 @@ func initParametersIterator(cfg *TConfig) (*ParametersIterator, error) { } func newParametersIterator(parameters map[string]Parameters, config *TParamsConfig) *ParametersIterator { + if config == nil { + config = &TParamsConfig{} + } iterator := &ParametersIterator{ data: parameters, hasNext: true, @@ -52,7 +55,8 @@ func newParametersIterator(parameters map[string]Parameters, config *TParamsConf } if len(parameters) == 0 { - iterator.hasNext = false + iterator.data = map[string]Parameters{} + iterator.limit = 1 return iterator } @@ -75,12 +79,23 @@ func newParametersIterator(parameters map[string]Parameters, config *TParamsConf // generate cartesian product for sequential parameters iterator.sequentialParameters = genCartesianProduct(parametersList) + + if iterator.limit < 0 { + log.Warn().Msg("parameters unlimited mode is only supported for load testing") + iterator.limit = 0 + } if iterator.limit == 0 { + // limit not set if len(iterator.sequentialParameters) > 0 { + // use cartesian product of sequential parameters size as limit iterator.limit = len(iterator.sequentialParameters) } else { + // all parameters are selected by random + // only run once iterator.limit = 1 } + } else { // limit > 0 + log.Info().Int("limit", iterator.limit).Msg("set limit for parameters") } return iterator @@ -98,6 +113,7 @@ type ParametersIterator struct { // SetUnlimitedMode is used for load testing func (iter *ParametersIterator) SetUnlimitedMode() { + log.Info().Msg("set parameters unlimited mode") iter.limit = -1 } @@ -134,15 +150,24 @@ func (iter *ParametersIterator) Next() map[string]interface{} { return nil } - selectedParameters := make(map[string]interface{}) - if iter.index < len(iter.sequentialParameters) { + var selectedParameters map[string]interface{} + if len(iter.sequentialParameters) == 0 { + selectedParameters = make(map[string]interface{}) + } else if iter.index < len(iter.sequentialParameters) { selectedParameters = iter.sequentialParameters[iter.index] + } else { + // loop back to the first sequential parameter + index := iter.index % len(iter.sequentialParameters) + selectedParameters = iter.sequentialParameters[index] } + // merge with random parameters for _, paramName := range iter.randomParameterNames { randSource := rand.New(rand.NewSource(time.Now().Unix())) randIndex := randSource.Intn(len(iter.data[paramName])) - selectedParameters[paramName] = iter.data[paramName][randIndex] + for k, v := range iter.data[paramName][randIndex] { + selectedParameters[k] = v + } } iter.index++ diff --git a/hrp/parameters_test.go b/hrp/parameters_test.go index 5f834313..f54516c3 100644 --- a/hrp/parameters_test.go +++ b/hrp/parameters_test.go @@ -4,7 +4,6 @@ import ( "fmt" "testing" - "github.com/rs/zerolog/log" "github.com/stretchr/testify/assert" ) @@ -31,7 +30,7 @@ func TestLoadParameters(t *testing.T) { {"test1", "111111"}, {"test2", "222222"}, }, - "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}, + "user_agent": []interface{}{"iOS/10.1", "iOS/10.2"}, "app_version": []interface{}{4.0}, }, map[string]Parameters{ @@ -40,8 +39,8 @@ func TestLoadParameters(t *testing.T) { {"username": "test2", "password": "222222"}, }, "user_agent": { - {"user_agent": "IOS/10.1"}, - {"user_agent": "IOS/10.2"}, + {"user_agent": "iOS/10.1"}, + {"user_agent": "iOS/10.2"}, }, "app_version": { {"app_version": 4.0}, @@ -79,17 +78,17 @@ func TestLoadParametersError(t *testing.T) { { map[string]interface{}{ "username_password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir), - "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, + "user_agent": []interface{}{"iOS/10.1", "iOS/10.2"}}, }, { map[string]interface{}{ "username-password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir), - "user-agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, + "user-agent": []interface{}{"iOS/10.1", "iOS/10.2"}}, }, { map[string]interface{}{ "username-password": fmt.Sprintf("${param(%s/account.csv)}", hrpExamplesDir), - "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, + "user_agent": []interface{}{"iOS/10.1", "iOS/10.2"}}, }, } for _, data := range testData { @@ -100,23 +99,69 @@ func TestLoadParametersError(t *testing.T) { } } -func TestInitParametersIterator(t *testing.T) { +func TestInitParametersIteratorCount(t *testing.T) { configParameters := map[string]interface{}{ "username-password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir), // 3 - "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}, - "app_version": []interface{}{4.0}, + "user_agent": []interface{}{"iOS/10.1", "iOS/10.2"}, // 2 + "app_version": []interface{}{4.0}, // 1 } testData := []struct { cfg *TConfig expectLimit int }{ + // default, no parameters setting { &TConfig{ Parameters: configParameters, ParametersSetting: &TParamsConfig{}, }, - 6, + 6, // 3 * 2 * 1 }, + { + &TConfig{ + Parameters: configParameters, + }, + 6, // 3 * 2 * 1 + }, + // default equals to set overall parameters strategy to "sequential" + { + &TConfig{ + Parameters: configParameters, + ParametersSetting: &TParamsConfig{ + Strategy: "sequential", + }, + }, + 6, // 3 * 2 * 1 + }, + // default equals to set each individual parameters strategy to "sequential" + { + &TConfig{ + Parameters: configParameters, + ParametersSetting: &TParamsConfig{ + Strategies: map[string]iteratorStrategy{ + "username-password": "sequential", + "user_agent": "sequential", + "app_version": "sequential", + }, + }, + }, + 6, // 3 * 2 * 1 + }, + { + &TConfig{ + Parameters: configParameters, + ParametersSetting: &TParamsConfig{ + Strategies: map[string]iteratorStrategy{ + "user_agent": "sequential", + "app_version": "sequential", + }, + }, + }, + 6, // 3 * 2 * 1 + }, + + // set overall parameters overall strategy to "random" + // each random parameters only select one item { &TConfig{ Parameters: configParameters, @@ -124,7 +169,20 @@ func TestInitParametersIterator(t *testing.T) { Strategy: "random", }, }, - 1, + 1, // 1 * 1 * 1 + }, + // set some individual parameters strategy to "random" + // this will override overall strategy + { + &TConfig{ + Parameters: configParameters, + ParametersSetting: &TParamsConfig{ + Strategies: map[string]iteratorStrategy{ + "user_agent": "random", + }, + }, + }, + 3, // 3 * 1 * 1 }, { &TConfig{ @@ -135,7 +193,37 @@ func TestInitParametersIterator(t *testing.T) { }, }, }, - 2, + 2, // 1 * 2 * 1 + }, + + // set limit for parameters + { + &TConfig{ + Parameters: configParameters, // total: 6 = 3 * 2 * 1 + ParametersSetting: &TParamsConfig{ + Limit: 4, // limit could be less than total + }, + }, + 4, + }, + { + &TConfig{ + Parameters: configParameters, // total: 6 = 3 * 2 * 1 + ParametersSetting: &TParamsConfig{ + Limit: 9, // limit could also be greater than total + }, + }, + 9, + }, + + // no parameters + // also will generate one empty item + { + &TConfig{ + Parameters: nil, + ParametersSetting: nil, + }, + 1, }, } for _, data := range testData { @@ -151,7 +239,7 @@ func TestInitParametersIterator(t *testing.T) { if !assert.True(t, iterator.HasNext()) { t.Fatal() } - log.Info().Interface("next", iterator.Next()).Msg("get next parameters") + iterator.Next() // consume next parameters } // should not have next if !assert.False(t, iterator.HasNext()) { @@ -160,6 +248,82 @@ func TestInitParametersIterator(t *testing.T) { } } +func TestInitParametersIteratorContent(t *testing.T) { + configParameters := map[string]interface{}{ + "username-password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir), // 3 + "user_agent": []interface{}{"iOS/10.1", "iOS/10.2"}, // 2 + "app_version": []interface{}{4.0}, // 1 + } + testData := []struct { + cfg *TConfig + checkIndex int + expectParameters map[string]interface{} + }{ + // default, no parameters setting + { + &TConfig{ + Parameters: configParameters, + }, + 0, // check first item + map[string]interface{}{ + "username": "test1", "password": "111111", "user_agent": "iOS/10.1", "app_version": 4.0, + }, + }, + + // set limit for parameters + { + &TConfig{ + Parameters: map[string]interface{}{ + "username-password": []map[string]interface{}{ // 1 + {"username": "test1", "password": 111111, "other": "111"}, + }, + "user_agent": []string{"iOS/10.1", "iOS/10.2"}, // 2 + }, + ParametersSetting: &TParamsConfig{ + Limit: 5, // limit could also be greater than total + Strategies: map[string]iteratorStrategy{ + "username-password": "random", + }, + }, + }, + 2, // check 3th item, equals to the first item + map[string]interface{}{ + "username": "test1", "password": 111111, "user_agent": "iOS/10.1", + }, + }, + + // no parameters + // also will generate one empty item + { + &TConfig{ + Parameters: nil, + ParametersSetting: nil, + }, + 0, + map[string]interface{}(nil), + }, + } + for _, data := range testData { + iterator, err := initParametersIterator(data.cfg) + if !assert.Nil(t, err) { + t.Fatal() + } + + // get expected parameters item + for i := 0; i < data.checkIndex; i++ { + if !assert.True(t, iterator.HasNext()) { + t.Fatal() + } + iterator.Next() // consume next parameters + } + parametersItem := iterator.Next() + + if !assert.Equal(t, data.expectParameters, parametersItem) { + t.Fatal() + } + } +} + func TestGenCartesianProduct(t *testing.T) { testData := []struct { multiParameters []Parameters diff --git a/hrp/session.go b/hrp/session.go index fa4dbfa0..8172d2ab 100644 --- a/hrp/session.go +++ b/hrp/session.go @@ -111,8 +111,13 @@ func (r *SessionRunner) MergeStepVariables(vars map[string]interface{}) (map[str // updateConfigVariables updates config variables with given variables. // this is used for data driven -func (r *SessionRunner) updateConfigVariables(givenVars map[string]interface{}) { - for k, v := range givenVars { +func (r *SessionRunner) updateConfigVariables(parameters map[string]interface{}) { + if parameters == nil { + return + } + + log.Info().Interface("parameters", parameters).Msg("update config variables") + for k, v := range parameters { r.parsedConfig.Variables[k] = v } }