MackerelにおけるGoのエコシステムとかテストとか
TIME rest time current/total
TopicsPlaceHolder

MackerelにおけるGoのエコシステムとかテストとか

golang.tokyo #2

Dec 12th, 2016

今朝2時半に北海道から帰ってきました

Profile

songmu

エンジニアとして

Goで書いたものの一部

【宣伝】みんなのGo言語という本を書きました

Agenda

Mackerel

利用言語

mackerel-agent

URL外形監視ワーカー

外形監視ワーカーのアーキテクチャ

OSSで提供しているもの

OSSにする理由

ユーザーがハックできる余地を残しておく

Travis CIの場合

https://github.com/travis-ci/travis-cookbooks/pull/320

github-servicesの場合

https://github.com/github/github-services/tree/master/lib/services

ソースをオープンにして第1段階クリア

何故githubにホストするか

どのようにパッチ受付体制を整えるのか

CI

Travis CI

mackerel-agentのCIでやってること

go vet をCIする

-printfuncs が便利

% go tool vet -all -printfuncs=Criticalf,Infof,Warningf,Debugf,Tracef .

golintをCIする

-set_exit_status が便利

% golint -set_exit_status ./...

Coveralls

複数パッケージのテストにはgotestcover、Coveralls投稿にはgoverallsが便利。

% gotestcover -covermode=count -coverprofile=.profile.cov ./...
% goveralls -coverprofile=.profile.cov

Windows対応

レビュー体制

リリースプロセスをオープンに

https://github.com/mackerelio/mackerel-agent-plugins/pull/45

パッケージの自動ビルド

Travisにtagをgit pushさせる

リリースフローの図

[tips] テストを回す前にgoimportsをかける

[tips] goimportsをかけるときの工夫

dragon-imports を使う

$ go get github.com/monochromegane/dragon-imports/...
$ dragon-imports

http://blog.monochromegane.com/blog/2015/12/23/dragon-imports/

最低限しかgoimportsしない

Middlewareのテスト

Redisの場合

go-test-redisserver

UNIX Socketにも対応している。

package main

import (
    "github.com/soh335/go-test-redisserver"
    "github.com/garyburd/redigo/redis"
)

func main() {
    s, err := redistest.NewRedisServer(nil)
    if err != nil {
        log.Fatal(err)
    }
    defer s.Stop()
    conn, err := redis.Dial("unix", s.Config.UnixSocket)
    if err != nil {
        log.Fatal(err)
    }
    _, err = conn.Do("PING")
    if err != nil {
        log.Fatal(err)
    }
}

MySQLの場合

最近MysQL 5.7.6以降で動くようにパッチを書きました

TestMainでDB初期化

func TestMain(m *testing.M) {
    os.Exit(runTests(m))
}

func runTests(m *testing.M) int {
    testMysqld, err := mysqltest.NewMysqld(nil)
    if err != nil {
       log.Fatal("runTests: failed launch mysql server:", err)
    }
    defer testMysqld.Stop()
    setupTestDB(testMysqld)

    return m.Run()
}

スキーマ流し込み

CREATE DATABASE した後にスキーマ流し込み。可能ならfixture類も

func setupTestDB(testMysqld *mysqltest.TestMysqld) {
    db, err := sql.Open("mysql", testMysqld.DSN(mysqltest.WithDbname("")))
    if err != nil {
        log.Fatalf("Failed to setup database for testing: %s", err)
    }

    const testdbname = "hoge_test"
    _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", testdbname))
    if err != nil {
        log.Fatal("create db error:", err)
    }
    // close and re-open with created db.
    db.Close()

    // set to global variable for other tests.
    testDB, err = sql.Open("mysql", testMysqld.DSN(mysqltest.WithDbname(testdbname)))
    if err != nil {
        log.Fatalf("Failed to setup database for testing: %s", err)
    }

    applySchema()
}

mysqltestのCopyDataFrom機能

mycnf := mysqltest.NewConfig()
mycnf.CopyDataFrom = "/tmp/mysqltest_data"
testMysqld, err := mysqltest.NewMysqld(mycnf)

mysqld起動時にコピー元のdataディレクトリを指定できる機能。テスト開始時にDBに大量のデータを入れておきたい場合に、MySQLの起動時間を節約できる。(MySQL5.7だと遅いかも…)

copyDataのセットアップ

これにも、 mysqltest を利用する。

mycnf := mysqltest.NewConfig()
mycnf.DataDir = "/tmp/mysqltest_data"
testMysqld, err := mysqltest.NewMysqld(mycnf)
// 以降セットアップ

その他のミドルウェアの場合

tcputil.EmptyPort()

空いているポートを探して一時的にデーモンを立てる

p, err := tcputil.EmptyPort()
cmd := exec.Command("memcached", "-p", fmt.Sprintf("%d", p))
go cmd.Run()
...
cmd.Process.Kill()

展望

以上

【急募】We are Hiring

hatena