Section 16/171 menit

16. Real Use Cases

16. Real Use Cases

Use Case 1: Setup CI/CD Lengkap untuk Startup iOS

Sebuah startup dengan tim 4 iOS developer ingin menyiapkan CI/CD yang zero maintenance. Mereka menggunakan GitHub, TestFlight untuk beta testing, dan langsung release ke App Store.

Workflow Setup:

swift
Workflow 1: "Feature PR"
├── Start: PR ke develop (file restriction: Sources/, Tests/)
├── Actions: Build (Debug) + Test (Unit Tests only)
├── Post: Notify Slack (on failure)
└── Target: cepat feedback ke developer, target <10 menit

Workflow 2: "Develop Integration"
├── Start: Push ke develop
├── Actions: Build (Debug) + Test (All Tests) + Archive (Debug)
├── Post: TestFlight Internal Testing + Notify Slack (always)
└── Target: QA bisa testing build terbaru setiap hari

Workflow 3: "Release"
├── Start: Tag yang match "v*.*.*"
├── Actions: Build (Release) + Test (All Tests) + Archive (Release)
├── Post: TestFlight External Testing + Notify Slack
└── Target: kirim ke beta external tester sebelum App Store release

ci_post_clone.sh untuk setup:

swift
#!/bin/zsh
# ci_scripts/ci_post_clone.sh

set -e

echo "Setting up build environment..."
echo "Branch: $CI_BRANCH | Build: $CI_BUILD_NUMBER"

# Inject environment-specific configuration
case "$CI_BRANCH" in
    main|v*.*.*)
        ENV_NAME="production"
        echo "$PROD_CONFIG_BASE64" | base64 --decode \
            > "$CI_PRIMARY_REPOSITORY_PATH/MyApp/Config.plist"
        echo "$PROD_GOOGLE_SERVICES_BASE64" | base64 --decode \
            > "$CI_PRIMARY_REPOSITORY_PATH/MyApp/GoogleService-Info.plist"
        ;;
    develop)
        ENV_NAME="staging"
        echo "$STAGING_CONFIG_BASE64" | base64 --decode \
            > "$CI_PRIMARY_REPOSITORY_PATH/MyApp/Config.plist"
        echo "$STAGING_GOOGLE_SERVICES_BASE64" | base64 --decode \
            > "$CI_PRIMARY_REPOSITORY_PATH/MyApp/GoogleService-Info.plist"
        ;;
    *)
        ENV_NAME="development"
        echo "$DEV_CONFIG_BASE64" | base64 --decode \
            > "$CI_PRIMARY_REPOSITORY_PATH/MyApp/Config.plist"
        echo "$DEV_GOOGLE_SERVICES_BASE64" | base64 --decode \
            > "$CI_PRIMARY_REPOSITORY_PATH/MyApp/GoogleService-Info.plist"
        ;;
esac

echo "Environment configured: $ENV_NAME"

ci_pre_xcodebuild.sh untuk build number:

swift
#!/bin/zsh
# ci_scripts/ci_pre_xcodebuild.sh

set -e

if [[ "$CI_XCODEBUILD_ACTION" == "archive" ]]; then
    echo "Setting build number to $CI_BUILD_NUMBER..."
    
    # Update xcconfig
    XCCONFIG="$CI_PRIMARY_REPOSITORY_PATH/Configuration/App.xcconfig"
    if [ -f "$XCCONFIG" ]; then
        sed -i '' "s/^CURRENT_PROJECT_VERSION = .*/CURRENT_PROJECT_VERSION = $CI_BUILD_NUMBER/" "$XCCONFIG"
    fi
    
    # Generate BuildInfo.swift
    cat > "$CI_PRIMARY_REPOSITORY_PATH/MyApp/Generated/BuildInfo.swift" << EOF
// Auto-generated — do not edit
enum BuildInfo {
    static let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
    static let build = "$CI_BUILD_NUMBER"
    static let commit = "${CI_COMMIT:0:8}"
    static let environment = "${ENV_NAME:-unknown}"
}
EOF
fi

ci_post_xcodebuild.sh untuk Sentry dSYM upload:

swift
#!/bin/zsh
# ci_scripts/ci_post_xcodebuild.sh

set -e

if [[ "$CI_XCODEBUILD_ACTION" == "archive" ]]; then
    echo "Uploading dSYMs to Sentry..."
    
    # Install Sentry CLI
    curl -sL https://sentry.io/get-cli/ | bash
    
    DSYM_PATH="${CI_ARCHIVE_PATH}/dSYMs"
    
    if [[ -d "$DSYM_PATH" && -n "$SENTRY_AUTH_TOKEN" ]]; then
        sentry-cli \
            --auth-token "$SENTRY_AUTH_TOKEN" \
            upload-dif \
            --org "${SENTRY_ORG}" \
            --project "${SENTRY_PROJECT}" \
            --include-sources \
            "$DSYM_PATH"
        echo "dSYMs uploaded successfully"
    else
        echo "Skipping Sentry upload (no dSYMs or auth token)"
    fi
fi

Use Case 2: Enterprise App dengan Multi-Target dan Testing Ketat

