테스트 관리 도구의 비교 분석: CI/CD 파이프라인과의 실제 통합

발행: (2025년 12월 5일 오후 12:48 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

Introduction

테스트가 별도의 단계였던 시절은 지났습니다. 오늘날 DevOps 환경에서는 테스트가 지속적으로 이루어지며, 테스트 관리 도구도 이에 맞춰야 합니다. 올바른 도구는 단순히 테스트 케이스를 정리하는 것을 넘어, 원활한 CI/CD 연동, 실시간 피드백, 실행 가능한 인사이트를 제공해야 합니다.

여러 조직에서 테스트 파이프라인을 구현해 본 경험을 바탕으로, 실제 운영 환경에서 효과적인 방법을 공유합니다.

1. TestRail – The Specialist

Best for: Dedicated QA teams needing detailed reporting

TestRail는 한 가지에 특화되어 있습니다: 테스트 관리. 이 도구는 이슈 트래커나 프로젝트 관리 도구가 되려는 것이 아니라, QA를 위해 설계되었습니다.

Real GitHub Actions Integration

# .github/workflows/testrail-ci.yml
name: CI with TestRail Integration

on:
  pull_request:
    branches: [main]
  push:
    branches:
      - 'releases/**'
      - 'hotfix/**'

jobs:
  test-and-report:
    runs-on: ubuntu-latest-8-cores
    timeout-minutes: 30

    services:
      postgres:
        image: postgres:14
        env:
          POSTGRES_PASSWORD: test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    strategy:
      matrix:
        test-type: [api, ui, security]

    steps:
    - name: Checkout code
      uses: actions/checkout@v3
      with:
        fetch-depth: 0

    - name: Setup Test Environment
      run: |
        # Database migrations
        npm run db:migrate
        # Seed test data
        npm run db:seed -- --environment test

    - name: Run ${{ matrix.test-type }} Tests
      id: run-tests
      env:
        TESTRAIL_ENABLED: true
        TESTRAIL_RUN_NAME: "${{ github.event_name }} - ${{ github.sha }}"
        NODE_ENV: test
      run: |
        case ${{ matrix.test-type }} in
          api)
            npm run test:api -- --reporter mocha-testrail-reporter
            ;;
          ui)
            npm run test:e2e -- --reporter cypress-testrail-reporter
            ;;
          security)
            npm run test:security -- --reporter testrail
            ;;
        esac

        # Capture exit code
        echo "exit_code=$?" >> $GITHUB_OUTPUT

    - name: Upload to TestRail
      if: always() && env.TESTRAIL_ENABLED == 'true'
      uses: testrail-community/upload-results-action@v1
      with:
        testrail-url: ${{ secrets.TESTRAIL_URL }}
        username: ${{ secrets.TESTRAIL_USER }}
        api-key: ${{ secrets.TESTRAIL_API_KEY }}
        project-id: ${{ secrets.TESTRAIL_PROJECT_ID }}
        suite-id: ${{ secrets.TESTRAIL_SUITE_ID }}
        run-name: ${{ env.TESTRAIL_RUN_NAME }}
        results-path: 'test-results/*.xml'

    - name: Quality Gate Check
      if: steps.run-tests.outputs.exit_code != 0
      run: |
        echo "❌ Tests failed - Blocking deployment"
        # Create TestRail defect automatically
        curl -X POST "${{ secrets.TESTRAIL_URL }}/index.php?/api/v2/add_result/$TEST_ID" \
          -H "Content-Type: application/json" \
          -u "${{ secrets.TESTRAIL_USER }}:${{ secrets.TESTRAIL_API_KEY }}" \
          -d '{
            "status_id": 5,
            "comment": "Build failed in CI: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
            "defects": "CI-${{ github.run_id }}"
          }'
        exit 1

Why this works

  • 유형별 병렬 테스트 실행
  • 통합 테스트를 위한 데이터베이스 서비스
  • 나쁜 빌드를 차단하는 품질 게이트
  • TestRail에서 자동으로 결함 생성

2. Zephyr Scale

Best for: Teams already living in Atlassian’s ecosystem

Jira가 두 번째 집처럼 느껴진다면, Zephyr Scale(구 Zephyr Squad)은 자연스러운 확장처럼 보입니다.

Jenkins Pipeline with Smart Test Selection

// Jenkinsfile - Smart Testing Pipeline
def testResults = []
def qualityMetrics = [:]

pipeline {
    agent {
        kubernetes {
            label 'test-agent'
            yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: test-runner
    image: node:18-alpine
    command: ['cat']
    tty: true
    resources:
      requests:
        memory: "2Gi"
        cpu: "1000m"
'''
        }
    }

    parameters {
        choice(name: 'TEST_SCOPE', 
               choices: ['SMOKE', 'REGRESSION', 'FULL'], 
               description: 'Test scope to execute')
        booleanParam(name: 'UPDATE_ZEPHYR', 
                    defaultValue: true, 
                    description: 'Update Zephyr with results')
    }

    environment {
        ZEPHYR_BASE_URL = 'https://api.zephyrscale.smartbear.com/v2'
        JIRA_PROJECT_KEY = 'QA'
        GIT_COMMIT = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
    }

    stages {
        stage('Test Analysis') {
            steps {
                script {
                    // Analyze code changes to determine affected tests
                    sh '''
                    git diff --name-only HEAD~1 HEAD | grep -E '\.(js|ts|java|py)$' > changed_files.txt
                    python scripts/test_impact_analyzer.py changed_files.txt
                    '''

                    // Read affected test cases
                    def impactedTests = readJSON file: 'impacted_tests.json'
                    qualityMetrics.impactedTestCount = impactedTests.size()

                    echo "📊 Running ${impactedTests.size()} impacted tests"
                }
            }
        }

        stage('Execute Tests') {
            parallel {
                stage('API Tests') {
                    steps {
                        script {
                            withCredentials([[
                                $class: 'StringBinding',
                                credentialsId: 'zephyr-access-token',
                                variable: 'ZEPHYR_TOKEN'
                            ]]) {
                                sh '''
                                # Run tests with Zephyr integration
                                npx newman run collections/api_suite.json \
                                  --reporters cli,zephyr \
                                  --reporter-zephyr-token $ZEPHYR_TOKEN \
                                  --reporter-zephyr-projectKey $JIRA_PROJECT_KEY \
                                  --reporter-zephyr-testCycle "API Cycle ${BUILD_NUMBER}"
                                '''
                            }
                        }
                    }
                }

                stage('UI Tests') {
                    steps {
                        script {
                            // Dynamic test allocation based on scope
                            def testFilter = params.TEST_SCOPE == 'SMOKE' ? 
                                '--grep @smoke' : 
                                params.TEST_SCOPE == 'REGRESSION' ? 
                                '--grep @regression' : ''

                            sh """
                            npx cypress run --headless \
                                --browser chrome \
                                ${testFilter} \
                                --env updateZephyr=${params.UPDATE_ZEPHYR}
                            """
                        }
                    }
                }
            }
        }

        stage('Zephyr Sync') {
            when {
                expression { params.UPDATE_ZEPHYR == true }
            }
            steps {
                script {
                    // Sync all test results to Zephyr
                    sh '''
                    python scripts/zephyr_sync.py \
                        --build-number ${BUILD_NUMBER} \
                        --commit ${GIT_COMMIT} \
                        --results-dir test-results
                    '''

                    // Update test execution status in Jira
                    jiraUpdateIssue idOrKey: 'QA-123', 
                        issue: [fields: [customfield_12345: 'E
                    '''
                }
            }
        }
    }
}
Back to Blog

관련 글

더 보기 »