init HttpBommer 🍰

This commit is contained in:
debugtalk
2021-09-19 13:20:22 +08:00
parent 6f824ea1cf
commit 6b7955a955
15 changed files with 561 additions and 1 deletions

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"cSpell.words": [
"debugtalk"
]
}

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2021 debugtalk
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

44
boomer.go Normal file
View File

@@ -0,0 +1,44 @@
package httpboomer
import (
"time"
"github.com/myzhan/boomer"
)
func HttpBoomer() *Boomer {
return &Boomer{}
}
type Boomer struct {
}
func (b *Boomer) Run(testcases ...*TestCase) {
var taskSlice []*boomer.Task
for _, testcase := range testcases {
task := convertBoomerTask(testcase)
taskSlice = append(taskSlice, task)
}
boomer.Run(taskSlice...)
}
func convertBoomerTask(testcase *TestCase) *boomer.Task {
return &boomer.Task{
Name: testcase.Config.Name,
Weight: testcase.Config.Weight,
Fn: func() {
for _, step := range testcase.TestSteps {
start := time.Now()
err := step.Run()
elapsed := time.Since(start).Nanoseconds() / int64(time.Millisecond)
tStep := step.ToStruct()
if err == nil {
boomer.RecordSuccess(string(tStep.Request.Method), tStep.Name, elapsed, int64(0))
} else {
boomer.RecordFailure(string(tStep.Request.Method), tStep.Name, elapsed, err.Error())
}
}
},
}
}

22
boomer_test.go Normal file
View File

@@ -0,0 +1,22 @@
package httpboomer
import (
"testing"
)
func TestHttpBoomer(t *testing.T) {
testcase1 := &TestCase{
Config: TConfig{
Name: "TestCase1",
Weight: 2,
},
}
testcase2 := &TestCase{
Config: TConfig{
Name: "TestCase2",
Weight: 3,
},
}
HttpBoomer().Run(testcase1, testcase2)
}

25
extract.go Normal file
View File

@@ -0,0 +1,25 @@
package httpboomer
// implements IStep interface
type StepRequestExtraction struct {
*TStep
}
func (req *StepRequestExtraction) WithJmesPath(jmesPath string, varName string) *StepRequestExtraction {
req.TStep.Extract[varName] = jmesPath
return req
}
func (req *StepRequestExtraction) Validate() *StepRequestValidation {
return &StepRequestValidation{
TStep: req.TStep,
}
}
func (req *StepRequestExtraction) ToStruct() *TStep {
return req.TStep
}
func (req *StepRequestExtraction) Run() error {
return req.TStep.Run()
}

19
go.mod Normal file
View File

@@ -0,0 +1,19 @@
module github.com/httprunner/httpboomer
go 1.16
require (
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/myzhan/boomer v1.6.0
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/shirou/gopsutil v3.21.8+incompatible // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect
github.com/ugorji/go v1.2.6 // indirect
github.com/zeromq/goczmq v4.1.0+incompatible // indirect
github.com/zeromq/gomq v0.0.0-20201031135124-cef4e507bb8e // indirect
github.com/zeromq/gomq/zmtp v0.0.0-20201031135124-cef4e507bb8e // indirect
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 // indirect
)

44
go.sum Normal file
View File

@@ -0,0 +1,44 @@
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef h1:2JGTg6JapxP9/R33ZaagQtAM4EkkSYnIAlOG5EI8gkM=
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/myzhan/boomer v1.6.0 h1:xjgvmhDjgU9IEKnB7nU1HyoVEfj8SuuU3u6oY3Nugj0=
github.com/myzhan/boomer v1.6.0/go.mod h1:Ma68Td5C5EAc1M9XA7yjC/tXg9u5qviNujytnX099ZQ=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shirou/gopsutil v3.21.8+incompatible h1:sh0foI8tMRlCidUJR+KzqWYWxrkuuPIGiO6Vp+KXdCU=
github.com/shirou/gopsutil v3.21.8+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/zeromq/goczmq v4.1.0+incompatible h1:cGVQaU6kIwwrGso0Pgbl84tzAz/h7FJ3wYQjSonjFFc=
github.com/zeromq/goczmq v4.1.0+incompatible/go.mod h1:1uZybAJoSRCvZMH2rZxEwWBSmC4T7CB/xQOfChwPEzg=
github.com/zeromq/gomq v0.0.0-20201031135124-cef4e507bb8e h1:vGjfCnWv/zWeO1ivv4+OUPgTzG/WV1iGfZwVdtUpLkM=
github.com/zeromq/gomq v0.0.0-20201031135124-cef4e507bb8e/go.mod h1:SkCxcSQ7BQEA9FvDzbj+3hV6EMhSywyxWnHwUXVIyLY=
github.com/zeromq/gomq/zmtp v0.0.0-20201031135124-cef4e507bb8e h1:pjp04/sSr2TYuaPdt+u6Cc1M38Aocp+3er0akr3auFg=
github.com/zeromq/gomq/zmtp v0.0.0-20201031135124-cef4e507bb8e/go.mod h1:LBjWEodY/ESvKRwLw3bc7mhn49oiI8qlXUqeqLn0pcU=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 h1:J27LZFQBFoihqXoegpscI10HpjZ7B5WQLLKL2FZXQKw=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

