Files
httprunner/runner.go
2021-10-16 23:14:16 +08:00

241 lines
5.6 KiB
Go

package hrp
import (
"log"
"net/http"
"testing"
"github.com/imroc/req"
"github.com/pkg/errors"
)
// run API test with default configs
func Run(t *testing.T, testcases ...ITestCase) error {
return NewRunner().WithTestingT(t).SetDebug(true).Run(testcases...)
}
func NewRunner() *Runner {
return &Runner{
t: &testing.T{},
debug: false, // default to turn off debug
client: req.New(),
}
}
type Runner struct {
t *testing.T
debug bool
client *req.Req
}
func (r *Runner) WithTestingT(t *testing.T) *Runner {
log.Printf("[init] WithTestingT: %v", t)
r.t = t
return r
}
func (r *Runner) SetDebug(debug bool) *Runner {
log.Printf("[init] SetDebug: %v", debug)
r.debug = debug
return r
}
func (r *Runner) SetProxyUrl(proxyUrl string) *Runner {
log.Printf("[init] SetProxyUrl: %s", proxyUrl)
r.client.SetProxyUrl(proxyUrl)
return r
}
func (r *Runner) Run(testcases ...ITestCase) error {
for _, iTestCase := range testcases {
testcase, err := iTestCase.ToTestCase()
if err != nil {
log.Printf("[Run] testcase.ToStruct() error: %v", err)
return err
}
if err := r.runCase(testcase); err != nil {
log.Printf("[Run] runCase error: %v", err)
return err
}
}
return nil
}
func (r *Runner) runCase(testcase *TestCase) error {
config := &testcase.Config
if err := r.parseConfig(config); err != nil {
return err
}
log.Printf("Start to run testcase: %v", config.Name)
extractedVariables := make(map[string]interface{})
for _, step := range testcase.TestSteps {
// override variables
// step variables > extracted variables from previous steps
stepVariables := mergeVariables(step.ToStruct().Variables, extractedVariables)
// step variables > testcase config variables
stepVariables = mergeVariables(stepVariables, config.Variables)
// parse step variables
parsedVariables, err := parseVariables(stepVariables)
if err != nil {
log.Printf("[parseConfig] parse variables: %v, error: %v", config.Variables, err)
return err
}
step.ToStruct().Variables = parsedVariables
stepData, err := r.runStep(step, config)
if err != nil {
return err
}
// update extracted variables
for k, v := range stepData.ExportVars {
extractedVariables[k] = v
}
}
return nil
}
func (r *Runner) runStep(step IStep, config *TConfig) (stepData *StepData, err error) {
log.Printf("run step [%v] begin >>>>>>", step.Name())
if tc, ok := step.(*testcaseWithOptionalArgs); ok {
// run referenced testcase
log.Printf("run referenced testcase: %v", tc.step.Name)
// TODO: override testcase config
stepData, err = r.runStepTestCase(tc.step)
if err != nil {
return
}
} else {
// run request
tStep := parseStep(step, config)
stepData, err = r.runStepRequest(tStep)
if err != nil {
return
}
}
log.Printf("run step [%v] end, success: %v, responseLength: %v <<<<<<\n",
step.Name(), stepData.Success, stepData.ResponseLength)
return
}
func (r *Runner) runStepRequest(step *TStep) (stepData *StepData, err error) {
stepData = &StepData{
Name: step.Name,
Success: false,
ResponseLength: 0,
}
// prepare request args
var v []interface{}
if len(step.Request.Headers) > 0 {
headers, err := parseHeaders(step.Request.Headers, step.Variables)
if err != nil {
return nil, err
}
v = append(v, req.Header(headers))
}
if len(step.Request.Params) > 0 {
params, err := parseData(step.Request.Params, step.Variables)
if err != nil {
return nil, err
}
v = append(v, req.Param(params.(map[string]interface{})))
}
if step.Request.Body != nil {
data, err := parseData(step.Request.Body, step.Variables)
if err != nil {
return nil, err
}
switch data.(type) {
case map[string]interface{}: // post json
v = append(v, req.BodyJSON(data))
default: // post raw data
v = append(v, data)
}
}
for cookieName, cookieValue := range step.Request.Cookies {
v = append(v, &http.Cookie{
Name: cookieName,
Value: cookieValue,
})
}
// do request action
req.Debug = r.debug
resp, err := r.client.Do(string(step.Request.Method), step.Request.URL, v...)
if err != nil {
return
}
defer resp.Response().Body.Close()
// new response object
respObj, err := NewResponseObject(r.t, resp)
if err != nil {
err = errors.Wrap(err, "init ResponseObject error")
return
}
// extract variables from response
extractors := step.Extract
extractMapping := respObj.Extract(extractors)
stepData.ExportVars = extractMapping
// override step variables with extracted variables
stepVariables := mergeVariables(step.Variables, extractMapping)
// validate response
err = respObj.Validate(step.Validators, stepVariables)
if err != nil {
return
}
stepData.Success = true
stepData.ResponseLength = resp.Response().ContentLength
return
}
func (r *Runner) runStepTestCase(step *TStep) (stepData *StepData, err error) {
stepData = &StepData{
Name: step.Name,
Success: false,
}
testcase := step.TestCase
err = r.runCase(testcase)
return
}
func (r *Runner) parseConfig(config *TConfig) error {
// parse config variables
parsedVariables, err := parseVariables(config.Variables)
if err != nil {
log.Printf("[parseConfig] parse variables: %v, error: %v", config.Variables, err)
return err
}
config.Variables = parsedVariables
// parse config name
parsedName, err := parseString(config.Name, config.Variables)
if err != nil {
return err
}
config.Name = convertString(parsedName)
// parse config base url
parsedBaseURL, err := parseString(config.BaseURL, config.Variables)
if err != nil {
return err
}
config.BaseURL = convertString(parsedBaseURL)
return nil
}
func (r *Runner) GetSummary() *TestCaseSummary {
return &TestCaseSummary{}
}