課題2: プロジェクト構築とツールチェーン実践
課題概要
この課題では、Go Modulesを使用してプロジェクトを作成し、標準ツールチェーンを活用してコード品質を向上させます。依存関係管理、テスト、フォーマット、静的解析など、実務で必須となるスキルを習得します。
マンダトリー要件
要件1: プロジェクトの作成
簡単な計算機アプリケーションを作成します。
プロジェクト構造:
calculator/
├── go.mod
├── go.sum
├── main.go
├── calc/
│ ├── calc.go
│ └── calc_test.go
└── README.md
ステップ1: プロジェクト初期化
mkdir calculator
cd calculator
go mod init github.com/yourname/calculator
ステップ2: calc/calc.go の実装
package calc
// Add は2つの整数を加算します
func Add(a, b int) int {
return a + b
}
// Subtract は2つの整数を減算します
func Subtract(a, b int) int {
return a - b
}
// Multiply は2つの整数を乗算します
func Multiply(a, b int) int {
return a * b
}
// Divide は2つの整数を除算します
// ゼロ除算の場合はエラーを返します
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, ErrDivisionByZero
}
return a / b, nil
}
// ErrDivisionByZero はゼロ除算エラーです
var ErrDivisionByZero = errors.New("division by zero")
ステップ3: calc/calc_test.go の実装
package calc
import "testing"
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 5, 3, 8},
{"negative numbers", -5, -3, -8},
{"mixed signs", 5, -3, 2},
{"with zero", 0, 5, 5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, result, tt.expected)
}
})
}
}
func TestDivide(t *testing.T) {
// 正常系
result, err := Divide(10, 2)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 5 {
t.Errorf("Divide(10, 2) = %d; want 5", result)
}
// ゼロ除算
_, err = Divide(10, 0)
if err != ErrDivisionByZero {
t.Errorf("expected ErrDivisionByZero, got %v", err)
}
}
// ベンチマーク
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(100, 200)
}
}
ステップ4: main.go の実装
package main
import (
"fmt"
"os"
"strconv"
"github.com/yourname/calculator/calc"
)
func main() {
if len(os.Args) < 4 {
fmt.Println("Usage: calculator <operation> <num1> <num2>")
fmt.Println("Operations: add, sub, mul, div")
os.Exit(1)
}
operation := os.Args[1]
a, err := strconv.Atoi(os.Args[2])
if err != nil {
fmt.Println("Error: invalid number:", os.Args[2])
os.Exit(1)
}
b, err := strconv.Atoi(os.Args[3])
if err != nil {
fmt.Println("Error: invalid number:", os.Args[3])
os.Exit(1)
}
var result int
switch operation {
case "add":
result = calc.Add(a, b)
fmt.Printf("%d + %d = %d\n", a, b, result)
case "sub":
result = calc.Subtract(a, b)
fmt.Printf("%d - %d = %d\n", a, b, result)
case "mul":
result = calc.Multiply(a, b)
fmt.Printf("%d × %d = %d\n", a, b, result)
case "div":
result, err := calc.Divide(a, b)
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}
fmt.Printf("%d ÷ %d = %d\n", a, b, result)
default:
fmt.Println("Error: unknown operation:", operation)
os.Exit(1)
}
}
要件2: テストとカバレッジ
すべてのテストが成功し、カバレッジが80%以上であることを確認してください。
# テスト実行
go test ./...
# 詳細出力
go test -v ./...
# カバレッジ測定
go test -cover ./...
# カバレッジレポート生成
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
# ベンチマーク実行
go test -bench=. ./...
要件3: コード品質チェック
以下のツールを実行し、すべての警告を解決してください。
# フォーマット
go fmt ./...
# 静的解析
go vet ./...
# レースコンディション検出(並行処理がある場合)
go test -race ./...
要件4: 実行とビルド
# 直接実行
go run main.go add 10 20
# ビルド
go build -o calculator
# 実行
./calculator add 10 20
# 出力: 10 + 20 = 30
./calculator div 10 0
# 出力: Error: division by zero
要件5: ドキュメント作成
README.md を作成してください。
# Calculator
シンプルなコマンドライン計算機
## インストール
bash
go install github.com/yourname/calculator@latest
## 使い方
bash
calculator
### 操作
- `add`: 加算
- `sub`: 減算
- `mul`: 乗算
- `div`: 除算
### 例
bash
calculator add 10 20 # 10 + 20 = 30
calculator sub 10 5 # 10 - 5 = 5
calculator mul 3 4 # 3 × 4 = 12
calculator div 10 2 # 10 ÷ 2 = 5
## テスト
bash
go test ./...
## ライセンス
MIT
期待される出力
テスト結果
go test -v ./...
=== RUN TestAdd
=== RUN TestAdd/positive_numbers
=== RUN TestAdd/negative_numbers
=== RUN TestAdd/mixed_signs
=== RUN TestAdd/with_zero
--- PASS: TestAdd (0.00s)
--- PASS: TestAdd/positive_numbers (0.00s)
--- PASS: TestAdd/negative_numbers (0.00s)
--- PASS: TestAdd/mixed_signs (0.00s)
--- PASS: TestAdd/with_zero (0.00s)
=== RUN TestDivide
--- PASS: TestDivide (0.00s)
PASS
ok github.com/yourname/calculator/calc 0.123s
カバレッジ結果
go test -cover ./...
ok github.com/yourname/calculator/calc 0.123s coverage: 95.0% of statements
実行例
./calculator add 10 20
10 + 20 = 30
./calculator div 10 0
Error: division by zero
ボーナス課題
> ボーナス: これらはオプションです。マンダトリー部分が完了してから取り組んでください。
ボーナス1: 外部パッケージの統合
カラー出力機能を追加してください。
# github.com/fatih/color パッケージを追加
go get github.com/fatih/color
main.goを改良:
import (
"github.com/fatih/color"
// ...
)
func main() {
// ...
switch operation {
case "add":
result = calc.Add(a, b)
color.Green("%d + %d = %d\n", a, b, result)
case "div":
result, err := calc.Divide(a, b)
if err != nil {
color.Red("Error: %v\n", err)
os.Exit(1)
}
color.Green("%d ÷ %d = %d\n", a, b, result)
// ...
}
}
依存関係が正しく管理されていることを確認:
go mod tidy
cat go.mod
cat go.sum
ボーナス2: クロスコンパイル
複数プラットフォーム向けにビルドスクリプトを作成してください。
build.sh:
#!/bin/bash
# バージョン情報
VERSION="1.0.0"
LDFLAGS="-s -w -X main.version=${VERSION}"
# プラットフォームリスト
PLATFORMS=(
"darwin/amd64"
"darwin/arm64"
"linux/amd64"
"linux/arm64"
"windows/amd64"
)
# ビルドディレクトリ
BUILD_DIR="build"
rm -rf ${BUILD_DIR}
mkdir -p ${BUILD_DIR}
for PLATFORM in "${PLATFORMS[@]}"; do
GOOS=${PLATFORM%/*}
GOARCH=${PLATFORM#*/}
OUTPUT="${BUILD_DIR}/calculator-${GOOS}-${GOARCH}"
if [ "$GOOS" = "windows" ]; then
OUTPUT="${OUTPUT}.exe"
fi
echo "Building for ${GOOS}/${GOARCH}..."
GOOS=${GOOS} GOARCH=${GOARCH} go build \
-ldflags="${LDFLAGS}" \
-o ${OUTPUT}
if [ $? -ne 0 ]; then
echo "Failed to build for ${GOOS}/${GOARCH}"
exit 1
fi
done
echo "Build complete!"
ls -lh ${BUILD_DIR}
実行:
chmod +x build.sh
./build.sh
ボーナス3: CI/CD設定
GitHub Actionsでテストとビルドを自動化してください。
.github/workflows/test.yml:
name: Test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Install dependencies
run: go mod download
- name: Format check
run: |
go fmt ./...
git diff --exit-code
- name: Vet
run: go vet ./...
- name: Test
run: go test -v -race -coverprofile=coverage.out ./...
- name: Coverage
run: go tool cover -func=coverage.out
- name: Build
run: go build -v ./...
ボーナス4: ベンチマーク最適化
各関数のベンチマークを実装し、パフォーマンスを測定してください。
// calc/calc_test.go に追加
func BenchmarkSubtract(b *testing.B) {
for i := 0; i < b.N; i++ {
Subtract(1000, 500)
}
}
func BenchmarkMultiply(b *testing.B) {
for i := 0; i < b.N; i++ {
Multiply(100, 200)
}
}
func BenchmarkDivide(b *testing.B) {
for i := 0; i < b.N; i++ {
Divide(1000, 10)
}
}
実行:
go test -bench=. -benchmem ./...
評価基準
| 項目 | 配点 | 詳細 |
|---|---|---|
| プロジェクト構造 | 15点 | 適切なディレクトリ構造とファイル配置 |
| 関数実装 | 20点 | 4つの演算が正しく実装されている |
| テスト | 25点 | テストが書かれ、カバレッジ80%以上 |
| コード品質 | 20点 | fmt, vet で警告なし |
| ドキュメント | 20点 | README.mdが適切に記述されている |
| **ボーナス1** | 5点 | 外部パッケージが正しく統合されている |
| **ボーナス2** | 5点 | クロスコンパイルが成功している |
| **ボーナス3** | 5点 | CI/CDが動作している |
| **ボーナス4** | 5点 | ベンチマークが実装されている |
提出方法
以下の構造でGitHubリポジトリを作成し、URLを提出してください:
calculator/
├── go.mod
├── go.sum
├── main.go
├── calc/
│ ├── calc.go
│ └── calc_test.go
├── README.md
├── coverage.html # カバレッジレポート
├── build.sh # ボーナス2
└── .github/ # ボーナス3
└── workflows/
└── test.yml
ヒント
- テーブルドリブンテスト: 複数のテストケースを効率的に記述できます
- エラー処理: Goでは戻り値でエラーを返すのが慣習です
- パッケージ設計: 再利用可能な機能は別パッケージに分離します
- go mod tidy: 使っていない依存関係を自動削除します
- -ldflags: ビルド時にバージョン情報などを埋め込めます
- Go Modules Reference
- Testing in Go
- Go Command Documentation
- Effective Go - Testing
- GitHub Actions for Go