Posts for: #Testing

寝台特急に初乗車

2時に寝て6時過ぎに起きた。本当はもっと早く寝た方がよいのに22時半から2時前まで作業してた。格闘家に学ぶ体脂肪コントロール 本に睡眠をとることや疲労を溜めないことが代謝をよくすると書いてあって、その通りだとは思うのだけど、なぜかここ最近は2時ぐらいまで作業していることが多い。いろいろうまくいかない。

今日の運動は腕立て,スクワット,背筋をした。統計を 運動の記録 にまとめる。

テスト環境の再構築

テスト環境の見直し の都築。先週末から cockpit 上の仮想マシンに OS をインストールしたりしていた。今日はテスト対象のアプリケーションをインストールして設定をしたり、既存のテスト環境を移行したりしていた。

いまテスト環境は1つしかないが、機能が増えてきて、複数の運用要件を満たす必要があって、1つの環境ですべてを検証するには不便な状態になっていた。そこで1つに全部入りするから相反する要件が競合するのであって、いまわかっている要件を用途別に、1つを3つの環境に分割した。1つしかないものを複数にすると、その重複するところのコストもかかる。その辺りの調整もしていた。テスト環境の構築について、過去に私が作ったものではあるけど、1回作って終わりだと思っていたのか、構築の issue にはなにか書いてあるかもしれないが、wiki には残していなかった。今回3つに作り直すにあたってドキュメントがあれば思い出したり、トライアンドエラーを行うコストも省略できて、ちゃんとドキュメントを書かないとあかんなと反省した。新規に設置した仮想マシンに openldap サーバーを構築する以外の作業は完了したはず。

サンライズ出雲の乗車と遅延

1ヶ月前に予約した 0時11分発の特急サンライズ出雲で東京へ向かう。

20時半にオフィスから家に帰ってきて、洗い物して、お風呂入って、ストレッチして、荷造りして、3時間もあれば余裕で出発できる。初めて寝台特急に乗る。間違いがないよう、改札で駅員に問い合わせる。すると、動物(シカ)と接触して列車が止まってしまっているという。再開予定ではあるが、駅員さんに話しかけた時点ではいつ再開するかはわかっておらず、その情報も乗客からオンラインでは調べられず、駅員さんしか状況を知る方法はないとのこと。動物とぶつかったぐらいで運休はないやろうという話しもあってそのままホームで待つことに。

ホームのアナウンスを聞いていると「運転しています。」と言い切るので再開したのかな?と期待するものの、35分遅れ→40分遅れ→45分遅れと遅延時間がどんどん増えていくので実際には停車していて点検中でも「運転している」とアナウンスするらしいということがわかった。最終的には80分遅れて三ノ宮に到着した。待合室で半分寝てたよ。そのまま目を覚さなくなるところでしたよ。

この時点で今日はゆっくり睡眠をとろうと思っていたのに大きな誤算だった。待ち疲れていたのもあったせいか、部屋に入って消灯してそのままわりとすぐに寝てしまった。列車に乗ったのが1時半、トイレに行って、駅員さんが切符確認に来て、2時頃には寝てしまったのではないかと思う。わりとよく眠れたと思う。列車の揺れと移動音は普通にはあるものの、私はこの手の環境には強いので、もちろん身体は疲労するかもしれないが、感覚的には気にせずそのまま寝ていたと思う。この状態で眠れるかどうかによって寝台特急の是非は変わってくるだろう。カプセルホテルよりは天井も高く、スペースも一回り広くて余裕がある。今回は下の真ん中ぐらいの部屋だったのだけど、場所によって揺れや移動音の聞こえ方も変わるという。通な人は部屋の位置もこだわるのかもしれない。

あと薄い毛布しかないため、冬はちょっと寒い。暖房入れて上着もかけてぎりぎりの暖かさかな。べつに眠れないほど寒いわけではないが、冬なんだからもう一枚布団を用意してくれてもいいのになという感覚はある。朝に起きて窓から線路は延々と眺められる。線路が好きな人にもよさそう。けっこうよかったのでまた来月も予約して寝台特急で出張へ行く。

