NoahOrberg Advent Calendar 2日目 君の名はを見たあとに君の縄を見てテンションが上がっているのあ

これは NoahOrberg Advent Calendar 2021 2日目 の記事です。

1日目は flying_hato_bus さんの 渋谷にクロックスを履いてくるのあ です 3日目は flying_hato_bus さんの記事です

学部三年のときに、旧はとバスハウスで「君の名は」を見たあとに「君の縄」を見てテンションが上がっているのあの画像です。

f:id:flying_hato_bus:20211202003454p:plain

NoahOrberg Advent Calendar 1日目 渋谷でクロックスを履いてくるのあ

これは NoahOrberg Advent Calendar 2021 1日目 の記事です。

2日目は flying_hato_bus さんの記事です

こちらは 学部三年生のときに渋谷で行われた逆求人イベントに出るため、ハチ公前で集合したところで雨が降っているのに素足にクロックスを履いてくるのあ です。

f:id:flying_hato_bus:20211202002457p:plain

2020年買ってよかったもの

すげー遅れて申し訳ないです。こちらは Aizu Advent Calender 2020 の記事になります。

adventar.org

書こう書こうと思いながら今になって書いていますが許してください。

私事ですが今年から社会人になり、ある程度経済的余裕が生まれた中で、自分は様々なものを購入し、消費しました。

よくあるやつですが、自分が今年一年買ったものの中で良かったものをまとめていこうかなと思います。

ガシェット

いろいろなものを買ったんですが、いくつか紹介していきます。

SONY ノイズキャンセリングイヤホン WI-1000X

www.sony.jp

前までに使っていたワイヤレスイヤホンが音質的にもデザイン的にも良かったんですが、ついに耳につける部分がお亡くなりになりました。 完全分離も視野に入れたんですが、確実になくすなぁというのと、ケースに入れても接点が死んでしまい充電しなくなることが前に使っていたイヤホンで発生したのでネックバンド式のものにしました。

なんと言ってもノイズキャンセリングがすごい、基本家で仕事をするときにはイヤホンを付けて作業をしているんですが、静寂の中で作業に没頭できます。 コードを無限に書くフェーズではでんぱ組の楽曲を聴いていますが音質もとてもいい。生産性のあがるいいものでした。

自作PC

高校一年のときに組んだPCがいい加減古くなったのでこれを機に一新しました。

高校時代にめちゃくちゃ組んだ自作PCですが、4年も経つと環境やらがだいぶ変わっていましたね。 内容的にはミドルスペックのゲーミングPCということでやっています。最近はApexにお熱ですが、上司にFF14をやるように 脅迫されて 勧められています。

モニターアーム & 作業用ディスプレイ

机の上を一新するときにモニタを空中に浮かせたかったのでモニターアームとディスプレイを購入しました。

モニターアーム

グリーンハウスのモニターアームを購入、変なメーカーのものでなければ何でもよく、レビューもそこまで悪いものではなかったのでこちらにしました。 がっちり固定できて、使い勝手も悪くないので可もなく不可もなくという感じです。

モニター

作業用 (IPS液晶 + DP, HDMI端子が付いている24型) と ゲーム用 (IPS液晶 + DP, HDMI が付いており 144Hz 以上が出る24型) を購入しました。なおベゼルが気になったので、できればベゼルレスのものを探しました。

作業用は要件を満たすものであれば安ければ安いほどいいという感じだったので HP のものを購入しました。

使用用途としてはプログラムを書く用のものなので、とりあえずIPS液晶のみやすさがあればいいというものでしたが大満足です。

ゲーム用も同様にIPSであれば安ければ安いほど良くて、色々あった結果acerのものになりました。

165Hzまで出るなんかもうそこまでしなくてもいいいだろうというモデルでしたが、やっぱりヌルヌル動くのはいいですね。ちょっとはApexでキルを取れるようになりました。

キーボード TEX Shinobi

Shinobi

台湾メーカーのThinkpad風キーボードです。少数生産のようで、知ってからすぐに注文しました。 中身としてはThinkpad伝統の7段キーボードをベースに、メカニカルスイッチで作られたいかにもオタク好みなものになっています。

通常はUSB-Cで接続するんですが、別売りのBluetoothキットを接続すると BLE で接続もできます。

学生時代から使用していた Lenovo 純正の Thinkpad キーボードのケーブルとの接続部分が弱ってきてしまったタイミングだったのでいい感じに乗り換えることができました。 キースイッチは定番の青赤黒、変わり種の銀と緑などから選べますが、自分はメカニカルキースイッチが初だったのでクセのない赤にしました。

音はそこそこでキータッチも上々、トラックポイントは今までのものよりも格段に動かしやすくなっており、職場で使うためにもう一台ほしいくらいの勢いで欲しくなりました。

家具・家電

自動昇降机 Flexispot E7

今年買って一番良かったものかもしれない自動昇降机です。

flexispot.jp

大学時代から使っていた机が壊れ始めたり、窮屈だったのでボーナスをはたいて購入しました。

机の高さが 580-1230mm で設定でき、好きな高さを最大4つまでプリセットできるものです。 自分は座位で 740mm 立位で 1100mm に設定しています。

立ったまま仕事するのは最初そんなに変わるものかと思いましたが、リフレッシュできたり、より自由に動くことができるので結構はかどりました。 立つことによって脳に血液が行くのか、仕事の進みが早かったように思います。

あと意外なことに、立ってゲームをすることで成績がよくなった感じもします。反応速度が変わったことよりも、立つことによって自分のキーボード、マウスの動かし方によりしっくりくる形で動かすことができ、柔軟な動きができるようになったというのが大きな利点なのなと思います。

