テストを書きなぐり

今日のバドミントン練習はエアシャトルでリフティングを20分した。連続最大回数は235回できた。出張休み明けだからあまりできないかと思ったが、そうでもなかった。他に122, 192回と20分しかやっていないのに200回超えして、さらに100回超えが2回もあった。練習の間が空いたから慎重にシャトルをみて取り組めたのかもしれない。エアシャトルでのリフティングも200回を超えるようになってきたら別の練習メニューへ移っていく。

神戸にいると、いつも通りのロウカット玄米と生卵と納豆に野菜サラダを食べられる。外食しなくて済むとほっとする。一昨日からの結合テストのリファクタリングを区切りとして終わらせた。一日中テストデータ生成処理のリファクタリングと足りないテストコードの追加をしていた。テストコードの妥当性をカバレッジで確認できるようになったので安心してテストの品質を管理できる。

レゾナンスの楽曲

apple music で 澤野弘之 氏をお気に入り登録しているせいか、新しいアルバムの配信が始まった的な通知をみかけた。これはなんだろう?と調べたら レゾナンス:無限号列車 というスマホゲームの音楽を担当しているらしい。プロモーションアニメも本格的なものが作られている。このイントロの数秒を聞いただけでこれは澤野弘之氏の音楽だとわかる。近未来且つ SF 的な雰囲気。

飛行機での出張帰り

夜に神戸に戻ってきてからオフィスに来て、機内でやっていた作業を区切りのよいところまで進めてコミットしたりしていた。その後スーパーへ買いものへ行ったりしていた。今日もバドミントン練習はおやすみ。

ローカルは standalone mongodb

mongodb の レプリケーション 機能を使っていて、replica set の設定が容易なことから bitnami/mongodb というコンテナを使っている。これまで結合テストもレプリケーション機能を設定して実行するようにしていた。結合テストの量が増えてきたのもあって実行時間が徐々に遅くなってきた。いまテストのリファクタリングをしているため、ローカルで検証のために何度も実行する必要がある。コードの品質を上げるためにテストを書きやすくする必要がある。実行時間は短いほどよい。そこでローカルでの実行はレプリケーションを設定しないようにしてみた。さらに go のデータ競合を検知する -race オプションも外してテストを実行してみたところ、この2つの改善で macbook の環境で改善前より45%ほど速くなった。十分な高速化を図れた。

飛行機ルートのふりかえり

8月に大雨で新幹線が止まってしまい 神戸に帰れない状況 に直面した。私はほとんど飛行機に乗ったことがなかったため、新幹線が動かないときの迂回手段として飛行機のルートに慣れておく。普段リュックサックに小さいカッターとマイナスドライバーを携帯している。これらは飛行機の手荷物検査でとめられてしまう。荷物を預けるか、検査場で処分してもらうしかない。前回は ANA で帰ってきたが、今回は スカイマーク の飛行機に乗った。値段が安いから LCC だと思っていたら スカイマークは安くて快適なMCC らしい。ANA に比べたらずっと小さい飛行機だった。

タイムスケジュールは次のようになった。

18:15 (都営浅草線) 五反田 発
18:56 (京急本線) 羽田空港 着
19:45 搭乗開始
19:55 飛行機の座席に着く
20:08 羽田空港の滑走路を発進
20:20 離陸
(機内は暗かった、ラップトップで作業)
21:16 神戸空港の滑走路に着陸
21:22 降機
21:25 空港内の荷物受け取り場に着く
21:30 荷物を受け取り
21:35 (ポートライナー) 神戸空港 発
21:51 (ポートライナー) 貿易センター 着

新幹線だと次のようなタイムスケジュールになる。トータルの時間でみれば新幹線の方が20-30分早いかなといったところ。一方で新幹線だと2時間半、同じ姿勢で座っている必要がある。飛行機だと座っている時間は1時間強。飛行機は搭乗手続きのチェックポイントがいくつかあってそれぞれに細かい待ち時間ができてしまうのが気になる。そのときの体調によってもどちらがよいかは変わってくるかもしれない。来月は行きは飛行機、帰りは新幹線にしてみる。

