diff --git a/cmd/run.go b/cmd/run.go index c63c9cff..4459c09a 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -36,7 +36,7 @@ type runOption struct { func newDefaultRunOption() *runOption { return &runOption{ - reporter: runner.NewmemoryTestReporter(), + reporter: runner.NewMemoryTestReporter(), reportWriter: runner.NewResultWriter(os.Stdout), } } @@ -206,6 +206,8 @@ func (o *runOption) runSuite(suite string, dataContext map[string]interface{}, c simpleRunner.WithTestReporter(o.reporter) if output, err = simpleRunner.RunTestCase(&testCase, dataContext, ctxWithTimeout); err != nil && !o.requestIgnoreError { return + } else { + err = nil } } dataContext[testCase.Name] = output diff --git a/pkg/runner/reporter_discard.go b/pkg/runner/reporter_discard.go index 4d1d14ed..0aa425dc 100644 --- a/pkg/runner/reporter_discard.go +++ b/pkg/runner/reporter_discard.go @@ -8,10 +8,15 @@ func NewDiscardTestReporter() TestReporter { return &discardTestReporter{} } +// PutRecord does nothing func (r *discardTestReporter) PutRecord(*ReportRecord) {} + +// GetAllRecords does nothing func (r *discardTestReporter) GetAllRecords() []*ReportRecord { return nil } + +// ExportAllReportResults does nothing func (r *discardTestReporter) ExportAllReportResults() (ReportResultSlice, error) { return nil, nil } diff --git a/pkg/runner/reporter_memory.go b/pkg/runner/reporter_memory.go index 150d65c4..13dce18b 100644 --- a/pkg/runner/reporter_memory.go +++ b/pkg/runner/reporter_memory.go @@ -9,24 +9,30 @@ type memoryTestReporter struct { records []*ReportRecord } -// NewmemoryTestReporter creates a memory based test reporter -func NewmemoryTestReporter() TestReporter { +// NewMemoryTestReporter creates a memory based test reporter +func NewMemoryTestReporter() TestReporter { return &memoryTestReporter{ records: []*ReportRecord{}, } } +// ReportResultWithTotal holds the total duration base on ReportResult type ReportResultWithTotal struct { ReportResult Total time.Duration } +// PutRecord puts the record to memory func (r *memoryTestReporter) PutRecord(record *ReportRecord) { r.records = append(r.records, record) } + +// GetAllRecords returns all the records func (r *memoryTestReporter) GetAllRecords() []*ReportRecord { return r.records } + +// ExportAllReportResults exports all the report results func (r *memoryTestReporter) ExportAllReportResults() (result ReportResultSlice, err error) { resultWithTotal := map[string]*ReportResultWithTotal{} for _, record := range r.records { diff --git a/pkg/runner/reporter_memory_test.go b/pkg/runner/reporter_memory_test.go new file mode 100644 index 00000000..333f2c59 --- /dev/null +++ b/pkg/runner/reporter_memory_test.go @@ -0,0 +1,91 @@ +package runner_test + +import ( + "errors" + "net/http" + "testing" + "time" + + "github.com/linuxsuren/api-testing/pkg/runner" + "github.com/stretchr/testify/assert" +) + +func TestExportAllReportResults(t *testing.T) { + now := time.Now() + + tests := []struct { + name string + records []*runner.ReportRecord + expect runner.ReportResultSlice + }{{ + name: "no records", + records: []*runner.ReportRecord{}, + expect: nil, + }, { + name: "normal", + records: []*runner.ReportRecord{{ + API: "http://foo", + Method: http.MethodGet, + BeginTime: now, + EndTime: now.Add(time.Second * 3), + }, { + API: "http://foo", + Method: http.MethodGet, + BeginTime: now, + EndTime: now.Add(time.Second * 4), + Error: errors.New("fake"), + }, { + API: "http://foo", + Method: http.MethodGet, + BeginTime: now, + EndTime: now.Add(time.Second * 2), + }, { + API: "http://bar", + Method: http.MethodGet, + BeginTime: now, + EndTime: now.Add(time.Second), + }, { + API: "http://fake", + Method: http.MethodGet, + BeginTime: now, + EndTime: now.Add(time.Second * 5), + }}, + expect: runner.ReportResultSlice{{ + API: "GET http://fake", + Average: time.Second * 5, + Max: time.Second * 5, + Min: time.Second * 5, + Count: 1, + Error: 0, + }, { + API: "GET http://foo", + Average: time.Second * 3, + Max: time.Second * 4, + Min: time.Second * 2, + Count: 3, + Error: 1, + }, { + API: "GET http://bar", + Average: time.Second, + Max: time.Second, + Min: time.Second, + Count: 1, + Error: 0, + }}, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reporter := runner.NewMemoryTestReporter() + assert.NotNil(t, reporter) + + for i := range tt.records { + reporter.PutRecord(tt.records[i]) + } + assert.Equal(t, tt.records, reporter.GetAllRecords()) + + result, err := reporter.ExportAllReportResults() + assert.Nil(t, err) + assert.Equal(t, tt.expect, result) + }) + } +} diff --git a/pkg/runner/simple.go b/pkg/runner/simple.go index 7dd5a18f..7e9348e1 100644 --- a/pkg/runner/simple.go +++ b/pkg/runner/simple.go @@ -28,6 +28,7 @@ type TestCaseRunner interface { WithTestReporter(TestReporter) TestCaseRunner } +// ReportRecord represents the raw data of a HTTP request type ReportRecord struct { Method string API string @@ -41,6 +42,7 @@ func (r *ReportRecord) Duration() time.Duration { return r.EndTime.Sub(r.BeginTime) } +// ErrorCount returns the count number of errors func (r *ReportRecord) ErrorCount() int { if r.Error == nil { return 0 @@ -55,6 +57,7 @@ func NewReportRecord() *ReportRecord { } } +// ReportResult represents the report result of a set of the same API requests type ReportResult struct { API string Count int @@ -64,26 +67,32 @@ type ReportResult struct { Error int } +// ReportResultSlice is the alias type of ReportResult slice type ReportResultSlice []ReportResult +// Len returns the count of slice items func (r ReportResultSlice) Len() int { return len(r) } +// Less returns if i bigger than j func (r ReportResultSlice) Less(i, j int) bool { return r[i].Average > r[j].Average } +// Swap swaps the items func (r ReportResultSlice) Swap(i, j int) { tmp := r[i] r[i] = r[j] r[j] = tmp } +// ReportResultWriter is the interface of the report writer type ReportResultWriter interface { Output([]ReportResult) error } +// TestReporter is the interface of the report type TestReporter interface { PutRecord(*ReportRecord) GetAllRecords() []*ReportRecord @@ -101,12 +110,15 @@ func NewSimpleTestCaseRunner() TestCaseRunner { return runner.WithOutputWriter(io.Discard).WithTestReporter(NewDiscardTestReporter()) } +// RunTestCase is the main entry point of a test case func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataContext interface{}, ctx context.Context) (output interface{}, err error) { fmt.Fprintf(r.writer, "start to run: '%s'\n", testcase.Name) record := NewReportRecord() defer func(rr *ReportRecord) { rr.EndTime = time.Now() rr.Error = err + rr.API = testcase.Request.API + rr.Method = testcase.Request.Method r.testReporter.PutRecord(rr) }(record) @@ -163,8 +175,6 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte if request, err = http.NewRequestWithContext(ctx, testcase.Request.Method, testcase.Request.API, requestBody); err != nil { return } - record.API = testcase.Request.API - record.Method = testcase.Request.Method // set headers for key, val := range testcase.Request.Header { @@ -266,11 +276,13 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte return } +// WithOutputWriter sets the io.Writer func (r *simpleTestCaseRunner) WithOutputWriter(writer io.Writer) TestCaseRunner { r.writer = writer return r } +// WithTestReporter sets the TestReporter func (r *simpleTestCaseRunner) WithTestReporter(reporter TestReporter) TestCaseRunner { r.testReporter = reporter return r diff --git a/pkg/runner/writer_std.go b/pkg/runner/writer_std.go index 4359f423..f5013f49 100644 --- a/pkg/runner/writer_std.go +++ b/pkg/runner/writer_std.go @@ -5,7 +5,6 @@ import ( _ "embed" "fmt" "io" - "os" "text/template" ) @@ -13,6 +12,7 @@ type stdResultWriter struct { writer io.Writer } +// NewResultWriter creates a result writer with the specific io.Writer func NewResultWriter(writer io.Writer) ReportResultWriter { return &stdResultWriter{writer: writer} } @@ -22,6 +22,7 @@ func NewDiscardResultWriter() ReportResultWriter { return &stdResultWriter{writer: io.Discard} } +// Output writer the report to target writer func (w *stdResultWriter) Output(result []ReportResult) error { fmt.Fprintf(w.writer, "API Average Max Min Count Error\n") for _, r := range result { @@ -35,13 +36,12 @@ type markdownResultWriter struct { writer io.Writer } +// NewMarkdownResultWriter creates the Markdown writer func NewMarkdownResultWriter(writer io.Writer) ReportResultWriter { - if writer == nil { - writer = os.Stdout - } return &markdownResultWriter{writer: writer} } +// Output writer the Markdown based report to target writer func (w *markdownResultWriter) Output(result []ReportResult) (err error) { var tpl *template.Template if tpl, err = template.New("report").Parse(markDownReport); err == nil {