上の画像を見ると、ケーブルがないように見えますが、実は天板の下にかごのようなものを取り付け、その中にケーブル類を収納しています。 せっかくいい机を購入したし、机周りをかっこよくしたいと思ってやったことでしたが、それ以上に掃除がしやすくなりました。床に接地するケーブルがないために掃除機で机の下を掃除しやすく、ちょっとでも汚れが見えたら掃除機で吸うということを心がけると、とても清潔な状態を保つことができます。

組み立てや配線は苦労しますが、それを考えても使い勝手や掃除も楽になり、今年一番買ってよかったものかなと思いました。

コードレス掃除機 makita CL180FDZ

オタクはみんな大好き、マキタのコードレス掃除機です。

今まで使っていた掃除機はコンセントにつなげるタイプだったんですが、そのせいで取り回しが悪かったり、そもそも出すのが億劫になったりしがちで、部屋の掃除をサボるとすぐホコリだらけになってました。

普段から手に取れる場所にあれば、汚れたときにもサッと掃除ができるんじゃないかと思って購入しました。 最高でした。思い立ったらすぐ吸える、マキタの18Vのパワーは吸引力も凄まじい、とりあえず鼻歌歌いながら10分20分家の中を行き来するだけでそれなりに部屋がきれいになります。

ごみ捨ては上の部分を外せばすぐにできるのでフィルターの掃除などをしなくてもいいのも良いです。 先程机の部分でも言った、ちょっとでも汚れを見たら吸うという運用方法があっているみたいで、それを気にすれば部屋の中はきれいなままで維持することができます。

オタク的な話をすると、実はマキタ製品で一番使われているバッテリーが 18v のものです。確か250種類くらいあります。 なので、この掃除機に使われているバッテリーは他のマキタ製品のものを動かすことができるため、普段はコードレス掃除機を使うけど、いざとなったら電動ドライバーでコーススレッド打てたりもするので18vのマキタ製品を一つ持っておくととても便利な生活を送ることができます!

バッテリーは純正品を使いましょう (異常発熱して死ぬかと思った)

ファッション

特にこだわりはないですが、ちょっと書きたかったので

オニツカタイガー TIGER ALLY

www.onitsukatigermagazine.com

今までは安いスニーカーでもなんとかなっていましたが、それなりにきっちりしたスニーカー買いたいなと思って購入しました。 別にそういうのはないんですが、会社に行くときや遊びに行くときに適した靴がなかったので灰色にしましたが、履き心地がとても良かった。

ふわふわしていて歩き疲れない絶妙なバランスで良かったですね。あとは人とあまりかぶらない。どんな服でも季節でもそれなりに合うので、一年中履けるコスパのいい靴だなと思いました。

アティカ スポーツサンダル

夏場に雑に履くことのできるサンダルです。キーンみたいなサンダルですが、こっちのほうが安い。 普段近所を散歩したりするならこれで十分でした。

そうは言いながらもしっかりホールド性もあるので車の運転もいけますし、海にも入れます。 しけいオタクとお盆に海に行ったときもしっかりこれを履いていきました。

なんやかんや本も結構読みました

村上春樹

なぜか年明けぐらいから村上春樹をちょくちょく読んでいます。好きじゃないですが。 自分がよく見ている Youtuber の 本棚食堂のGenさんの動画に感化されて読み始めたのが最初な気がします。 おすすめするほど僕は本を読めていませんが、蛍という短編が自分の中ではいいなと思っています。

伊坂幸太郎

話の作りが凝っていて、その上で話をつなげるのがとてもうまい作家さんですね。 読書始めようと思っている方にもおすすめできる作家さんです。

オー!ファーザー

オー!ファーザー

オー!ファーザー

高校生の主人公と、年も性格もてんでバラバラな四人の父親が繰り広げるドタバタ話。 ただでさえ父親が四人いるという状況でもカオスで、物語として書くのは難しそうなのに普通にまとまりのある面白い話に昇華する伊坂幸太郎の文章力と構成力に驚かされました。 なんなら中学生から読んでも面白いと思うのでなにか人に本をおすすめしたいときにはとりあえず外さない本だと思います。

その他

坂口安吾 白痴

白痴 (新潮文庫)

白痴 (新潮文庫)

落差がすごいとお思いのあなた、わかります。なんで自分もこんな本を読んだかわからないです。 短編集なのでどこから読んでもそれなりに面白いと思います。

個人的には「青鬼の褌を洗う女」という話の冒頭にある

私は近頃死んだ母が生き返ってきたので恐縮している。私がだんだん母に似てきたのだ。あ、また――私は母を発見するたびにすくんでしまう。

という一文が頭に残っています。

滝本竜彦 NHKにようこそ

NHKにようこそ! (角川文庫)

NHKにようこそ! (角川文庫)

しけいオタクからずっと前にもらったものですが、いい加減読まないとと思って読みました。 内容ですか?死にたくなる内容でしたよ。 人間みんな心のそこには地獄が広がっているなということを思い知らされるいい作品でした。

チェンソーマン

今年一番良かった漫画です。内容はあれでしたね。藤本タツキ先生はもっと人の心持って。 レゼちゃん...

僕の心のヤバイやつ

隔週火曜日、会社のslackでオタクたちが死んでました。 桜井のりお先生はそろそろ戦時国際法で使用禁止武器に採択されそうな勢いですね。

最後に