17:57 (JR) 五反田 発
18:03 (JR) 品川 着
18:19 (新幹線) 品川 発
20:53 (新幹線) 新神戸 着
21:10 (市営地下鉄) 新神戸
21:12 (市営地下鉄) 三宮

本番導入へ向けての社内キックオフ

出張で道具がないのでバドミントン練習はおやすみ。

プロジェクトの進捗報告

出張したときの月例報告の21回目。前々回の進捗報告はこちら 。前回の9月は特別な話題がなかったのもあって書いてなかった。今回も淡々と進捗を報告してあまり話題はないものの、プログラミングを教えることの、抽象化能力と論理的思考、モジュール設計について話題にしてみた。経験のあるプログラマーやできる人は勝手にできてしまうものの、普通の人へどうやって指導していけばいいのか。どこでもよく聞く話しではあるし、プロダクト開発で課題管理をする上での延長上にある話題かもしれない。プロジェクトで解かないといけない問題の本質を認識できるかどうか、気付くかどうかという個人の素養に基づくところが大きい。経験を積んでスキルアップする過程で身につけていく特性を言語化したり明文化したりすることには意味がある。いまの私は答えをもっていないのだけど、とりとめのないの話題だけ共有した。

その後、ある案件の社内向けキックオフがあった。うちらが約2年開発してきたプロダクトがお客さん先で導入することが決まった。1月にテストして2月下旬から本番導入となる。最初のお客さんなので本番導入後のトラブルがあることも想定しておく。それが終わったらフルタイムもお仕事もようやく終えられるかもしれない。うちの会社のプロダクト開発に工数を取るようにしていきたい。

とりとめのない出張初日

目次

出張で道具がないのでバドミントン練習はおやすみ。

始発の新幹線で東京へ。定例会議に出て、昨日の カバレッジ計測 の成果をメンバーに共有して、その後もバグ修正や結合テストのリファクタリングをしていた。夕方には疲労で眠くなってきて17時でお仕事を終えて、お気に入りの 中華料理屋さんの晩酌セット を食べて、ホテルに帰って3時間ほど寝てた。起きてから軽く散歩に外に出たぐらいでまたホテルに戻って寝てた。毎月の出張で生活のリズムが乱れる。食べものと生活の拠点がいつもと違うのがしんどくなってきた。

出張前日の資料づくり

今日は1日中オフィスにこもって作業と出張準備をしていたのでバドミントン練習はお休み。夕方にお土産として モロゾフのプリンクッキー を購入するために本店へ行ってきた。これも神戸限定らしい。

パッケージ外のディレクトリにあるテストのカバレッジ計測

先日 深夜にバイナリのビルド・起動調査 をしたのに、最終的にはそんなことしなくてもよかった。デフォルトではパッケージ外のディレクトリにあるテストのカバレッジを計測しないことから普通にはできないと考えていた。しかし、調べたら次の SO で解決法をみつけた。結論としては -coverpkg ./... のように go test で実行している結合テストに対してオプションを指定するだけでよかった。

この調査を終えて最終的な makefile の coverage ターゲットは次のようになった。

coverage: lint
	rm -f coverage.*
	go test -tags=integration -race -cover ./... -coverpkg ./... -covermode atomic -coverprofile=coverage.out
	go tool cover -html coverage.out -o coverage.html

この make ターゲットを gitlab ci/cd から実行して coverage.out/coverage.html を自動生成する。coverage.out があるので必要に応じて好みのツールで統計情報を解析すればよい。たとえば nikolaydubina/go-cover-treemap でツリーマップを作るなら次のように実行する。

$ go-cover-treemap -coverprofile coverage.out > treemap.svg

近況報告の資料作り

普段は週末に報告資料を作っているのにもうやる気が無さ過ぎてお仕事を終えてから作っている。今回はネガティブな内容も報告に入れる。過去の経緯などを調査しながら3時間もあれば一通りのアウトラインはできた。明日がマイルストーンの最終日になるため、明日を終えてからでないと細かい数字は決定しない。マイルストーンの issue を調べたら qa テストでみつけた不具合の issue がそれなりに溜まっているようにみえる。本当は次のマイルストーンで5次開発は完了予定だが、もう1つ増やしてもよいかもしれない。いずれにしても12月/1月に本番導入のための準備もある。今の開発フェーズを完了しても次の開発フェーズには入らないのではないかという見通しもある。

