第7章:Goテストヘルパー編 - runnの最終兵器!¶
ついに来た、本書のクライマックス! これまでCLIツールとしてrunnを使ってきたが、それはほんの入り口に過ぎない。runnの真の力は、Goテストヘルパーとして使用することで爆発的に解放される!Goの強力なテストフレームワークと完璧に融合し、最強のテスト環境を構築しよう!
🤔 なぜGoテストヘルパーなのか? - 従来の方法の限界!¶
😩 従来のAPIテストの悲惨な現実¶
// 従来のGoでのAPIテスト例
func TestUserAPI(t *testing.T) {
// 1. テストサーバーの起動
server := httptest.NewServer(handler)
defer server.Close()
// 2. HTTPクライアントの設定
client := &http.Client{Timeout: 10 * time.Second}
// 3. リクエストの作成
reqBody := `{"name":"Alice","email":"alice@example.com"}`
req, _ := http.NewRequest("POST", server.URL+"/users", strings.NewReader(reqBody))
req.Header.Set("Content-Type", "application/json")
// 4. リクエストの実行
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
// 5. レスポンスの検証
if resp.StatusCode != 201 {
t.Errorf("Expected 201, got %d", resp.StatusCode)
}
// 6. ボディの解析と検証
var user User
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
t.Fatal(err)
}
if user.Name != "Alice" {
t.Errorf("Expected Alice, got %s", user.Name)
}
// さらに複雑な検証が続く...
}
🎆 runnを使った場合 - 革命的なシンプルさ!¶
func TestUserAPI(t *testing.T) {
// 1. テストサーバーの起動
server := httptest.NewServer(handler)
defer server.Close()
// 2. runnでテストを実行
opts := []runn.Option{
runn.T(t),
runn.Runner("api", server.URL),
}
o, err := runn.Load("testdata/user_test.yml", opts...)
if err != nil {
t.Fatal(err)
}
if err := o.RunN(context.Background()); err != nil {
t.Fatal(err)
}
}
# testdata/user_test.yml
desc: ユーザーAPI テスト
runners:
api: https://api.example.com
steps:
create_user:
req:
api:///users:
post:
body:
application/json:
name: "Alice"
email: "alice@example.com"
test: |
current.res.status == 201 &&
current.res.body.name == "Alice" &&
current.res.body.id > 0
get_user:
req:
api:///users/{{ steps.create_user.res.body.id }}:
get:
test: |
current.res.status == 200 &&
current.res.body.name == "Alice"
🚀 基本的な統合方法 - runnとGoの幸せな結婚!¶
📁 プロジェクト構造 - 理想的なディレクトリ構成¶
myproject/
├── main.go
├── handler.go
├── model.go
├── go.mod
├── go.sum
└── testdata/
├── scenarios/
│ ├── user/
│ │ ├── create_user.yml
│ │ ├── update_user.yml
│ │ └── delete_user.yml
│ ├── auth/
│ │ ├── login.yml
│ │ └── logout.yml
│ └── integration/
│ └── full_workflow.yml
└── fixtures/
├── users.json
└── products.json
🔧 基本的なテストセットアップ - これがrunnテストの基本形!¶
package main
import (
"context"
"database/sql"
"net/http/httptest"
"testing"
"github.com/k1LoW/runn"
_ "github.com/lib/pq"
)
func TestAPI(t *testing.T) {
// データベースのセットアップ
db := setupTestDB(t)
defer db.Close()
// テストサーバーの起動
app := NewApp(db)
server := httptest.NewServer(app.Handler())
defer server.Close()
// runnの設定
opts := []runn.Option{
runn.T(t), // テストコンテキストを渡す
runn.Runner("api", server.URL), // APIサーバーのURL
runn.DBRunner("db", db), // データベース接続
runn.Var("test_user_email", "test@example.com"),
}
// シナリオの実行
o, err := runn.Load("testdata/scenarios/**/*.yml", opts...)
if err != nil {
t.Fatal(err)
}
if err := o.RunN(context.Background()); err != nil {
t.Fatal(err)
}
}
func setupTestDB(t *testing.T) *sql.DB {
// テスト用データベースの設定
db, err := sql.Open("postgres", "postgres://test:test@localhost/testdb?sslmode=disable")
if err != nil {
t.Fatal(err)
}
// マイグレーションの実行
if err := runMigrations(db); err != nil {
t.Fatal(err)
}
// テストデータの投入
if err := seedTestData(db); err != nil {
t.Fatal(err)
}
return db
}
🎯 高度な統合パターン - プロフェッショナルの極意!¶
🗄️ テストごとの独立したデータベース - 完全なアイソレーション!¶
func TestUserCRUD(t *testing.T) {
// 各テストで独立したデータベースを使用
dbName := fmt.Sprintf("test_%d", time.Now().UnixNano())
db := createTestDatabase(t, dbName)
defer dropTestDatabase(t, dbName)
defer db.Close()
// アプリケーションの起動
app := NewApp(db)
server := httptest.NewServer(app.Handler())
defer server.Close()
opts := []runn.Option{
runn.T(t),
runn.Runner("api", server.URL),
runn.DBRunner("db", db),
runn.Var("test_db_name", dbName),
}
o, err := runn.Load("testdata/user_crud.yml", opts...)
if err != nil {
t.Fatal(err)
}
if err := o.RunN(context.Background()); err != nil {
t.Fatal(err)
}
}
func createTestDatabase(t *testing.T, dbName string) *sql.DB {
// 管理用データベースに接続
adminDB, err := sql.Open("postgres", "postgres://admin:admin@localhost/postgres?sslmode=disable")
if err != nil {
t.Fatal(err)
}
defer adminDB.Close()
// テスト用データベースを作成
_, err = adminDB.Exec(fmt.Sprintf("CREATE DATABASE %s", dbName))
if err != nil {
t.Fatal(err)
}
// 新しいデータベースに接続
testDB, err := sql.Open("postgres", fmt.Sprintf("postgres://admin:admin@localhost/%s?sslmode=disable", dbName))
if err != nil {
t.Fatal(err)
}
// スキーマの作成
if err := createSchema(testDB); err != nil {
t.Fatal(err)
}
return testDB
}
🎭 モックサーバーとの統合 - 外部APIを完璧にシミュレート!¶
func TestExternalAPIIntegration(t *testing.T) {
// 外部APIのモックサーバー
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/external/users":
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
json.NewEncoder(w).Encode(map[string]interface{}{
"users": []map[string]interface{}{
{"id": 1, "name": "External User 1"},
{"id": 2, "name": "External User 2"},
},
})
case "/external/auth":
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
json.NewEncoder(w).Encode(map[string]string{
"token": "mock-jwt-token",
"expires_in": "3600",
})
default:
w.WriteHeader(404)
}
}))
defer mockServer.Close()
// メインアプリケーション
db := setupTestDB(t)
defer db.Close()
app := NewApp(db)
app.SetExternalAPIURL(mockServer.URL) // 外部APIのURLを設定
server := httptest.NewServer(app.Handler())
defer server.Close()
opts := []runn.Option{
runn.T(t),
runn.Runner("api", server.URL),
runn.Runner("external", mockServer.URL),
runn.DBRunner("db", db),
}
o, err := runn.Load("testdata/external_integration.yml", opts...)
if err != nil {
t.Fatal(err)
}
if err := o.RunN(context.Background()); err != nil {
t.Fatal(err)
}
}
🎆 複雑なテストデータの準備 - リアルなデータで真のテスト!¶
func TestComplexScenario(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
// 複雑なテストデータの準備
testData := prepareComplexTestData(t, db)
server := httptest.NewServer(NewApp(db).Handler())
defer server.Close()
opts := []runn.Option{
runn.T(t),
runn.Runner("api", server.URL),
runn.DBRunner("db", db),
// 準備したテストデータを変数として渡す
runn.Var("admin_user_id", testData.AdminUser.ID),
runn.Var("regular_user_id", testData.RegularUser.ID),
runn.Var("test_products", testData.Products),
runn.Var("test_categories", testData.Categories),
// 動的に生成されたデータ
runn.Var("test_orders", generateTestOrders(testData)),
}
o, err := runn.Load("testdata/complex_scenario.yml", opts...)
if err != nil {
t.Fatal(err)
}
if err := o.RunN(context.Background()); err != nil {
t.Fatal(err)
}
}
type TestData struct {
AdminUser User
RegularUser User
Products []Product
Categories []Category
}
func prepareComplexTestData(t *testing.T, db *sql.DB) *TestData {
data := &TestData{}
// 管理者ユーザーの作成
adminUser := User{
Name: "Admin User",
Email: "admin@example.com",
Role: "admin",
Active: true,
}
if err := createUser(db, &adminUser); err != nil {
t.Fatal(err)
}
data.AdminUser = adminUser
// 一般ユーザーの作成
regularUser := User{
Name: "Regular User",
Email: "user@example.com",
Role: "user",
Active: true,
}
if err := createUser(db, ®ularUser); err != nil {
t.Fatal(err)
}
data.RegularUser = regularUser
// カテゴリの作成
categories := []Category{
{Name: "Electronics", Description: "Electronic products"},
{Name: "Books", Description: "Books and publications"},
{Name: "Clothing", Description: "Clothing and accessories"},
}
for i := range categories {
if err := createCategory(db, &categories[i]); err != nil {
t.Fatal(err)
}
}
data.Categories = categories
// 商品の作成
products := []Product{
{Name: "Laptop", Price: 999.99, CategoryID: categories[0].ID, Stock: 10},
{Name: "Smartphone", Price: 599.99, CategoryID: categories[0].ID, Stock: 20},
{Name: "Programming Book", Price: 49.99, CategoryID: categories[1].ID, Stock: 50},
{Name: "T-Shirt", Price: 19.99, CategoryID: categories[2].ID, Stock: 100},
}
for i := range products {
if err := createProduct(db, &products[i]); err != nil {
t.Fatal(err)
}
}
data.Products = products
return data
}
💼 実践的なテストパターン - 現場で使える最強テクニック!¶
🔐 認証フローのテスト - セキュリティを完璧に検証!¶
func TestAuthenticationFlow(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
// JWTシークレットキーを設定
jwtSecret := "test-secret-key"
app := NewApp(db)
app.SetJWTSecret(jwtSecret)
server := httptest.NewServer(app.Handler())
defer server.Close()
opts := []runn.Option{
runn.T(t),
runn.Runner("api", server.URL),
runn.DBRunner("db", db),
runn.Var("jwt_secret", jwtSecret),
runn.Var("test_username", "testuser"),
runn.Var("test_password", "testpass123"),
}
o, err := runn.Load("testdata/auth_flow.yml", opts...)
if err != nil {
t.Fatal(err)
}
if err := o.RunN(context.Background()); err != nil {
t.Fatal(err)
}
}
# testdata/auth_flow.yml
desc: 認証フローの完全テスト
runners:
api: https://api.example.com
vars:
test_username: "testuser"
test_password: "testpass123"
steps:
# ユーザー登録
register_user:
req:
api:///auth/register:
post:
body:
application/json:
username: "{{ vars.test_username }}"
password: "{{ vars.test_password }}"
email: "test@example.com"
test: current.res.status == 201
# ログイン
login:
req:
api:///auth/login:
post:
body:
application/json:
username: "{{ vars.test_username }}"
password: "{{ vars.test_password }}"
test: |
current.res.status == 200 &&
current.res.body.token != null &&
current.res.body.expires_in > 0
# 認証が必要なエンドポイントへのアクセス
access_protected:
req:
api:///profile:
get:
headers:
Authorization: "Bearer {{ steps.login.res.body.token }}"
test: |
current.res.status == 200 &&
current.res.body.username == vars.test_username
# トークンの検証
verify_token:
dump:
# JWTトークンをデコード(実際の実装では適切なライブラリを使用)
token_payload: |
fromBase64(split(steps.login.res.body.token, ".")[1])
test: |
current.token_payload.username == vars.test_username
# 無効なトークンでのアクセス
invalid_token_access:
req:
api:///profile:
get:
headers:
Authorization: "Bearer invalid-token"
test: current.res.status == 401
# ログアウト
logout:
req:
api:///auth/logout:
post:
headers:
Authorization: "Bearer {{ steps.login.res.body.token }}"
test: current.res.status == 200
# ログアウト後のアクセス
access_after_logout:
req:
api:///profile:
get:
headers:
Authorization: "Bearer {{ steps.login.res.body.token }}"
test: current.res.status == 401
🌐 E2Eワークフローテスト - システム全体を完全テスト!¶
func TestE2EWorkflow(t *testing.T) {
// 複数のサービスを起動
db := setupTestDB(t)
defer db.Close()
// メインAPIサーバー
mainApp := NewMainApp(db)
mainServer := httptest.NewServer(mainApp.Handler())
defer mainServer.Close()
// 通知サービス
notificationApp := NewNotificationApp()
notificationServer := httptest.NewServer(notificationApp.Handler())
defer notificationServer.Close()
// 決済サービス(モック)
paymentServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 決済処理のモック
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
json.NewEncoder(w).Encode(map[string]interface{}{
"transaction_id": "txn_" + generateRandomID(),
"status": "completed",
"amount": 1000,
})
}))
defer paymentServer.Close()
// 外部APIの設定
mainApp.SetPaymentServiceURL(paymentServer.URL)
mainApp.SetNotificationServiceURL(notificationServer.URL)
opts := []runn.Option{
runn.T(t),
runn.Runner("api", mainServer.URL),
runn.Runner("notification", notificationServer.URL),
runn.Runner("payment", paymentServer.URL),
runn.DBRunner("db", db),
// テストデータ
runn.Var("test_customer", map[string]interface{}{
"name": "Test Customer",
"email": "customer@example.com",
"phone": "+81-90-1234-5678",
}),
runn.Var("test_product_id", 1),
runn.Var("test_quantity", 2),
}
o, err := runn.Load("testdata/e2e_workflow.yml", opts...)
if err != nil {
t.Fatal(err)
}
if err := o.RunN(context.Background()); err != nil {
t.Fatal(err)
}
}
# testdata/e2e_workflow.yml
desc: E2Eワークフローテスト - 顧客登録から注文完了まで
runners:
api: https://api.example.com
notification: https://notification.example.com
payment: https://payment.example.com
db: postgres://user:pass@localhost:5432/testdb?sslmode=disable
vars:
test_customer:
name: "Test Customer"
email: "customer@example.com"
phone: "+81-90-1234-5678"
test_product_id: 1
test_quantity: 2
steps:
# 1. 顧客登録
register_customer:
req:
api:///customers:
post:
body:
application/json: "{{ vars.test_customer }}"
test: |
current.res.status == 201 &&
current.res.body.id > 0
# 2. 商品情報の取得
get_product:
req:
api:///products/{{ vars.test_product_id }}:
get:
test: |
current.res.status == 200 &&
current.res.body.stock >= vars.test_quantity
# 3. カートに商品を追加
add_to_cart:
req:
api:///customers/{{ steps.register_customer.res.body.id }}/cart:
post:
body:
application/json:
product_id: "{{ vars.test_product_id }}"
quantity: "{{ vars.test_quantity }}"
test: current.res.status == 200
# 4. 注文の作成
create_order:
req:
api:///orders:
post:
body:
application/json:
customer_id: "{{ steps.register_customer.res.body.id }}"
items:
- product_id: "{{ vars.test_product_id }}"
quantity: "{{ vars.test_quantity }}"
price: "{{ steps.get_product.res.body.price }}"
test: |
current.res.status == 201 &&
current.res.body.order_id != null &&
current.res.body.total_amount == steps.get_product.res.body.price * vars.test_quantity
# 5. 決済処理
process_payment:
req:
payment:///payments:
post:
body:
application/json:
order_id: "{{ steps.create_order.res.body.order_id }}"
amount: "{{ steps.create_order.res.body.total_amount }}"
customer_id: "{{ steps.register_customer.res.body.id }}"
test: |
current.res.status == 200 &&
current.res.body.status == "completed"
# 6. 注文ステータスの更新
update_order_status:
req:
api:///orders/{{ steps.create_order.res.body.order_id }}/payment:
put:
body:
application/json:
transaction_id: "{{ steps.process_payment.res.body.transaction_id }}"
status: "paid"
test: current.res.status == 200
# 7. 在庫の確認
verify_stock_update:
req:
api:///products/{{ vars.test_product_id }}:
get:
test: |
current.res.body.stock == steps.get_product.res.body.stock - vars.test_quantity
# 8. 通知の送信確認
verify_notification:
req:
notification:///notifications:
get:
query:
customer_id: "{{ steps.register_customer.res.body.id }}"
type: "order_confirmation"
test: |
current.res.status == 200 &&
len(current.res.body.notifications) > 0 &&
current.res.body.notifications[0].order_id == steps.create_order.res.body.order_id
# 9. データベースの整合性確認
verify_database_consistency:
db:
db:///:
query: |
SELECT
o.id as order_id,
o.status,
o.total_amount,
c.name as customer_name,
p.name as product_name,
p.stock
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.id = $1
params:
- "{{ steps.create_order.res.body.order_id }}"
test: |
len(current.rows) == 1 &&
current.rows[0].status == "paid" &&
current.rows[0].customer_name == vars.test_customer.name &&
current.rows[0].stock == steps.get_product.res.body.stock - vars.test_quantity
⚡ パフォーマンステスト - 速度の限界に挑戦!¶
🚀 負荷テストの実装 - システムの耐久力を測れ!¶
func TestAPIPerformance(t *testing.T) {
if testing.Short() {
t.Skip("Skipping performance test in short mode")
}
db := setupTestDB(t)
defer db.Close()
// パフォーマンステスト用のデータを大量に準備
preparePerformanceTestData(t, db, 10000) // 10,000件のテストデータ
app := NewApp(db)
server := httptest.NewServer(app.Handler())
defer server.Close()
opts := []runn.Option{
runn.T(t),
runn.Runner("api", server.URL),
runn.DBRunner("db", db),
runn.Var("concurrent_users", 50),
runn.Var("requests_per_user", 100),
}
o, err := runn.Load("testdata/performance_test.yml", opts...)
if err != nil {
t.Fatal(err)
}
start := time.Now()
if err := o.RunN(context.Background()); err != nil {
t.Fatal(err)
}
duration := time.Since(start)
t.Logf("Performance test completed in %v", duration)
// パフォーマンス要件の検証
totalRequests := 50 * 100 // concurrent_users * requests_per_user
rps := float64(totalRequests) / duration.Seconds()
if rps < 100 { // 最低100 RPS要求
t.Errorf("Performance requirement not met: %.2f RPS (required: 100 RPS)", rps)
}
t.Logf("Achieved %.2f requests per second", rps)
}
# testdata/performance_test.yml
desc: APIパフォーマンステスト
vars:
concurrent_users: 50
requests_per_user: 100
steps:
# 並行負荷テスト
load_test:
loop:
count: "{{ vars.concurrent_users }}"
include:
path: ./performance/user_simulation.yml
vars:
user_id: "{{ i }}"
requests_count: "{{ vars.requests_per_user }}"
# testdata/performance/user_simulation.yml
desc: 単一ユーザーのシミュレーション
runners:
api: https://api.example.com
vars:
user_id: 1
requests_count: 100
steps:
user_requests:
loop:
count: "{{ vars.requests_count }}"
req:
api:///users:
get:
query:
page: "{{ (i % 100) + 1 }}"
limit: 10
test: |
current.res.status == 200 &&
current.res.response_time < 1000 # 1秒以内のレスポンス
🔍 デバッグとトラブルシューティング - 問題を瞬時に解決!¶
📝 デバッグ情報の出力 - すべてを可視化せよ!¶
func TestWithDebug(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
server := httptest.NewServer(NewApp(db).Handler())
defer server.Close()
opts := []runn.Option{
runn.T(t),
runn.Runner("api", server.URL),
runn.DBRunner("db", db),
// デバッグ情報を有効化
runn.Debug(true),
runn.Verbose(true),
// カスタムログ出力
runn.Logger(log.New(os.Stdout, "[RUNN] ", log.LstdFlags)),
}
o, err := runn.Load("testdata/debug_test.yml", opts...)
if err != nil {
t.Fatal(err)
}
if err := o.RunN(context.Background()); err != nil {
t.Fatal(err)
}
}
💥 テスト失敗時の詳細情報 - 失敗から学べ!¶
func TestWithDetailedErrorInfo(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
server := httptest.NewServer(NewApp(db).Handler())
defer server.Close()
opts := []runn.Option{
runn.T(t),
runn.Runner("api", server.URL),
runn.DBRunner("db", db),
// 失敗時のスクリーンショット保存
runn.ScreenshotDir("./test_screenshots"),
// 失敗時のHTTPダンプ保存
runn.HTTPDumpDir("./test_http_dumps"),
}
o, err := runn.Load("testdata/scenarios/**/*.yml", opts...)
if err != nil {
t.Fatal(err)
}
if err := o.RunN(context.Background()); err != nil {
// 詳細なエラー情報を出力
if runnErr, ok := err.(*runn.Error); ok {
t.Logf("Failed step: %s", runnErr.StepName)
t.Logf("Error details: %s", runnErr.Details)
t.Logf("Request: %s", runnErr.Request)
t.Logf("Response: %s", runnErr.Response)
}
t.Fatal(err)
}
}
🔄 CI/CDとの統合 - 自動化の極み!¶
💙 GitHub Actionsでの実行 - クラウドで最強テスト!¶
# .github/workflows/api_test.yml
name: API Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.21
- name: Install dependencies
run: go mod download
- name: Run database migrations
run: |
go run ./cmd/migrate up
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/testdb?sslmode=disable
- name: Run API tests
run: |
go test -v ./... -tags=integration
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/testdb?sslmode=disable
TEST_ENV: ci
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: |
test_screenshots/
test_http_dumps/
coverage.out
🐳 Dockerを使った統合テスト - コンテナで完璧なテスト環境!¶
func TestWithDocker(t *testing.T) {
if testing.Short() {
t.Skip("Skipping Docker integration test in short mode")
}
// Docker Composeでテスト環境を起動
cmd := exec.Command("docker-compose", "-f", "docker-compose.test.yml", "up", "-d")
if err := cmd.Run(); err != nil {
t.Fatal(err)
}
// テスト終了時にクリーンアップ
defer func() {
cmd := exec.Command("docker-compose", "-f", "docker-compose.test.yml", "down", "-v")
cmd.Run()
}()
// サービスの起動を待機
time.Sleep(10 * time.Second)
opts := []runn.Option{
runn.T(t),
runn.Runner("api", "http://localhost:8080"),
runn.Runner("db", "postgres://test:test@localhost:5433/testdb?sslmode=disable"),
}
o, err := runn.Load("testdata/docker_integration.yml", opts...)
if err != nil {
t.Fatal(err)
}
if err := o.RunN(context.Background()); err != nil {
t.Fatal(err)
}
}
🎆 まとめ - Goテストヘルパーの達人誕生!¶
素晴らしい! あなたは今、runnの最強の使い方をマスターした!
🏆 この章で習得した7つの極意:¶
- 🔗 基本的な統合:
runn.T(t)
でGoテストと完璧な融合! - 🎯 高度な統合パターン: 独立データベース、モックサーバーで理想的なテスト環境!
- 🎆 複雑なテストデータ準備: 動的生成でリアルなテストシナリオ!
- 💼 実践的なテストパターン: 認証フロー、E2Eワークフローを完璧にカバー!
- ⚡ パフォーマンステスト: 負荷の限界を見極めろ!
- 🔍 デバッグとトラブルシューティング: エラーを瞬時に特定!
- 🔄 CI/CDとの統合: GitHub Actions、Dockerで完全自動化!
runnの真の価値は、Goテストヘルパーとして使用することで爆発的に発揮される。YAMLの宣言的な美しさとGoの圧倒的なパワーが融合し、史上最強のテストスイートが誕生する!
あなたはもう、単なるテスターではない。テストの芸術家だ!