72
models.go Normal file
View File

@@ -0,0 +1,72 @@
package httpboomer
type Variables map[string]interface{}
type Params map[string]interface{}
type Headers map[string]string
type Cookies map[string]string
type enumHTTPMethod string
const (
GET enumHTTPMethod = "GET"
HEAD enumHTTPMethod = "HEAD"
POST enumHTTPMethod = "POST"
PUT enumHTTPMethod = "PUT"
DELETE enumHTTPMethod = "DELETE"
OPTIONS enumHTTPMethod = "OPTIONS"
PATCH enumHTTPMethod = "PATCH"
)
type TConfig struct {
Name string `json:"name"`
Verify bool `json:"verify"`
BaseURL string `json:"base_url"`
Variables Variables `json:"variables"`
Parameters Variables `json:"parameters"`
Export []string `json:"export"`
Weight int `json:"weight"`
}
type TRequest struct {
Method enumHTTPMethod `json:"method"`
URL string `json:"url"`
Params Params `json:"params"`
Headers Headers `json:"headers"`
Cookies Cookies `json:"cookies"`
Data interface{} `json:"data"`
JSON interface{} `json:"json"`
Timeout float32 `json:"timeout"`
AllowRedirects bool `json:"allow_redirects"`
Verify bool `json:"verify"`
}
type TValidator struct {
Check string // get value with jmespath
Comparator string
Expect interface{}
Message string
}
type TStep struct {
Name string `json:"name"`
Request TRequest `json:"request"`
Variables Variables `json:"variables"`
SetupHooks []string `json:"setup_hooks"`
TeardownHooks []string `json:"teardown_hooks"`
Extract map[string]string `json:"extract"`
Validators []TValidator `json:"validators"`
Export []string `json:"export"`
}
// interface for all types of steps
type IStep interface {
ToStruct() *TStep
Run() error
}
type TestCase struct {
Config TConfig `json:"config"`
TestSteps []IStep `json:"teststeps"`
}
type TestCaseSummary struct{}

148
request.go Normal file
View File