バドミントンの基本練習

今日のバドミントン練習は磯上体育館で9時から12時半頃まで3時間ほど練習できた。ひとりでリフティング練習をするよりも相手がいて打ち合いする方がずっと楽しい。午前中にしっかり動いたので2万歩を超えて昨日の消費エネルギーは4500カロリーを超えた。

体育館でバドミントン

前回の所感 。磯上体育館では月に2回ぐらい個人利用という先着順で予約できる仕組みがある。8時過ぎに磯上体育館へ行ったらすでに2組並んでいた。先頭の方は9時から2面予約するという。私は3番目で並んでいた。8時半頃には10人以上後ろに並んでいたので8時頃に行くと確実に予約できると思う。事務側も本当は9時から受付だけど、並んでいるせいか8時45分頃から予約受け付けしてれくた。こうやって予約を取るためにどんどん並ぶ時間が早くなっていくのかもしれない。

先頭に並んでいたシニアの方が気さくに周りに声をかけていて、話しているうちに仲良くなってその人たちの9時からの練習に混ぜてもらうことになった。ベテランのシニアな方で経験もあって教え方もとても上手だった。丁寧に教えてくれてすごく勉強になった。来週も個人利用で9時から借りると話されていたのを聞いて来週も練習に混ぜてもらうことにした。今日は3つの打ち方の基本を教えてもらいながら練習した。

  • クリア
    • コートの奥から相手のコート奥へ大きく前方へ打ち返す
  • ドロップ
    • クリアを同じ打ち方でコートの奥からネットを超えた前方位置へゆるく落とす
  • プッシュ
    • ネット前で浮いた球を下方に叩きつけるような打ち方になる

打ちながら、私のラケットの振り方や持ち方のよくないところを指摘してもらった。我流でやっていたからちゃんとした知識やフォームを教えてもらえるのは本当にありがたい。教えてもらったことで覚えていることを書いておく。

  • 足は肩幅に開いて準備をする
    • まだ言語化できないが、右足/左足の使い方について説明されてた
    • 足の親指部分にチカラを入れてコントロールするように話されていた
  • フットワークと足の位置を意識してみる
    • 私は無意識に一歩目は左足から動いてしまう
      • フォアハンドで打つときは右足から動いた方が速いのかもしれない
  • シャトルが来てから動くのではなく、来る前から位置を予測して動いて備える
  • ラケットを振るときに左手をあげてカラダのバランスをとる
    • 基本として左手をどう使うのかがすごく大事とのこと
    • 左手を下げない、肘を90度にまげてあげるのに備えておく
    • 手首をまげずに固定してラケットを振る
  • ラケットは親指と人差し指でつまむ気持ちで軽くもち、シャトルを打ち返す瞬間だけ握る
    • この感覚で打ち返すと真っ直ぐ強い打球を返せるようになった
    • バックハンドで返すときも同様でシャトルを打つ瞬間だけ握り込んで止めるような感覚で返す
  • 向かってくるシャトルに対してカラダの正面でとらえて打つ
    • この位置を固定にすることで安定して打ち返せる気がする

1時間ちょっと練習の指導をしてもらって30分ほどダブルスの試合をした。私は下手だからすぐ失敗してラリーが止まってしまう。試合形式になると簡単そうにみえるシャトルの打ち返しも緊張や前後左右の動きの中で失敗してしまう。練習でできないことは試合では絶対にできない。ラリーを止めてしまって他の人たちに悪いなと罪悪感を感じるし、練習する方がたくさんシャトルを打つことができて楽しい。もう少し上達するまでは試合は棄権しようかなとも思う。11時からいとうさんが来られて、前の時間帯に教えてもらった打ち方を意識しながらひたすら打ち合いの練習をしていた。その前の練習で教えてもらったことをゆっくり反復する時間をとれてこの時間もよかった。基本の打ち方を教えてもらった通りにやっていると狙ったところにうまく返せる回数が増えたように感じた。

