Skip to content

Commit

Permalink
feat: support to output the HTML report
Browse files Browse the repository at this point in the history
  • Loading branch information
LinuxSuRen committed Jun 11, 2023
1 parent 1fd4586 commit 15622d8
Show file tree
Hide file tree
Showing 19 changed files with 323 additions and 87 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ collector-coverage.out
dist/
.vscode/launch.json
sample.yaml
.DS_Store
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This is a API testing tool.

## Features

* Multiple test report formats: Markdown, HTML, Stdout
* Response Body fields equation check
* Response Body [eval](https://expr.medv.io/)
* Verify the Kubernetes resources
Expand Down
8 changes: 5 additions & 3 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func newDefaultRunOption() *runOption {
}
}

func newDiskCardRunOption() *runOption {
func newDiscardRunOption() *runOption {
return &runOption{
reporter: runner.NewDiscardTestReporter(),
reportWriter: runner.NewDiscardResultWriter(),
Expand Down Expand Up @@ -74,7 +74,7 @@ See also https://github.com/LinuxSuRen/api-testing/tree/master/sample`,
flags.DurationVarP(&opt.duration, "duration", "", 0, "Running duration")
flags.DurationVarP(&opt.requestTimeout, "request-timeout", "", time.Minute, "Timeout for per request")
flags.BoolVarP(&opt.requestIgnoreError, "request-ignore-error", "", false, "Indicate if ignore the request error")
flags.StringVarP(&opt.report, "report", "", "", "The type of target report. Supported: markdown, md, discard, std")
flags.StringVarP(&opt.report, "report", "", "", "The type of target report. Supported: markdown, md, html, discard, std")
flags.StringVarP(&opt.reportFile, "report-file", "", "", "The file path of the report")
flags.BoolVarP(&opt.reportIgnore, "report-ignore", "", false, "Indicate if ignore the report output")
flags.Int64VarP(&opt.thread, "thread", "", 1, "Threads of the execution")
Expand All @@ -98,6 +98,8 @@ func (o *runOption) preRunE(cmd *cobra.Command, args []string) (err error) {
switch o.report {
case "markdown", "md":
o.reportWriter = runner.NewMarkdownResultWriter(writer)
case "html":
o.reportWriter = runner.NewHTMLResultWriter(writer)
case "discard":
o.reportWriter = runner.NewDiscardResultWriter()
case "", "std":
Expand Down Expand Up @@ -178,7 +180,7 @@ func (o *runOption) runSuiteWithDuration(suite string) (err error) {
defer sem.Release(1)
defer wait.Done()
defer func() {
fmt.Println("routing end with", time.Now().Sub(now))
fmt.Println("routing end with", time.Since(now))
}()

dataContext := getDefaultContext()
Expand Down
2 changes: 1 addition & 1 deletion cmd/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestRunSuite(t *testing.T) {
defer gock.Clean()
util.MakeSureNotNil(tt.prepare)()
ctx := getDefaultContext()
opt := newDiskCardRunOption()
opt := newDiscardRunOption()
opt.requestTimeout = 30 * time.Second
opt.limiter = limit.NewDefaultRateLimiter(0, 0)
stopSingal := make(chan struct{}, 1)
Expand Down
11 changes: 11 additions & 0 deletions pkg/render/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package render

import (
"bytes"
"fmt"
"html/template"
"io"
"strings"

"github.com/Masterminds/sprig/v3"
Expand Down Expand Up @@ -31,3 +33,12 @@ func FuncMap() template.FuncMap {
}
return funcs
}

// RenderThenPrint renders the template then prints the result
func RenderThenPrint(name, text string, ctx interface{}, w io.Writer) (err error) {
var report string
if report, err = Render(name, text, ctx); err == nil {
fmt.Fprint(w, report)
}
return
}
30 changes: 30 additions & 0 deletions pkg/render/template_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package render

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -56,3 +57,32 @@ func TestRender(t *testing.T) {
})
}
}

func TestRenderThenPrint(t *testing.T) {
tests := []struct {
name string
tplText string
ctx interface{}
buf *bytes.Buffer
expect string
}{{
name: "simple",
tplText: `{{max 1 2 3}}`,
ctx: nil,
buf: new(bytes.Buffer),
expect: `3`,
}, {
name: "with a map as context",
tplText: `{{.name}}`,
ctx: map[string]string{"name": "linuxsuren"},
buf: new(bytes.Buffer),
expect: "linuxsuren",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := RenderThenPrint(tt.name, tt.tplText, tt.ctx, tt.buf)
assert.NoError(t, err)
assert.Equal(t, tt.expect, tt.buf.String())
})
}
}
33 changes: 33 additions & 0 deletions pkg/runner/data/html.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE>
<html lang="zh">
<head>
<title>API Testing Report</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
[leading-7=""] {
line-height: 1.75rem;
}
.text-center, [text-center=""] {
text-align: center;
}
footer {
position: fixed;
bottom: 0;
width: 100%;
height: 60px;
}
</style>
</head>
<body>
<table>
<caption>API Testing Report</caption>
<tr><th>API</th><th>Average</th><th>Max</th><th>Min</th><th>Count</th><th>Error</th></tr>
{{- range $val := .}}
<tr><td>{{$val.API}}</td><td>{{$val.Average}}</td><td>{{$val.Max}}</td><td>{{$val.Min}}</td><td>{{$val.Count}}</td><td>{{$val.Error}}</td></tr>
{{- end}}
</table>
<footer text-center="" leading-7="">
<p text-sm=""><a href="https://github.com/LinuxSuRen/api-testing" target="_blank" rel="noopener">Powered by API Testing</a></p>
</footer>
</body>
</html>
7 changes: 5 additions & 2 deletions pkg/runner/reporter_memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ func (r *memoryTestReporter) ExportAllReportResults() (result ReportResultSlice,
if record.EndTime.After(item.Last) {
item.Last = record.EndTime
}
if record.BeginTime.Before(item.First) {
item.First = record.BeginTime
if record.ErrorCount() > 0 && record.Body != "" {
item.LastErrorMessage = record.Body
}
} else {
resultWithTotal[api] = &ReportResultWithTotal{
Expand All @@ -77,6 +77,9 @@ func (r *memoryTestReporter) ExportAllReportResults() (result ReportResultSlice,
Last: record.EndTime,
Total: duration,
}
if record.ErrorCount() > 0 {
resultWithTotal[api].LastErrorMessage = record.Body
}
}
}

Expand Down
33 changes: 27 additions & 6 deletions pkg/runner/reporter_memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func TestExportAllReportResults(t *testing.T) {
BeginTime: now,
EndTime: now.Add(time.Second * 4),
Error: errors.New("fake"),
Body: "fake",
}, {
API: urlFoo,
Method: http.MethodGet,
Expand All @@ -62,12 +63,13 @@ func TestExportAllReportResults(t *testing.T) {
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://foo",
Average: time.Second * 3,
Max: time.Second * 4,
Min: time.Second * 2,
Count: 3,
Error: 1,
LastErrorMessage: "fake",
}, {
API: "GET http://bar",
Average: time.Second,
Expand All @@ -77,6 +79,25 @@ func TestExportAllReportResults(t *testing.T) {
Count: 1,
Error: 0,
}},
}, {
name: "first record has error",
records: []*runner.ReportRecord{{
API: urlFoo,
Method: http.MethodGet,
BeginTime: now,
EndTime: now.Add(time.Second * 4),
Error: errors.New("fake"),
Body: "fake",
}},
expect: runner.ReportResultSlice{{
API: "GET http://foo",
Average: time.Second * 4,
Max: time.Second * 4,
Min: time.Second * 4,
Count: 1,
Error: 1,
LastErrorMessage: "fake",
}},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
19 changes: 8 additions & 11 deletions pkg/runner/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,14 @@ func NewReportRecord() *ReportRecord {

// ReportResult represents the report result of a set of the same API requests
type ReportResult struct {
API string
Count int
Average time.Duration
Max time.Duration
Min time.Duration
QPS int
Error int
API string
Count int
Average time.Duration
Max time.Duration
Min time.Duration
QPS int
Error int
LastErrorMessage string
}

// ReportResultSlice is the alias type of ReportResult slice
Expand Down Expand Up @@ -202,10 +203,6 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte
}(record)

defer func() {
if testcase.Clean.CleanPrepare {
err = r.doCleanPrepare(testcase)
}

if err == nil {
err = runJob(testcase.After)
}
Expand Down
3 changes: 0 additions & 3 deletions pkg/runner/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,6 @@ func TestTestCase(t *testing.T) {
Before: atest.Job{
Items: []string{"sleep(1)"},
},
Clean: atest.Clean{
CleanPrepare: true,
},
},
execer: fakeruntime.FakeExecer{},
prepare: func() {
Expand Down
31 changes: 31 additions & 0 deletions pkg/runner/testdata/report.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!DOCTYPE>
<html lang="zh">
<head>
<title>API Testing Report</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
[leading-7=""] {
line-height: 1.75rem;
}
.text-center, [text-center=""] {
text-align: center;
}
footer {
position: fixed;
bottom: 0;
width: 100%;
height: 60px;
}
</style>
</head>
<body>
<table>
<caption>API Testing Report</caption>
<tr><th>API</th><th>Average</th><th>Max</th><th>Min</th><th>Count</th><th>Error</th></tr>
<tr><td>/foo</td><td>3ns</td><td>3ns</td><td>3ns</td><td>1</td><td>0</td></tr>
</table>
<footer text-center="" leading-7="">
<p text-sm=""><a href="https://github.com/LinuxSuRen/api-testing" target="_blank" rel="noopener">Powered by API Testing</a></p>
</footer>
</body>
</html>
25 changes: 25 additions & 0 deletions pkg/runner/writer_html.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package runner

import (
_ "embed"
"io"

"github.com/linuxsuren/api-testing/pkg/render"
)

type htmlResultWriter struct {
writer io.Writer
}

// NewHTMLResultWriter creates a new htmlResultWriter
func NewHTMLResultWriter(writer io.Writer) ReportResultWriter {
return &htmlResultWriter{writer: writer}
}

// Output writes the HTML base report to target writer
func (w *htmlResultWriter) Output(result []ReportResult) (err error) {
return render.RenderThenPrint("html-report", htmlReport, result, w.writer)
}

//go:embed data/html.html
var htmlReport string
43 changes: 43 additions & 0 deletions pkg/runner/writer_html_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package runner_test

import (
"bytes"
"testing"

_ "embed"

"github.com/linuxsuren/api-testing/pkg/runner"
"github.com/stretchr/testify/assert"
)

func TestHTMLResultWriter(t *testing.T) {
tests := []struct {
name string
buf *bytes.Buffer
results []runner.ReportResult
expect string
}{{
name: "simple",
buf: new(bytes.Buffer),
results: []runner.ReportResult{{
API: "/foo",
Max: 3,
Min: 3,
Average: 3,
Error: 0,
Count: 1,
}},
expect: htmlReportExpect,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := runner.NewHTMLResultWriter(tt.buf)
err := w.Output(tt.results)
assert.NoError(t, err)
assert.Equal(t, tt.expect, tt.buf.String())
})
}
}

//go:embed testdata/report.html
var htmlReportExpect string
Loading

0 comments on commit 15622d8

Please sign in to comment.