0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RESTful のチュートリアルのテストコードの作成

Last updated at Posted at 2024-12-09

概要

本記事では、Go 公式にある Web フレームワーク Gin を利用して作成する RESTful API に testing, httptest を使用したテストを作成します。

環境

  • Ubuntu 24.04.1 LTS (Oracle VM Virtualbox 上に構築しております)
  • Go 1.23.3

ライブラリバージョン

  • Gin 1.10

本題

Go のインストール

公式の下記ページを参考にインストールを実施します。

Ubuntu を利用しているので、上記 URL の Linux の手順に従ってインストールします。

※ リンク先の Linux タブにある 「Note:」 の通り $HOME/.profile or /etc/profileexport PATH=$PATH:/usr/local/go/bin を追記しただけでは、 go version が実行できない可能性があります。
その場合は、 source $HOME/.profile or source /etc/profile を実行して追記した設定を反映させてください。

RESTful API の作成

公式の下記ページにある、 Gin Web Framework を用いた、 RESTful API を作成します。

チュートリアルを実施すると、以下3つの API とコードが完成します。

  • (GET) /albums ← サーバーが持つアルバムの情報を JSON で取得する API
  • (GET) /albums/:id ← URL で指定した ID のアルバム情報を JSON で取得する API
  • (POST) /albums ← サーバーが持つアルバムの情報に、リクエストで指定した情報を追加する API

チュートリアルを行うことで完成するコード

go main.go
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

type album struct {
	ID     string  `json:"id"`
	Title  string  `json:"title"`
	Artist string  `json:"artist"`
	Price  float64 `json:"price"`
}

var albums = []album{
	{
		ID:     "1",
		Title:  "Blue Train",
		Artist: "John Coltrane",
		Price:  56.99,
	},
	{
		ID:     "2",
		Title:  "Jeru",
		Artist: "Gerry Mulligan",
		Price:  17.99,
	},
	{
		ID:     "3",
		Title:  "Sarah Vaughan and Clifford Brown",
		Artist: "Sarah Vaughan",
		Price:  39.99,
	},
}

func getAlbums(c *gin.Context) {
	c.JSON(http.StatusOK, albums)
}

func getAlbumByID(c *gin.Context) {
	id := c.Param("id")

	for _, a := range albums {
		if a.ID == id {
			c.JSON(http.StatusOK, a)
			return
		}
	}
	c.JSON(http.StatusNotFound, gin.H{"message": "album not found"})
}

func postAlbums(c *gin.Context) {
	var newAlbum album
	if err := c.BindJSON(&newAlbum); err != nil {
		return
	}

	albums = append(albums, newAlbum)
	c.JSON(http.StatusOK, albums)
}

func setupRouter() *gin.Engine {
	r := gin.Default()
	return r
}

func main() {
	router := setupRouter()
	router.GET("/albums", getAlbums)
	router.GET("/albums/:id", getAlbumByID)
	router.POST("/albums", postAlbums)

	router.Run(":8080")
}

テストコードの作成

テストコードの検討

今回用意した 3 つの API で作成するテストについて検討しましょう。

  • (GET) /albums API
    • 全てのアルバム情報を JSON で返す API です。テストコードでは、API が返すアルバムの情報が、想定しているものと一致しているかを確認します
  • (GET) /albums/:id API
    • URL で指定した ID を持つアルバム情報を返す API です。テストコードでは、 API が返すアルバムの情報が、指定した ID のものと一致しているかを確認します
  • (POST) /albums API
    • サーバーが持つアルバム情報に、 POST で送った情報を追加する API です。テストコードでは、 POST で送った情報が追加されているかを確認します。

(GET) /albums API

(GET) /albums API は、全てのアルバム情報を返す API です。
なので、テストコードでは、 API レスポンスの内容と、 HTTP レスポンスステータスコードが想定通りかを確認したいと思います。

go main_test.go

package main

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

// テストコードは Test~~ という名前にします。この名称を付けることで、テストとして実行されるようになります。
func TestGetAlbums(t *testing.T) {

    // テスト用の HTTP リクエストを記録する構造体の設定
	w := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "/albums", nil)
	if err != nil {
		t.Fatal(err)
	}

    // テスト用のサーバー設定。 /albums で、全アルバムの情報を返すよう設定
	router := setupRouter()
	router.GET("/albums", getAlbums)

    // サーバーの起動
	router.ServeHTTP(w, req)

    // API が返すレスポンスの内容を設定
	expected, err := json.Marshal(albums)
	if err != nil {
		t.Fatal(err)
	}

    // HTTP のレスポンスコードが想定通りか判定
	if http.StatusOK != w.Code {
		t.Errorf("HTTP response code is not match: result (%d), want (%d)", w.Code, http.StatusOK)
	}

    // HTTP のレスポンスの内容が想定通りか判定。 JSON のままだと比較ができないため、文字列に変換して内容が一致しているか判定しています。
	if w.Body.String() != string(expected) {
		t.Errorf("HTTP response is not match: result(%s), want (%s)", w.Body.String(), string(expected))
	}

}

(GET) /albums/:id API

(GET) albums API は、リクエストで指定した ID のアルバム情報を返す API です。
テストコードでは、 指定した ID で返す API レスポンスの内容と、 HTTP レスポンスステータスコードが想定通りかを確認したいと思います。