だいぶ偏った内容でしたが、自分の買ってよかったものを駆け足的に書いていきました。 来年もいいものをいっぱい見つけていきたいですね!!!!!

それではよいお年を!!!

t.Cleanup()で並列に動かすことのできるテーブルドリブンテストを書く

最近は副業でGoを書いています。

最近よくテーブルドリブンテストを書いています。 テストの数が多くなってくるとテストを動かすだけでも長い時間がかかるので、t.Parallel()を使い、(DB等の同時にアクセスすると不整合が起きたりする場合などを除いては)できるだけ並列にテストを動かしています。

テーブルドリブンテストを並列で行うということについては、テストを変更してエラーが起きた際にテストコード起因なのか、それとも並列に動かしていることによって起因したものなのかがわかりづらいという論点があると思いますが、そこは考えずに今回は進めて行きます。

A, Bという2つのテスト用関数がある時、A, Bを並列にテストするというのはあると思いますが、この中でテーブルドリブンテストを動かす際には、テーブルの情報によって作られたサブテストも並列に動かしたくなると思います。

つまりはテーブルをfor文で回して1回ずつテストするのではなく、あらかじめテーブルの情報からサブテストを作成しておき、並列でサブテストを全て回すということです。

func TestFunctionA(t *testing.T){
    t.Parallel() // TestFunctionA を並列に動かす

    type testCase struct {
        name string
        title string
        expectStatusCode int
    }
    
    testCases := map[string]*testCase{
        "Yurine-sanはアクセス可能": &testCase {
            name: "Yurine",
            title: "DROPKICK ON MY DEVIL",
            expectStatusCode: http.StatusOK,
        },
        "Medusa-sanはアクセス可能": &testCase {
            name: "Medusa",
            title: "DROPKICK ON MY DEVIL",
            expectStatusCode: http.StatusOK,
        },
        "zyashin-changはアクセスできない": &testCase {
            name: "Zyashin",
            title: "DROPKICK ON MY DEVIL",
            expectStatusCode: http.StatusBadRequest,
        },
    }
    
    for testName, tc := range testCases {
        tc := tc
        t.Run(testName, func(t *testing.T){
            t.Parallel()
            t.Log(tc)
        })
    } 
}

func TestFunctionB(t *testing.T){
    t.Parallel() // TestFunctionB を並列に動かす

    type testCase struct {
        name string
        title string
        expectStatusCode int
    }
    
    testCases := map[string]*testCase{
        "Rin-sanはアクセス可能": &testCase {
            name: "Rin",
            title: "Yurucamp",
            expectStatusCode: http.StatusOK,
        },
        "Ena-sanはアクセス可能": &testCase {
            name: "Ena",
            title: "Yurucamp",
            expectStatusCode: http.StatusOK,
        },
        "Nadeshiko-changはアクセスできない": &testCase {
            name: "Nadeshiko",
            title: "Yurucamp",
            expectStatusCode: http.StatusBadRequest,
        },
    }
    
    for testName, tc := range testCases {
        tc := tc
        t.Run(testName, func(t *testing.T){
            t.Parallel()
            t.Log(tc)
        })
    }  
}

このコードについては、特に何も意識せずに動くはずです。

実際に動くコード

動くとこうなるはずですね

=== RUN   TestFunctionA
=== PAUSE TestFunctionA
=== RUN   TestFunctionB
=== PAUSE TestFunctionB
=== CONT  TestFunctionA
=== RUN   TestFunctionA/zyashin-changはアクセスできない
=== PAUSE TestFunctionA/zyashin-changはアクセスできない
=== RUN   TestFunctionA/Yurine-sanはアクセス可能
=== PAUSE TestFunctionA/Yurine-sanはアクセス可能
=== RUN   TestFunctionA/Medusa-sanはアクセス可能
=== PAUSE TestFunctionA/Medusa-sanはアクセス可能
=== CONT  TestFunctionB
=== RUN   TestFunctionB/Rin-sanはアクセス可能
=== PAUSE TestFunctionB/Rin-sanはアクセス可能
=== RUN   TestFunctionB/Ena-sanはアクセス可能
=== PAUSE TestFunctionB/Ena-sanはアクセス可能
=== RUN   TestFunctionB/Nadeshiko-changはアクセスできない
=== PAUSE TestFunctionB/Nadeshiko-changはアクセスできない
=== CONT  TestFunctionB/Rin-sanはアクセス可能
    TestFunctionB/Rin-sanはアクセス可能: prog.go:75: &{Rin Yurucamp 200}
=== CONT  TestFunctionB/Nadeshiko-changはアクセスできない
    TestFunctionB/Nadeshiko-changはアクセスできない: prog.go:75: &{Nadeshiko Yurucamp 400}
=== CONT  TestFunctionA/Medusa-sanはアクセス可能
    TestFunctionA/Medusa-sanはアクセス可能: prog.go:39: &{Medusa DROPKICK ON MY DEVIL 200}
=== CONT  TestFunctionA/Yurine-sanはアクセス可能
    TestFunctionA/Yurine-sanはアクセス可能: prog.go:39: &{Yurine DROPKICK ON MY DEVIL 200}
=== CONT  TestFunctionB/Ena-sanはアクセス可能
    TestFunctionB/Ena-sanはアクセス可能: prog.go:75: &{Ena Yurucamp 200}
--- PASS: TestFunctionB (0.00s)
    --- PASS: TestFunctionB/Rin-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionB/Nadeshiko-changはアクセスできない (0.00s)
    --- PASS: TestFunctionB/Ena-sanはアクセス可能 (0.00s)