@@ -0,0 +1,148 @@
package httpboomer
func RunRequest(name string) *Request {
return &Request{
TStep: &TStep{
Name: name,
Variables: make(Variables),
},
}
}
type Request struct {
*TStep
}
func (req *Request) WithVariables(variables Variables) *Request {
req.TStep.Variables = variables
return req
}
func (req *Request) GET(url string) *RequestWithOptionalArgs {
req.TStep.Request.Method = GET
req.TStep.Request.URL = url
return &RequestWithOptionalArgs{
TStep: req.TStep,
}
}
func (req *Request) HEAD(url string) *RequestWithOptionalArgs {
req.TStep.Request.Method = HEAD
req.TStep.Request.URL = url
return &RequestWithOptionalArgs{
TStep: req.TStep,
}
}
func (req *Request) POST(url string) *RequestWithOptionalArgs {
req.TStep.Request.Method = POST
req.TStep.Request.URL = url
return &RequestWithOptionalArgs{
TStep: req.TStep,
}
}
func (req *Request) PUT(url string) *RequestWithOptionalArgs {
req.TStep.Request.Method = PUT
req.TStep.Request.URL = url
return &RequestWithOptionalArgs{
TStep: req.TStep,
}
}
func (req *Request) DELETE(url string) *RequestWithOptionalArgs {
req.TStep.Request.Method = DELETE
req.TStep.Request.URL = url
return &RequestWithOptionalArgs{
TStep: req.TStep,
}
}
func (req *Request) OPTIONS(url string) *RequestWithOptionalArgs {
req.TStep.Request.Method = OPTIONS
req.TStep.Request.URL = url
return &RequestWithOptionalArgs{
TStep: req.TStep,
}
}
func (req *Request) PATCH(url string) *RequestWithOptionalArgs {
req.TStep.Request.Method = PATCH
req.TStep.Request.URL = url
return &RequestWithOptionalArgs{
TStep: req.TStep,
}
}
func (req *Request) Run() error {
return req.TStep.Run()
}
// implements IStep interface
type RequestWithOptionalArgs struct {
*TStep
}
func (req *RequestWithOptionalArgs) SetVerify(verify bool) *RequestWithOptionalArgs {
req.TStep.Request.Verify = verify
return req
}
func (req *RequestWithOptionalArgs) SetTimeout(timeout float32) *RequestWithOptionalArgs {
req.TStep.Request.Timeout = timeout
return req
}
func (req *RequestWithOptionalArgs) SetProxies(proxies map[string]string) *RequestWithOptionalArgs {
// TODO
return req
}
func (req *RequestWithOptionalArgs) SetAllowRedirects(allowRedirects bool) *RequestWithOptionalArgs {
req.TStep.Request.AllowRedirects = allowRedirects
return req
}
func (req *RequestWithOptionalArgs) SetAuth(auth map[string]string) *RequestWithOptionalArgs {
// TODO
return req
}
func (req *RequestWithOptionalArgs) WithParams(params Params) *RequestWithOptionalArgs {
req.TStep.Request.Params = params
return req
}
func (req *RequestWithOptionalArgs) WithHeaders(headers Headers) *RequestWithOptionalArgs {
req.TStep.Request.Headers = headers
return req
}
func (req *RequestWithOptionalArgs) WithCookies(cookies Cookies) *RequestWithOptionalArgs {
req.TStep.Request.Cookies = cookies
return req
}
func (req *RequestWithOptionalArgs) WithData(data interface{}) *RequestWithOptionalArgs {
req.TStep.Request.Data = data
return req
}
func (req *RequestWithOptionalArgs) WithJSON(json interface{}) *RequestWithOptionalArgs {
req.TStep.Request.JSON = json
return req
}
func (req *RequestWithOptionalArgs) Validate() *StepRequestValidation {
return &StepRequestValidation{
TStep: req.TStep,
}
}
func (req *RequestWithOptionalArgs) ToStruct() *TStep {
return req.TStep
}
func (req *RequestWithOptionalArgs) Run() error {
return req.TStep.Run()
}

79
request_test.go Normal file
View File