ストレッチ

先週は実家へ帰ってストレッチをお休みして2週間ぶり。午前中にバドミントンの練習をたくさんして疲れていたのと、試合をしているときにころんで足と腰を少し痛めていた。その部位を伸ばす意図でもストレッチしてもらってちょうどよかった。運動して筋肉痛もある状態でストレッチするとよく伸びて、その日の睡眠もよく取れるし疲労回復も早くなる。今日の開脚幅は開始前146cmで、ストレッチ後155cmだった。ストレッチ前は筋肉痛で伸びないのがストレッチ後によく伸びていることが伺える。トレーナーさんはサッカーをずっとされていた方なのでたまたまはてブでみかけた町田ゼルビアの記事を話題にしたら盛り上がった。この著者の記事が3つもあがっているようにいまホット話題ではあるらしい。

ストレッチを終えて晩ご飯を食べた後にオフィスで作業しようと思っていたものの、家に帰ったら疲れてそのまま休んでしまった。体調が悪いわけでもないのに、最近はやらないといけないことをがんばる気力がなくなっている。なにか内からのやりたいが出てこない。

コンパクトな REALFORCE R1 キーボード

コンパクトな REALFORCE R1 キーボード

今日は午前中寝ていたのと雨降りだったのもありバドミントン練習はお休みした。

REALFORCE R1 レビュー

bluetooth の初回ペアリング設定を過去の R3 のペアリング設定 をみながら行った。手順を忘れていたのでまた書いておく。agent を介してパスキーを入力する必要がある。その手順が少しわかりにくい。30分ほどペアリング設定をうまくできなくてやり直ししたりしていた。滅多にやらないことだし。今回も GUI ツールではなく bluetoothctl を実行してペアリングした。

$ bluetoothctl
[REALFORCE_2 (R3)]# default-agent
Default agent request successful
[REALFORCE_2 (R3)]# scan on
Discovery started
... (デバイスを検索しているうちに RC1 がみつかる)
... MAC アドレスを確認してペアリングする

[REALFORCE_2 (R3)]# pair F7:62:4B:02:D6:EB
Attempting to pair with F7:62:4B:02:D6:EB  (キーボードに接続を試みる)
..
[agent] Passkey: 946185 (エージェントがパスキーを受け取る)
...

エージェントが受け取ったパスキーを、bluetooth 接続するキーボードで入力して Enter を押下する。

[NEW] Primary Service (Handle 0x0000)
	/org/bluez/hci0/dev_F7_62_4B_02_D6_EB/service000a
	00001801-0000-1000-8000-00805f9b34fb
	Generic Attribute Profile
[NEW] Primary Service (Handle 0x0000)
	/org/bluez/hci0/dev_F7_62_4B_02_D6_EB/service000b
	0000180a-0000-1000-8000-00805f9b34fb
	Device Information
[NEW] Characteristic (Handle 0x0000)
	/org/bluez/hci0/dev_F7_62_4B_02_D6_EB/service000b/char000c
	00002a29-0000-1000-8000-00805f9b34fb
	Manufacturer Name String
[NEW] Characteristic (Handle 0x0000)
	/org/bluez/hci0/dev_F7_62_4B_02_D6_EB/service000b/char000e
	00002a50-0000-1000-8000-00805f9b34fb
	PnP ID
[NEW] Primary Service (Handle 0x0000)
	/org/bluez/hci0/dev_F7_62_4B_02_D6_EB/service0010
	0000180f-0000-1000-8000-00805f9b34fb
	Battery Service
[NEW] Characteristic (Handle 0x0000)
	/org/bluez/hci0/dev_F7_62_4B_02_D6_EB/service0010/char0011
	00002a19-0000-1000-8000-00805f9b34fb
	Battery Level
[NEW] Descriptor (Handle 0x0000)
	/org/bluez/hci0/dev_F7_62_4B_02_D6_EB/service0010/char0011/desc0013
	00002902-0000-1000-8000-00805f9b34fb
	Client Characteristic Configuration