=== CONT  TestFunctionA/zyashin-changはアクセスできない
    TestFunctionA/zyashin-changはアクセスできない: prog.go:39: &{zyashin DROPKICK ON MY DEVIL 400}
--- PASS: TestFunctionA (0.00s)
    --- PASS: TestFunctionA/Medusa-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionA/Yurine-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionA/zyashin-changはアクセスできない (0.00s)
PASS

HTTPハンドラのテスト

カンが良い方ならテストケース等から「これはHTTPハンドラ周りのテストだな?」ということが解ると思いますが、実際にこのようなテストを書いてテストを書くとなると、どのような関数になるでしょうか?

特に考えずに書くとするなら、以下のような手順になりそうです。

  • テストをするハンドラを書く
  • テストの中でリクエストを待つようなサーバーを起動して待ち受ける
  • for文の中で起動しておいたサーバーに向かってリクエストを投げつける
  • responseに入っているステータスコードやBodyを使ってassertする

それでは実際に書いていきましょう。なお、今回の論点はサーバー周りの実装に対するものではないため、ある程度簡易的な実装までとします。

テストをするハンドラを書く

別ファイルでもいいですが、今回は実装を簡単にするためにとても簡素なものにします。

type Anime struct {
    Title string `json:"title"`
    Name string `json:"name"`
}

func getTestHandler() http.HandlerFunc {
    return func(w http.ResponseWriter, req *http.Request) {
        if req.Method != http.MethodPost {
            w.WriteHeader(http.StatusNotImplemented)
            return
        }

        b, err := ioutil.ReadAll(req.Body)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            return
        }

        var anime Anime
        err = json.Unmarshal(b, &anime)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            return
        }

        if anime.Title == "DROPKICK ON MY DEVIL" {
            if anime.Name == "Zyashin" {
                w.WriteHeader(http.StatusUnauthorized)
            } else {
                w.WriteHeader(http.StatusOK)
                w.Write([]byte(fmt.Sprintf("Hello %v San!", anime.Name)))
            }
        } else {
            w.WriteHeader(http.StatusBadRequest)
        }
        return
    }
}

getTestHandler() を呼べばハンドラが返ってきます。これをテストごとに動かせば良いはずです。いい感じに動かすとすればこうなるでしょう。

func TestFunctionA(t *testing.T) {
    t.Parallel() // TestFunctionA を並列に動かす

    type testCase struct {
        name             string
        title            string
        expectStatusCode int
    }

    testCases := map[string]*testCase{
        "Yurine-sanはアクセス可能": &testCase{
            name:             "Yurine",
            title:            "DROPKICK ON MY DEVIL",
            expectStatusCode: http.StatusOK,
        },
        "Medusa-sanはアクセス可能": &testCase{
            name:             "Medusa",
            title:            "DROPKICK ON MY DEVIL",
            expectStatusCode: http.StatusOK,
        },
        "zyashin-changはアクセスできない": &testCase{
            name:             "Zyashin",
            title:            "DROPKICK ON MY DEVIL",
            expectStatusCode: http.StatusUnauthorized,
        },
    }

    ts := httptest.NewServer(getTestHandler())
    defer ts.Close()

    for testName, tc := range testCases {
        tc := tc
        t.Run(testName, func(t *testing.T) {
            t.Parallel()

            var u bytes.Buffer
            u.WriteString(string(ts.URL))

            anime := &Anime{Title: tc.title, Name: tc.name}
            b, err := json.Marshal(anime)
            if err != nil {
                t.Fatalf("request json create failed: %v", err)
            }

            client := http.DefaultClient
            defer client.CloseIdleConnections()

            req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewBuffer(b))
            if err != nil {
                t.Fatalf("error occured at create new request : %v", err)
            }

            res, err := client.Do(req)
            if err != nil {
                t.Fatalf("test api request error: %v", err)
            }

            defer res.Body.Close()

            body, err := ioutil.ReadAll(res.Body)
            if err != nil {
                t.Fatalf("response read failed: %v", err)
            }

            if tc.expectStatusCode != res.StatusCode {
                t.Fatalf("unexpected response status code: want %v, but got %v, response body: %v", tc.expectStatusCode, res.StatusCode, string(body))
            }

            var resdata Anime
            err = json.NewDecoder(res.Body).Decode(&resdata)
            if reflect.DeepEqual(anime, &resdata) {
                t.Fatalf("unexpected server response want: %v but got %v, response body: %v", anime, &resdata, string(body))
            }
        })
    }
}

https://play.golang.org/p/ZWGY5JsTjvL

しかし、これで動かすと上手く動きません。

=== RUN   TestFunctionA
=== PAUSE TestFunctionA
=== CONT  TestFunctionA
=== RUN   TestFunctionA/Medusa-sanはアクセス可能
=== PAUSE TestFunctionA/Medusa-sanはアクセス可能
=== RUN   TestFunctionA/zyashin-changはアクセスできない
=== PAUSE TestFunctionA/zyashin-changはアクセスできない
=== RUN   TestFunctionA/Yurine-sanはアクセス可能
=== PAUSE TestFunctionA/Yurine-sanはアクセス可能
=== CONT  TestFunctionA/Medusa-sanはアクセス可能
=== CONT  TestFunctionA/Yurine-sanはアクセス可能
=== CONT  TestFunctionA/zyashin-changはアクセスできない
    prog.go:108: test api request error: Post "http://127.0.0.1:30426": dial tcp 127.0.0.1:30426: connect: connection refused
