![]()
runnは、HTTPリクエストのシナリオテストに便利なツールです。CLI として使用することもできますが、Go のテストコード内で runn を使用することで、より柔軟で強力なテストが可能になります。
実際のサーバーに対してリクエストを行おうとすると、正常なリクエストだと判定させるために authentication token を用意したり、 middleware にリクエストが弾かれないような workaround が必要なことがあります。単にリソースサーバーの endpoint に対してシナリオテストを実行したい場合、runn を Go のテストヘルパーとして使うことで、 application 内の router 部分に対してのみテストを実行できます(図)。

runnの公式サイトには多くの情報が掲載されていますが、 機能が豊富な分、具体的な実装を完全な形で見つけるのが少々骨です。実際に動かすことで、テストヘルパーとしての runn の使い方を理解したので、書いておきます。
実装方法
runn を Go のテストヘルパーとして使用する際の重要なポイントは、clientを外部から渡し、runbook内で定義しないことです。
CLI tool として runn の参考文献を勉強していくと、以下のように runner を定義する形で yml ファイルを書く方法に慣れます。
runners:
req: http://localhost:8080
steps:
- req:
/resource:
get:
headers:
Accept: application/json
body: null
test: |
current.res.status == 200
しかし、httptestserver などを用いる場合、runner は runn をコード上で呼び出す際に option で渡すため、yml ファイル内の定義は消しておくべきです
ts := httptest.NewServer(NewRouter())
t.Cleanup(func() {
ts.Close()
})
opts := []runn.Option{
runn.T(t),
runn.Runner("req", ts.URL), # ここで runner を渡す
}
# runners:
# req: http://localhost:8080 <-- yml ファイル内では定義しない
steps:
- req:
/resource:
get:
headers:
Accept: application/json
body: null
実装例
以下が、動作するコードの全体です
main.go
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
mux := NewRouter()
fmt.Println("Server is running on http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
func NewRouter() http.Handler {
mux := http.NewServeMux()
// GETメソッドの処理
mux.HandleFunc("GET /resource", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "GET request received")
})
return mux
}
main_test.go
package main
import (
"context"
"net/http/httptest"
"testing"
"github.com/k1LoW/runn"
)
func TestSample(t *testing.T) {
ctx := context.Background()
ts := httptest.NewServer(NewRouter())
t.Cleanup(func() {
ts.Close()
})
opts := []runn.Option{
runn.T(t),
runn.Runner("req", ts.URL),
}
o, err := runn.Load("*.yml", opts...)
if err != nil {
t.Fatal(err)
}
if err := o.RunN(ctx); err != nil {
t.Fatal(err)
}
}
get_resource.yml
desc: resource を GET する
# runners:
# req: http://localhost:8080
steps:
- req:
/resource:
get:
headers:
Accept: application/json
body: null
test: |
current.res.status == 200
&& current.res.headers["Content-Length"][0] == "21"
&& current.res.headers["Content-Type"][0] == "text/plain; charset=utf-8"
&& "Date" in current.res.headers
&& current.res.rawBody == "GET request received\n"