QA のマイルストーンに入る

0時半に寝て4時頃に起きてそれからうたた寝して6時半に起きた。

今日の筋トレは腹筋:10x1,腕立て:15x1,スクワット15x2をした。

QA テストの段取り

今日から2週間 x 2 で QA テストをしていく。これまで余裕があったら手伝おうと思いつつ、なんやかんやインフラの作業やアプリケーションのリファクタリングやらをやっていて、QA テストはメンバーに任せて、私がやってみたことはなかった。今回は機能が増えてきて、物理的に検証の作業量も増えている。1人ですべてやるのはしんどいので役割分担をした方がよいだろうというので私も入ってやることにした。google sheets でカテゴリごとのテストケースをまとめている。そのシートごとに issue を作って担当者をアサインする。QA という観点からは同じテストを複数人で行うのは構わないので、issue も担当者ごとに同じものを複製してアサインすることにした。私もそうだけど、issue の担当者は1人の方が作業しやすい。メンバーからもそういう意見が出て、うちのチームに課題管理の考え方がかなり馴染んできたようにも思えた。

テスト環境を作り直す

21時頃から寝て何度か起きて7時半に起きた。昨日は頭が痛かったら早く帰って安静にしてた。安静にしたら直ったので大したことはなかったみたい。

今日の筋トレは腹筋:15x2,腕立て:10x1,スクワット15x2をした。

テスト環境の見直し

いまどきの開発の定番としては ci/cd でコミット単位にテスト環境にデプロイする仕組みを構築している。テスト環境は1つだけになるのだけど、プロダクトの開発を1年以上やってきて機能が増えたことによって、1つのテスト環境で相容れない複数の要件や機能を混在させることで管理や運用が煩雑になってきた。気付いたタイミングがよい時期だと思うのでこの機会に1つのテスト環境を3つのテスト環境に分散させようと思う。既存データを確認するだけなら1つでもよいが、id 連携という機能の特性上、複数のシステム間でデータ連携するため、システム間での依存関係が発生する。それを整理しないといけなくなったという次第。今日のところは現状把握や移行に必要な段取りなどを設計していた。

hugo のハンズオン資料作り

昨日の続き 。23時過ぎに外に出たついでにオフィスに寄り道して書き始めたらまた熱中してしまって2時ぐらいまで書いていた。ハンズオンで説明する内容は一通り書いたつもり。せっかくの機会だから上級者向けにもう少し知っていることを書いてみようとは思う。

頭痛でダウン

4時半に寝て7時半に起きた。深夜にハンズオン資料を作っていたらまた眠れなくなった。朝から頭痛くて調子が悪い。きっと生活のリズムが崩れているせい。早くお仕事を終えて帰って寝てた。

今日の筋トレは腕立て:10x1,スクワット15x1をした。

ldap エントリーの更新リクエストをランダムに行うツール

先週末から作っていたテストツールがようやく一通り動くようになった。ldap エントリーを生成したり、既存のエントリーに対して更新、削除、パスワード変更のリクエストをランダムに自動生成して送る。常時 id 連携の処理を動かしたときの並行処理の問題であったり、やや負荷をかけたときになにかの敷居値を超えて不具合がないかといった検証に使える。8月末に開発に着手して、年末から再開して、1月明けでようやく完成した。テストツールの開発の優先度が低いので後回し後回しにしているうちにどんどん後ろに流れていった。悪い開発の典型例。

hugo のハンズオン資料作り

昨日の続き 、、、をやろうと思っていたけど、頭痛でしんどかったので今日はお休み。イベントが公開された。

他のツールをみていて slate というツールは知らなかった。api ドキュメントを書くためのツールとある。

うちはいま redocly というツールを使って openapi のフォーマット (yml) で api ドキュメントを管理している。開発は openapi を使ってなくてドキュメントのためだけに openapi を使っている。量が増えてくると yml でドキュメントを書くのも辛くなってくるので markdown で api ドキュメントを書けるならそれもよいかもしれない。

結合テストのデバッグ