=== CONT  TestFunctionA/Yurine-sanはアクセス可能
    prog.go:108: test api request error: Post "http://127.0.0.1:30426": dial tcp 127.0.0.1:30426: connect: connection refused
=== CONT  TestFunctionA/Medusa-sanはアクセス可能
    prog.go:108: test api request error: Post "http://127.0.0.1:30426": dial tcp 127.0.0.1:30426: connect: connection refused
--- FAIL: TestFunctionA (0.00s)
    --- FAIL: TestFunctionA/zyashin-changはアクセスできない (0.00s)
    --- FAIL: TestFunctionA/Yurine-sanはアクセス可能 (0.00s)
    --- FAIL: TestFunctionA/Medusa-sanはアクセス可能 (0.00s)
FAIL

全部 connection refused エラーになります。つまりクエストが上手く出来ていません。

一度並列に動かすのをやめてみましょう。

テーブルからサブテストを生成する

   for testName, tc := range testCases {
        //tc := tc
        t.Run(testName, func(t *testing.T) {
            //t.Parallel()

この部分をコメントアウトします。

=== RUN   TestFunctionA
=== PAUSE TestFunctionA
=== CONT  TestFunctionA
=== RUN   TestFunctionA/Yurine-sanはアクセス可能
=== RUN   TestFunctionA/Medusa-sanはアクセス可能
=== RUN   TestFunctionA/zyashin-changはアクセスできない
--- PASS: TestFunctionA (0.00s)
    --- PASS: TestFunctionA/Yurine-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionA/Medusa-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionA/zyashin-changはアクセスできない (0.00s)
PASS
ok      command-line-arguments  0.004s

コメントアウトする、つまり並列にテストをしなくなるといい感じに動いているようです。

並列にしたいけれど、並列にすると何故か connection refused になるという状況です。

connection refusedの原因として、一番に考えられるのはサーバーが立ち上がっていないという状況です。どうやら

 ts := httptest.NewServer(getTestHandler())
    defer ts.Close()

このあたりで起動したサーバーが死んでいるようです。 どうもdeferあたりが怪しいですね。

どうもサブテストが回る前にdeferが実行され、サーバーが落ちている可能性があります。

というわけで簡単なテストのためのテストを書きました。

package main

import (
    "log"
    "testing"
)

func TestDefer(t *testing.T) {
    tcs := map[string]string{
        "a": "あ",
        "b": "い",
        "c": "う",
    }

    defer log.Printf("えお")

    for tn, tc := range tcs {
        tc := tc
        t.Run(tn, func(t *testing.T) {
            t.Parallel()
            log.Println(tc)
        })
    }
}

https://play.golang.org/p/VY0h5gl86zX

ここで問題、上のようなparallelな環境下で、deferはどのタイミングで呼ばれるでしょうか?

普通に考えれば出力としては

あ
い
う
えお

となりそうです。これは defer が最後の最後に実行されるということからも自然であると言えます。

しかし、実際の出力はこうでした

=== RUN   TestDefer
=== RUN   TestDefer/a
=== PAUSE TestDefer/a
=== RUN   TestDefer/b
=== PAUSE TestDefer/b
=== RUN   TestDefer/c
=== PAUSE TestDefer/c
2009/11/10 23:00:00 えお
=== CONT  TestDefer/a
2009/11/10 23:00:00 あ
=== CONT  TestDefer/b
2009/11/10 23:00:00 い
=== CONT  TestDefer/c
2009/11/10 23:00:00 う
--- PASS: TestDefer (0.00s)
    --- PASS: TestDefer/a (0.00s)
    --- PASS: TestDefer/b (0.00s)
    --- PASS: TestDefer/c (0.00s)

??????????????????

どうもdeferがサブテストに入る前に実行されているらしい。調べていると、同じようなことが issueに上がっているのが確認できました。

https://github.com/golang/go/issues/17791

作成者は同様なテストを書いていて、deferが先に呼ばれている問題に遭遇していた模様。 その中でなされていた会話の一部で

t.Parallel is making the t.Run itself a parallel subtest of TestBug. There is no parallel subtest of the t.Run subtest. TestBug will not complete until the parallel test completes. But TestBug's body has to finish because that is what triggers running the parallel tests in the first place.

とある。サブテストが起動する前にdeferが呼ばれるのはどうも仕様らしい。

deferも上手く使えないみたいだし、どうしようと思っていたら大学の先輩が書いた記事がヒットした。どうやら t.Cleanup() を使うことで解消することができるようです。

https://syfm.hatenablog.com/entry/2020/05/17/161842

https://godoc.org/testing#T.Cleanup

Cleanup registers a function to be called when the test and all its subtests complete. Cleanup functions will be called in last added, first called order.

t.Cleanup() はサブテストが終わってからLIFOで呼び出されるらしい。この周りはdeferと同じ挙動をしているようです。

書き換えてみました。

https://play.golang.org/p/e_K7M5VqJJI

package main

import (
    "log"
    "testing"
)

func TestDefer(t *testing.T) {
    tcs := map[string]string{
        "a": "あ",
        "b": "い",
        "c": "う",
    }

    t.Cleanup(func() {
        log.Printf("えお")
    })

    for tn, tc := range tcs {
        tc := tc
        t.Run(tn, func(t *testing.T) {
            t.Parallel()
            log.Println(tc)
        })
    }
}
=== RUN   TestDefer
=== RUN   TestDefer/a
=== PAUSE TestDefer/a
=== RUN   TestDefer/b
=== PAUSE TestDefer/b
=== RUN   TestDefer/c
=== PAUSE TestDefer/c
=== CONT  TestDefer/a
=== CONT  TestDefer/b
=== CONT  TestDefer/c
2009/11/10 23:00:00 あ
2009/11/10 23:00:00 い
2009/11/10 23:00:00 う
2009/11/10 23:00:00 えお
--- PASS: TestDefer (0.00s)
    --- PASS: TestDefer/a (0.00s)
    --- PASS: TestDefer/b (0.00s)
    --- PASS: TestDefer/c (0.00s)
PASS

確かにdeferのときとは違い、サブテストが終了してから えお が出力されており、期待していた挙動をしています。

これを元に、上のe2eテストっぽいものを書き換えてみましょう。

https://play.golang.org/p/-buH40moEg4

=== RUN   TestFunctionA
=== PAUSE TestFunctionA
=== CONT  TestFunctionA
=== RUN   TestFunctionA/Yurine-sanはアクセス可能
=== PAUSE TestFunctionA/Yurine-sanはアクセス可能
=== RUN   TestFunctionA/Medusa-sanはアクセス可能
=== PAUSE TestFunctionA/Medusa-sanはアクセス可能
=== RUN   TestFunctionA/zyashin-changはアクセスできない
=== PAUSE TestFunctionA/zyashin-changはアクセスできない
=== CONT  TestFunctionA/Yurine-sanはアクセス可能
=== CONT  TestFunctionA/zyashin-changはアクセスできない
=== CONT  TestFunctionA/Medusa-sanはアクセス可能
--- PASS: TestFunctionA (0.00s)
    --- PASS: TestFunctionA/Yurine-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionA/Medusa-sanはアクセス可能 (0.00s)
    --- PASS: TestFunctionA/zyashin-changはアクセスできない (0.00s)
PASS

t.Cleanup()は1.14から入った機能で、割りと新しめのものとなっているがかゆいところに手が届く感じでとてもいいと思いました。

抵抗の値をslackで確認したい!!!

どうもこんにちは、最近いろいろバタバタしているはとバスです。

この記事はAizu Advent Calenderの19日目の記事になります

18日目 fatman???? さん

20日目 のあ

当日の朝に担当だったことを知り、ほぼ半日で作った適当なslackのコマンドを紹介します。

カラーコードを確認したい

最近はめっきり機会が減ってしまったんですが、電子工作が好きです。

大学一年のときから始めて、いろいろな物を作ってきました。

hatobus.hatenablog.jp

hatobus.hatenablog.jp

電子部品を使うときのあるあるなんですけど、抵抗の値って、難しくないですか?????

f:id:flying_hato_bus:20191219214131p:plain

12の色と値を暗記しなければいけないし、パッと見ただけでは分からないので調べてから使うという人もいると思います。

そんな時に、slack上で簡単に確認できたら良さそうじゃないですか?

作りました

slackのslashコマンドを作りました。

f:id:flying_hato_bus:20191220095723p:plain

/resistor [抵抗の値]と書くことで、カラーコードの画像が返ってきます。

f:id:flying_hato_bus:20191220100244p:plain

f:id:flying_hato_bus:20191220100322p:plain

実装

使っている技術としては

  • サーバー
    • GCP cloud functions
  • 言語
    • Go

です。cloud functionsをサーバーにして、リクエストに応じてGoで画像を生成、そのままslackに画像を投稿しています。

f:id:flying_hato_bus:20191220101110p:plain

ポイント

github.com

カラーコードを生成する部分です。

抵抗の値を数字だけで表現する場合は入力された文字列をint64にキャストすればいいだけですが、抵抗の値を指定するときには少し特殊なフォーマットの場合があります。4700 Ωを 4k7などに表現するやつです。このようなフォーマットで表現する時に、以下のような表現の仕方があります。

  • 1k
  • 4k7
  • 47k
  • 470k

また、k以外にもMの接頭辞のときもあります。

このように表現されたときの抵抗値も対応しています。

SI接頭辞付きの抵抗値の場合

330Ωなど、接頭辞がついてないときには、 strconv.Atoi()などを使えば、stringをintにキャストできます。まずはこれで当たってみて、接頭辞が付いているときにはエラーが返ってくるため、Splitするセクションに入ります。

上で上手くキャストできなかった場合、SI接頭辞(k, Mなど)が付いているときには、入力された文字をSplitします。Goでは strings.Splitという関数があります。

golang.org

これは文字列を指定文字で切り取ることができるという関数です。

srcstr := "a,b,c"
splitstr := strings.Split(srcstr, ",")

// splitstr -> ["a" "b" "c"]

つまり、これでk, Mの場合でSplitすれば、 4k7などは ["4", "7"] という値が返ります。Splitした値はこのようになります。

  • 1k --> ["1", ""]
  • 4k7 --> ["4", "7"]
  • 47k --> ["47", ""]
  • 470k --> ["470", ""]

補足ですが、Splitには罠が存在して、 47kのように、最後に切り取る文字が来ている場合には最後に空白が入ります。気をつけましょう。

これで上手くSplitできればできたときの文字で乗数も割り出せますね。

  • kでSplitできた --> 乗数 = 3
  • MでSplitできた--> 乗数 = 5

あとはカラーコードを割り出すまでもうちょっとです。

f:id:flying_hato_bus:20191220111435p:plain

抵抗は上2桁の数字をそれぞれの色に対応させ、3本目の線を乗数の色にします。

- 4k7の場合

4700なので 1本目は 黄色 2本目は 紫 乗数は10^2なので 赤

のように求まります。

上にある 1k, 4k7, 47k, 470k の場合は以下のようになります。 (Split済みの配列をcontainsと書きます)

- 1k
contains[0]の文字は1桁、contains[1]の文字は無し、この場合1st barは1になり、2nd barは0になる、乗数は1減るので減らしておく

- 4k7
contains[0]の文字は1桁、contains[1]の文字は1桁、この場合1st barは4になり、2nd barは7になる、乗数に変化はない。

- 47k
contains[0]の文字は2桁、contains[1]の文字は無し、この場合1st barは4になり、2nd barは7になる、乗数は1増やす必要がある。

-470k
contains[0]の文字は3桁、contains[1]の文字は無し、この場合1st barは4になり、2nd barは7になる、乗数は2増やす必要がある。

contain[0]の長さでif-elseしています。

 if len(contain[0]) == 1 {
        fv, _ := strconv.Atoi(contain[0])
        firstband = int64(fv)
        if contain[1] == "" {
            secondband = 0
        } else {
            sv, _ := strconv.Atoi(contain[1])
            secondband = int64(sv)
        }
    } else if len(contain[0]) == 2 {
        fv, err := strconv.Atoi(contain[0])
        if err != nil {
            return nil, err
        }
        firstband = int64(fv / 10)
        secondband = int64(fv % 10)
        mag += 1
    } else {
        resistor_topvalue, err := strconv.Atoi(contain[0])
        if err != nil {
            return nil, err
        }
        for i := 0; ; i++ {
            last1digit := resistor_topvalue % 10
            resistor_topvalue /= 10
            if int64(resistor_topvalue/10) == 0 {
                firstband = int64(resistor_topvalue)
                secondband = int64(last1digit)
                break
            }
            mag += 1
        }
    }

これによって、firstband, secondband, magの3つの数値が求められ、これを元に色を出しています。

色を求めて、いい感じに画像を生成しています。

cloudfunctionsについて

cloud functionsの仕様として、ユーザーが自由にファイルを書き込める場所に制限があります。

cloud.google.com

ファイル システムで書き込み可能な部分は /tmp ディレクトリだけです。このディレクトリは、関数インスタンスの一時ファイルの保存先として使用できます。

とあるので、slackに投稿する画像は /tmp以下に置かなければいけないことがわかります。

大急ぎで書いたのでクソ適当になりましたが、今回のアドベントカレンダーネタは以上になります。

来年からはもっとちゃんと書きたい。

もっと書くべき内容があったと思いますが、内容がバカでかい騒動にまで発展してしまったので書けませんでした。

気をつけよう!!!!!!

野菜を食べる技術

早いものでもう12月ですね

今年もアドベントカレンダーの時期ですね。みなさんはいかがお過ごしでしょうか?

今回のアドベントカレンダー、僕だけ2つの記事を書くみたいになってしまって本当に申し訳ないです(見たときには前半のほうが開いていて、埋まってなかったらなんとも言えない気持ちになってしまうので急いで入れてしまった...)

その貴重な枠をこのようなポエムで汚してしまうことをどうかお許しください...

てなわけでこの記事は Aizu Advent Calendar 2019 2日目の記事になります。

adventar.org

1日目は まさちゃこさん

3日目は インターネットミームの使い手 dennougorilla くんの記事になります。

健康的な生活をしたい

定期的に言っていますが、僕は結構な デブ です。

小学校から高校まではテニス部でそれなりに汗を流していたので、大学入学時には70kgを切っていましたが、大学生活によってブクブク太っていき去年一番太っていた時期は96kgありました。

単純に30kg太ったことになります。

今は85kgあたりをウロウロしています。

切実な目標として、健康的な生活をしたいです。

そもそも健康的な生活とは...? という問題に直面しますが、WHO憲章では、その前文の中で「健康」について、次のように定義しています。

Health is a state of complete physical, mental and social well-being and not merely the absence of disease or infirmity.

公式の訳としては

健康とは、病気でないとか、弱っていないということではなく、肉体的にも、精神的にも、そして社会的にも、すべてが満たされた状態にあることをいいます。

このWHOの健康の定義、めちゃくちゃ厳しくないですか?????

この健康的な生活を目指すのは流石に厳しいので記事の中ではWHOの健康の定義からは離れ、自分が実践していたある程度健康的に暮らす方法として 効率的に野菜を摂る方法 を書こうと思います。

野菜を食べる

野菜を食べるということはビタミンやミネラルを補給でき体のバランスを整えることに繋がります。

健康的な生活をするためには不可欠です。

あと「俺は野菜を意識して食べているぞ、そんじゃそこらの人よりも偉いはず、俺は偉い偉い偉い」と他人にマウントを取れます。精神的な健康も保てるはず。

たまに二郎の上に乗っているもやしを野菜と言っている人もいますが、あれは野菜ではなく油まみれのなにかです。健康的な生活をしたいのであれば二郎を月イチくらいに抑えたほうが絶対にいいです。

効率的に野菜を食べよう

おすすめの食べ方は鍋。一人鍋用スープの素があれば片手鍋でグツグツ野菜を煮込めば放っておくだけで美味しい何かが出来上がり、野菜を摂取できます。

食材を保存する

野菜は水分が多く、すぐに傷んでしまいます。あんまり自炊をしないという人に生野菜をおすすめすることは難しいと思うんですが、冷凍することでこの問題を解決できます。

週の初めや野菜が安い時にキャベツや玉ねぎ、えのきやしめじを買っていい感じの大きさに無心で刻み、100均に売っている安いジップロックみたいなものに詰め込んでそのまま冷凍庫にぶち込んでください。

お肉も一緒に買ってきて、ラップ等で小分けにし一緒に冷凍することもでき、タンパク質も楽に摂れます。

おすすめの野菜

  • キャベツ
    • 味噌や坦々系の鍋におすすめ、よく煮込むと甘みが出てきておいしい
    • 半玉買ってざく切りにするとジップロック特大1袋くらいになる
    • 冬に買うと葉が硬いので注意する
  • 白菜
    • 鍋に使う野菜の王道
    • どんな鍋にも合う
    • 味がしみて美味しい、1/4くらいでジップロック1袋くらいになる
  • 玉ねぎ
    • 味噌汁やラーメンを作るときとかにも使える
    • 常温でも保存が効くので買い置きしてもいい
  • ニンジン
    • 短冊切りにすると鍋で食べやすい
    • ラーメンを作る時に玉ねぎ一緒に入れるだけでもかさ増しになる
  • ネギ
    • 斜めの輪切りにすれば鍋や味噌汁の実として
    • 細切りにすれば薬味としてそのまま乗せられる
    • ゆっくり煮ればトロトロになっておいしい
  • えのき
    • 意味不明なくらい安いときが多い
    • どんな料理にも合うのでハズレが少ない
  • しめじ
    • デフォルトのきのこ
    • どこのスーパーにもあって安く手に入る

お肉はウィンナーをおすすめします。

冷蔵室に置いておいてもそれなりに日持ちがするし、なんなら常温でボリボリ行けるので何か口に入れないととなった時に食った気になれます。

三等分にして冷凍しても鍋にぶちこめばおいしい出汁が出てくれますし、包丁で切るのもめんどくさいときは冷蔵庫から出して手でいい感じの大きさにちぎるだけでもいいです。

鍋を作る

素晴らしいもので、世の中には 一人鍋の素 というものがあります。

絶大な信頼を置いているプチッと鍋シリーズ

ちょっとの水に鍋の素をいれて好きな野菜をぶち込んで10分くらい煮込めば野菜の入った美味しい何かが出来上がります。

一緒に生姜を入れてあげれば手足の先の冷えの対策になります(なってほしい)

鍋の具材を食べ終わった後に冷凍のうどんや餃子を入れればシメも美味しく楽しめます。

???「ガスを使うのめんどくさい、鍋を洗わなくちゃいけないのが嫌」

エビス 電子レンジ調理用品 ブラック 1000ml

エビス 電子レンジ調理用品 ブラック 1000ml

レンジ対応の一人鍋容器でできます。

使い方としては

  1. 容器に水と鍋の素を入れる
  2. 好きな野菜と肉を入れる(肉は最初に入れないと焦げる可能性がある)
  3. 500Wのレンジで10分くらいチンする
  4. おいしい

シメにうどんやご飯を入れたいときも物を入れて5分チンすればおいしいうどんやおじやができます。

洗い物も箸と容器だけで済むので楽ちんです。

鍋という選択

鍋はめちゃくちゃ楽でオススメです。具材をぶち込んで煮るだけで美味しい鍋が出来上がり、野菜も摂れるしいいことずくめです。

ちなみに、友達が家に来たときとかにも楽で、一人鍋の素を人数分に増やせば鍋の量を調整できます。鍋のオートスケーリング(申し訳程度の技術成分)が可能です。

おすすめの鍋の素

おすすめの鍋の素をいくつか貼っておきます。スーパーならこのなかで1つは必ずあるはず。 ちなみに、ドラッグストアのワゴンセールで意味不明なくらい安いときもあるのでそういうときは狙い目です。

  • こなべっち 鶏だし生姜鍋つゆ

最高の鍋の素、生姜の香りと鶏のうまみが感じられる美味しいやつです。シメのうどんも最高にうまいのでオススメ、鍋以外の料理として茹でたそうめんを入れてにゅうめんにしても美味しいですよ。 なかなか店頭に並んでいるのを見ないので、見たら速攻で買いだめするのが吉

オススメの具材
- 白菜
- 鶏肉
- にんじん
- しめじ
- ネギ
  • プチッと鍋 寄せ鍋

寄せ鍋にしてもよし、そのままうどんのつゆにしてもよし、万能でハズレがないおいしい鍋の素 会津だと簡単に手に入る。

オススメの具材
- 白菜
- 肉(なんでも)
- にんじん
- しめじ or えのき
- ネギ
  • プチッと鍋 濃厚みそ鍋

最近ハマっているおいしいやつです、キャベツを煮込んであげると甘くておいしい。シメには業務スーパーで買った安い乾麺を入れてあげるとシメの味噌ラーメンになります。

オススメの具材
- キャベツ
- 肉(なんでも)
- にんじん
- もやし
- しめじ or えのき
- コーン

さいごに

後輩とかを見ていると、ちゃんとした食事を摂って欲しい人が数名います。

↓↓↓↓↓↓↓↓例です↓↓↓↓↓↓↓

食というのは生きる上での基本であって、栄養の偏りや不規則な食事などによる肥満や、それらが原因と考えられる生活習慣病などになっては元も子もありません。

長期的なことを考えればエンジニアも結局は体が資本であり、自身の健康は自分が作っていかなければなりません。

日頃の不摂生がたたったせいでエンジニアとして生きていけなくなるの、嫌じゃないですか?

なんか普段のツイートとは真逆のすごく意識の高い感じがして自分が嫌になったのでここらへんで失礼いたします。

みんな!!!野菜を食べて健康的になろうな!!!!!