Skip to content

Commit 078d4c8

Browse files
authored
feat: support output the test report (#17)
Co-authored-by: rick <[email protected]>
1 parent 4a4554d commit 078d4c8

11 files changed

+408
-13
lines changed

CONTRIBUTION.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Print the code of lines:
2+
3+
```shell
4+
git ls-files | xargs cloc
5+
```

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ build:
66
copy: build
77
sudo cp bin/atest /usr/local/bin/
88
test:
9-
go test ./...
9+
go test ./... -cover

cmd/run.go

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33
import (
44
"context"
55
"fmt"
6+
"os"
67
"path"
78
"path/filepath"
89
"strings"
@@ -28,18 +29,36 @@ type runOption struct {
2829
burst int32
2930
limiter limit.RateLimiter
3031
startTime time.Time
32+
reporter runner.TestReporter
33+
reportWriter runner.ReportResultWriter
34+
report string
35+
}
36+
37+
func newDefaultRunOption() *runOption {
38+
return &runOption{
39+
reporter: runner.NewmemoryTestReporter(),
40+
reportWriter: runner.NewResultWriter(os.Stdout),
41+
}
42+
}
43+
44+
func newDiskCardRunOption() *runOption {
45+
return &runOption{
46+
reporter: runner.NewDiscardTestReporter(),
47+
reportWriter: runner.NewDiscardResultWriter(),
48+
}
3149
}
3250

3351
// CreateRunCommand returns the run command
3452
func CreateRunCommand() (cmd *cobra.Command) {
35-
opt := &runOption{}
53+
opt := newDefaultRunOption()
3654
cmd = &cobra.Command{
3755
Use: "run",
3856
Aliases: []string{"r"},
3957
Example: `atest run -p sample.yaml
4058
See also https://github.com/LinuxSuRen/api-testing/tree/master/sample`,
41-
Short: "Run the test suite",
42-
RunE: opt.runE,
59+
Short: "Run the test suite",
60+
PreRunE: opt.preRunE,
61+
RunE: opt.runE,
4362
}
4463

4564
// set flags
@@ -52,6 +71,15 @@ See also https://github.com/LinuxSuRen/api-testing/tree/master/sample`,
5271
flags.Int64VarP(&opt.thread, "thread", "", 1, "Threads of the execution")
5372
flags.Int32VarP(&opt.qps, "qps", "", 5, "QPS")
5473
flags.Int32VarP(&opt.burst, "burst", "", 5, "burst")
74+
flags.StringVarP(&opt.report, "report", "", "", "The type of target report")
75+
return
76+
}
77+
78+
func (o *runOption) preRunE(cmd *cobra.Command, args []string) (err error) {
79+
switch o.report {
80+
case "markdown", "md":
81+
o.reportWriter = runner.NewMarkdownResultWriter(cmd.OutOrStdout())
82+
}
5583
return
5684
}
5785

@@ -73,6 +101,14 @@ func (o *runOption) runE(cmd *cobra.Command, args []string) (err error) {
73101
}
74102
}
75103
}
104+
105+
// print the report
106+
if err == nil {
107+
var results []runner.ReportResult
108+
if results, err = o.reporter.ExportAllReportResults(); err == nil {
109+
err = o.reportWriter.Output(results)
110+
}
111+
}
76112
return
77113
}
78114

