Skip to content

Commit 1ad11f3

Browse files
authored
chore: add release note v0.0.13 (#175)
1 parent bd78fa4 commit 1ad11f3

14 files changed

+244
-83
lines changed

Makefile

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
IMG_TOOL?=podman
2+
BINARY?=atest
23

34
build:
45
mkdir -p bin
@@ -9,10 +10,12 @@ build-embed-ui:
910
cp console/atest-ui/dist/index.html cmd/data/index.html
1011
cp console/atest-ui/dist/assets/*.js cmd/data/index.js
1112
cp console/atest-ui/dist/assets/*.css cmd/data/index.css
12-
go build -ldflags "-w -s -X github.com/linuxsuren/api-testing/pkg/version.version=$(shell git rev-parse --short HEAD)" -o bin/atest main.go
13+
GOOS=${OS} go build -ldflags "-w -s -X github.com/linuxsuren/api-testing/pkg/version.version=$(shell git rev-parse --short HEAD)" -o bin/${BINARY} main.go
1314
echo -n '' > cmd/data/index.html
1415
echo -n '' > cmd/data/index.js
1516
echo -n '' > cmd/data/index.css
17+
build-win-embed-ui:
18+
BINARY=atest.exe OS=windows make build-embed-ui
1619
goreleaser:
1720
goreleaser build --rm-dist --snapshot
1821
build-image:

cmd/convert.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,7 @@ type convertOption struct {
6464
}
6565

6666
func (o *convertOption) preRunE(c *cobra.Command, args []string) (err error) {
67-
if o.target == "" {
68-
o.target = "sample.jmx"
69-
}
67+
o.target = util.EmptyThenDefault(o.target, "sample.jmx")
7068
return
7169
}
7270

docs/README.md

+8
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ Please see the following example usage:
3535
sudo atest service install -m podman --version master
3636
```
3737

38+
or run in Docker:
39+
```shell
40+
docker run -v /var/www/sample:/var/www/sample \
41+
--network host \
42+
linuxsuren/api-testing:master
43+
```
44+
3845
the default web server port is `8080`. So you can visit it via: http://localhost:8080
3946

4047
## Run in k3s
@@ -203,6 +210,7 @@ You could find the official images from both [Docker Hub](https://hub.docker.com
203210
The tag `latest` represents the latest release version. The tag `master` represents the image of the latest master branch. We highly recommend you using a fixed version instead of those in a production environment.
204211

205212
## Release Notes
213+
* [v0.0.13](release-note-v0.0.13.md)
206214
* [v0.0.12](release-note-v0.0.12.md)
207215

208216
## Articles

docs/release-note-v0.0.13.md

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
`atest` 版本发布 `v0.0.13`
2+
3+
`atest` 是一款用 Golang 编写的、开源的接口测试工具。
4+
5+
你可以在容器中启动:
6+
7+
```shell
8+
docker run -v /var/www/sample:/var/www/sample \
9+
--network host \
10+
linuxsuren/api-testing:master
11+
```
12+
13+
或者,直接[下载二进制文件](https://github.com/LinuxSuRen/api-testing/releases/tag/v0.0.12)后启动:
14+
15+
```shell
16+
atest server --local-storage /var/www/sample
17+
```
18+
19+
对于持续集成(CI)场景,可以通过在流水线中执行命令的方式:
20+
21+
```shell
22+
# 执行本地文件
23+
atest run -p your-test-suite.yaml
24+
# 执行远程文件
25+
atest run -p https://gitee.com/linuxsuren/api-testing/raw/master/sample/testsuite-gitee.yaml
26+
# 容器中执行
27+
docker run linuxsuren/api-testing:master atest run -p https://gitee.com/linuxsuren/api-testing/raw/master/sample/testsuite-gitee.yaml
28+
```
29+
30+
你也可以把测试用例转为 JMeter 文件并执行:
31+
32+
```shell
33+
# 格式转换
34+
atest convert --converter jmeter -p https://gitee.com/linuxsuren/api-testing/raw/master/sample/testsuite-gitee.yaml --target gitee.jmx
35+
# 执行
36+
jmeter -n -t gitee.jmx
37+
```
38+
39+
## 主要的新功能
40+
41+
* 增加了插件扩展机制,支持以 Git、S3、关系型数据为后端存储,支持从 [Vault](https://github.com/hashicorp/vault) 获取密码等敏感信息
42+
* 新增对 gRPC 接口的用例支持 @Ink-33
43+
* 支持导出 [JMeter](https://github.com/apache/jmeter) 文件
44+
* 支持通过 [Operator](https://operatorhub.io/operator/api-testing-operator) 的方式安装,并上架 OperatorHub.io
45+
* 提供了基本的 Web UI
46+
* 支持导出 PDF 格式的测试报告 @wjsvec
47+
48+
本次版本发布,包含了以下 5 位 contributor 的努力:
49+
50+
* [@Ink-33](https://github.com/Ink-33)
51+
* [@LinuxSuRen](https://github.com/LinuxSuRen)
52+
* [@chan158](https://github.com/chan158)
53+
* [@setcy](https://github.com/setcy)
54+
* [@wjsvec](https://github.com/wjsvec)
55+
56+
## 相关数据
57+
58+
下面是 `atest` 截止到 `v0.0.13` 的部分数据:
59+
60+
* watch 7
61+
* fork 18
62+
* star 69
63+
* contributor 8
64+
* 二进制文件下载量 872
65+
* 代码行数 45k
66+
* 单元测试覆盖率 84%
67+
68+
想了解完整信息的话,请访问 https://github.com/LinuxSuRen/api-testing/releases/tag/v0.0.13

pkg/generator/converter_jmeter.go

+59-17
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ package generator
2626

2727
import (
2828
"encoding/xml"
29+
"fmt"
2930
"net/url"
3031

3132
"github.com/linuxsuren/api-testing/pkg/testing"
@@ -50,27 +51,31 @@ func (c *jmeterConverter) Convert(testSuite *testing.TestSuite) (result string,
5051
}
5152

5253
func (c *jmeterConverter) buildJmeterTestPlan(testSuite *testing.TestSuite) (result *JmeterTestPlan, err error) {
53-
if err = testSuite.Render(make(map[string]interface{})); err != nil {
54+
emptyCtx := make(map[string]interface{})
55+
if err = testSuite.Render(emptyCtx); err != nil {
5456
return
5557
}
5658

5759
requestItems := []interface{}{}
5860
for _, item := range testSuite.Items {
5961
item.Request.RenderAPI(testSuite.API)
62+
if reqRenderErr := item.Request.Render(emptyCtx, ""); reqRenderErr != nil {
63+
fmt.Println("Error rendering request: ", reqRenderErr)
64+
}
6065

6166
api, err := url.Parse(item.Request.API)
6267
if err != nil {
6368
continue
6469
}
6570

66-
requestItems = append(requestItems, &HTTPSamplerProxy{
71+
requestItem := &HTTPSamplerProxy{
6772
GUIClass: "HttpTestSampleGui",
6873
TestClass: "HTTPSamplerProxy",
6974
Enabled: true,
7075
Name: item.Name,
7176
StringProp: []StringProp{{
7277
Name: "HTTPSampler.domain",
73-
Value: api.Host,
78+
Value: api.Hostname(),
7479
}, {
7580
Name: "HTTPSampler.port",
7681
Value: api.Port(),
@@ -81,7 +86,36 @@ func (c *jmeterConverter) buildJmeterTestPlan(testSuite *testing.TestSuite) (res
8186
Name: "HTTPSampler.method",
8287
Value: item.Request.Method,
8388
}},
84-
})
89+
}
90+
if item.Request.Body != "" {
91+
requestItem.BoolProp = append(requestItem.BoolProp, BoolProp{
92+
Name: "HTTPSampler.postBodyRaw",
93+
Value: "true",
94+
})
95+
requestItem.ElementProp = append(requestItem.ElementProp, ElementProp{
96+
Name: "HTTPsampler.Arguments",
97+
Type: "Arguments",
98+
CollectionProp: []CollectionProp{{
99+
Name: "Arguments.arguments",
100+
ElementProp: []ElementProp{{
101+
Name: "",
102+
Type: "HTTPArgument",
103+
BoolProp: []BoolProp{{
104+
Name: "HTTPArgument.always_encode",
105+
Value: "false",
106+
}},
107+
StringProp: []StringProp{{
108+
Name: "Argument.value",
109+
Value: item.Request.Body,
110+
}, {
111+
Name: "Argument.metadata",
112+
Value: "=",
113+
}},
114+
}},
115+
}},
116+
})
117+
}
118+
requestItems = append(requestItems, requestItem)
85119
requestItems = append(requestItems, HashTree{})
86120
}
87121
requestItems = append(requestItems, &ResultCollector{
@@ -177,12 +211,14 @@ type ThreadGroup struct {
177211
}
178212

179213
type HTTPSamplerProxy struct {
180-
XMLName xml.Name `xml:"HTTPSamplerProxy"`
181-
StringProp []StringProp `xml:"stringProp"`
182-
Name string `xml:"testname,attr"`
183-
GUIClass string `xml:"guiclass,attr"`
184-
TestClass string `xml:"testclass,attr"`
185-
Enabled bool `xml:"enabled,attr"`
214+
XMLName xml.Name `xml:"HTTPSamplerProxy"`
215+
Name string `xml:"testname,attr"`
216+
GUIClass string `xml:"guiclass,attr"`
217+
TestClass string `xml:"testclass,attr"`
218+
Enabled bool `xml:"enabled,attr"`
219+
StringProp []StringProp `xml:"stringProp"`
220+
BoolProp []BoolProp `xml:"boolProp"`
221+
ElementProp []ElementProp `xml:"elementProp"`
186222
}
187223

188224
type ResultCollector struct {
@@ -194,13 +230,19 @@ type ResultCollector struct {
194230
}
195231

196232
type ElementProp struct {
197-
Name string `xml:"name,attr"`
198-
Type string `xml:"elementType,attr"`
199-
GUIClass string `xml:"guiclass,attr"`
200-
TestClass string `xml:"testclass,attr"`
201-
Enabled bool `xml:"enabled,attr"`
202-
StringProp []StringProp `xml:"stringProp"`
203-
BoolProp []BoolProp `xml:"boolProp"`
233+
Name string `xml:"name,attr"`
234+
Type string `xml:"elementType,attr"`
235+
GUIClass string `xml:"guiclass,attr"`
236+
TestClass string `xml:"testclass,attr"`
237+
Enabled bool `xml:"enabled,attr"`
238+
StringProp []StringProp `xml:"stringProp"`
239+
BoolProp []BoolProp `xml:"boolProp"`
240+
CollectionProp []CollectionProp `xml:"collectionProp"`
241+
}
242+
243+
type CollectionProp struct {
244+
Name string `xml:"name,attr"`
245+
ElementProp []ElementProp `xml:"elementProp"`
204246
}
205247

206248
type StringProp struct {

pkg/generator/converter_jmeter_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ func TestJmeterConvert(t *testing.T) {
5252
func createTestSuiteForTest() *atest.TestSuite {
5353
return &atest.TestSuite{
5454
Name: "API Testing",
55-
API: "http://localhost:8080",
55+
API: `{{default "http://localhost:8080/server.Runner" (env "SERVER")}}`,
5656
Items: []atest.TestCase{{
5757
Name: "hello-jmeter",
5858
Request: atest.Request{
5959
Method: "POST",
6060
API: "/GetSuites",
61+
Body: `sample`,
6162
},
6263
}},
6364
}

pkg/generator/testdata/expected_jmeter.jmx

+12-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,20 @@
1313
</ThreadGroup>
1414
<hashTree>
1515
<HTTPSamplerProxy testname="hello-jmeter" guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" enabled="true">
16-
<stringProp name="HTTPSampler.domain">localhost:8080</stringProp>
16+
<stringProp name="HTTPSampler.domain">localhost</stringProp>
1717
<stringProp name="HTTPSampler.port">8080</stringProp>
18-
<stringProp name="HTTPSampler.path">/GetSuites</stringProp>
18+
<stringProp name="HTTPSampler.path">/server.Runner/GetSuites</stringProp>
1919
<stringProp name="HTTPSampler.method">POST</stringProp>
20+
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
21+
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="" testclass="" enabled="false">
22+
<collectionProp name="Arguments.arguments">
23+
<elementProp name="" elementType="HTTPArgument" guiclass="" testclass="" enabled="false">
24+
<stringProp name="Argument.value">sample</stringProp>
25+
<stringProp name="Argument.metadata">=</stringProp>
26+
<boolProp name="HTTPArgument.always_encode">false</boolProp>
27+
</elementProp>
28+
</collectionProp>
29+
</elementProp>
2030
</HTTPSamplerProxy>
2131
<hashTree></hashTree>
2232
<ResultCollector enabled="true" guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report"></ResultCollector>
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
name: API Testing
2-
api: http://localhost:8080
2+
api: http://localhost:8080/server.Runner
33
items:
44
- name: hello-jmeter
55
request:
66
api: /GetSuites
77
method: POST
8+
body: sample

pkg/testing/loader_file.go

+10-6
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,17 @@ func NewFileWriter(parent string) Writer {
3636
// HasMore returns if there are more test cases
3737
func (l *fileLoader) HasMore() bool {
3838
l.index++
39-
return l.index < len(l.paths)
39+
return l.index < len(l.paths) && l.index >= 0
4040
}
4141

4242
// Load returns the test case content
4343
func (l *fileLoader) Load() (data []byte, err error) {
4444
targetFile := l.paths[l.index]
45+
data, err = loadData(targetFile)
46+
return
47+
}
48+
49+
func loadData(targetFile string) (data []byte, err error) {
4550
if strings.HasPrefix(targetFile, "http://") || strings.HasPrefix(targetFile, "https://") {
4651
var ok bool
4752
data, ok, err = gRPCCompitableRequest(targetFile)
@@ -132,14 +137,13 @@ func (l *fileLoader) Reset() {
132137
}
133138

134139
func (l *fileLoader) ListTestSuite() (suites []TestSuite, err error) {
135-
defer func() {
136-
l.Reset()
137-
}()
140+
l.lock.RLocker().Lock()
141+
defer l.lock.RUnlock()
138142

139-
for l.HasMore() {
143+
for _, target := range l.paths {
140144
var data []byte
141145
var loadErr error
142-
if data, loadErr = l.Load(); err != nil {
146+
if data, loadErr = loadData(target); err != nil {
143147
fmt.Println("failed to load data", loadErr)
144148
continue
145149
}

pkg/testing/parser.go

+2-18
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ func (r *Request) Render(ctx interface{}, dataDir string) (err error) {
156156
}
157157

158158
// setting default values
159-
r.Method = EmptyThenDefault(r.Method, http.MethodGet)
159+
r.Method = util.EmptyThenDefault(r.Method, http.MethodGet)
160160
return
161161
}
162162

@@ -201,7 +201,7 @@ func (r *Request) GetBody() (reader io.Reader, err error) {
201201

202202
// Render renders the response
203203
func (r *Response) Render(ctx interface{}) (err error) {
204-
r.StatusCode = ZeroThenDefault(r.StatusCode, http.StatusOK)
204+
r.StatusCode = util.ZeroThenDefault(r.StatusCode, http.StatusOK)
205205
return
206206
}
207207

@@ -217,19 +217,3 @@ func renderMap(ctx interface{}, data map[string]string, title string) (result ma
217217
result = data
218218
return
219219
}
220-
221-
// ZeroThenDefault return the default value if the val is zero
222-
func ZeroThenDefault(val, defVal int) int {
223-
if val == 0 {
224-
val = defVal
225-
}
226-
return val
227-
}
228-
229-
// EmptyThenDefault return the default value if the val is empty
230-
func EmptyThenDefault(val, defVal string) string {
231-
if strings.TrimSpace(val) == "" {
232-
val = defVal
233-
}
234-
return val
235-
}

0 commit comments

Comments
 (0)