[CHG] Device F7:62:4B:02:D6:EB UUIDs: 00001800-0000-1000-8000-00805f9b34fb
[CHG] Device F7:62:4B:02:D6:EB UUIDs: 00001801-0000-1000-8000-00805f9b34fb
[CHG] Device F7:62:4B:02:D6:EB UUIDs: 0000180a-0000-1000-8000-00805f9b34fb
[CHG] Device F7:62:4B:02:D6:EB UUIDs: 0000180f-0000-1000-8000-00805f9b34fb
[CHG] Device F7:62:4B:02:D6:EB UUIDs: 00001812-0000-1000-8000-00805f9b34fb
[CHG] Device F7:62:4B:02:D6:EB ServicesResolved: yes
[CHG] Device F7:62:4B:02:D6:EB Paired: yes
Pairing successful
[CHG] Device F7:62:4B:02:D6:EB Modalias: usb:v0853p031Ad0001
...
[REALFORCE_2 (R3)]# exit

ペアリングが成功したらキーボード入力できるようになる。bluetoothctl を eixt してから再実行すると今度は RC1 で実行された。

$ bluetoothctl
[RC1_1]# info
Device F7:62:4B:01:D6:EB (random)
	Name: RC1_1
	Alias: RC1_1
	Appearance: 0x03c1
	Icon: input-keyboard
	Paired: yes
	Trusted: no
	Blocked: no
	Connected: yes
	WakeAllowed: yes
	LegacyPairing: no
	UUID: Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)
	UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
	UUID: Device Information        (0000180a-0000-1000-8000-00805f9b34fb)
	UUID: Battery Service           (0000180f-0000-1000-8000-00805f9b34fb)
	UUID: Human Interface Device    (00001812-0000-1000-8000-00805f9b34fb)
	Modalias: usb:v0853p031Ad0001
	Battery Percentage: 0x5b (91)
[RC1_1]# trust 
[CHG] Device F7:62:4B:02:D6:EB Trusted: yes

この日記を R1 のキーボードで書いている。R3 と比べてたしかにコンパクトで軽い。キーボードを持ち運びするならよさそう。R3 と比べて右シフトキーが小さいのでその隣にある PgUp キーをタイプミスしてしまう。私は左右両方のシフトキーを使う。!記号などの左手でタイプするときは右シフトキーを使う。それからコンパクトサイズのせいか、キーとキーの隙間が若干狭くなったように感じる。矢印キーの位置もエンターキーの下の方に移動している。R3 の矢印キーの位置を無意識にタイプしようとして空打ちしてしまう。意外と矢印キーを私は使っていたことに気付く。これらの操作の違和感は慣れの問題だと思う。キーボードの打鍵感も R3 と比べて若干違う。気持ち、深さが増して軽い印象を受ける。キー荷重は 45g を選択した。R3 では変荷重モデルを使っていたので小指キーが 30g から 45g に変わる。その違いはあまり感じない。

キーボードは毎日たくさん使うものだから慣れによる無意識の最適化が自然と行われる。私の用途では R3 と R1 では右シフトキーと矢印キーの位置の違いに違和感を感じる。両方のキーボードを使い分けるというようにはならないのかもしれない。どちらかに慣れるともう一方に違和感を抱くように思える。

能楽の勉強

玄宗皇帝と楊貴妃の愛情物語の後日談《楊貴妃》 第41回能のことばを読んでみる会 に参加してきた。前回の所感はここ 。中国史上もっとも有名な美人といわれる 楊貴妃 の幽霊を主人公とした能になる。方士 と呼ばれる修行者が玄宗皇帝の命令で楊貴妃の魂魄の在り処を探していて蓬莱島でみつけて話しを聞くといった、とくに変わったこともない普通の物語の構成になっている。作者ははっきり分かっていないらしいが、世阿弥の娘婿になる金春禅竹 (こんぱる ぜんちく) ではないかと推定されるとのこと。