1時に寝て5時半に起きて7時半に起きた。なんか週の前半からバテている。

gitlab ci/cd の dind で mongodb のレプリカセット接続ができない

先日対応した mongodb のレプリカセット対応 で残った最後の課題。ローカルで実行すれば結合テストは動くが、gitlab ci/cd 環境では動作しないという問題が残っていた。gitlab-runner をローカルで実行できる ようにして、設定やパラメーターを変えたり、デバッグコードを埋め込んだり、コンテナに attach して振る舞いを確認したり、いろいろデバッグして原因はレプリカセット接続におけるホスト名の解決がコンテナ間でできていなかったことがわかった。

mongodb の結合テストは dockertest を使って実装している。これを gitlab ci/cd で動かすには dind を有効にする必要がある。dind 環境では2つのコンテナを使って結合テストが実行されるわけだが、テストが実行されるコンテナと mongodb コンテナが起動するコンテナの2つが生成される。このときにテストが実行されるコンテナから実際に mongodb が起動するコンテナのホスト名の解決と、mongodb が起動するコンテナ上での自分のホスト名の解決の2つが成立していないとレプリカセット接続ができない。要は1台のローカルホスト上で結合テストを実行するのと、2つのコンテナ上で実行されるのでは設定を変更する必要があるということに気付いた。

具体的には dockertest の次のパラメーターを、実行環境から解決するホスト名を考慮して設定すればよいと気付いた。

pool.RunWithOptions(&dockertest.RunOptions{
    ... 
    Hostname:   executor,
    Env: []string{
        ...
        fmt.Sprintf("MONGODB_ADVERTISED_HOSTNAME=%s", executor),
        ...
    }
})

たったこれだけの修正だし、現状の動作の振る舞いが分かればすぐに直せるものではあるけれど、このデバッグにはまた2-3時間を費やした。mongodb のレプリカセット接続はなかなか大変。

mongodb のレプリカセットのデプロイ調査

4時前に寝て6時半に起きた。1時過ぎまで作業して、帰って少しゲームして、うまく眠れなくてだらだらしていた。

mongodb のレプリカセットの調査

以前 mongodb でトランザクションを使うときにレプリカセットが必要 なことがわかった。他機能の開発途中だったので一旦後回しにしていたものを回収している。状況によってはメンバーに委譲してもよかったんだけど、私が遊撃で出張ってみることにした。実際に調べてみてコンテナの運用も考慮するとけっこう難しいことがわかってきた。

mongosh からは Replication Methods を使ってレプリカセットの操作ができる。これはユーティリティのようなもので mongodb としての低レベルのコマンド操作は Replication Commands になる。mongo-go-driver はレプリカセット向けのユーティリティを提供していないため、Replication Commands を RunCommand() の低レベル API を使って自分で実装しないといけない。

例えば、レプリカセットの初期化をするときは次のように replSetInitiate というコマンドを適切なパラメーターで呼び出す。あまりドキュメントで丁寧に説明されていないので試行錯誤でエラーメッセージをみながら実装することになる。とくにはまるのが mongod のサーバーは --replSet myrs のようにレプリカセットを指定して起動させるものの、初期化コマンドを実行するときはまだレプリカセットを設定していないため、レプリカセットを指定せず、且つ direct パラメーターをセットしないと mongod サーバーに接続できない。この微妙な設定を把握するのにはまった。これが正しい手順かどうかもわからないが、ググったりしているとフォーラムでそういったコメントが散見されたりする。おそらく mongosh の Replication Methods を使うと、クライアントからサーバー接続は裏方でよしなにやってくれるのでそっちの方が簡単ではある。

func (r *ReplicaSet) Initiate(ctx context.Context, config bson.M) error {
	client, err := r.connectDirect(ctx)
	if err != nil {
		return fmt.Errorf("failed to connect with direct: %w", err)
	}
	defer client.Disconnect(ctx)

	var result bson.M
	cmd := bson.D{{Key: "replSetInitiate", Value: config}}
	if err := client.Database(r.db).RunCommand(ctx, cmd).Decode(&result); err != nil {
		return fmt.Errorf("failed to run replSetInitiate(): %w", err)
	}
	log.PrettyPrint("completed to initiate", result)
	return nil
}