@@ -165,7 +201,10 @@ func (o *runOption) runSuite(suite string, dataContext map[string]interface{}, c
165201
o.limiter.Accept()
166202

167203
ctxWithTimeout, _ := context.WithTimeout(ctx, o.requestTimeout)
168-
if output, err = runner.RunTestCase(&testCase, dataContext, ctxWithTimeout); err != nil && !o.requestIgnoreError {
204+
205+
simpleRunner := runner.NewSimpleTestCaseRunner()
206+
simpleRunner.WithTestReporter(o.reporter)
207+
if output, err = simpleRunner.RunTestCase(&testCase, dataContext, ctxWithTimeout); err != nil && !o.requestIgnoreError {
169208
return
170209
}
171210
}

cmd/run_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,9 @@ func TestRunSuite(t *testing.T) {
4949

5050
tt.prepare()
5151
ctx := getDefaultContext()
52-
opt := &runOption{
53-
requestTimeout: 30 * time.Second,
54-
limiter: limit.NewDefaultRateLimiter(0, 0),
55-
}
52+
opt := newDiskCardRunOption()
53+
opt.requestTimeout = 30 * time.Second
54+
opt.limiter = limit.NewDefaultRateLimiter(0, 0)
5655
stopSingal := make(chan struct{}, 1)
5756

5857
err := opt.runSuite(tt.suiteFile, ctx, context.TODO(), stopSingal)

pkg/runner/data/report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
| API | Average | Max | Min | Count | Error |
2+
|---|---|---|---|---|---|
3+
{{- range $val := .}}
4+
| {{$val.API}} | {{$val.Average}} | {{$val.Max}} | {{$val.Min}} | {{$val.Count}} | {{$val.Error}} |
5+
{{- end}}

pkg/runner/reporter_discard.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package runner
2+
3+
type discardTestReporter struct {
4+
}
5+
6+
// NewDiscardTestReporter creates a test reporter which discard everything
7+
func NewDiscardTestReporter() TestReporter {
8+
return &discardTestReporter{}
9+
}
10+
11+
func (r *discardTestReporter) PutRecord(*ReportRecord) {}
12+
func (r *discardTestReporter) GetAllRecords() []*ReportRecord {
13+
return nil
14+
}
15+
func (r *discardTestReporter) ExportAllReportResults() (ReportResultSlice, error) {
16+
return nil, nil
17+
}

pkg/runner/reporter_discard_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package runner_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/linuxsuren/api-testing/pkg/runner"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestDiscardTestReporter(t *testing.T) {
11+
reporter := runner.NewDiscardTestReporter()
12+
assert.NotNil(t, reporter)
13+
assert.Nil(t, reporter.GetAllRecords())
14+
15+
result, err := reporter.ExportAllReportResults()
16+
assert.Nil(t, result)
17+
assert.Nil(t, err)
18+
19+
reporter.PutRecord(&runner.ReportRecord{})
20+
}

pkg/runner/reporter_memory.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package runner
2+
3+
import (
4+
"sort"
5+
"time"
6+
)
7+
8+
type memoryTestReporter struct {
9+
records []*ReportRecord
10+
}
11+
12+
// NewmemoryTestReporter creates a memory based test reporter
13+
func NewmemoryTestReporter() TestReporter {
14+
return &memoryTestReporter{
15+
records: []*ReportRecord{},
16+
}
17+
}
18+
19+
type ReportResultWithTotal struct {
20+
ReportResult
21+
Total time.Duration
22+
}
23+
24+
func (r *memoryTestReporter) PutRecord(record *ReportRecord) {
25+
r.records = append(r.records, record)
26+
}
27+
func (r *memoryTestReporter) GetAllRecords() []*ReportRecord {
28+
return r.records
29+
}
30+
func (r *memoryTestReporter) ExportAllReportResults() (result ReportResultSlice, err error) {
31+
resultWithTotal := map[string]*ReportResultWithTotal{}
32+
for _, record := range r.records {
33+
api := record.Method + " " + record.API
34+
duration := record.Duration()
35+
36+
if item, ok := resultWithTotal[api]; ok {
37+
if item.Max < duration {
38+
item.Max = duration
39+
}
40+
41+
if item.Min > duration {
42+
item.Min = duration
43+
}
44+
item.Error += record.ErrorCount()
45+
item.Total += duration
46+
item.Count += 1
47+
} else {
48+
resultWithTotal[api] = &ReportResultWithTotal{
49+
ReportResult: ReportResult{
50+
API: api,
51+
Count: 1,
52+
Max: duration,
53+
Min: duration,
54+
Error: record.ErrorCount(),
55+
},
56+
Total: duration,
57+
}
58+
}
59+
}
60+
61+
for _, r := range resultWithTotal {
62+
r.Average = r.Total / time.Duration(r.Count)
63+
result = append(result, r.ReportResult)
64+
}
65+
66+
sort.Sort(result)
67+
return
68+
}

pkg/runner/simple.go

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"os"
1313
"reflect"
1414
"strings"
15+
"time"
1516

1617
"github.com/andreyvit/diff"
1718
"github.com/antonmedv/expr"
@@ -21,9 +22,94 @@ import (
2122
unstructured "github.com/linuxsuren/unstructured/pkg"
2223
)
2324

24-
// RunTestCase runs the test case
25-
func RunTestCase(testcase *testing.TestCase, dataContext interface{}, ctx context.Context) (output interface{}, err error) {
26-
fmt.Printf("start to run: '%s'\n", testcase.Name)
25+
type TestCaseRunner interface {
26+
RunTestCase(testcase *testing.TestCase, dataContext interface{}, ctx context.Context) (output interface{}, err error)
27+
WithOutputWriter(io.Writer) TestCaseRunner
28+
WithTestReporter(TestReporter) TestCaseRunner
29+
}
30+
31+
type ReportRecord struct {
32+
Method string
33+
API string
34+
BeginTime time.Time
35+
EndTime time.Time
36+
Error error
37+
}
38+
39+
// Duration returns the duration between begin and end time
40+
func (r *ReportRecord) Duration() time.Duration {
41+
return r.EndTime.Sub(r.BeginTime)
42+
}
43+
44+
func (r *ReportRecord) ErrorCount() int {
45+
if r.Error == nil {
46+
return 0
47+
}
48+
return 1
49+
}
50+
51+
// NewReportRecord creates a record, and set the begin time to be now
52+
func NewReportRecord() *ReportRecord {
53+
return &ReportRecord{
54+
BeginTime: time.Now(),
55+
}
56+
}
57+
58+
type ReportResult struct {
59+
API string
60+
Count int
61+
Average time.Duration
62+
Max time.Duration
63+
Min time.Duration
64+
Error int
65+
}
66+
67+
type ReportResultSlice []ReportResult
68+
69+
func (r ReportResultSlice) Len() int {
70+
return len(r)
71+
}
72+
73+
func (r ReportResultSlice) Less(i, j int) bool {
74+
return r[i].Average > r[j].Average
75+
}
76+
77+
func (r ReportResultSlice) Swap(i, j int) {
78+
tmp := r[i]
79+
r[i] = r[j]
80+
r[j] = tmp
81+
}
82+
83+
type ReportResultWriter interface {
84+
Output([]ReportResult) error
85+
}
86+
87+
type TestReporter interface {
88+
PutRecord(*ReportRecord)
89+
GetAllRecords() []*ReportRecord
90+
ExportAllReportResults() (ReportResultSlice, error)
91+
}
92+
93+
type simpleTestCaseRunner struct {
94+
testReporter TestReporter
95+
writer io.Writer
96+
}
97+
98+
// NewSimpleTestCaseRunner creates the instance of the simple test case runner
99+
func NewSimpleTestCaseRunner() TestCaseRunner {
100+
runner := &simpleTestCaseRunner{}
101+
return runner.WithOutputWriter(io.Discard).WithTestReporter(NewDiscardTestReporter())
102+
}
103+
104+
func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataContext interface{}, ctx context.Context) (output interface{}, err error) {
105+
fmt.Fprintf(r.writer, "start to run: '%s'\n", testcase.Name)
106+
record := NewReportRecord()
107+
defer func(rr *ReportRecord) {
108+
rr.EndTime = time.Now()
109+
rr.Error = err
110+
r.testReporter.PutRecord(rr)
111+
}(record)
112+
27113
if err = doPrepare(testcase); err != nil {
28114
err = fmt.Errorf("failed to prepare, error: %v", err)
29115
return
@@ -77,13 +163,15 @@ func RunTestCase(testcase *testing.TestCase, dataContext interface{}, ctx contex
77163
if request, err = http.NewRequestWithContext(ctx, testcase.Request.Method, testcase.Request.API, requestBody); err != nil {
78164
return
79165
}
166+
record.API = testcase.Request.API
167+
record.Method = testcase.Request.Method
80168

81169
// set headers
82170
for key, val := range testcase.Request.Header {
83171
request.Header.Add(key, val)
84172
}
85173

86-
fmt.Println("start to send request to", testcase.Request.API)
174+
fmt.Fprintf(r.writer, "start to send request to %s\n", testcase.Request.API)
87175

88176
// send the HTTP request
89177
var resp *http.Response
@@ -178,6 +266,22 @@ func RunTestCase(testcase *testing.TestCase, dataContext interface{}, ctx contex
178266
return
179267
}
180268

269+
func (r *simpleTestCaseRunner) WithOutputWriter(writer io.Writer) TestCaseRunner {
270+
r.writer = writer
271+
return r
272+
}
273+
274+
func (r *simpleTestCaseRunner) WithTestReporter(reporter TestReporter) TestCaseRunner {
275+
r.testReporter = reporter
276+
return r
277+
}
278+
279+
// Deprecated
280+
// RunTestCase runs the test case.
281+
func RunTestCase(testcase *testing.TestCase, dataContext interface{}, ctx context.Context) (output interface{}, err error) {
282+
return NewSimpleTestCaseRunner().WithOutputWriter(os.Stdout).RunTestCase(testcase, dataContext, ctx)
283+
}
284+
181285
func doPrepare(testcase *testing.TestCase) (err error) {
182286
for i := range testcase.Prepare.Kubernetes {
183287
item := testcase.Prepare.Kubernetes[i]

0 commit comments

Comments
 (0)