貴妃 というのは皇帝の后に与えられる位の1つで皇后に次ぐ高い位だという。楊が姓になる。唐の 玄宗皇帝 は在位期間が45年と長い。前半は善政をしいて唐は絶頂期を迎える。後半は楊貴妃にうつつを抜かすようになる。楊一族が政治の重要なポストを占めるようになり、玄宗皇帝が政治に関心をなくして最終的に反乱が起きて国が乱れてしまう。その後、白居易 (はくきょい) という著名な詩人が玄宗皇帝と楊貴妃の物語を 長恨歌 にした。この漢詩が素晴らしい内容だと評価されて後世にまで楊貴妃の物語が広く伝わる影響を与えたという。

源氏物語に出てくる 桐壺更衣 (光源氏の母) が楊貴妃のイメージや長恨歌から引用されて創作されているという。この能は源氏物語に出てくる長恨歌を引用した文章を引用して作られてもいて、伝言ゲームのように、引用を繰り返すうちに少しその内容が変わってしまうといったことも起きているらしい。おもしろかったのが、漢詩に序文をつけたりするのを詩序と呼ぶ。オリジナルの中国の長恨歌には詩序がないのに、日本に伝わって残っている長恨歌には詩序が残っていて、その詩序の内容を能でも引用していたりする。当時は長恨歌には詩序があったのに現代では失われてしまった可能性もないわけではないが、おそらく日本に伝わってきた後に誰かが詩序を追加したのではないかと考えられているらしい。

会者定離ぞと 聞く時は、逢うこそ別れなりけれ

以前 蝉丸の能 を読んだときにも 会者定離 (えしゃじょうり) が出てきた。一種の悟りのような表現だと朝原さんが解説していて私も記憶によく残っている。直接なにかの役に立つわけではないけれど、こういう言葉や概念に触れて自分の価値観をふりかえる機会があるのはおもしろい。能の言葉を読んでいるとそういう出会いがある。

exec とスクリプト

今日のバドミントン練習はエアシャトルでリフティングを60分した。連続最大回数は191回だった。もう少しで200回だったのに残念。木曜日は睡眠をたくさんとって疲れは少し取れたし、安定的に50回前後は続くようになりつつも、100回までに失敗してしまう。今日は100回を超えたのが2回だけだった。ラケットのスィートスポットでとらえたときにきれいに真上にあがる感覚が楽しい。うまくいくときは数回は続く。それが自然にできるときとそうじゃないときの違いを私は制御できてなくて言語化もできない。

エアシャトルとメイビスにおけるリフティングの違いを比べてみると、メイビスの方が打ち上げて落ちてくるときにあまり回転せずコルクが下を向く傾向が多いようにみえる。エアシャトルの方がコルクが重い分、縦方向に回転し始めるとその回転が止まらず、回転しているからラケット面でとらえるのが難しくなる。だからエアシャトルの方がメイビスよりもリフティングが難しいといえる。シャトルを高く打ち上げると、落下してくる距離が長くなりその回転が落ち着く傾向があるからリフティングしやすくなるのではないかと仮説を考えた。伸び悩みかもしれないし、地道に練習を継続するときかもしれない。

exec とエントリーポイントのスクリプト

コンテナを起動して stop すると SIGTERM が送られる。そのときに api サーバーでシグナルの処理をしているのに、気付いたらシグナル処理が行われずタイムアウトするようになっていた。デフォルトでは10秒でタイムアウトして強制終了となる。なぜシグナルを捕捉しなくなったかを調査したら、あるときサーバーの起動前に前処理が必要になってエントリーポイントをシェルスクリプトにしていた。そのときに exec しないと、シェルスクリプトのプロセスに対してシグナルが送られるため、api サーバーがシグナルを検知できなくなるという副作用があることに気付いた。これまでも exec を使うとプロセス ID は変更されないという知識を知っていたが、それがどういう状況で役に立つかを理解できていなかった。シグナルを用いた同期処理に exec が役に立つ状況があることを学んだ。修正は次の1行のみ。

--- a/docker/entrypoint.sh
+++ b/docker/entrypoint.sh
@@ -2,4 +2,4 @@
 ...
 ... (pre process)
 ...
-./bin/api "$@"
+exec ./bin/api "$@"

go test からバイナリをビルドしてサーバーを起動する

