feat: support to generate robot-framework script (#563)
Co-authored-by: rick <linuxsuren@users.noreply.github.com>
This commit is contained in:
parent
33703da27b
commit
0f0298455e
20
cmd/run.go
20
cmd/run.go
|
@ -51,7 +51,7 @@ type runOption struct {
|
|||
duration time.Duration
|
||||
requestTimeout time.Duration
|
||||
requestIgnoreError bool
|
||||
caseFilter string
|
||||
caseFilter []string
|
||||
thread int64
|
||||
context context.Context
|
||||
qps int32
|
||||
|
@ -117,7 +117,7 @@ See also https://github.com/LinuxSuRen/api-testing/tree/master/sample`,
|
|||
flags.DurationVarP(&opt.duration, "duration", "", 0, "Running duration")
|
||||
flags.DurationVarP(&opt.requestTimeout, "request-timeout", "", time.Minute, "Timeout for per request")
|
||||
flags.BoolVarP(&opt.requestIgnoreError, "request-ignore-error", "", false, "Indicate if ignore the request error")
|
||||
flags.StringVarP(&opt.caseFilter, "case-filter", "", "", "The filter of the test case")
|
||||
flags.StringArrayVarP(&opt.caseFilter, "case-filter", "", nil, "The filter of the test case")
|
||||
flags.StringVarP(&opt.report, "report", "", "", "The type of target report. Supported: markdown, md, html, json, discard, std, prometheus, http, grpc")
|
||||
flags.StringVarP(&opt.reportFile, "report-file", "", "", "The file path of the report")
|
||||
flags.BoolVarP(&opt.reportIgnore, "report-ignore", "", false, "Indicate if ignore the report output")
|
||||
|
@ -359,8 +359,20 @@ func (o *runOption) runSuite(loader testing.Loader, dataContext map[string]inter
|
|||
}
|
||||
runLogger.Info("run test suite", "name", testSuite.Name, "filter", caseFilter)
|
||||
for _, testCase := range testSuite.Items {
|
||||
if caseFilterObj != nil && !strings.Contains(testCase.Name, caseFilterObj.(string)) {
|
||||
continue
|
||||
if caseFilterObj != nil {
|
||||
if filter, ok := caseFilterObj.([]string); ok && len(filter) > 0{
|
||||
match := false
|
||||
for _, ff := range filter {
|
||||
if strings.Contains(testCase.Name, ff) {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if !testCase.InScope(o.caseItems) {
|
||||
continue
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
+++
|
||||
title = "代码生成"
|
||||
+++
|
||||
|
||||
`atest` 支持把测试用例生成多种开发语言的代码:
|
||||
|
||||
* curl
|
||||
* Java
|
||||
* Golang
|
||||
* Python
|
||||
* JavaScript
|
||||
* [Robot Framework](https://robotframework.org/)
|
||||
|
||||
> 该功能需要在 Web UI 上使用。
|
|
@ -26,6 +26,15 @@ services:
|
|||
command:
|
||||
- /workspace/entrypoint.sh
|
||||
- python
|
||||
robot-framework:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- LAN_ENV=docker.io/library/python:3.8
|
||||
command:
|
||||
- /workspace/entrypoint.sh
|
||||
- robot-framework
|
||||
javascript:
|
||||
build:
|
||||
context: .
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
export sourcefile=$1
|
||||
# exit if no source file is specified
|
||||
if [ -z "$sourcefile" ]
|
||||
then
|
||||
echo "no source file is specified"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv ${sourcefile} test.robot
|
||||
pip install robotframework robotframework-requests
|
||||
robot test.robot
|
|
@ -3,7 +3,7 @@ set -e
|
|||
|
||||
docker compose version
|
||||
|
||||
targets=(golang java python javascript curl)
|
||||
targets=(golang java python javascript curl robot-framework)
|
||||
for target in "${targets[@]}"
|
||||
do
|
||||
docker compose down
|
||||
|
|
|
@ -15,7 +15,14 @@ limitations under the License.
|
|||
*/
|
||||
package generator
|
||||
|
||||
import "github.com/linuxsuren/api-testing/pkg/testing"
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"text/template"
|
||||
|
||||
"github.com/linuxsuren/api-testing/pkg/testing"
|
||||
)
|
||||
|
||||
// CodeGenerator is the interface of code generator
|
||||
type CodeGenerator interface {
|
||||
|
@ -64,3 +71,35 @@ func GetTestSuiteConverters() (result map[string]TestSuiteConverter) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
func generate(testsuite *testing.TestSuite, testcase *testing.TestCase, templateName, templateText string) (result string, err error) {
|
||||
if testcase != nil && testcase.Request.Method == "" {
|
||||
testcase.Request.Method = http.MethodGet
|
||||
}
|
||||
if testsuite != nil && testsuite.Items != nil {
|
||||
for i, _ := range testsuite.Items {
|
||||
if testsuite.Items[i].Request.Method == "" {
|
||||
testsuite.Items[i].Request.Method = http.MethodGet
|
||||
}
|
||||
}
|
||||
}
|
||||
var tpl *template.Template
|
||||
if tpl, err = template.New(templateName).
|
||||
Funcs(template.FuncMap{"safeString": safeString}).
|
||||
Parse(templateText); err == nil {
|
||||
buf := new(bytes.Buffer)
|
||||
var ctx interface{}
|
||||
if testcase == nil {
|
||||
ctx = testsuite
|
||||
} else {
|
||||
ctx = testcase
|
||||
}
|
||||
|
||||
if err = tpl.Execute(buf, ctx); err == nil {
|
||||
result = buf.String()
|
||||
}
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
*** Settings ***
|
||||
Library RequestsLibrary
|
||||
|
||||
*** Test Cases ***
|
||||
{{- range $item := .Items}}
|
||||
{{$item.Name}}
|
||||
{{- if $item.Request.Header}}
|
||||
${headers}= Create Dictionary {{- range $key, $val := $item.Request.Header}} {{$key}} {{$val}}{{- end}}
|
||||
{{- end}}
|
||||
${response}= {{$item.Request.Method}} {{$item.Request.API}}{{- if .Request.Header}} headers=${headers}{{end}}
|
||||
{{- end}}
|
|
@ -0,0 +1,9 @@
|
|||
*** Settings ***
|
||||
Library RequestsLibrary
|
||||
|
||||
*** Test Cases ***
|
||||
{{.Name}}
|
||||
{{- if .Request.Header}}
|
||||
${headers}= Create Dictionary {{- range $key, $val := .Request.Header}} {{$key}} {{$val}}{{- end}}
|
||||
{{- end}}
|
||||
${response}= {{.Request.Method}} {{.Request.API}}{{- if .Request.Header}} headers=${headers}{{end}}
|
|
@ -16,10 +16,6 @@ limitations under the License.
|
|||
package generator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"text/template"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/linuxsuren/api-testing/pkg/testing"
|
||||
|
@ -32,18 +28,8 @@ func NewJavaScriptGenerator() CodeGenerator {
|
|||
return &javascriptGenerator{}
|
||||
}
|
||||
|
||||
func (g *javascriptGenerator) Generate(testSuite *testing.TestSuite, testcase *testing.TestCase) (result string, err error) {
|
||||
if testcase.Request.Method == "" {
|
||||
testcase.Request.Method = http.MethodGet
|
||||
}
|
||||
var tpl *template.Template
|
||||
if tpl, err = template.New("javascript template").Funcs(template.FuncMap{"safeString": safeString}).Parse(javascriptTemplate); err == nil {
|
||||
buf := new(bytes.Buffer)
|
||||
if err = tpl.Execute(buf, testcase); err == nil {
|
||||
result = buf.String()
|
||||
}
|
||||
}
|
||||
return
|
||||
func (g *javascriptGenerator) Generate(testSuite *testing.TestSuite, testcase *testing.TestCase) (string, error) {
|
||||
return generate(testSuite, testcase, "javascript template", javascriptTemplate)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -16,10 +16,6 @@ limitations under the License.
|
|||
package generator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"text/template"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/linuxsuren/api-testing/pkg/testing"
|
||||
|
@ -33,17 +29,7 @@ func NewPythonGenerator() CodeGenerator {
|
|||
}
|
||||
|
||||
func (g *pythonGenerator) Generate(testSuite *testing.TestSuite, testcase *testing.TestCase) (result string, err error) {
|
||||
if testcase.Request.Method == "" {
|
||||
testcase.Request.Method = http.MethodGet
|
||||
}
|
||||
var tpl *template.Template
|
||||
if tpl, err = template.New("python template").Parse(pythonTemplate); err == nil {
|
||||
buf := new(bytes.Buffer)
|
||||
if err = tpl.Execute(buf, testcase); err == nil {
|
||||
result = buf.String()
|
||||
}
|
||||
}
|
||||
return
|
||||
return generate(testSuite, testcase, "python template", pythonTemplate)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
Copyright 2024 API Testing Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package generator
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
|
||||
"github.com/linuxsuren/api-testing/pkg/testing"
|
||||
)
|
||||
|
||||
type robotGenerator struct {
|
||||
}
|
||||
|
||||
func NewRobotGenerator() CodeGenerator {
|
||||
return &robotGenerator{}
|
||||
}
|
||||
|
||||
func (g *robotGenerator) Generate(testSuite *testing.TestSuite, testcase *testing.TestCase) (string, error) {
|
||||
tpl := robotTemplate
|
||||
if testcase == nil {
|
||||
tpl = robotSuiteTemplate
|
||||
}
|
||||
return generate(testSuite, testcase, "robot-framework", tpl)
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterCodeGenerator("robot-framework", NewRobotGenerator())
|
||||
}
|
||||
|
||||
//go:embed data/robot.tpl
|
||||
var robotTemplate string
|
||||
|
||||
//go:embed data/robot-suite.tpl
|
||||
var robotSuiteTemplate string
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
Copyright 2024 API Testing Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package generator
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"testing"
|
||||
|
||||
atest "github.com/linuxsuren/api-testing/pkg/testing"
|
||||
)
|
||||
|
||||
func TestRobotGenerator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
testCase *atest.TestCase
|
||||
testSuite *atest.TestSuite
|
||||
expect string
|
||||
}{{
|
||||
name: "simple",
|
||||
testCase: &atest.TestCase{
|
||||
Name: "simple",
|
||||
Request: atest.Request{
|
||||
API: fooForTest,
|
||||
},
|
||||
},
|
||||
expect: simpleRobot,
|
||||
}, {
|
||||
name: "with header",
|
||||
testCase: &atest.TestCase{
|
||||
Name: "simple",
|
||||
Request: atest.Request{
|
||||
API: fooForTest,
|
||||
Header: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
expect: headerRobot,
|
||||
}, {
|
||||
name: "test suite",
|
||||
testSuite: &atest.TestSuite{
|
||||
Items: []atest.TestCase{{
|
||||
Name: "one",
|
||||
Request: atest.Request{
|
||||
API: fooForTest,
|
||||
Header: map[string]string{
|
||||
"key1": "value1",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: "two",
|
||||
Request: atest.Request{
|
||||
API: fooForTest,
|
||||
Header: map[string]string{
|
||||
"key2": "value2",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
expect: suiteRobot,
|
||||
}}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewRobotGenerator()
|
||||
if got, err := g.Generate(tt.testSuite, tt.testCase); err != nil || got != tt.expect {
|
||||
t.Errorf("got %q, want %q, error: %v", got, tt.expect, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed testdata/simple.robot
|
||||
var simpleRobot string
|
||||
|
||||
//go:embed testdata/with-headers.robot
|
||||
var headerRobot string
|
||||
|
||||
//go:embed testdata/suite.robot
|
||||
var suiteRobot string
|
|
@ -0,0 +1,6 @@
|
|||
*** Settings ***
|
||||
Library RequestsLibrary
|
||||
|
||||
*** Test Cases ***
|
||||
simple
|
||||
${response}= GET http://foo
|
|
@ -0,0 +1,10 @@
|
|||
*** Settings ***
|
||||
Library RequestsLibrary
|
||||
|
||||
*** Test Cases ***
|
||||
one
|
||||
${headers}= Create Dictionary key1 value1
|
||||
${response}= GET http://foo headers=${headers}
|
||||
two
|
||||
${headers}= Create Dictionary key2 value2
|
||||
${response}= GET http://foo headers=${headers}
|
|
@ -0,0 +1,7 @@
|
|||
*** Settings ***
|
||||
Library RequestsLibrary
|
||||
|
||||
*** Test Cases ***
|
||||
simple
|
||||
${headers}= Create Dictionary key value
|
||||
${response}= GET http://foo headers=${headers}
|
|
@ -21,7 +21,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/expr-lang/expr/builtin"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
|
@ -33,6 +32,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/expr-lang/expr/builtin"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
|
||||
|
@ -965,14 +966,19 @@ func (s *server) GenerateCode(ctx context.Context, in *CodeGenerateRequest) (rep
|
|||
return
|
||||
}
|
||||
|
||||
if result, err = loader.GetTestCase(in.TestSuite, in.TestCase); err == nil {
|
||||
var output string
|
||||
var genErr error
|
||||
if in.TestCase == "" {
|
||||
output, genErr = instance.Generate(&suite, nil)
|
||||
} else {
|
||||
if result, err = loader.GetTestCase(in.TestSuite, in.TestCase); err == nil {
|
||||
result.Request.RenderAPI(suite.API)
|
||||
|
||||
result.Request.RenderAPI(suite.API)
|
||||
|
||||
output, genErr := instance.Generate(&suite, &result)
|
||||
reply.Success = genErr == nil
|
||||
reply.Message = util.OrErrorMessage(genErr, output)
|
||||
output, genErr = instance.Generate(&suite, &result)
|
||||
}
|
||||
}
|
||||
reply.Success = genErr == nil
|
||||
reply.Message = util.OrErrorMessage(genErr, output)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -723,7 +723,7 @@ func TestCodeGenerator(t *testing.T) {
|
|||
t.Run("ListCodeGenerator", func(t *testing.T) {
|
||||
generators, err := server.ListCodeGenerator(ctx, &Empty{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 6, len(generators.Data))
|
||||
assert.Equal(t, 7, len(generators.Data))
|
||||
})
|
||||
|
||||
t.Run("GenerateCode, no generator found", func(t *testing.T) {
|
||||
|
|
|
@ -26,6 +26,11 @@ test-operator: test.operator
|
|||
test-e2e: ## Run e2e tests
|
||||
test-e2e: test.e2e
|
||||
|
||||
.PHONY: full-test-e2e
|
||||
full-test-e2e: ## Build image then run e2e tests
|
||||
full-test-e2e:
|
||||
TAG=master REGISTRY=ghcr.io make image test-e2e
|
||||
|
||||
.PHONY: test-fuzz
|
||||
test-fuzz: ## Run fuzz tests
|
||||
test-fuzz: test.fuzz
|
||||
|
|
Loading…
Reference in New Issue