🔧 AI Nachrichten Julian Goldie SEO: GPT 6 Astra: How to Build 3D Worlds!(16.09.2026 um 15:00 Uhr)
🍏 iOS / Mac OSAusweisApp für macOS 27: Governikus erklärt Verzögerung(16.09.2026 um 15:03 Uhr)
🍏 iOS / Mac OSApple's Xserve may return with Nvidia tech(16.09.2026 um 15:30 Uhr)
🪟 Windows TippsAdobe Announces Photoshop and Premiere Elements 2027(16.09.2026 um 15:00 Uhr)
🔧 AI Nachrichten Julian Goldie SEO: GPT 6 Astra: How to Build 3D Worlds!(16.09.2026 um 15:00 Uhr)
🍏 iOS / Mac OSAusweisApp für macOS 27: Governikus erklärt Verzögerung(16.09.2026 um 15:03 Uhr)
🍏 iOS / Mac OSApple's Xserve may return with Nvidia tech(16.09.2026 um 15:30 Uhr)
🪟 Windows TippsAdobe Announces Photoshop and Premiere Elements 2027(16.09.2026 um 15:00 Uhr)

🔧 Programmierung 🕛 vor 11 Monaten 14 Min Lesezeit
0

Golang For PHP Developer : Testing & Deployment

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht




Day 7: Testing & Deployment



Goal: Write tests and deploy your API to production


Time: ~4-5 hours


What you'll build: Tested, production-ready API with deployment







1. Testing Basics in Go (1 hour)





Go's Built-in Testing



Go has testing built into the language - no PHPUnit needed!



Test file naming: *_test.go





Your First Test



Create utils/password_test.go:




CODE
package utils

import "testing"

func TestHashPassword(t *testing.T) {
password := "mySecretPassword123"

hash, err := HashPassword(password)
if err != nil {
t.Fatalf("HashPassword failed: %v", err)
}

if hash == "" {
t.Error("Hash should not be empty")
}

if hash == password {
t.Error("Hash should not equal plain password")
}
}

func TestCheckPassword(t *testing.T) {
password := "mySecretPassword123"
hash, _ := HashPassword(password)

// Test correct password
if !CheckPassword(password, hash) {
t.Error("CheckPassword should return true for correct password")
}

// Test wrong password
if CheckPassword("wrongPassword", hash) {
t.Error("CheckPassword should return false for wrong password")
}
}

func TestHashPasswordTooShort(t *testing.T) {
password := "short"

_, err := HashPassword(password)
if err == nil {
t.Error("Expected error for short password")
}
}









Running Tests






CODE
# Run all tests
go test ./...

# Run tests in specific package
go test ./utils

# Verbose output
go test -v ./utils

# Run specific test
go test -v -run TestHashPassword ./utils

# With coverage
go test -cover ./...

# Coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out









Table-Driven Tests (Best Practice)






CODE
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
want bool
}{
{"valid email", "[email protected]", true},
{"valid with subdomain", "[email protected]", true},
{"missing @", "johnexample.com", false},
{"missing domain", "john@", false},
{"empty string", "", false},
{"spaces", "john @example.com", false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ValidateEmail(tt.email)
if got != tt.want {
t.Errorf("ValidateEmail(%q) = %v, want %v",
tt.email, got, tt.want)
}
})
}
}









Test Assertions Helper



Create testutils/assertions.go:




CODE
package testutils

import "testing"

func AssertEqual(t *testing.T, got, want interface{}) {
t.Helper()
if got != want {
t.Errorf("got %v, want %v", got, want)
}
}

func AssertNotEqual(t *testing.T, got, want interface{}) {
t.Helper()
if got == want {
t.Errorf("got %v, should not equal %v", got, want)
}
}

func AssertNil(t *testing.T, got interface{}) {
t.Helper()
if got != nil {
t.Errorf("expected nil, got %v", got)
}
}

func AssertNotNil(t *testing.T, got interface{}) {
t.Helper()
if got == nil {
t.Error("expected not nil, got nil")
}
}












2. Testing HTTP Handlers (1 hour)






HTTP Test Basics






CODE
package handlers

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)

func TestHealthCheck(t *testing.T) {
// Create request
req := httptest.NewRequest(http.MethodGet, "/health", nil)

// Create response recorder
w := httptest.NewRecorder()

// Call handler
healthCheckHandler(w, req)

// Check status code
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}

// Check response body
var response map[string]string
json.NewDecoder(w.Body).Decode(&response)

if response["status"] != "healthy" {
t.Errorf("Expected healthy status, got %s", response["status"])
}
}









Testing JSON APIs






