0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

CircleCIでGo言語製のRest APIのテストを自動で回す〜docker-composeを利用して〜

Last updated at Posted at 2020-01-25

はじめに

こんにちは

コンテナを使ったアプリケーション開発もぼちぼち増えてきたように感じます。
その中でも開発環境において、複数のコンテナ・プロセスを管理したいためにdocker-composeを利用している方も多いのではないでしょうか。

今回は、開発環境で利用しているdocker-composeをそのまま利用して、
CircleCIで自動テストする一例をご紹介したいと思います。

コンテナ構成

docker-compose.yml
version: '2'
services:
  db:
    build:
      context: db/
    expose:
      - "3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root #rootパスワードの設定
      - MYSQL_DATABASE=test
      - MYSQL_USER=user
      - MYSQL_PASSWORD=password
    volumes:
      - ./db/mysql_data:/db/mysql_data
    ports:
      - "3306:3306"
  app:
    build:
      context: app/
    volumes:
      - ./app/:/go/src/app
    command: realize start --run --no-config # DEBUG
    restart: always
    ports:
      - "8080:8080"
    depends_on:
      - db

今回作成するAPI

GETだけできるとてもシンプルなものにします。

Status Code: 200

リクエスト

GET /user/1

レスポンス

{
    "name": "山田",
    "age": 11
}

Status Code: 404

リクエスト

GET /user/3

レスポンス

{
    "code": "Not Found",
    "message": "レコードが見つかりません"
}

対応するテーブル・スキーマ

desc users:
Field, Type, Null, Key, Extra
---------------------------------------
id , int(11), No, PRI, auto_increment
name, varchar(255), YES
age, int(11), YES

実装コード

main.go
r.GET("/user/:id", func(c *gin.Context) {
		userId, _ := strconv.Atoi(c.Param("id"))
		db := getDB()
		var user model.User
		if err := db.Where("id = ?", userId).First(&user).Error; gorm.IsRecordNotFoundError(err) {
			c.JSON(http.StatusNotFound, gin.H{
				"code":    "Not Found",
				"message": "レコードが見つかりません",
			})
			return
		}
		c.JSON(http.StatusOK, user)
		defer db.Close()
	})

※Webフレームワークにgin, ORMにgormを採用しています。

マッピングしているUser構造体

type User struct {
	Id   int    `json:"-"`
	Name string `json:"name"`
	Age  int    `json:"age"`
}

テストコード

200と404を確認します。

テストレコードの作成もテストコードの中でやってしまいます。
※headerのチェックのみの簡略的なものとします。

main_test.go
func Test_Main(t *testing.T) {
	db, err := gorm.Open("mysql", "user:password@tcp(db:3306)/test?charset=utf8mb4&parseTime=True&loc=Local")
	if err != nil {
		panic(err.Error())
	}
	
	// make db scheme
	db.AutoMigrate(&model.User{})
	// insert test record
	db.Create(&model.User{Id: 1, Name: "山田", Age: 11})

	t.Run("Check HTTP 200", func(t *testing.T) {
		w := httptest.NewRecorder()
		req, _ := http.NewRequest(http.MethodGet, "/user/1", nil)
		req.Header.Set("Content-Type", "application/json")
		router.ServeHTTP(w, req)
		assert.Equal(t, http.StatusOK, w.Code)
	})

	t.Run("Check HTTP 404", func(t *testing.T) {
		w := httptest.NewRecorder()
		req, _ := http.NewRequest(http.MethodGet, "/user/2", nil)
		req.Header.Set("Content-Type", "application/json")
		router.ServeHTTP(w, req)
		assert.Equal(t, http.StatusNotFound, w.Code)
	})
}

CircleCIのconfig.yml

ポイントは、
buildタイプにmachineビルドを選択し、imageにcircleci/classic:edgeを選択する点です。

.circleci/config.yml
version: 2
jobs:
  build:
    machine:
      image: circleci/classic:edge
    working_directory: ~/repo
    steps:
      - checkout
      - run:
          name: install docker-compose
          command: |
            curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` > ~/docker-compose
            chmod +x ~/docker-compose
            sudo mv ~/docker-compose /usr/local/bin/docker-compose
      - run:
          name: docker-compose up
          command: |
            set -x
            docker-compose up -d
      - run:
          name: test
          command: docker-compose exec app go test -v main_test.go main.go
      - run:
          name: docker-compose down
          command: docker-compose down

参考

build結果

$ #!/bin/bash -eo pipefail
docker-compose exec app go test -v main_test.go main.go
^@^@[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /user/:id                 --> command-line-arguments.Router.func1 (3 handlers)
=== RUN   Test_Main
=== RUN   Test_Main/Check_HTTP_200
[GIN] 2020/01/25 - 07:51:33 | 200 |     2.23664ms |                 | GET      /user/1
=== RUN   Test_Main/Check_HTTP_404
[GIN] 2020/01/25 - 07:51:33 | 404 |    1.848185ms |                 | GET      /user/2
--- PASS: Test_Main (0.03s)
    --- PASS: Test_Main/Check_HTTP_200 (0.00s)
    --- PASS: Test_Main/Check_HTTP_404 (0.00s)
PASS
ok  	command-line-arguments	0.040s

細かいところはソースをご覧下さい。

注意点

image: circleci/classic:edgeはローカル環境でpullすることができないため(2020/01/25時点)、ローカルでの動作確認はコンテナにアタッチして手動で行う必要があります。要は、

ローカルコマンド

$ circleci build

が必ずコケて使えないということです。
※おそらくCircleCI側の都合だと思います。#リソース制限など

おわりに

Pull Requestの段階で強制的に自動テストが走るようにすることは、もはやチーム開発において必須の仕組みだと感じました。
これからもCircleCIなどのCIツールを活用して、アプリケーションの品質向上と将来的な開発速度の向上を目指したいと思います。
CircleCIを活用したおもしろい例があれば、ぜひご紹介下さい。

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?