Skip to content

Commit ff187bb

Browse files
committed
feat: support output the test report
1 parent 4a4554d commit ff187bb

File tree

9 files changed

+357
-12
lines changed

9 files changed

+357
-12
lines changed

cmd/run.go

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,36 @@ type runOption struct {
2828
burst int32
2929
limiter limit.RateLimiter
3030
startTime time.Time
31+
reporter runner.TestReporter
32+
reportWriter runner.ReportResultWriter
33+
report string
34+
}
35+
36+
func newDefaultRunOption() *runOption {
37+
return &runOption{
38+
reporter: runner.NewmemoryTestReporter(),
39+
reportWriter: runner.NewStdResultWriter(),
40+
}
41+
}
42+
43+
func newDiskCardRunOption() *runOption {
44+
return &runOption{
45+
reporter: runner.NewDiscardTestReporter(),
46+
reportWriter: runner.NewDisCardResultWriter(),
47+
}
3148
}
3249

3350
// CreateRunCommand returns the run command
3451
func CreateRunCommand() (cmd *cobra.Command) {
35-
opt := &runOption{}
52+
opt := newDefaultRunOption()
3653
cmd = &cobra.Command{
3754
Use: "run",
3855
Aliases: []string{"r"},
3956
Example: `atest run -p sample.yaml
4057
See also https://github.com/LinuxSuRen/api-testing/tree/master/sample`,
41-
Short: "Run the test suite",
42-
RunE: opt.runE,
58+
Short: "Run the test suite",
59+
PreRunE: opt.preRunE,
60+
RunE: opt.runE,
4361
}
4462

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

@@ -73,6 +100,14 @@ func (o *runOption) runE(cmd *cobra.Command, args []string) (err error) {
73100
}
74101
}
75102
}
103+
104+
// print the report
105+
if err == nil {
106+
var results []runner.ReportResult
107+
if results, err = o.reporter.ExportAllReportResults(); err == nil {
108+
err = o.reportWriter.Output(results)
109+
}
110+
}
76111
return
77112
}
78113

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

167202
ctxWithTimeout, _ := context.WithTimeout(ctx, o.requestTimeout)
168-
if output, err = runner.RunTestCase(&testCase, dataContext, ctxWithTimeout); err != nil && !o.requestIgnoreError {
203+
204+
simpleRunner := runner.NewSimpleTestCaseRunner()
205+
simpleRunner.WithTestReporter(o.reporter)
206+
if output, err = simpleRunner.RunTestCase(&testCase, dataContext, ctxWithTimeout); err != nil && !o.requestIgnoreError {
169207
return
170208
}
171209
}

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+
}
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)