CODE
func TestRegisterUser(t *testing.T) {
// Setup test database
db := setupTestDB(t)
defer teardownTestDB(db)

handler := NewAuthHandler(repository.NewUserRepository(db))

// Prepare request body
reqBody := map[string]string{
"name": "John Doe",
"email": "[email protected]",
"password": "password123",
}
bodyBytes, _ := json.Marshal(reqBody)

// Create request
req := httptest.NewRequest(
http.MethodPost,
"/api/v1/register",
bytes.NewBuffer(bodyBytes),
)
req.Header.Set("Content-Type", "application/json")

// Create response recorder
w := httptest.NewRecorder()

// Call handler
handler.Register(w, req)

// Check status
if w.Code != http.StatusCreated {
t.Fatalf("Expected status 201, got %d", w.Code)
}

// Parse response
var response struct {
Token string `json:"token"`
User models.User `json:"user"`
}
json.NewDecoder(w.Body).Decode(&response)

// Assertions
if response.Token == "" {
t.Error("Expected token in response")
}
if response.User.Email != "[email protected]" {
t.Errorf("Expected email [email protected], got %s", response.User.Email)
}
}









Test Database Setup






CODE
package handlers

import (
"testing"

"gorm.io/driver/sqlite"
"gorm.io/gorm"
"your-project/models"
)

func setupTestDB(t *testing.T) *gorm.DB {
// Use in-memory SQLite for tests
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
t.Fatal("Failed to connect to test database:", err)
}

// Auto migrate
err = db.AutoMigrate(&models.User{}, &models.Post{})
if err != nil {
t.Fatal("Failed to migrate test database:", err)
}

return db
}

func teardownTestDB(db *gorm.DB) {
sqlDB, _ := db.DB()
sqlDB.Close()
}

// Helper to create test user
func createTestUser(t *testing.T, db *gorm.DB) *models.User {
user := &models.User{
Name: "Test User",
Email: "[email protected]",
Password: "hashed_password",
}

if err := db.Create(user).Error; err != nil {
t.Fatal("Failed to create test user:", err)
}

return user
}









Testing Protected Endpoints






CODE
func TestGetProfile_Authenticated(t *testing.T) {
db := setupTestDB(t)
defer teardownTestDB(db)

// Create test user
user := createTestUser(t, db)

// Generate token
token, _ := utils.GenerateToken(user.ID, user.Email)

handler := NewAuthHandler(repository.NewUserRepository(db))

// Create request with auth header
req := httptest.NewRequest(http.MethodGet, "/api/v1/profile", nil)
req.Header.Set("Authorization", "Bearer "+token)

// Add user to context (normally done by middleware)
claims := &utils.Claims{UserID: user.ID, Email: user.Email}
ctx := context.WithValue(req.Context(), middleware.UserContextKey, claims)
req = req.WithContext(ctx)

w := httptest.NewRecorder()
handler.GetProfile(w, req)

if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
}

func TestGetProfile_Unauthenticated(t *testing.T) {
db := setupTestDB(t)
defer teardownTestDB(db)

handler := NewAuthHandler(repository.NewUserRepository(db))

// Request without auth
req := httptest.NewRequest(http.MethodGet, "/api/v1/profile", nil)
w := httptest.NewRecorder()

handler.GetProfile(w, req)

if w.Code != http.StatusUnauthorized {
t.Errorf("Expected status 401, got %d", w.Code)
}
}









Testing Middleware






CODE
func TestAuthMiddleware(t *testing.T) {
// Create a test handler
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})

// Wrap with auth middleware
handler := middleware.AuthMiddleware(nextHandler)

t.Run("Valid Token", func(t *testing.T) {
token, _ := utils.GenerateToken(1, "[email protected]")

req := httptest.NewRequest(http.MethodGet, "/test", nil)
req.Header.Set("Authorization", "Bearer "+token)

w := httptest.NewRecorder()
handler.ServeHTTP(w, req)

if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
})

t.Run("Missing Token", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/test", nil)
w := httptest.NewRecorder()

handler.ServeHTTP(w, req)

if w.Code != http.StatusUnauthorized {
t.Errorf("Expected status 401, got %d", w.Code)
}
})

t.Run("Invalid Token", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/test", nil)
req.Header.Set("Authorization", "Bearer invalid-token")

w := httptest.NewRecorder()
handler.ServeHTTP(w, req)

if w.Code != http.StatusUnauthorized {
t.Errorf("Expected status 401, got %d", w.Code)
}
})
}









Benchmark Tests






CODE
func BenchmarkHashPassword(b *testing.B) {
password := "mySecretPassword123"

for i := 0; i < b.N; i++ {
HashPassword(password)
}
}

func BenchmarkCheckPassword(b *testing.B) {
password := "mySecretPassword123"
hash, _ := HashPassword(password)

for i := 0; i < b.N; i++ {
CheckPassword(password, hash)
}
}

// Run benchmarks
// go test -bench=. -benchmem ./utils












3. Building for Production (1 hour)






Building Your Application






CODE
# Build for current platform
go build -o blog-api