func (r *ReplicaSet) connectDirect(ctx context.Context) (*mongo.Client, error) {
	opts := options.Client().
		SetAuth(options.Credential{
			Username: r.config.User,
			Password: r.config.Passwd.String(),
		}).
		SetHosts(r.config.Hosts).
		SetDirect(true) // must be true
	return mongo.Connect(ctx, opts)
}

func InitSingleReplicaSet(
	ctx context.Context, cfg *config.MongoDB,
) error {
	rs := NewReplicaSet(cfg)
	initConfig := bson.M{
		"_id": cfg.ReplicaSet,
		"members": []bson.M{
			{"_id": 0, "host": "localhost:27017"},
		},
	}
	return rs.Initiate(ctx, initConfig)
}

さらに mongod サーバーを起動するときに --replSet--keyFile (認証が必要な場合のみ?) という2つのパラメーターを指定する必要がある。--replSet はレプリカセットの識別子を指定する。そして --keyFile は共通鍵を指定する。この共通鍵を生成するには次のようにする。

$ openssl rand -base64 756 > my-mongo-keyfile
$ chown mongodb:mongodb my-mongo-keyfile
$ chmod 400 my-mongo-keyfile

普通のサーバーインスタンスならすぐできることだが、コンテナの運用において面倒なのが owner とパーミッションを設定しないといけないところ。mongo のコンテナは mongodb ユーザーで起動するため、root でマウントされたファイルシステムには書き込みできなかったりして keyFile の配置をどう扱えばよいのかが難しい。docker hub の mongo の issues でもどうやって設定したらいいの?って議論が発散している。mongo 本体が公式のスクリプトや仕組みを提供していれば済む話しだけど、どうもそうではないみたい。だから泥臭い方法で自分でなんとかしないといけないようにみえる。

dockertest でもレプリカセットの設定について次の issue として登録されている。mongo のコンテナを使ったテストの場合、dockertest のレイヤーが挟まるのでさらにわかりにくくなっている。テストを動かすためにどういった設定が必要かは把握できたのでなにかよい方法を考えてコントリビュートしたい。

メールの結合テストのツール

0時に寝て4時と5時に起きて6時に起きた。最近は4時から1時間ごとに起きたりする。メールの送信処理のリファクタリングのコードレビューが長く続いていてなかなかややこしい。

mailhog の web api を使って検索する

メール送信の結合テストに mailhog というツールを使っている。smtp サーバーとしてメールを受け付けて web ui でそのメールの内容を確認できる機能をもっている。さらに web api で任意のメール取り出すこともできる。パスワードリセットの一時トークンをメールで送信するため、結合テストで一連のパスワードリセットを検証するにはメールから一時トークンを取り出さないといけない。そこで mailhog の出番だ。

検索 api を使うと任意のメールをフィルターできる。例えば、送り先のメールアドレスで検索するときは次のようにリクエストする。

$ curl -s "http://localhost:8025/api/v2/search?kind=to&query=myuser1@example.com" | jq .

うちらのやり方はメールのメッセージ ID に意図した uuid をセットしている。メールのメッセージから指定した uuid を含んでいるかを検索することで任意のメールをフィルターできる。

$ curl -s "http://localhost:8025/api/v2/search?kind=containing&query=28c9391f-25a5-4a8f-9035-7b8d5ac2d0f4" | jq .

それを結合テストのテストコードから呼び出すようにユーティリティを作ると次のようなコードになる。

import (
	"github.com/mailhog/data"
)

type mmSearchResult struct {
	Total int            `json:"total"`
	Count int            `json:"count"`
	Start int            `json:"start"`
	Items []data.Message `json:"items"`
}