@@ -0,0 +1,79 @@
package httpboomer
import (
"testing"
)
var (
tStepGET = RunRequest("get with params").
GET("/get").
WithParams(Params{"foo1": "bar1", "foo2": "bar2"}).
WithHeaders(Headers{"User-Agent": "HttpBoomer"}).
WithCookies(Cookies{"user": "debugtalk"}).
Validate().
AssertEqual("status_code", 200, "check status code")
tStepPOSTData = RunRequest("post form data").
POST("/post").
WithParams(Params{"foo1": "bar1", "foo2": "bar2"}).
WithHeaders(Headers{"User-Agent": "HttpBoomer", "Content-Type": "application/x-www-form-urlencoded"}).
WithData("a=1&b=2").
WithCookies(Cookies{"user": "debugtalk"}).
Validate().
AssertEqual("status_code", 200, "check status code")
)
func TestRunRequestGetToStruct(t *testing.T) {
tStep := tStepGET.ToStruct()
if tStep.Request.Method != GET {
t.Fatalf("tStep.Request.Method != GET")
}
if tStep.Request.URL != "/get" {
t.Fatalf("tStep.Request.URL != '/get'")
}
if tStep.Request.Params["foo1"] != "bar1" || tStep.Request.Params["foo2"] != "bar2" {
t.Fatalf("tStep.Request.Params mismatch")
}
if tStep.Request.Headers["User-Agent"] != "HttpBoomer" {
t.Fatalf("tStep.Request.Headers mismatch")
}
if tStep.Request.Cookies["user"] != "debugtalk" {
t.Fatalf("tStep.Request.Cookies mismatch")
}
if tStep.Validators[0].Check != "status_code" || tStep.Validators[0].Expect != 200 {
t.Fatalf("tStep.Validators mismatch")
}
}
func TestRunRequestPostDataToStruct(t *testing.T) {
tStep := tStepPOSTData.ToStruct()
if tStep.Request.Method != POST {
t.Fatalf("tStep.Request.Method != POST")
}
if tStep.Request.URL != "/post" {
t.Fatalf("tStep.Request.URL != '/post'")
}
if tStep.Request.Params["foo1"] != "bar1" || tStep.Request.Params["foo2"] != "bar2" {
t.Fatalf("tStep.Request.Params mismatch")
}
if tStep.Request.Headers["User-Agent"] != "HttpBoomer" {
t.Fatalf("tStep.Request.Headers mismatch")
}
if tStep.Request.Cookies["user"] != "debugtalk" {
t.Fatalf("tStep.Request.Cookies mismatch")
}
if tStep.Request.Data != "a=1&b=2" {
t.Fatalf("tStep.Request.Data mismatch")
}
if tStep.Validators[0].Check != "status_code" || tStep.Validators[0].Expect != 200 {
t.Fatalf("tStep.Validators mismatch")
}
}
func TestRunRequestRun(t *testing.T) {
if err := tStepGET.Run(); err != nil {
t.Fatalf("tStep.Run() error: %s", err)
}
if err := tStepPOSTData.Run(); err != nil {
t.Fatalf("tStepPOSTData.Run() error: %s", err)
}
}

1
response.go Normal file
View File

@@ -0,0 +1 @@
package httpboomer

34
runner.go Normal file
View File

@@ -0,0 +1,34 @@
package httpboomer
func HttpRunner() *Runner {
return &Runner{}
}
type Runner struct {
}
func (r *Runner) Run(testcases ...*TestCase) error {
for _, testcase := range testcases {
if err := r.runCase(testcase); err != nil {
return err
}
}
return nil
}
func (r *Runner) runCase(testcase *TestCase) error {
for _, step := range testcase.TestSteps {
if err := r.runStep(step); err != nil {
return err
}
}
return nil
}
func (r *Runner) runStep(req IStep) error {
return req.Run()
}
func (r *Runner) GetSummary() *TestCaseSummary {
return &TestCaseSummary{}
}

37
runner_test.go Normal file
View File

@@ -0,0 +1,37 @@
package httpboomer
import (
"testing"
)
func TestHttpRunner(t *testing.T) {
testcase1 := &TestCase{
Config: TConfig{
Name: "TestCase1",
BaseURL: "http://httpbin.org",
},
TestSteps: []IStep{
RunRequest("headers").
GET("/headers").
Validate().
AssertEqual("status_code", 200, "check status code").
AssertEqual("headers.Host", "httpbin.org", "check http response host"),
RunRequest("user-agent").
GET("/user-agent").
Validate().
AssertEqual("status_code", 200, "check status code").
AssertEqual("body.\"user-agent\"", "python-requests", "check User-Agent"),
},
}
testcase2 := &TestCase{
Config: TConfig{
Name: "TestCase2",
Weight: 3,
},
}
err := HttpRunner().Run(testcase1, testcase2)
if err != nil {
t.Fatalf("run testcase error: %v", err)
}
}

5
step.go Normal file
View File

@@ -0,0 +1,5 @@
package httpboomer
func (step *TStep) Run() error {
return nil
}

25
validate.go Normal file
View File

@@ -0,0 +1,25 @@
package httpboomer
// implements IStep interface
type StepRequestValidation struct {
*TStep
}
func (req *StepRequestValidation) AssertEqual(jmesPath string, expected interface{}, msg string) *StepRequestValidation {
validator := TValidator{
Check: jmesPath,
Comparator: "equals",
Expect: expected,
Message: msg,
}
req.TStep.Validators = append(req.TStep.Validators, validator)
return req
}
func (req *StepRequestValidation) ToStruct() *TStep {
return req.TStep
}
func (req *StepRequestValidation) Run() error {
return req.TStep.Run()
}