先日 結合テスト向けカバレッジ計測の調査 をした成果を使って実際に go test からカバレッジ計測のカスタマイズを施したバイナリをビルドしてサーバー起動するコードを書いてみた。やや手間取ったが、一通り動いてカバレッジを計測できた。例えば、単体テストのカバレッジを計測するための makefile のターゲットは次のようになる。

GO_COVER_DIR:=$(CURDIR)/tests/coverage

coverage:
	@mkdir -p $(GO_COVER_DIR)
	go test -tags=integration -race -cover ./... -covermode atomic -args -test.gocoverdir=$(GO_COVER_DIR)

go は fork ができない。fork の代わりに exec を使う。How do I fork a go process? に go の goroutine のスケジューリングと fork は相性が悪くてうまく動かないということが背景だと説明されている。それはともかく exec を使ってもサーバープロセスを非同期に起動できたのでそのスニペットを書いておく。

binaryPath, err := buildBinary()
if err != nil {
	return 1
}
args := []string{
	"-verbose",
	"-port",
	strconv.Itoa(ServerPort),
}

r, w := io.Pipe()
go func() {
	s := bufio.NewScanner(r)
	for s.Scan() {
		fmt.Println(s.Text())
	}
}()

cmd := exec.Command(binaryPath, args...)
cmd.Stdout = w
if err := cmd.Start(); err != nil {
	slog.Error("failed to start api server", "err", err)
	return 1
}
defer func() {
	if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
		slog.Error("failed to terminate the api process", "err", err)
	}
	if s, err := cmd.Process.Wait(); err != nil {
		slog.Error("failed to wait terminating the api process", "err", err)
	} else {
        w.Close()
		slog.Info("completed to terminate the api process", "s", s.String())
	}
}()

// サーバーに対するテストを実行

サーバープロセスの標準出力のログを io.Pipe を使って出力することもできる。exec で生成したプロセスに対してもシグナルを送ったり終了を待つこともできる。デバッグしている分にはこれで意図したように制御できた。この知見は将来的に役に立つ気がする。0時過ぎから調査を再開して4時前ぐらいまでやっていた。少しはまって時間はかかったものの、久しぶりに集中してデバッグしていた。

変な疲れかた

目次

今日のバドミントン練習はエアシャトルでリフティングを40分した。連続最大回数は168回だった。いくつか練習場所を転々としながら最終的にはホームのビルの軒下に行き着いた。環境のよい場所を知ってしまうと、それよりも劣る環境で練習することに抵抗を感じてしまう。昼間なら風がなくて人通りの少ないところであればどこでもそう大差はないが、夜は証明と背景でシャトル視認性 が変わってくる。21時まではホームのビルで、21時以降はその隣のビルでといった定番の練習場所ができつつある。今日はなんとなく調子がよくなくて練習していて手応えや集中力を感じなかった。

午前中に結合テスト改善に関するドキュメントを書いて、お昼から rpm パッケージングのリファクタリングやって、夕方に午前中に書いたドキュメントを使いながらメンバーに結合テスト改善の要項について共有した。そして17時半にはお仕事終えて帰った。晩ご飯も外食してリフティング練習も早めに切り上げて帰った。ここ数日あまり睡眠時間を取れていなかったのもあり、疲れているかも?と21時過ぎから横になって寝ていた。

テストライブラリの導入

今日のバドミントン練習はエアシャトルでリフティングを25分した。連続最大回数は277回できた。初めてエアシャトルで200回を超えた。しかも夜なのに。やや高めに打ち上げれば安定的にリフティングできるようになってきた。連続回数が増えることはラケットとシャトルの距離感、ラケットコントロールが上達していることの証左でもあるので200回を超えたときは嬉しかった。

テストライブラリの追加

結合テストを改善するために2つのライブラリを新たに使うよう導入した。

testify は名前だけ知っていたが、実際に使ってみるのは初めて。testify は大きく3つの機能もっている。

  • アサーション
  • テストスィート
  • モック

うちらのチームで使うのはアサーション機能だけになる。モックも将来的に使う可能性はある。アサーションを使うと自分でエラーメッセージを書く手間を省ける。次のようなコードがあった場合、

if expected != actual {
    t.Errorf("expected %d, but got %d", expected, actual)
    return
}