func searchMail(
	host string, kind string, query string,
) (*mmSearchResult, error) {
	q := url.Values{}
	q.Set("kind", kind)
	q.Set("query", query)
	u := &url.URL{
		Scheme:   "http",
		Host:     host,
		Path:     "/api/v2/search",
		RawQuery: q.Encode(),
	}
	url := u.String()
	slog.Debug("mailhog search endpoint", "url", url)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("http create new request error. err: %s", err)
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("http request error. err: %s", err)
	}
	if res.StatusCode >= 400 {
		return nil, fmt.Errorf("returned response code is %d", res.StatusCode)
	}
	var r mmSearchResult
	if err := convertBody(res, &r); err != nil {
		return nil, fmt.Errorf("failed to convert: %s", err)
	}
	return &r, nil
}

歯医者

17時から歯科検診へ行ってきた。歯科検診をしてくれた今回のスタッフはおそらく初めてだったと思うが、手際がよくていつもよりもストレスが少なかった気がした。うちのチームのメンバーは朝方なので8時過ぎにはだいたいみんな始業し始める。なので、朝よりも夜にいない方がメンバーにとってよいだろうと考えて、朝一 (9時) に歯医者へ行くよりも最終 (17時) に行くようにしている。

pycamp ふりかえり

先週末の Python Boot Camp のふりかえり。ここまでがスタッフのお仕事なので一般的な kpt でイベントのふりかえりをした。仕事じゃないし、大きなトラブルもなかったし、参加者の評判 (アンケートの内容) もよかったので概ねよい内容だったと思う。テキストの改善点は直接 issue に追加してほしいと言われたので報告した。是非も含めてスタッフに判断してほしいので私が直すつもりはない。

パスワードリセットのテストのためのガイドライン

0時に寝て何度か起きて4時に起きて仮眠して6時に起きた。あまり寝た気がしない。

OWASP のパスワードリセットのガイドライン

パスワードリセットの仕組みをメンバーに開発してもらっている。そのコードレビューが先週から白熱している。セキュリティが関連するので堅牢に作る必要があるのでここはあまり妥協せきない。お手伝い先のシニアエンジニアから独自設計で作るのではなく、最低限、世の中の一般的なガイドラインに従っているかを確認するために OWASP のガイドラインを紹介してくれた。これはパスワードリセットの仕組みをテストするための要項をあげている。

これをメンバーに読んでもらって理解して実装しろと言いたいところでもあるが、私自身、読んだことがないとレビューできないことに気付いて、これは私が読んだ上で既存の設計や実装を見直すべきだと判断して deepl を駆使しながらほとんどを読んでみた。たしかに読んでみていくつか抜け・漏れに気付いたり、うちのセキュリティポリシーとして意図的に緩和しているところも認識できたりして、結論から言って読んでよかったと思う。当初はパスワードリセットのために一時トークンを1つだけ使っていたのだけど、それも2つを別々の経路に送って、割符のように組み合わせて認証する仕組みに変更した。よりセキュアにするという意図では一時トークンも1つよりも2つの方がよいというのは概ね正しいと思う。

openldap サーバーのデバッグ

1時に寝て3時に起きて5時に起きて6時半に起きた。あとひと踏ん張りなのでこのまま突っ切る。

openldap 2.5 の ldappasswd の振る舞い

openldap サーバーでパスワードを変更時の平文パスワードを連携するために カスタム overlay モジュール を使っている。前回の改修をしたときは openldap 2.4 向けのみの振る舞いを検証していた。今回は開発フェーズでは openldap 2.5 向けにもモジュールをビルドしてパッケージングしていた。その qa テストをしていて ldappasswd だけ、意図したパスワード連携が行われないという。

開発時に私が振る舞いを検証したつもりが ldapadd, ldapmodify は確認済みだったが、ldappasswd の確認をしていなかった。これは完全に私のミスで2つのフックポイントに対してカスタム overlay モジュールが動くのだから ldappasswd も大丈夫だろうと見通していた。しかし、そうではなかった。それぞれにフックポイントのコールバック設定があって、フックポイントもロジックが違うのだから当然ではあるのだけど、ちゃんと動作検証をしないといけないという、初歩的なミスをした。こんなこともあるんやと反省した。

gdb でデバッグしていて原因は 2.5.3 に含まれる次の修正だとわかった。私が検証していた openldap サーバーのバージョンは 2.5.14 だった。

