2016/11/01


いい記事に感化されて僕も何か書きたくなった。

GolangでAPI Clientを実装する | SOTA

GolangでAPI Clientを実装する 特定のAPIを利用するコマンドラインツールやサービスを書く場合はClientパッケージ(SDKと呼ばれることも多いが本記事ではClientと呼ぶ)を使うこ...

http://deeeet.com/writing/2016/11/01/go-api-client/

この先、JSON REST API のエンドポイントに対して Golang の struct を用意していく訳だけど、ここが一番かったるい作業で一番手を抜きたい所だと思います。そこで便利なのが JSON-to-Go です。

JSON-to-Go: Convert JSON to Go instantly

JSON-to-Go Convert JSON to Go struct This tool instantly converts JSON into a Go type definition. Pa...

https://mholt.github.io/json-to-go/ JSON-to-Go

このサイトの左側ペインに JSON を与えると、Golang の struct が一気に生成できます。例えば GitHub API でユーザ情報を取得する API の JSON は以下の様になります。

{
  "login": "octocat",
  "id": 1,
  "avatar_url": "https://github.com/images/error/octocat_happy.gif",
  "gravatar_id": "",
  "url": "https://api.github.com/users/octocat",
  "html_url": "https://github.com/octocat",
  "followers_url": "https://api.github.com/users/octocat/followers",
  "following_url": "https://api.github.com/users/octocat/following{/other_user}",
  "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
  "organizations_url": "https://api.github.com/users/octocat/orgs",
  "repos_url": "https://api.github.com/users/octocat/repos",
  "events_url": "https://api.github.com/users/octocat/events{/privacy}",
  "received_events_url": "https://api.github.com/users/octocat/received_events",
  "type": "User",
  "site_admin": false,
  "name": "monalisa octocat",
  "company": "GitHub",
  "blog": "https://github.com/blog",
  "location": "San Francisco",
  "email": "octocat@github.com",
  "hireable": false,
  "bio": "There once was...",
  "public_repos": 2,
  "public_gists": 1,
  "followers": 20,
  "following": 0,
  "created_at": "2008-01-14T04:33:35Z",
  "updated_at": "2008-01-14T04:33:35Z"
}

これを JSON-to-Go に食わせると

