こんにちは!がちむちです。
最近昔の知り合いに「成長が留まるところを知らないな」と言われました。
もちろん体重の話で、技術力は全く変わってない窓際ムチムチエンジニアです。
皆さんは2024年いかがでしたか?
今年はありがたいことに、会社の福利厚生でGoogle Cloud Next '24に参加させていただきました。
ラスベガスで開催されたのですが、オーシャンズ11という映画でジョージ・クルーニーさんに痺れた私には、憧れの地に行くことができて本当に嬉しかったです。
帰りの飛行機では、いつかカジノディーラーに 巻き上げられた 預けたチップを取り返しに行くと一筋の涙を流しました。
Recapイベントや開発合宿を行ったりとそこそこ 遊んだ 外部発信やチームビルディングなど様々な活動を行えた2024年でした。
来年も張り切っていきたいです。
以下が今年の成果になります。
課題
あるAPIのシステムでシナリオテストが遅すぎてCI/CDで時間がかかってしまっていました。(1回のテストで10〜15分)
各テストは直列で動いていたのですが、並列で動かすとデータベースのデータがケースごとに影響してしまい、並列で動かすのは、難しい状態でした。
対応 データベースをケースごとに作る
MySQLはデータベースを無制限に作ることができるため、テストケースごとにデータベースを作成するようにしました。
MySQL has no limit on the number of databases. The underlying file system may have a limit on the number of directories.
簡単な疑似コードは以下になります。
func newTestDB(name string) {
db, _ := gorm.Open(mysql.Open("DB_DSN"))
db.Exec("DROP DATABASE IF EXISTS " + name)
db.Exec("CREATE DATABASE " + name)
db.Exec("USE " + name)
_ = filepath.Walk("DDLファイルのディレクトリのパス", func(path string, info os.FileInfo, err error) error {
if !strings.HasSuffix(path, ".sql") {
return nil
}
b, _ := os.ReadFile(path)
db.Exec(string(b))
return nil
})
}
func setupTestServer(dbName string) {
// APIサーバーのテストなので、サーバーを起動するときに、dbNameにつなぐようにする
// 環境変数で接続するデータベースを設定できるようにしておき
// 環境変数読み込んだ構造体の該当の変数をdbNameで上書きしてからサーバーを起動する
}
func TestXxxxx(t *testing.T) {
t.Parallel()
newTestDB("TestXxxxx")
setupTestServer("TestXxxxx")
// 実際のテスト
}
func TestYyyyy(t *testing.T) {
t.Parallel()
newTestDB("TestYyyyy")
setupTestServer("TestYyyyy")
// 実際のテスト
}
これによって10〜15分かかっていたテストが4〜6分になりました。
また、テストケースが増えれば時間が増えていましたが、並列実行できるようにしたので、ケースが増えても全体の時間はあまり伸びないようにすることができました。
没対応策 in-memoryデータベースを使う
最初対応としてgo-mysql-serverを使ってケースごとにデータベースを立ち上げてDDLを流して他のテストケースのデータが影響しないようにしました。
実際に修正をしてみるといくつかはうまく動いたのですが、関数のいくつかにMySQLと動きが違うケースあり全部はうまく動かなかったです。
PRやパッチを当てて回避しようと思いましたが、結合テストやe2eなどのテストで実環境と違う状態でテストするのは、こういった問題が今後も出てくると感じられたのでこの方法は断念しました。
※単体テストなどで利用するなどは、mockやこういったin-memoryサーバーを利用するのは、全然いい方法だと思います。
最後に
今回はDDLを同じレポジトリで管理していたため、新しくデータベース作ってDDLを流したらケース事に影響がない状況を作れるのではないかと思い実現できました。
MySQLに複数のデータベースをあまり作ったことはなかったのと、上限があると勝手に思っていたのですが、まさか無制限で作れる(リソースの上限による)とは知らなかったです。
今回はGoでしたが、他の言語でも同様の実装ができると思っています。
同様の課題を持っている方の参考になれば幸いです!