簡潔に言えば、なんらかの不具合対応でもともと設定してあるコールバックを別のものに上書きしていた。カスタム overlay モジュールが設定したコールバックが別のものに上書きされてしまって意図した振る舞いをしないという現象が起きていた。これは明らかに openldap のリグレッションなので 2.5.15 で修正されてた。

たまたまピンポイントにバグを踏んだ形にはなったが、qa テストという別の人がテストをする仕組みでこのバグを検出できたことがうちの開発の品質基準を担保していることの表れでもある。

生きるということは嬉しいこと半分、辛いこと半分なのですよ。 采王

週末遊んで月曜日を迎える

21時に寝て何度か起きて7時に起きた。決して夜更ししているわけでもないのにずっと疲れている感があって早めに寝ていた。それでさらにお仕事が溜まる。

qa テストとバグ修正

先々週から qa テストが始まっていて、私はあまり参加できていないけれど、上がってくる issue のトリアージや致命的なバグの修正、インフラ周りの不具合の修正などをしている。1つは1つは難しくない小さい粒度のものだけれど、他の issue との関連があったり、アーキテクチャや既存の仕様そのものを見直した方がよいものもあったりと、背景の調査に時間がかかっていて、思ったよりも「ちぎっては投げちぎっては投げ」といった対応は取れていない。issue の優先順位付けしようにも、先に解決しないといけない issue があったり、この仕様はそもそもおかしいとか、よいことではあるのだけど qa でみつかる issue のトリアージが難しくなっている。それだけメンバーのシステムへの習熟度が上がって精度の高い issue をあげられるようになってきたという現れでもある。私がまごまごしているのは紛れもない事実ではある。

qa と最初のキャリア

0時に寝て何度か起きて6時に起きた。1-2週間前に2年ほどやっていたドラクエタクトをやめた。飽きたのか自然ともういいかって感じでやめられた。それ以来、家に帰ってからゆっくり休む時間が増えた気がする。

qa という業務の懐の広さ

先週の水曜日から qa テストに移行している。スケジュールとしてはこのために1ヶ月を確保している。おそらくもう少し早く終えられるんじゃないかという気はしている。早く終われば次の開発の計画づくりを前倒しにすればよいのでそれは構わない。私は先週から残タスクのリファクタリングが終わりきらなかったのでややバタバタしていたが、メンバーはテストに専念してテスト環境で動かして意図しない振る舞いを issue 登録したり、直感とは反する振る舞いを issue 登録したりしている。

新人さんや、未経験だけと開発者になりたいとジョブチェンジする人たち向けに、最初のキャリアとしてテスターや qa をするのがよいのではないかと私は考えている。きっかけは More Joel on Software に、テクニカルサポートは開発者を配置する必要があると書いてあった。しかし、テクニカルサポートだとスキルを身につけると持て余してしまうため、その業務ためのキャリアパスを考えないといけないと書いてあった。まったく同感だ。私がお手伝いしたある会社でもテクニカルサポートは1-2年で辞めているのを見聞きした。みんな開発したいからね。

(おまけ) カスタマーサービスの人たちのためのキャリアパスを用意する

  • テクニカルサポートにはデバッグ能力を要求するため、資質の高い人を配置する必要がある

More Joel on Software

新卒以外の採用ルートで未経験から開発者になるのは、いまは相当に難しいと思う。そんな人たちがキャリアアップするための試金石としてテスターがよいと思う。重要なお仕事だし、テストツールをプログラミングすることで開発者になるための準備期間 (学習) にもあてられる。システムの振る舞いや知識もテストを通して身につけられる。このお仕事を2-3年務められて、プログラミングも少し理解できるようになって、それでも開発者になりたいという意志があるなら開発者にステップアップすればよい。適正があるかどうかわからない状態で開発者を始めるよりも、ゆっくり学んでいけるのでうまくいくのではないか?と思ったりする。うちの会社はまだ社員を雇う余裕がないので私の持論の検証はできないが、どこかの会社でやってみてほしい。