次のようにエラーレポートを testify に委譲できる。このぐらいの利便性でしかないが、テストの規模が大きくなったり、量が増えていけば読み書きのコストを削減できるかもしれない。

if !assert.Equal(expected, actual) {
    return
}

httpexpect はまったく知らなくて初めて使ってみたが、感触がよい。これもデフォルトのエラーレポート機能は testify のアサーション機能を使っているようにみえる。これまでは http リクエストに対して自前のユーティリティ関数と組み合わせて次のようなコードを書いていた。

res, err := doHTTPRequest(body, UserPath, http.MethodGet, "")
if res.StatusCode != http.StatusOK {
    t.Errorf("expected to get %d, but got %d", http.StatusOK, res.StatusCode)
    return
}
var actual entry.User
if err := convertBody(res, &actual); err != nil {
    t.Errorf("failed to convert: %s", err)
    return
}

httpexpect を使うと次のように簡潔に記述できる上にエラーレポートを httpexpect に委譲できる。これはかなりテストコードを読み書きする工数を削減できると思う。

var actual entry.User
e.GET(UserPath).
    WithJSON(map[string]any{ ... }).
    Expect().
    Status(http.StatusOK).
    JSON().
    Decode(&actual)

mongodb のテストデータ管理

今日のバドミントン練習はエアシャトルでリフティングを45分した。連続最大回数は146回できた。調子は悪くなく安定的に30回前後は続くものの、50回を超えたぐらいで失敗してしまう。打ち上げる高さの違いかな?と気付いて少し高めに打ち上げるようにしたら100回を超えた。エアシャトルはラケット面に対して適切な角度でコルクを打たないとあらぬ方向に飛んでいってしまう。高く打ち上げるほど、重力で落ちてくるときにコルクが下を向きやすくなるため、うまくシャトルを打ち上げやすくなる。ラケットコントロールをうまくできれば、エアシャトルをより低い高さでリフティングできるようになるかもしれない。まだ私はそのレベルには満たない。

json からの mongodb にテストデータを追加する

結合テストの改善をしていてテストデータを json で管理したい。これまで go の構造体でテストデータを定義して mongodb の client で insert するといったことをしていた。それも役に立つのだけど、共有のテストデータがどこにあるのか、ソースコードに書いてしまうと時間とともに散らばっていって把握できなくなっていく。テストデータを管理するためのディレクトリを設け、そこに json で記述してどのテスト関数で使うかといったメタ情報も定義できるようにした。次のコードは mongodb に json からデータをインポートするための原理を説明するための疑似コードのようなもの。

go の構造体で定義したテストデータと json で管理するのとどちらがよいかというのは議論の余地はあるし、一概に言えないとは思う。

type testData struct {
	Documents    []bson.Raw `bson:"documents"`
}

func InsertData(
	t *testing.T, client *mongo.Client, b []byte,
) (func()) {
	var data testData
    err := bson.UnmarshalExtJSON(b, false, &data)
	require.NoError(t, err, "failed to get json files: %v", err)

	ctx := context.Background()
	col := client.Database(dbName).Collection("mycollection")
	r, err := col.InsertMany(ctx, docsToInterfaces(data.Documents...))
	require.NoError(t, err, "failed to insert: %v", err)

	return insertResult, func() {
		t.Helper()
	    col := client.Database(dbName).Collection("mycollection")
		for _, id := range r.InsertedIDs {
			filter := bson.D{{Key: "_id", Value: id}}
			if _, err := col.DeleteOne(ctx, filter); err != nil {
				t.Errorf("failed to delete %s: %v", id, err)
				return
			}
		}
	}
}

呼び出し側のイメージ。defer で teardown を呼ぶことでテスト完了時に追加したテストデータを削除してくれる。

teardown := mongotest.InsertData(t, mongoClient, b)
defer teardown()

...

bson.Raw として読み込める json データは bson パッケージのユーティリティを使って dump できる。

func dumpAsJSON(value any) {
	b, err := bson.MarshalExtJSON(value, false, false)
	if err != nil {
		slog.Error("failed to marshal as extended JSON", "err", err)
		return
	}
	fmt.Println(string(b))
}