はじめに
こんにちは、サーバーサイドエンジニアの@shiroemonsです。
こちらはトレタAdventCalendar2024 22日目の記事です。
今回は、現在進行中のGoプロジェクトにおけるディレクトリ構成と、開発体験を向上させるために実施した施策について紹介します。
ディレクトリ構成
このディレクトリ構成は、チーム全体で議論を重ね、各メンバーが実装しやすく効率的な形を目指して構築しました。以下がその詳細です。
. ├── cmd # エントリーポイント │ ├── api # APIサーバー(実際はProject名) │ └── seeder # 開発環境ダミーデータ生成処理 ├── config # 環境変数読み込み ├── db │ └── migrations # DBマイグレーション ├── docker # Dockerfile ├── docs # ドキュメント ├── firebase # Firebase Emulator Suite設定ファイル └── internal ├── db ├── handler # 抽象化先の処理への振り分け │ ├── router.go # ルーティング定義 │ └── v1 │ └── executor # ユースケース初期化 ├── http │ └── response # レスポンス定義 ├── infrastructure # 共通インフラ処理 ├── model # DBモデル定義 ├── {module} # 抽象化先の名称 │ ├── infrastructure # 各モジュールのインフラ処理 │ ├── prepare # 各モジュールの事前処理 │ ├── values # モジュール内で使用する値オブジェクト │ └── {domain} # ビジネスロジックやドメイン処理 │ ├── repository # DBや外部APIとのやり取り │ └── usecase # ユースケース ├── errors # エラー定義 ├── service # 共通サービス処理 ├── testhelper # テストヘルパー関数 │ ├── ddl # テスト用DDL │ └── testdata # テスト用データ ├── util # 汎用的なコード └── values # 共通値オブジェクト
開発体験向上の施策
1. 初期データ挿入ツールを作成
プロジェクトの初期段階では、各メンバーが開発環境のデータベースに個別でテストデータを作成し、動作確認を行っていました。しかし、データ構造が複雑であったため、データ作成には多くの手間がかかり、さらにメンバー間で作成するデータにばらつきが生じていました。この状況は動作確認の効率を低下させる一因となっていました。
この問題を解決するために、初期データを効率的に挿入・更新するツール「seeder」を開発しました。このツールはGoで独自に実装され、開発環境において使用することを前提としています。seeder
は、TSVファイルからデータを読み取り、加工してデータベースにアップサート(挿入・更新)する機能を提供します。
このツールをプロジェクトの早い段階で導入したことで、以下のような効果が得られました。
- テストデータの準備が容易に: 手作業で複雑なデータを作成する必要がなくなり、効率的にデータを準備できるようになりました。
- 動作確認のハードルが低下: メンバー全員が同一のデータを使用できるため、動作確認が容易になり、プロジェクト全体のスムーズな進行に寄与しました。
- 開発効率の向上: テストデータの準備にかかる時間が短縮され、その分のリソースを開発や改善に集中させることができました。
seeder
の導入により、プロジェクトの開発環境を標準化し、チーム全体の生産性を向上させることができました。これにより、プロジェクトの初期段階からスムーズな開発体制を築くことができ、メンバーの負担軽減にもつながりました。
2. カバレッジレポートの精度向上
seeder
を導入することで開発効率が大幅に向上した一方、カバレッジレポートの値が大きく下がるという課題が発生しました。この問題の主な原因は以下の2点です。
- 開発環境のみで使用する
seeder
がカバレッジ対象に含まれていたこと - テストで使用する自動生成されたモックファイルがカバレッジ計測に影響していたこと
これを解決するために、カバレッジレポートから除外すべきファイルを明確に定義し、それらを簡単にフィルタリングできるスクリプトを作成しました。この取り組みにより、以下の改善を実現しました。
改善内容
- 正確なカバレッジレポートの生成
- 実際のコードカバレッジに影響を与えない部分を除外することで、レポートの精度を向上させました。
- 効率的なテスト分析
- 本来注力すべきコードのカバレッジを明確に把握できるようになり、テストの改善ポイントを特定しやすくなりました。
- メンテナンス性の向上
- 除外対象をスクリプト化することで、プロジェクト全体での運用が容易になりました。
解決策: 除外スクリプトの導入
以下は、カバレッジレポートから不要なファイルを除外するためのスクリプトfilter_coverage.sh
です。
#!/bin/sh # 入力引数のチェック if [ $# -ne 1 ]; then echo "Usage: $0 <coverage_file>" exit 1 fi COVERAGE_FILE=$1 # .coverageignore ファイルの存在チェック if [ ! -f .coverageignore ]; then echo ".coverageignore file not found!" exit 1 fi # .coverageignore ファイルを読み込む IGNORE_FILES=$(cat .coverageignore) # 一時ファイルを作成 TEMP_FILE="coverage_filtered.out" cp "$COVERAGE_FILE" "$TEMP_FILE" # 除外ファイルごとにフィルタリング for FILE in $IGNORE_FILES do grep -Ev "$FILE" "$TEMP_FILE" > temp.out && mv temp.out "$TEMP_FILE" done mv "$TEMP_FILE" "$COVERAGE_FILE" echo "Filtered coverage report saved to $COVERAGE_FILE"
.coverageignore
例
以下は、カバレッジレポートから除外すべきファイルを定義する.coverageignore
ファイルの例です。
# このファイルには、カバレッジレポートから除外したいファイルパスを記載します。 # 各ファイルパスは新しい行に記載してください。 # ファイルパスには正規表現を使用できます。 # 開発環境用のアプリのため除外 cmd/seeder/main\.go # 自動生成されたモックファイルを除外 .*_moq\.go
この設定例では、cmd/seeder/main.go
は開発環境でのみ使用されるスクリプトであり、本番環境には関係がないためカバレッジレポートから除外しています。また、*_moq.go
は自動生成されたモックファイルであり、実際のコードではないためレポートに含める必要がありません。
使い方
カバレッジ計測を実行
go test -race -shuffle=on ./... -coverprofile=tmp/coverage.out
スクリプトで除外ファイルをフィルタリング
./filter_coverage.sh tmp/coverage.out
カバレッジレポートを確認
go tool cover -html=tmp/coverage.out
- カバレッジレポートの確認には、octcovの利用がおすすめです。
Makefileで簡易化
手順1と手順2を1つのMakeコマンドとして統合し、さらに手順3も別のMakeコマンドとして設定することで、テスト実行とカバレッジレポートの確認を効率化できます。
test: go test -race -shuffle=on ./... -coverprofile=tmp/coverage.out ./filter_coverage.sh tmp/coverage.out coverage-report: go tool cover -html=tmp/coverage.out
これにより、以下のコマンドでカバレッジ計測とレポート確認が可能になります。
make test
make coverage-report
3. テストデータの自動生成
データベース関連のテストでは、テストデータの準備が大きな手間となることがあります。特に複雑なデータ構造を扱う場合、手動でのデータ作成には多くの労力が必要であり、チーム内でデータ内容がばらつくリスクも存在していました。しかし、seeder
を活用することで、効率的にデータを生成し活用できるようになり、これを基盤としてtestfixtures
を用いた自動生成の仕組みを導入しました。
このアプローチにより、以下の効果を実現しています。
- テストデータ準備の効率化: 手動での作業を削減し、開発速度を向上。
- データの一貫性確保: チーム内で同じデータセットを共有可能に。
- 信頼性向上: 自動生成されたデータにより、テストの再現性が高まる。
これにより、テストデータの準備に伴う負担を大幅に軽減し、開発環境を標準化することで、プロジェクト全体の効率を高めました。
解決策: テストデータ自動生成スクリプトの導入
以下は、テストデータを自動生成するためのシェルスクリプトgenerate_testdata.sh
です。
このスクリプトでは、PostgreSQLデータベースからのデータダンプを利用して、不要なファイルの削除やタイムスタンプの加工も自動化しています。
#!/bin/sh # testfixtures の dump を実行する testfixtures --dump -d postgres -c "postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:5432/$POSTGRES_DB?sslmode=disable" -D internal/testhelper/testdata # 不要なファイルを削除 find ./internal/testhelper/testdata -type f -name "*.yml" -exec grep -q '^\[\]$' {} \; -exec rm {} + echo "データがないテーブルの不要なファイルを削除しました。" # created_at と updated_at を RAW=NOW() に置換 find ./internal/testhelper/testdata -type f -name "*.yml" -exec sed -i -E \ -e 's/created_at: [-0-9T:.Z]+/created_at: RAW=NOW()/g' \ -e 's/updated_at: [-0-9T:.Z]+/updated_at: RAW=NOW()/g' {} + echo "created_at と updated_at を RAW=NOW() に置換しました。" echo "すべての処理が完了しました。"
使い方
- スクリプトを実行してテストデータを生成
- データベース用の環境変数は用意しておく
./generate_testdata.sh
Makefileで簡易化
手動でスクリプトを実行する手間を減らすため、Makefileに統合することでワンコマンドでの操作が可能になります。
generate-testdata: sh ./generate_testdata.sh
実行コマンド:
make generate-testdata
4. git push前の品質チェック
GitHubでコード管理をしており、GitHub Actionsでテストとリントのチェックを行っています。しかし、開発環境上でテストやリントを実行せずにgit push
を行い、CIで失敗して気付くという課題がありました。
この問題を解決するため、Git hooksのpre-push
を活用し、git push
前にテストとリントを実行するスクリプトを導入しました。このスクリプトは、開発者がコードをリモートリポジトリに送信する際に自動的に呼び出され、以下を確認します。
- テストの成功: コードの変更が既存の機能に影響を与えていないことを保証します。
- リントチェック: コーディングスタイルの統一と品質の確保を支援します。
この仕組みにより、コード品質の低下を防ぎ、以下の効果を得られます。
- 早期の問題発見: テストやリントの失敗を事前に検出でき、手戻りを防ぎます。
- CI/CDパイプラインの成功率向上: CIでのエラー発生率を削減し、スムーズなデプロイを支援します。
- チームの効率化: コーディングスタイルの統一により、レビューの効率が向上します。
デメリットとして、pushまでにテストとリントをチェックするため、実行時間がかかり開発スピードが低下する場合があります。しかし、手戻りを防げると考えると十分に有効なアプローチです。
解決策: git hooks pre-push の活用
以下は、git push
前にコード品質を確保するためのスクリプト.githooks/pre-push
です。
このスクリプトでは、git push
の実行前にテストとリントを自動的に実行することで、コード品質を向上させるとともに、CI/CDパイプラインでのエラーを未然に防ぐ仕組みを提供しています。
#!/bin/sh # Dockerのパスを追加 export PATH=$PATH:/usr/local/bin # テストを実行 echo "🚀 Running tests..." if ! make test; then echo "" echo "==========================" echo "❌ Tests failed " echo "==========================" echo "" exit 1 fi # Lintを実行 echo "" echo "🔍 Running linter..." if ! make lint; then echo "" echo "==========================" echo "❌ Linting failed " echo "==========================" echo "" exit 1 fi echo "" echo "==========================" echo "✅ All checks passed " echo "==========================" echo "" exit 0
make lint
でリントのチェックをしています。
使い方
git hooksを設定
git config --local core.hooksPath .githooks
コードの品質チェックを自動化
git push
コマンド実行時にスクリプトが自動的に呼び出され、テストとリントが実行されます。
5. APIクライアントツール「Bruno」の導入
APIの動作確認を行う際には、従来はPostmanを使用していましたが、チームメンバーごとに確認方法が異なることや、設定に手間がかかるといった課題がありました。さらに、新規メンバーへの情報共有も効率的とは言えませんでした。
これらの課題を解決するため、APIのドキュメント作成と共有にはBrunoを採用しました。Brunoは、Postmanに代わる軽量なAPIクライアントツールであり、特にテキストベースの管理とバージョン管理に優れています。
導入した理由
- ドキュメントとしての扱いやすさ:
- Brunoではリクエストをテキスト形式(
.bru
拡張子)で保存できるため、Gitを用いた変更履歴の管理や差分の確認が容易です。
- Brunoではリクエストをテキスト形式(
- チームでの共有が簡単:
.bru
ファイルをリポジトリに保存して共有することで、新しいメンバーでも簡単に環境をセットアップできます。
- 軽量で効率的:
- PostmanのようなGUIを使用して直感的に操作できるため、APIリクエストの作成や実行が簡単です。また、CLIツールとしても利用できるため、スクリプト化や自動化にも対応しています。
解決策: Brunoを導入する
macOSであれば以下のコマンドで簡単にインストールできます。
brew install bruno
初期設定は直感的なGUIで構成されており、PostmanなどのAPIクライアントを使用した経験があれば、初めてでも簡単に操作できます。さらに、設定済みのファイルを読み込むだけでセットアップが完了し、すぐに利用を開始できます。
効果
Brunoの特長を活かすことで、APIドキュメントの管理が格段に効率化しました。これにより、チーム全体のAPI開発が迅速かつスムーズに進行し、情報共有の手間を大幅に削減できました。結果として、プロジェクトの生産性と進行速度が向上しました。
まとめ
Goプロジェクトにおける開発体験を向上させるための施策について紹介しました。
開発体験を向上することで、テストカバレッジが20%だったのを70%以上まで引き上げることができました。今回の施策により、特にDBのテストを書くハードルが下がり、テストが書きやすいディレクトリ構造にしたことが大きな要因だと感じています。
この記事が、皆さんの開発に少しでも役立てば幸いです。
おわりに
トレタでは一緒にチームで働いてくれるエンジニアのメンバーやプロダクトマネージャーとして活躍してくれる人を求めています。飲食店の未来をアップデートする事業に興味のある方はお気軽に話を聞きにきてください。