NoahOrberg Advent Calendar 2日目 君の名はを見たあとに君の縄を見てテンションが上がっているのあ
これは NoahOrberg Advent Calendar 2021 2日目 の記事です。
1日目は flying_hato_bus さんの 渋谷にクロックスを履いてくるのあ です 3日目は flying_hato_bus さんの記事です
学部三年のときに、旧はとバスハウスで「君の名は」を見たあとに「君の縄」を見てテンションが上がっているのあの画像です。
NoahOrberg Advent Calendar 1日目 渋谷でクロックスを履いてくるのあ
これは NoahOrberg Advent Calendar 2021 1日目 の記事です。
2日目は flying_hato_bus さんの記事です
こちらは 学部三年生のときに渋谷で行われた逆求人イベントに出るため、ハチ公前で集合したところで雨が降っているのに素足にクロックスを履いてくるのあ です。
2020年買ってよかったもの
すげー遅れて申し訳ないです。こちらは Aizu Advent Calender 2020 の記事になります。
書こう書こうと思いながら今になって書いていますが許してください。
私事ですが今年から社会人になり、ある程度経済的余裕が生まれた中で、自分は様々なものを購入し、消費しました。
よくあるやつですが、自分が今年一年買ったものの中で良かったものをまとめていこうかなと思います。
ガシェット
いろいろなものを買ったんですが、いくつか紹介していきます。
SONY ノイズキャンセリングイヤホン WI-1000X
前までに使っていたワイヤレスイヤホンが音質的にもデザイン的にも良かったんですが、ついに耳につける部分がお亡くなりになりました。 完全分離も視野に入れたんですが、確実になくすなぁというのと、ケースに入れても接点が死んでしまい充電しなくなることが前に使っていたイヤホンで発生したのでネックバンド式のものにしました。
なんと言ってもノイズキャンセリングがすごい、基本家で仕事をするときにはイヤホンを付けて作業をしているんですが、静寂の中で作業に没頭できます。 コードを無限に書くフェーズではでんぱ組の楽曲を聴いていますが音質もとてもいい。生産性のあがるいいものでした。
自作PC
— 飛ばすはとバス (@flying_hato_bus) 2020年8月28日
高校一年のときに組んだPCがいい加減古くなったのでこれを機に一新しました。
高校時代にめちゃくちゃ組んだ自作PCですが、4年も経つと環境やらがだいぶ変わっていましたね。
内容的にはミドルスペックのゲーミングPCということでやっています。最近はApexにお熱ですが、上司にFF14をやるように 脅迫されて 勧められています。
モニターアーム & 作業用ディスプレイ
机の上を一新するときにモニタを空中に浮かせたかったのでモニターアームとディスプレイを購入しました。
モニターアーム
グリーンハウス モニターアーム ガス圧式 10-27インチ対応 耐荷重3-7kg GH-AMCD01
- 発売日: 2015/11/19
- メディア: Personal Computers
グリーンハウスのモニターアームを購入、変なメーカーのものでなければ何でもよく、レビューもそこまで悪いものではなかったのでこちらにしました。 がっちり固定できて、使い勝手も悪くないので可もなく不可もなくという感じです。
モニター
作業用 (IPS液晶 + DP, HDMI端子が付いている24型) と ゲーム用 (IPS液晶 + DP, HDMI が付いており 144Hz 以上が出る24型) を購入しました。なおベゼルが気になったので、できればベゼルレスのものを探しました。
作業用は要件を満たすものであれば安ければ安いほどいいという感じだったので HP のものを購入しました。
HP ProDisplay 23.8インチワイドモニター P244 5QG35AA#ABJ
- メディア: Personal Computers
使用用途としてはプログラムを書く用のものなので、とりあえずIPS液晶のみやすさがあればいいというものでしたが大満足です。
ゲーム用も同様にIPSであれば安ければ安いほど良くて、色々あった結果acerのものになりました。
165Hzまで出るなんかもうそこまでしなくてもいいいだろうというモデルでしたが、やっぱりヌルヌル動くのはいいですね。ちょっとはApexでキルを取れるようになりました。
キーボード TEX Shinobi
My new gear… pic.twitter.com/uP3cAob7Md
— 飛ばすはとバス (@flying_hato_bus) 2020年7月12日
台湾メーカーのThinkpad風キーボードです。少数生産のようで、知ってからすぐに注文しました。 中身としてはThinkpad伝統の7段キーボードをベースに、メカニカルスイッチで作られたいかにもオタク好みなものになっています。
通常はUSB-Cで接続するんですが、別売りのBluetoothキットを接続すると BLE で接続もできます。
学生時代から使用していた Lenovo 純正の Thinkpad キーボードのケーブルとの接続部分が弱ってきてしまったタイミングだったのでいい感じに乗り換えることができました。 キースイッチは定番の青赤黒、変わり種の銀と緑などから選べますが、自分はメカニカルキースイッチが初だったのでクセのない赤にしました。
音はそこそこでキータッチも上々、トラックポイントは今までのものよりも格段に動かしやすくなっており、職場で使うためにもう一台ほしいくらいの勢いで欲しくなりました。
家具・家電
自動昇降机 Flexispot E7
今年買って一番良かったものかもしれない自動昇降机です。
大学時代から使っていた机が壊れ始めたり、窮屈だったのでボーナスをはたいて購入しました。
一気に部屋がおしゃれになった pic.twitter.com/X2GcvUS3Ba
— 飛ばすはとバス (@flying_hato_bus) 2020年11月29日
机の高さが 580-1230mm で設定でき、好きな高さを最大4つまでプリセットできるものです。 自分は座位で 740mm 立位で 1100mm に設定しています。
立ったまま仕事するのは最初そんなに変わるものかと思いましたが、リフレッシュできたり、より自由に動くことができるので結構はかどりました。 立つことによって脳に血液が行くのか、仕事の進みが早かったように思います。
あと意外なことに、立ってゲームをすることで成績がよくなった感じもします。反応速度が変わったことよりも、立つことによって自分のキーボード、マウスの動かし方によりしっくりくる形で動かすことができ、柔軟な動きができるようになったというのが大きな利点なのなと思います。
上の画像を見ると、ケーブルがないように見えますが、実は天板の下にかごのようなものを取り付け、その中にケーブル類を収納しています。 せっかくいい机を購入したし、机周りをかっこよくしたいと思ってやったことでしたが、それ以上に掃除がしやすくなりました。床に接地するケーブルがないために掃除機で机の下を掃除しやすく、ちょっとでも汚れが見えたら掃除機で吸うということを心がけると、とても清潔な状態を保つことができます。
組み立てや配線は苦労しますが、それを考えても使い勝手や掃除も楽になり、今年一番買ってよかったものかなと思いました。
コードレス掃除機 makita CL180FDZ
マキタ コードレス掃除機CL180青 カプセル式 18Vバッテリ充電器別売 CL180FDZ
- メディア: Tools & Hardware
オタクはみんな大好き、マキタのコードレス掃除機です。
今まで使っていた掃除機はコンセントにつなげるタイプだったんですが、そのせいで取り回しが悪かったり、そもそも出すのが億劫になったりしがちで、部屋の掃除をサボるとすぐホコリだらけになってました。
普段から手に取れる場所にあれば、汚れたときにもサッと掃除ができるんじゃないかと思って購入しました。 最高でした。思い立ったらすぐ吸える、マキタの18Vのパワーは吸引力も凄まじい、とりあえず鼻歌歌いながら10分20分家の中を行き来するだけでそれなりに部屋がきれいになります。
ごみ捨ては上の部分を外せばすぐにできるのでフィルターの掃除などをしなくてもいいのも良いです。 先程机の部分でも言った、ちょっとでも汚れを見たら吸うという運用方法があっているみたいで、それを気にすれば部屋の中はきれいなままで維持することができます。
オタク的な話をすると、実はマキタ製品で一番使われているバッテリーが 18v のものです。確か250種類くらいあります。 なので、この掃除機に使われているバッテリーは他のマキタ製品のものを動かすことができるため、普段はコードレス掃除機を使うけど、いざとなったら電動ドライバーでコーススレッド打てたりもするので18vのマキタ製品を一つ持っておくととても便利な生活を送ることができます!
バッテリーは純正品を使いましょう (異常発熱して死ぬかと思った)
ファッション
特にこだわりはないですが、ちょっと書きたかったので
オニツカタイガー TIGER ALLY
今までは安いスニーカーでもなんとかなっていましたが、それなりにきっちりしたスニーカー買いたいなと思って購入しました。 別にそういうのはないんですが、会社に行くときや遊びに行くときに適した靴がなかったので灰色にしましたが、履き心地がとても良かった。
ふわふわしていて歩き疲れない絶妙なバランスで良かったですね。あとは人とあまりかぶらない。どんな服でも季節でもそれなりに合うので、一年中履けるコスパのいい靴だなと思いました。
アティカ スポーツサンダル
[アティカ] メンズ スポーツサンダル アウトドア サンダル TESLA M121-KKR_270
- メディア: ウェア&シューズ
夏場に雑に履くことのできるサンダルです。キーンみたいなサンダルですが、こっちのほうが安い。 普段近所を散歩したりするならこれで十分でした。
そうは言いながらもしっかりホールド性もあるので車の運転もいけますし、海にも入れます。 しけいオタクとお盆に海に行ったときもしっかりこれを履いていきました。
本
なんやかんや本も結構読みました
村上春樹
なぜか年明けぐらいから村上春樹をちょくちょく読んでいます。好きじゃないですが。 自分がよく見ている Youtuber の 本棚食堂のGenさんの動画に感化されて読み始めたのが最初な気がします。 おすすめするほど僕は本を読めていませんが、蛍という短編が自分の中ではいいなと思っています。
伊坂幸太郎
話の作りが凝っていて、その上で話をつなげるのがとてもうまい作家さんですね。 読書始めようと思っている方にもおすすめできる作家さんです。
オー!ファーザー
高校生の主人公と、年も性格もてんでバラバラな四人の父親が繰り広げるドタバタ話。 ただでさえ父親が四人いるという状況でもカオスで、物語として書くのは難しそうなのに普通にまとまりのある面白い話に昇華する伊坂幸太郎の文章力と構成力に驚かされました。 なんなら中学生から読んでも面白いと思うのでなにか人に本をおすすめしたいときにはとりあえず外さない本だと思います。
その他
坂口安吾 白痴
落差がすごいとお思いのあなた、わかります。なんで自分もこんな本を読んだかわからないです。 短編集なのでどこから読んでもそれなりに面白いと思います。
個人的には「青鬼の褌を洗う女」という話の冒頭にある
私は近頃死んだ母が生き返ってきたので恐縮している。私がだんだん母に似てきたのだ。あ、また――私は母を発見するたびにすくんでしまう。
という一文が頭に残っています。
滝本竜彦 NHKにようこそ
しけいオタクからずっと前にもらったものですが、いい加減読まないとと思って読みました。 内容ですか?死にたくなる内容でしたよ。 人間みんな心のそこには地獄が広がっているなということを思い知らされるいい作品でした。
チェンソーマン
今年一番良かった漫画です。内容はあれでしたね。藤本タツキ先生はもっと人の心持って。 レゼちゃん...
僕の心のヤバイやつ
隔週火曜日、会社のslackでオタクたちが死んでました。 桜井のりお先生はそろそろ戦時国際法で使用禁止武器に採択されそうな勢いですね。
最後に
だいぶ偏った内容でしたが、自分の買ってよかったものを駆け足的に書いていきました。 来年もいいものをいっぱい見つけていきたいですね!!!!!
それではよいお年を!!!
手書きはてなブログはじめた
vivaldiくんもうちょっとそういうのどうにかならんの
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のコマンドを紹介します。
カラーコードを確認したい
最近はめっきり機会が減ってしまったんですが、電子工作が好きです。
大学一年のときから始めて、いろいろな物を作ってきました。
電子部品を使うときのあるあるなんですけど、抵抗の値って、難しくないですか?????
12の色と値を暗記しなければいけないし、パッと見ただけでは分からないので調べてから使うという人もいると思います。
そんな時に、slack上で簡単に確認できたら良さそうじゃないですか?
作りました
slackのslashコマンドを作りました。
/resistor [抵抗の値]
と書くことで、カラーコードの画像が返ってきます。
実装
使っている技術としては
- サーバー
- GCP cloud functions
- 言語
- Go
です。cloud functionsをサーバーにして、リクエストに応じてGoで画像を生成、そのままslackに画像を投稿しています。
ポイント
カラーコードを生成する部分です。
抵抗の値を数字だけで表現する場合は入力された文字列をint64にキャストすればいいだけですが、抵抗の値を指定するときには少し特殊なフォーマットの場合があります。4700 Ωを 4k7
などに表現するやつです。このようなフォーマットで表現する時に、以下のような表現の仕方があります。
- 1k
- 4k7
- 47k
- 470k
また、k以外にもMの接頭辞のときもあります。
このように表現されたときの抵抗値も対応しています。
SI接頭辞付きの抵抗値の場合
330Ωなど、接頭辞がついてないときには、 strconv.Atoi()
などを使えば、stringをintにキャストできます。まずはこれで当たってみて、接頭辞が付いているときにはエラーが返ってくるため、Splitするセクションに入ります。
上で上手くキャストできなかった場合、SI接頭辞(k, Mなど)が付いているときには、入力された文字をSplitします。Goでは strings.Split
という関数があります。
これは文字列を指定文字で切り取ることができるという関数です。
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
あとはカラーコードを割り出すまでもうちょっとです。
抵抗は上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の仕様として、ユーザーが自由にファイルを書き込める場所に制限があります。
ファイル システムで書き込み可能な部分は /tmp ディレクトリだけです。このディレクトリは、関数インスタンスの一時ファイルの保存先として使用できます。
とあるので、slackに投稿する画像は /tmp
以下に置かなければいけないことがわかります。
大急ぎで書いたのでクソ適当になりましたが、今回のアドベントカレンダーネタは以上になります。
来年からはもっとちゃんと書きたい。
もっと書くべき内容があったと思いますが、内容がバカでかい騒動にまで発展してしまったので書けませんでした。
気をつけよう!!!!!!
野菜を食べる技術
早いものでもう12月ですね
今年もアドベントカレンダーの時期ですね。みなさんはいかがお過ごしでしょうか?
今回のアドベントカレンダー、僕だけ2つの記事を書くみたいになってしまって本当に申し訳ないです(見たときには前半のほうが開いていて、埋まってなかったらなんとも言えない気持ちになってしまうので急いで入れてしまった...)
その貴重な枠をこのようなポエムで汚してしまうことをどうかお許しください...
てなわけでこの記事は Aizu Advent Calendar 2019 2日目の記事になります。
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の健康の定義からは離れ、自分が実践していたある程度健康的に暮らす方法として 効率的に野菜を摂る方法 を書こうと思います。
野菜を食べる
野菜を食べるということはビタミンやミネラルを補給でき体のバランスを整えることに繋がります。
健康的な生活をするためには不可欠です。
あと「俺は野菜を意識して食べているぞ、そんじゃそこらの人よりも偉いはず、俺は偉い偉い偉い」と他人にマウントを取れます。精神的な健康も保てるはず。
たまに二郎の上に乗っているもやしを野菜と言っている人もいますが、あれは野菜ではなく油まみれのなにかです。健康的な生活をしたいのであれば二郎を月イチくらいに抑えたほうが絶対にいいです。
効率的に野菜を食べよう
おすすめの食べ方は鍋。一人鍋用スープの素があれば片手鍋でグツグツ野菜を煮込めば放っておくだけで美味しい何かが出来上がり、野菜を摂取できます。
和平フレイズ 片手鍋 煮物 茹で物 マーブル・プレミアム 16cm 軽量タイプ IH対応 MR-7049
- 出版社/メーカー: 和平フレイズ(Wahei freiz)
- メディア: ホーム&キッチン
- この商品を含むブログを見る
食材を保存する
野菜は水分が多く、すぐに傷んでしまいます。あんまり自炊をしないという人に生野菜をおすすめすることは難しいと思うんですが、冷凍することでこの問題を解決できます。
週の初めや野菜が安い時にキャベツや玉ねぎ、えのきやしめじを買っていい感じの大きさに無心で刻み、100均に売っている安いジップロックみたいなものに詰め込んでそのまま冷凍庫にぶち込んでください。
- 出版社/メーカー: 旭化成ホームプロダクツ
- 発売日: 2015/08/06
- メディア: ヘルスケア&ケア用品
- この商品を含むブログを見る
お肉も一緒に買ってきて、ラップ等で小分けにし一緒に冷凍することもでき、タンパク質も楽に摂れます。
おすすめの野菜
- キャベツ
- 味噌や坦々系の鍋におすすめ、よく煮込むと甘みが出てきておいしい
- 半玉買ってざく切りにするとジップロック特大1袋くらいになる
- 冬に買うと葉が硬いので注意する
- 白菜
- 鍋に使う野菜の王道
- どんな鍋にも合う
- 味がしみて美味しい、1/4くらいでジップロック1袋くらいになる
- 玉ねぎ
- 味噌汁やラーメンを作るときとかにも使える
- 常温でも保存が効くので買い置きしてもいい
- ニンジン
- 短冊切りにすると鍋で食べやすい
- ラーメンを作る時に玉ねぎ一緒に入れるだけでもかさ増しになる
- ネギ
- 斜めの輪切りにすれば鍋や味噌汁の実として
- 細切りにすれば薬味としてそのまま乗せられる
- ゆっくり煮ればトロトロになっておいしい
- えのき
- 意味不明なくらい安いときが多い
- どんな料理にも合うのでハズレが少ない
- しめじ
- デフォルトのきのこ
- どこのスーパーにもあって安く手に入る
お肉はウィンナーをおすすめします。
冷蔵室に置いておいてもそれなりに日持ちがするし、なんなら常温でボリボリ行けるので何か口に入れないととなった時に食った気になれます。
三等分にして冷凍しても鍋にぶちこめばおいしい出汁が出てくれますし、包丁で切るのもめんどくさいときは冷蔵庫から出して手でいい感じの大きさにちぎるだけでもいいです。
ポークソーセージ 1kg 人気 業務用 50本 売れてます! プロ仕様の粗挽きウインナー
- 出版社/メーカー: シーフードマックス
- メディア: その他
- この商品を含むブログを見る
鍋を作る
素晴らしいもので、世の中には 一人鍋の素 というものがあります。
ちょっとの水に鍋の素をいれて好きな野菜をぶち込んで10分くらい煮込めば野菜の入った美味しい何かが出来上がります。
一緒に生姜を入れてあげれば手足の先の冷えの対策になります(なってほしい)
鍋の具材を食べ終わった後に冷凍のうどんや餃子を入れればシメも美味しく楽しめます。
???「ガスを使うのめんどくさい、鍋を洗わなくちゃいけないのが嫌」
- 出版社/メーカー: エビス
- 発売日: 2018/08/28
- メディア: ホーム&キッチン
- この商品を含むブログを見る
レンジ対応の一人鍋容器でできます。
使い方としては
- 容器に水と鍋の素を入れる
- 好きな野菜と肉を入れる(肉は最初に入れないと焦げる可能性がある)
- 500Wのレンジで10分くらいチンする
- おいしい
シメにうどんやご飯を入れたいときも物を入れて5分チンすればおいしいうどんやおじやができます。
洗い物も箸と容器だけで済むので楽ちんです。
鍋という選択
鍋はめちゃくちゃ楽でオススメです。具材をぶち込んで煮るだけで美味しい鍋が出来上がり、野菜も摂れるしいいことずくめです。
ちなみに、友達が家に来たときとかにも楽で、一人鍋の素を人数分に増やせば鍋の量を調整できます。鍋のオートスケーリング(申し訳程度の技術成分)が可能です。
おすすめの鍋の素
おすすめの鍋の素をいくつか貼っておきます。スーパーならこのなかで1つは必ずあるはず。 ちなみに、ドラッグストアのワゴンセールで意味不明なくらい安いときもあるのでそういうときは狙い目です。
- こなべっち 鶏だし生姜鍋つゆ
- 出版社/メーカー: ミツカン
- 発売日: 2018/08/03
- メディア: 食品&飲料
- この商品を含むブログを見る
最高の鍋の素、生姜の香りと鶏のうまみが感じられる美味しいやつです。シメのうどんも最高にうまいのでオススメ、鍋以外の料理として茹でたそうめんを入れてにゅうめんにしても美味しいですよ。 なかなか店頭に並んでいるのを見ないので、見たら速攻で買いだめするのが吉
オススメの具材
- 白菜
- 鶏肉
- にんじん
- しめじ
- ネギ
- プチッと鍋 寄せ鍋
- 出版社/メーカー: エバラ
- メディア: 食品&飲料
- この商品を含むブログを見る
寄せ鍋にしてもよし、そのままうどんのつゆにしてもよし、万能でハズレがないおいしい鍋の素 会津だと簡単に手に入る。
オススメの具材
- 白菜
- 肉(なんでも)
- にんじん
- しめじ or えのき
- ネギ
- プチッと鍋 濃厚みそ鍋
- 出版社/メーカー: エバラ食品工業
- メディア: 食品&飲料
- この商品を含むブログを見る
最近ハマっているおいしいやつです、キャベツを煮込んであげると甘くておいしい。シメには業務スーパーで買った安い乾麺を入れてあげるとシメの味噌ラーメンになります。
オススメの具材
- キャベツ
- 肉(なんでも)
- にんじん
- もやし
- しめじ or えのき
- コーン
さいごに
後輩とかを見ていると、ちゃんとした食事を摂って欲しい人が数名います。
↓↓↓↓↓↓↓↓例です↓↓↓↓↓↓↓
食というのは生きる上での基本であって、栄養の偏りや不規則な食事などによる肥満や、それらが原因と考えられる生活習慣病などになっては元も子もありません。
長期的なことを考えればエンジニアも結局は体が資本であり、自身の健康は自分が作っていかなければなりません。
日頃の不摂生がたたったせいでエンジニアとして生きていけなくなるの、嫌じゃないですか?
なんか普段のツイートとは真逆のすごく意識の高い感じがして自分が嫌になったのでここらへんで失礼いたします。
みんな!!!野菜を食べて健康的になろうな!!!!!