type AutoGenerated struct {
    Login string `json:"login"`
    ID int `json:"id"`
    AvatarURL string `json:"avatar_url"`
    GravatarID string `json:"gravatar_id"`
    URL string `json:"url"`
    HTMLURL string `json:"html_url"`
    FollowersURL string `json:"followers_url"`
    FollowingURL string `json:"following_url"`
    GistsURL string `json:"gists_url"`
    StarredURL string `json:"starred_url"`
    SubscriptionsURL string `json:"subscriptions_url"`
    OrganizationsURL string `json:"organizations_url"`
    ReposURL string `json:"repos_url"`
    EventsURL string `json:"events_url"`
    ReceivedEventsURL string `json:"received_events_url"`
    Type string `json:"type"`
    SiteAdmin bool `json:"site_admin"`
    Name string `json:"name"`
    Company string `json:"company"`
    Blog string `json:"blog"`
    Location string `json:"location"`
    Email string `json:"email"`
    Hireable bool `json:"hireable"`
    Bio string `json:"bio"`
    PublicRepos int `json:"public_repos"`
    PublicGists int `json:"public_gists"`
    Followers int `json:"followers"`
    Following int `json:"following"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

Golang の struct が一瞬で出来上がります。struct 名は AutoGenerated になっているので目的の名前で置き換えて下さい。このサイト、javascript だけで実装されているので JSON はどこにも送信されません。もしどうしても外部のサイトに JSON を食わしたくないというのであれば以下のリポジトリを clone して自前で実行すれば良いです。

GitHub - mholt/json-to-go: Translates JSON into a Go type in your browser instantly

README.md Translates JSON into a Go type definition. Check it out! This is a sister tool to curl-to-...

https://github.com/mholt/json-to-go

尚、いちいちサーバ立ち上げるのめんどくさいという事であれば、コマンドラインで実行するためのパッチもあります。

Publish CLI to npm? · Issue #17 · mholt/json-to-go · GitHub
https://github.com/mholt/json-to-go/issues/17#issuecomment-224544937

便利ですね。


2016/10/25


mattn - Creators' Stickers

Operating Environment LINE for iOS or Android version 3.1.1 or higher, LINE for NOKIA Asha version 1...

https://store.line.me/stickershop/product/1337465

LINE スタンプ作りました。我慢しきれなかったオッサンギャグを言ってしまった後、なるはやでこのスタンプを投下して下さい。罪悪感が少し減るかもしれません。

LINEスタンプ


いい記事に感化されて僕も何か書きたくなった。

Golangにおけるinterfaceをつかったテスト技法 | SOTA

Golangにおけるinterfaceをつかったテスト技法 最近何度か聞かれたので自分がGolangでCLIツールやAPIサーバーを書くときに実践してるinterfaceを使ったテスト技法について簡単...

http://deeeet.com/writing/2016/10/25/go-interface-testing/

僕も1つ golang のテストの tips を。golang を書いていて良くあるのが「コマンドがステータス 0 で終了する事」のテスト。

package main

import "os"

func doSomething() {
    os.Exit(0)
}

func main() {
    doSomething()
}

でも os.Exit しちゃうとテスト自体が死んでしまうので「テスト出来ない」と思われがち。しかしラッパーを書いてあげる事でテストは出来る。まず os.Exit を関数リファレンスにしてしまう。

package main

import "os"

var exit = os.Exit

func doSomething() {
    exit(0)
}

func main() {
    doSomething()
}

次に以下の様なテストツールを書く。ファイル名は main_test.go

package main

import (
    "errors"
    "fmt"
    "testing"
)

type ExitError int

func (e ExitError) Error() string {
    return fmt.Sprintf("exited with code %d"int(e))
}

func init() {
    exit = func(n int) {
        panic(ExitError(n))
    }
}

func testExit(code int, f func()) (err error) {
    defer func() {
        e := recover()
        switch t := e.(type) {
        case ExitError:
            if int(t) == code {
                err = nil
            } else {
                err = fmt.Errorf("expected exit with %v but %v", code, e)
            }
        default:
            err = fmt.Errorf("expected exit with %v but %v", code, e)
        }
    }()

    f()
    return errors.New("expected exited but not")
}

第一引数は期待する終了コード。第二引数は関数を呼び出すためのクロージャ。後は以下の様なテストを書くだけ。

func TestExit(t *testing.T) {
    err := testExit(0func() {
        doSomething()
    })

    if err != nil {
        t.Fatal(err)
    }
}

テストを実行すると

$ go test
PASS
ok      _/C_/dev/go-sandbox/exittest    0.080s

今度はコードを修正して

package main

import "os"

var exit = os.Exit

func doSomething() {
    exit(1)
}

func main() {
    doSomething()
}
$ go test
--- FAIL: TestExit (0.00s)
        main_test.go:46: expected exit with 0 but exited with code 1
FAIL
FAIL    _/C_/dev/go-sandbox/exittest    0.080s

また log.Fatal で panic を起こさせても大丈夫。

package main

import (
    "log"
    "os"
)

var exit = os.Exit

func doSomething() {
    log.Fatal("unknown error")
}

func main() {
    doSomething()
}
$ go test
2016/10/25 11:34:03 unknown error
FAIL    _/C_/dev/go-sandbox/exittest    0.254s

出来ないと思ったらそこで勝負は終わりよ!

注意点としては、os.Exit の実行と同じスコープで recover する処理があると、そちらに拾われてしまうのでその辺が良く分かっている人だけお使い下さい。

あと、もちろんテストしやすい様に、以下の様に実装するのが正しいのです。(上記はそれが出来ない場合の得策です)

package main

import (
    "log"
    "os"
)

func doSomething() int {
    log.Print("unknown error")
    return 1
}

func main() {
    os.Exit(doSomething())
}