# Build for Linux
GOOS=linux GOARCH=amd64 go build -o blog-api-linux

# Build for Windows
GOOS=windows GOARCH=amd64 go build -o blog-api.exe

# Build with optimization (smaller binary)
go build -ldflags="-s -w" -o blog-api

# Run the binary
./blog-api









Dockerfile



Create Dockerfile:




CODE
# Build stage
FROM golang:1.21-alpine AS builder

WORKDIR /app

# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY . .

# Build application
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o blog-api .

# Run stage
FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /root/

# Copy binary from builder
COPY --from=builder /app/blog-api .

# Expose port
EXPOSE 8080

# Run
CMD ["./blog-api"]









Docker Compose



Create docker-compose.yml:




CODE
version: '3.8'

services:
api:
build: .
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://postgres:postgres@db:5432/blog_db?sslmode=disable
- JWT_SECRET=your-super-secret-key
- PORT=8080
depends_on:
- db
restart: unless-stopped

db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=blog_db
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped

volumes:
postgres_data:









Run with Docker






CODE
# Build and run
docker-compose up -d

# View logs
docker-compose logs -f api

# Stop
docker-compose down

# Rebuild
docker-compose up -d --build









.dockerignore



Create .dockerignore:




CODE
.env
.git
.gitignore
*.md
*.test
coverage.out
blog-api
blog-api-linux
blog-api.exe












4. Environment Configuration (30 min)






Production-Ready Config



Update config/config.go:




CODE
package config

import (
"fmt"
"os"
"strconv"
)

type Config struct {
// Server
Port string
Environment string // development, staging, production

// Database
DatabaseURL string

// JWT
JWTSecret string
JWTExpiration int // hours

// CORS
AllowedOrigins []string

// Rate Limiting
RateLimitEnabled bool
RateLimit int
}

func Load() *Config {
return &Config{
Port: getEnv("PORT", "8080"),
Environment: getEnv("ENVIRONMENT", "development"),
DatabaseURL: getEnv("DATABASE_URL", ""),
JWTSecret: getEnv("JWT_SECRET", ""),
JWTExpiration: getEnvAsInt("JWT_EXPIRATION", 24),
AllowedOrigins: getEnvAsSlice("ALLOWED_ORIGINS", []string{"*"}),
RateLimitEnabled: getEnvAsBool("RATE_LIMIT_ENABLED", true),
RateLimit: getEnvAsInt("RATE_LIMIT", 100),
}
}

func (c *Config) Validate() error {
if c.DatabaseURL == "" {
return fmt.Errorf("DATABASE_URL is required")
}
if c.JWTSecret == "" {
return fmt.Errorf("JWT_SECRET is required")
}
if c.Environment == "production" && c.JWTSecret == "default-secret" {
return fmt.Errorf("JWT_SECRET must be changed in production")
}
return nil
}

func (c *Config) IsProduction() bool {
return c.Environment == "production"
}

func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}

func getEnvAsInt(key string, defaultValue int) int {
if value := os.Getenv(key); value != "" {
if intVal, err := strconv.Atoi(value); err == nil {
return intVal
}
}
return defaultValue
}

func getEnvAsBool(key string, defaultValue bool) bool {
if value := os.Getenv(key); value != "" {
if boolVal, err := strconv.ParseBool(value); err == nil {
return boolVal
}
}
return defaultValue
}

func getEnvAsSlice(key string, defaultValue []string) []string {
if value := os.Getenv(key); value != "" {
return strings.Split(value, ",")
}
return defaultValue
}









Multiple Environment Files



Create .env.development:




CODE
ENVIRONMENT=development
PORT=8080
DATABASE_URL=postgres://postgres:postgres@localhost:5432/blog_db_dev?sslmode=disable
JWT_SECRET=dev-secret-key
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173






Create .env.production:




CODE
ENVIRONMENT=production
PORT=8080
DATABASE_URL=postgres://user:password@prod-db:5432/blog_db?sslmode=require
JWT_SECRET=super-secret-production-key-change-this
ALLOWED_ORIGINS=https://yourapp.com,https://www.yourapp.com
RATE_LIMIT_ENABLED=true
RATE_LIMIT=100












5. Deployment Options (1 hour)






Option 1: Deploy to Railway (Easiest)





  1. Create account at


  2. https://awesome-go.com





You're now a Go developer! 🚀

Vollständiger Original-Artikel
Den kompletten Beitrag mit allen Details direkt auf dev.to lesen.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
2 Quellen
Data regions support for Google Apps Script now generally available
1 Quelle
Adobe Announces Photoshop and Premiere Elements 2027
1 Quelle
Microsoft built a local PC-to-PC transfer tool for Windows 11, now it’s killing it to push OneDrive-based solution
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Golang For PHP Developer : Testing & Deployment

Thematisch verwandte Begriffe: Golang, Developer, Testing, Deployment · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...