go main_test.go
func TestGetAlbumsByID(t *testing.T) {
	router := setupRouter()
	router.GET("/albums/:id", getAlbumByID)

	w := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "/albums/2", nil)
	if err != nil {
		t.Fatal(err)
	}
	router.ServeHTTP(w, req)

    // ID 2 が API が返すレスポンスの内容を設定
	expected, err := json.Marshal(album{
		ID:     "2",
		Title:  "Jeru",
		Artist: "Gerry Mulligan",
		Price:  17.99,
	})
	if err != nil {
		t.Fatal(err)
	}

	if http.StatusOK != w.Code {
		t.Errorf("HTTP response code is not match: result (%d), want (%d)", w.Code, http.StatusOK)
	}

	if w.Body.String() != string(expected) {
		t.Errorf("HTTP response is not match: result(%s), want (%s)", w.Body.String(), string(expected))
	}
}

(POST) /albums API

(POST) /albums は、新しいアルバムの情報を追加する API です。アルバム情報を追加した後で、サーバーが持つ全ての情報をレスポンスとして返します。
テストコードでは、レスポンスが、あらかじめ設定されているアルバムの情報と、新しいアルバム情報両方が設定されているかと、HTTP レスポンスステータスコードが想定通りかを確認したいと思います。

go main_test.go
func TestPostAlbums(t *testing.T) {

	beforeAlbums := albums

    // サーバに送るデータの準備
	postData := album{
		ID:     "4",
		Title:  "Test Title",
		Artist: "Test Taro",
		Price:  100,
	}
	postJson, err := json.Marshal(postData)
	if err != nil {
		t.Fatal(err)
	}

	req, err := http.NewRequest("POST", "/albums/", strings.NewReader(string(postJson)))
	if err != nil {
		t.Fatal(err)
	}
	router := setupRouter()
	router.POST("/albums/", postAlbums)

	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if http.StatusOK != w.Code {
		t.Errorf("HTTP response code is not match: result (%d), want (%d)", w.Code, http.StatusOK)
	}

    // API が返すレスポンスの内容を設定。 サーバーであらかじめ設定しているアルバムの情報に、今回 Post で送るデータを追加する。
	expected, err := json.Marshal(append(beforeAlbums, postData))
	if err != nil {
		t.Fatal(err)
	}

	if w.Body.String() != string(expected) {
		t.Errorf("HTTP response is not match: result(%s), want (%s)", w.Body.String(), string(expected))
	}
}

完成形

go main_test.go
package main

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestGetAlbums(t *testing.T) {

	w := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "/albums", nil)
	if err != nil {
		t.Fatal(err)
	}

	router := setupRouter()
	router.GET("/albums", getAlbums)
	router.ServeHTTP(w, req)

	expected, err := json.Marshal(albums)
	if err != nil {
		t.Fatal(err)
	}

	if http.StatusOK != w.Code {
		t.Errorf("HTTP response code is not match: result (%d), want (%d)", w.Code, http.StatusOK)
	}

	if w.Body.String() != string(expected) {
		t.Errorf("HTTP response is not match: result(%s), want (%s)", w.Body.String(), string(expected))
	}

}

func TestGetAlbumsByID(t *testing.T) {
	router := setupRouter()
	router.GET("/albums/:id", getAlbumByID)

	w := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "/albums/2", nil)
	if err != nil {
		t.Fatal(err)
	}
	router.ServeHTTP(w, req)

	expected, err := json.Marshal(album{
		ID:     "2",
		Title:  "Jeru",
		Artist: "Gerry Mulligan",
		Price:  17.99,
	})
	if err != nil {
		t.Fatal(err)
	}

	if http.StatusOK != w.Code {
		t.Errorf("HTTP response code is not match: result (%d), want (%d)", w.Code, http.StatusOK)
	}

	if w.Body.String() != string(expected) {
		t.Errorf("HTTP response is not match: result(%s), want (%s)", w.Body.String(), string(expected))
	}
}

func TestPostAlbums(t *testing.T) {

	beforeAlbums := albums

	postData := album{
		ID:     "4",
		Title:  "Test Title",
		Artist: "Test Taro",
		Price:  100,
	}
	postJson, err := json.Marshal(postData)
	if err != nil {
		t.Fatal(err)
	}

	req, err := http.NewRequest("POST", "/albums/", strings.NewReader(string(postJson)))
	if err != nil {
		t.Fatal(err)
	}
	router := setupRouter()
	router.POST("/albums/", postAlbums)

	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	if http.StatusOK != w.Code {
		t.Errorf("HTTP response code is not match: result (%d), want (%d)", w.Code, http.StatusOK)
	}

	expected, err := json.Marshal(append(beforeAlbums, postData))
	if err != nil {
		t.Fatal(err)
	}

	if w.Body.String() != string(expected) {
		t.Errorf("HTTP response is not match: result(%s), want (%s)", w.Body.String(), string(expected))
	}
}

main_test.go があるディレクトリに移動して、 go test . と実行すると、今回用意した3つのテストを実行した結果が表示されます。

実行結果
image.png

また、 go test -v . と実行すると、詳細なテストの結果が表示されます。

結果

今回用意したテストコードで、 3 つの API が動作することが確認できました。
ですが、今回用意したテストケースでは、 API が正しく動作するのかの確認ができません。

例えば、 (GET) /albums/:id API で、 2 以外の ID を送ったときに、対応したアルバム情報を返すのか?
(POST) /albums API に、2 回別々の情報を送った際に、2 つ分のアルバム情報が追加されているか?
この通り、いくつか懸念事項が残っております。

終わりに

次回の記事では、今回用意した RESTful API を拡張しつつ、テストコードも充実させていこうと思います。

参考資料

Go 公式ページ

Go RESTful API チュートリアルページ

Gin Web Framework

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?