Sebuah perusahaan fintech memiliki app iOS dengan beberapa target (main app, Share Extension, Widget), ribuan unit test, dan regulasi yang mengharuskan code coverage minimum 80%.

Struktur project:

swift
FinanceApp/
├── FinanceApp/          ← main target
├── ShareExtension/      ← share extension target
├── WidgetExtension/     ← widget target
├── FinanceAppTests/     ← unit tests
├── FinanceAppUITests/   ← UI tests (slow)
└── ci_scripts/

Test Plans:

swift
// UnitTests.xctestplan — digunakan di PR workflow
{
  "configurations": [
    {
      "id": "unit",
      "name": "Unit Tests Only",
      "options": {
        "codeCoverageEnabled": true,
        "codeCoverageTargets": [
          {"name": "FinanceApp"},
          {"name": "ShareExtension"},
          {"name": "WidgetExtension"}
        ]
      }
    }
  ],
  "testTargets": [
    {
      "target": {"name": "FinanceAppTests"},
      "enabled": true
    }
  ],
  "version": 1
}
swift
// AllTests.xctestplan — digunakan di nightly workflow
{
  "configurations": [
    {
      "id": "all",
      "name": "All Tests",
      "options": {
        "codeCoverageEnabled": true,
        "maximumTestExecutionTimeAllowance": 120,
        "testTimeoutsEnabled": true
      }
    }
  ],
  "testTargets": [
    {"target": {"name": "FinanceAppTests"}, "enabled": true},
    {"target": {"name": "FinanceAppUITests"}, "enabled": true}
  ],
  "version": 1
}

Coverage enforcement:

swift
#!/bin/zsh
# ci_scripts/ci_post_xcodebuild.sh

set -e

MINIMUM_COVERAGE_PERCENT=80

if [[ "$CI_XCODEBUILD_ACTION" == "test" && -n "$CI_RESULT_BUNDLE_PATH" ]]; then
    echo "Checking code coverage..."
    
    # Export coverage menggunakan xccov
    xcrun xccov view \
        --report \
        --json \
        "$CI_RESULT_BUNDLE_PATH" > /tmp/coverage.json 2>/dev/null || {
        echo "Warning: Could not generate coverage report"
        exit 0
    }
    
    # Parse overall coverage
    COVERAGE=$(python3 -c "
import json, sys
with open('/tmp/coverage.json') as f:
    data = json.load(f)
targets = [t for t in data.get('targets', []) 
           if t['name'] in ['FinanceApp.app', 'ShareExtension.appex', 'WidgetExtension.appex']]
if targets:
    total_lines = sum(t.get('executableLines', 0) for t in targets)
    covered_lines = sum(t.get('coveredLines', 0) for t in targets)
    pct = (covered_lines / total_lines * 100) if total_lines > 0 else 0
    print(f'{pct:.1f}')
else:
    print('0')
" 2>/dev/null || echo "0")
    
    echo "Code coverage: ${COVERAGE}%"
    
    COVERAGE_INT=$(echo "$COVERAGE" | cut -d. -f1)
    
    if (( COVERAGE_INT < MINIMUM_COVERAGE_PERCENT )); then
        echo "❌ Coverage ${COVERAGE}% di bawah minimum ${MINIMUM_COVERAGE_PERCENT}%"
        
        # Kirim alert ke Slack
        if [[ -n "$SLACK_WEBHOOK_URL" ]]; then
            curl -s -X POST -H 'Content-type: application/json' \
                --data "{\"text\":\"⚠️ Code coverage ${COVERAGE}% di bawah minimum ${MINIMUM_COVERAGE_PERCENT}% — Build #${CI_BUILD_NUMBER} — Branch: ${CI_BRANCH}\"}" \
                "$SLACK_WEBHOOK_URL"
        fi
        
        exit 1
    fi
    
    echo "✅ Coverage ${COVERAGE}% memenuhi minimum ${MINIMUM_COVERAGE_PERCENT}%"
fi

Workflow fintech:

swift
Workflow 1: "PR Security Check"
├── Start: PR ke develop/main
├── Actions: Build (Debug) + Analyze + Test (UnitTests plan)
├── Post: Notify Slack (on failure, mandatory)
└── Note: Analyze mendeteksi potential security issues via static analysis

Workflow 2: "Integration Build"
├── Start: Push ke develop
├── Actions: Build + Test (AllTests plan) + Archive
├── Post: TestFlight Internal (QA team) + Notify Slack
└── Note: Wajib coverage ≥80%

Workflow 3: "Release Candidate"
├── Start: Tag "rc-v*.*.*"
├── Actions: Build (Release) + Test (AllTests) + Archive
├── Post: TestFlight External + Notify email ke compliance team
└── Note: Butuh approval manual sebelum App Store submit

Workflow 4: "Nightly Regression"
├── Start: Schedule  01:00 UTC setiap hari
├── Actions: Build + Analyze + Test (AllTests plan, multiple devices)
├── Post: Notify Slack hanya jika gagal
└── Note: Jalankan di iPhone SE (kecil) + iPad Pro + iPhone 15 Pro