Skip to content

Commit 0f02984

Browse files
authoredNov 18, 2024··
feat: support to generate robot-framework script (#563)
Co-authored-by: rick <linuxsuren@users.noreply.github.com>
1 parent 33703da commit 0f02984

18 files changed

+298
-45
lines changed
 

‎cmd/run.go

+16-4
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ type runOption struct {
5151
duration time.Duration
5252
requestTimeout time.Duration
5353
requestIgnoreError bool
54-
caseFilter string
54+
caseFilter []string
5555
thread int64
5656
context context.Context
5757
qps int32
@@ -117,7 +117,7 @@ See also https://github.com/LinuxSuRen/api-testing/tree/master/sample`,
117117
flags.DurationVarP(&opt.duration, "duration", "", 0, "Running duration")
118118
flags.DurationVarP(&opt.requestTimeout, "request-timeout", "", time.Minute, "Timeout for per request")
119119
flags.BoolVarP(&opt.requestIgnoreError, "request-ignore-error", "", false, "Indicate if ignore the request error")
120-
flags.StringVarP(&opt.caseFilter, "case-filter", "", "", "The filter of the test case")
120+
flags.StringArrayVarP(&opt.caseFilter, "case-filter", "", nil, "The filter of the test case")
121121
flags.StringVarP(&opt.report, "report", "", "", "The type of target report. Supported: markdown, md, html, json, discard, std, prometheus, http, grpc")
122122
flags.StringVarP(&opt.reportFile, "report-file", "", "", "The file path of the report")
123123
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
359359
}
360360
runLogger.Info("run test suite", "name", testSuite.Name, "filter", caseFilter)
361361
for _, testCase := range testSuite.Items {
362-
if caseFilterObj != nil && !strings.Contains(testCase.Name, caseFilterObj.(string)) {
363-
continue
362+
if caseFilterObj != nil {
363+
if filter, ok := caseFilterObj.([]string); ok && len(filter) > 0{
364+
match := false
365+
for _, ff := range filter {
366+
if strings.Contains(testCase.Name, ff) {
367+
match = true
368+
break
369+
}
370+
}
371+
372+
if !match {
373+
continue
374+
}
375+
}
364376
}
365377
if !testCase.InScope(o.caseItems) {
366378
continue
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
+++
2+
title = "代码生成"
3+
+++
4+
5+
`atest` 支持把测试用例生成多种开发语言的代码:
6+
7+
* curl
8+
* Java
9+
* Golang
10+
* Python
11+
* JavaScript
12+
* [Robot Framework](https://robotframework.org/)
13+
14+
> 该功能需要在 Web UI 上使用。

‎e2e/code-generator/compose.yaml

+9
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ services:
2626
command:
2727
- /workspace/entrypoint.sh
2828
- python
29+
robot-framework:
30+
build:
31+
context: .
32+
dockerfile: Dockerfile
33+
args:
34+
- LAN_ENV=docker.io/library/python:3.8
35+
command:
36+
- /workspace/entrypoint.sh
37+
- robot-framework
2938
javascript:
3039
build:
3140
context: .

‎e2e/code-generator/robot-framework.sh

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
set -e
3+
4+
export sourcefile=$1
5+
# exit if no source file is specified
6+
if [ -z "$sourcefile" ]
7+
then
8+
echo "no source file is specified"
9+
exit 1
10+
fi
11+
12+
mv ${sourcefile} test.robot
13+
pip install robotframework robotframework-requests
14+
robot test.robot

‎e2e/code-generator/start.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ set -e
33

44
docker compose version
55

6-
targets=(golang java python javascript curl)
6+
targets=(golang java python javascript curl robot-framework)
77
for target in "${targets[@]}"
88
do
99
docker compose down

‎pkg/generator/code_generator.go

+40-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@ limitations under the License.
1515
*/
1616
package generator
1717

18-
import "github.com/linuxsuren/api-testing/pkg/testing"
18+
import (
19+
"bytes"
20+
"fmt"
21+
"net/http"
22+
"text/template"
23+
24+
"github.com/linuxsuren/api-testing/pkg/testing"
25+
)
1926

2027
// CodeGenerator is the interface of code generator
2128
type CodeGenerator interface {
@@ -64,3 +71,35 @@ func GetTestSuiteConverters() (result map[string]TestSuiteConverter) {
6471
}
6572
return
6673
}
74+
75+
func generate(testsuite *testing.TestSuite, testcase *testing.TestCase, templateName, templateText string) (result string, err error) {
76+
if testcase != nil && testcase.Request.Method == "" {
77+
testcase.Request.Method = http.MethodGet
78+
}
79+
if testsuite != nil && testsuite.Items != nil {
80+
for i, _ := range testsuite.Items {
81+
if testsuite.Items[i].Request.Method == "" {
82+
testsuite.Items[i].Request.Method = http.MethodGet
83+
}
84+
}
85+
}
86+
var tpl *template.Template
87+
if tpl, err = template.New(templateName).
88+
Funcs(template.FuncMap{"safeString": safeString}).
89+
Parse(templateText); err == nil {
90+
buf := new(bytes.Buffer)
91+
var ctx interface{}
92+
if testcase == nil {
93+
ctx = testsuite
94+
} else {
95+
ctx = testcase
96+
}
97+
98+
if err = tpl.Execute(buf, ctx); err == nil {
99+
result = buf.String()
100+
}
101+
} else {
102+
fmt.Println(err)
103+
}
104+
return
105+
}

‎pkg/generator/data/robot-suite.tpl

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
*** Settings ***
2+
Library RequestsLibrary
3+
4+
*** Test Cases ***
5+
{{- range $item := .Items}}
6+
{{$item.Name}}
7+
{{- if $item.Request.Header}}
8+
${headers}= Create Dictionary {{- range $key, $val := $item.Request.Header}} {{$key}} {{$val}}{{- end}}
9+
{{- end}}
10+
${response}= {{$item.Request.Method}} {{$item.Request.API}}{{- if .Request.Header}} headers=${headers}{{end}}
11+
{{- end}}

‎pkg/generator/data/robot.tpl

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
*** Settings ***
2+
Library RequestsLibrary
3+
4+
*** Test Cases ***
5+
{{.Name}}
6+
{{- if .Request.Header}}
7+
${headers}= Create Dictionary {{- range $key, $val := .Request.Header}} {{$key}} {{$val}}{{- end}}
8+
{{- end}}
9+
${response}= {{.Request.Method}} {{.Request.API}}{{- if .Request.Header}} headers=${headers}{{end}}

‎pkg/generator/javascript_generator.go

+2-16
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ limitations under the License.
1616
package generator
1717

1818
import (
19-
"bytes"
20-
"net/http"
21-
"text/template"
22-
2319
_ "embed"
2420

2521
"github.com/linuxsuren/api-testing/pkg/testing"
@@ -32,18 +28,8 @@ func NewJavaScriptGenerator() CodeGenerator {
3228
return &javascriptGenerator{}
3329
}
3430

35-
func (g *javascriptGenerator) Generate(testSuite *testing.TestSuite, testcase *testing.TestCase) (result string, err error) {
36-
if testcase.Request.Method == "" {
37-
testcase.Request.Method = http.MethodGet
38-
}
39-
var tpl *template.Template
40-
if tpl, err = template.New("javascript template").Funcs(template.FuncMap{"safeString": safeString}).Parse(javascriptTemplate); err == nil {
41-
buf := new(bytes.Buffer)
42-
if err = tpl.Execute(buf, testcase); err == nil {
43-
result = buf.String()
44-
}
45-
}
46-
return
31+
func (g *javascriptGenerator) Generate(testSuite *testing.TestSuite, testcase *testing.TestCase) (string, error) {
32+
return generate(testSuite, testcase, "javascript template", javascriptTemplate)
4733
}
4834

4935
func init() {

‎pkg/generator/python_generator.go

+1-15
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ limitations under the License.
1616
package generator
1717

1818
import (
19-
"bytes"
20-
"net/http"
21-
"text/template"
22-
2319
_ "embed"
2420

2521
"github.com/linuxsuren/api-testing/pkg/testing"
@@ -33,17 +29,7 @@ func NewPythonGenerator() CodeGenerator {
3329
}
3430

3531
func (g *pythonGenerator) Generate(testSuite *testing.TestSuite, testcase *testing.TestCase) (result string, err error) {
36-
if testcase.Request.Method == "" {
37-
testcase.Request.Method = http.MethodGet
38-
}
39-
var tpl *template.Template
40-
if tpl, err = template.New("python template").Parse(pythonTemplate); err == nil {
41-
buf := new(bytes.Buffer)
42-
if err = tpl.Execute(buf, testcase); err == nil {
43-
result = buf.String()
44-
}
45-
}
46-
return
32+
return generate(testSuite, testcase, "python template", pythonTemplate)
4733
}
4834

4935
func init() {

‎pkg/generator/robot_generator.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
Copyright 2024 API Testing Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package generator
17+
18+
import (
19+
_ "embed"
20+
21+
"github.com/linuxsuren/api-testing/pkg/testing"
22+
)
23+
24+
type robotGenerator struct {
25+
}
26+
27+
func NewRobotGenerator() CodeGenerator {
28+
return &robotGenerator{}
29+
}
30+
31+
func (g *robotGenerator) Generate(testSuite *testing.TestSuite, testcase *testing.TestCase) (string, error) {
32+
tpl := robotTemplate
33+
if testcase == nil {
34+
tpl = robotSuiteTemplate
35+
}
36+
return generate(testSuite, testcase, "robot-framework", tpl)
37+
}
38+
39+
func init() {
40+
RegisterCodeGenerator("robot-framework", NewRobotGenerator())
41+
}
42+
43+
//go:embed data/robot.tpl
44+
var robotTemplate string
45+
46+
//go:embed data/robot-suite.tpl
47+
var robotSuiteTemplate string

‎pkg/generator/robot_generator_test.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
Copyright 2024 API Testing Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package generator
17+
18+
import (
19+
_ "embed"
20+
"testing"
21+
22+
atest "github.com/linuxsuren/api-testing/pkg/testing"
23+
)
24+
25+
func TestRobotGenerator(t *testing.T) {
26+
tests := []struct {
27+
name string
28+
testCase *atest.TestCase
29+
testSuite *atest.TestSuite
30+
expect string
31+
}{{
32+
name: "simple",
33+
testCase: &atest.TestCase{
34+
Name: "simple",
35+
Request: atest.Request{
36+
API: fooForTest,
37+
},
38+
},
39+
expect: simpleRobot,
40+
}, {
41+
name: "with header",
42+
testCase: &atest.TestCase{
43+
Name: "simple",
44+
Request: atest.Request{
45+
API: fooForTest,
46+
Header: map[string]string{
47+
"key": "value",
48+
},
49+
},
50+
},
51+
expect: headerRobot,
52+
}, {
53+
name: "test suite",
54+
testSuite: &atest.TestSuite{
55+
Items: []atest.TestCase{{
56+
Name: "one",
57+
Request: atest.Request{
58+
API: fooForTest,
59+
Header: map[string]string{
60+
"key1": "value1",
61+
},
62+
},
63+
}, {
64+
Name: "two",
65+
Request: atest.Request{
66+
API: fooForTest,
67+
Header: map[string]string{
68+
"key2": "value2",
69+
},
70+
},
71+
}},
72+
},
73+
expect: suiteRobot,
74+
}}
75+
for _, tt := range tests {
76+
t.Run(tt.name, func(t *testing.T) {
77+
g := NewRobotGenerator()
78+
if got, err := g.Generate(tt.testSuite, tt.testCase); err != nil || got != tt.expect {
79+
t.Errorf("got %q, want %q, error: %v", got, tt.expect, err)
80+
}
81+
})
82+
}
83+
}
84+
85+
//go:embed testdata/simple.robot
86+
var simpleRobot string
87+
88+
//go:embed testdata/with-headers.robot
89+
var headerRobot string
90+
91+
//go:embed testdata/suite.robot
92+
var suiteRobot string

‎pkg/generator/testdata/simple.robot

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*** Settings ***
2+
Library RequestsLibrary
3+
4+
*** Test Cases ***
5+
simple
6+
${response}= GET http://foo

‎pkg/generator/testdata/suite.robot

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
*** Settings ***
2+
Library RequestsLibrary
3+
4+
*** Test Cases ***
5+
one
6+
${headers}= Create Dictionary key1 value1
7+
${response}= GET http://foo headers=${headers}
8+
two
9+
${headers}= Create Dictionary key2 value2
10+
${response}= GET http://foo headers=${headers}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
*** Settings ***
2+
Library RequestsLibrary
3+
4+
*** Test Cases ***
5+
simple
6+
${headers}= Create Dictionary key value
7+
${response}= GET http://foo headers=${headers}

‎pkg/server/remote_server.go

+13-7
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"context"
2222
"errors"
2323
"fmt"
24-
"github.com/expr-lang/expr/builtin"
2524
"io"
2625
"mime"
2726
"net/http"
@@ -33,6 +32,8 @@ import (
3332
"strings"
3433
"time"
3534

35+
"github.com/expr-lang/expr/builtin"
36+
3637
"github.com/prometheus/client_golang/prometheus"
3738
"github.com/prometheus/client_golang/prometheus/promauto"
3839

@@ -965,14 +966,19 @@ func (s *server) GenerateCode(ctx context.Context, in *CodeGenerateRequest) (rep
965966
return
966967
}
967968

968-
if result, err = loader.GetTestCase(in.TestSuite, in.TestCase); err == nil {
969-
970-
result.Request.RenderAPI(suite.API)
969+
var output string
970+
var genErr error
971+
if in.TestCase == "" {
972+
output, genErr = instance.Generate(&suite, nil)
973+
} else {
974+
if result, err = loader.GetTestCase(in.TestSuite, in.TestCase); err == nil {
975+
result.Request.RenderAPI(suite.API)
971976

972-
output, genErr := instance.Generate(&suite, &result)
973-
reply.Success = genErr == nil
974-
reply.Message = util.OrErrorMessage(genErr, output)
977+
output, genErr = instance.Generate(&suite, &result)
978+
}
975979
}
980+
reply.Success = genErr == nil
981+
reply.Message = util.OrErrorMessage(genErr, output)
976982
}
977983
return
978984
}

‎pkg/server/remote_server_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,7 @@ func TestCodeGenerator(t *testing.T) {
723723
t.Run("ListCodeGenerator", func(t *testing.T) {
724724
generators, err := server.ListCodeGenerator(ctx, &Empty{})
725725
assert.NoError(t, err)
726-
assert.Equal(t, 6, len(generators.Data))
726+
assert.Equal(t, 7, len(generators.Data))
727727
})
728728

729729
t.Run("GenerateCode, no generator found", func(t *testing.T) {

‎tools/make/test.mk

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ test-operator: test.operator
2626
test-e2e: ## Run e2e tests
2727
test-e2e: test.e2e
2828

29+
.PHONY: full-test-e2e
30+
full-test-e2e: ## Build image then run e2e tests
31+
full-test-e2e:
32+
TAG=master REGISTRY=ghcr.io make image test-e2e
33+
2934
.PHONY: test-fuzz
3035
test-fuzz: ## Run fuzz tests
3136
test-fuzz: test.fuzz

0 commit comments

Comments
 (0)
Please sign in to comment.