You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GitLab CI/CD集成Salesforce Apex代码覆盖率校验问题求助

GitLab Windows Runner下Apex代码覆盖率质量门禁实现问题

已完成工作

  • 部署了可用的Windows环境GitLab Runner
  • 本地Apex类及对应测试类代码覆盖率可达100%
  • CI/CD流水线已能成功运行Apex测试

需求

实现代码覆盖率质量门禁:当Apex代码覆盖率低于80%时,流水线自动失败。

遇到的困境

  1. 无法在流水线中正确获取并评估Apex代码覆盖率数据
  2. 不清楚如何将覆盖率校验逻辑正确集成到.gitlab-ci.yml配置中
  3. 不确定是否需要使用特殊Salesforce CLI命令、脚本或第三方工具

尝试的配置

我尝试了两种.gitlab-ci.yml配置,但均未成功实现预期功能:

配置1:两次执行Apex测试(分别输出JUnit和JSON格式)

stages:
  - build-testing
  - prod-deploy

variables:
  SF_INSTANCE_URL: "https://test.salesforce.com"
  TEST_RESULTS_DIR: "artifacts/test-results"
  LOG_DIR: "artifacts/logs"

default:
  tags:
    - meh-playground
  before_script:
    - '$ErrorActionPreference = "Stop"'
    - 'Write-Host "Using PowerShell on Windows Runner"'
    - 'New-Item -ItemType Directory -Force -Path "assets","$Env:TEST_RESULTS_DIR","$Env:LOG_DIR" | Out-Null'
    # SERVER_KEY ist eine GitLab File-Variable → Pfad in $Env:SERVER_KEY
    - 'if (!(Test-Path $Env:SERVER_KEY)) { throw "SERVER_KEY File Variable not found!" }'
    - 'Copy-Item -Path $Env:SERVER_KEY -Destination "server.key"'
    # CLI bereitstellen + Übersicht
    - 'npx -y @salesforce/cli --version'
    - 'npx -y @salesforce/cli plugins --core'
  after_script:
    # Clean-up immer versuchen
    - 'if (Test-Path "server.key") { Remove-Item "server.key" -Force -ErrorAction SilentlyContinue }'

# --- Build & Test auf Scratch-Org ---
build-testing:
  stage: build-testing
  script:
    - |
      try {
        Write-Host "JWT login to $Env:SF_USERNAME_SANDBOX via $Env:SF_INSTANCE_URL"
        npx -y @salesforce/cli org login jwt `
          --instance-url "$Env:SF_INSTANCE_URL" `
          --client-id "$Env:SF_CONSUMER_KEY_SANDBOX" `
          --jwt-key-file "server.key" `
          --username "$Env:SF_USERNAME_SANDBOX" `
          --alias HubOrg `
          --set-default-dev-hub

        npx -y @salesforce/cli org whoami --target-org HubOrg

 
        npx -y @salesforce/cli org create scratch `
          --target-dev-hub HubOrg `
          --definition-file "config/project-scratch-def.json" `
          --alias ApexHours `
          --set-default `
          --wait 20 `
          --duration-days 7

        npx -y @salesforce/cli org display --target-org ApexHours

        # Metadaten deployen
        npx -y @salesforce/cli project deploy start `
          --target-org ApexHours `
          --source-dir "force-app" `
          --ignore-conflicts

        # Apex-Tests ausführen mit Coverage-Validierung
        Write-Host "Running Apex tests with code coverage validation (minimum 80%)"
        
        # JUnit-Format für GitLab Test Reports
        npx -y @salesforce/cli apex test run `
          --target-org ApexHours `
          --wait 30 `
          --result-format junit `
          --output-dir "$Env:TEST_RESULTS_DIR" `
          --loglevel warn

        # JSON-Format für Coverage-Analyse
        $testOutput = npx -y @salesforce/cli apex test run `
          --target-org ApexHours `
          --wait 30 `
          --result-format json `
          --loglevel warn

        # Test-Output in Datei speichern
        $testOutput | Out-File -FilePath "$Env:TEST_RESULTS_DIR\apex-tests.json" -Encoding UTF8

        # Coverage-Validierung
        try {
          $testJson = $testOutput | ConvertFrom-Json
          $orgWideCoverageStr = $testJson.result.summary.orgWideCoverage
          
          # Entferne das %-Zeichen und konvertiere zu Double
          if ($orgWideCoverageStr -match '(\d+(?:\.\d+)?)%?') {
            $coverage = [double]$matches[1]
            Write-Host "Org-wide coverage: $coverage%"
            
            if ($coverage -lt 80) {
              throw "Code coverage ($coverage%) is below the required minimum of 80%"
            } else {
              Write-Host "✓ Code coverage requirement met: $coverage% >= 80%"
            }
          } else {
            Write-Warning "Could not parse coverage value: $orgWideCoverageStr"
          }
        } catch {
          Write-Warning "Coverage validation failed: $($_.Exception.Message)"
          Write-Host "Test output for debugging:"
          Write-Host $testOutput
        }

        # (Optional) Org-Logs
        npx -y @salesforce/cli org display --target-org ApexHours | Out-File -FilePath "$Env:LOG_DIR\org-display.txt" -Encoding UTF8
      }
      finally {
        # Scratch-Org aufräumen, auch bei Fehlern
        try {
          npx -y @salesforce/cli org delete scratch --target-org ApexHours --no-prompt
        } catch {
          Write-Host "Scratch org cleanup skipped/failed."
        }
      }
  artifacts:
    when: always
    expire_in: 2 weeks
    reports:
      junit:
        - artifacts/test-results/**/*.xml
    paths:
      - artifacts/

# --- Manueller Deploy-Job ---
prod-deploy:
  stage: prod-deploy
  when: manual
  needs: ["build-testing"]
  resource_group: prod-deploy  
  script:
    - |
      Write-Host "JWT login to $Env:SF_USERNAME_SANDBOX via $Env:SF_INSTANCE_URL"
      npx -y @salesforce/cli org login jwt `
        --instance-url "$Env:SF_INSTANCE_URL" `
        --client-id "$Env:SF_CONSUMER_KEY_SANDBOX" `
        --jwt-key-file "server.key" `
        --username "$Env:SF_USERNAME_SANDBOX" `
        --alias HubOrg `
        --set-default-dev-hub

      # Hinweis: hier wird direkt in die angegebene Org deployed (aktuell: Sandbox/HubOrg)
      npx -y @salesforce/cli project deploy start `
        --target-org HubOrg `
        --source-dir "force-app/main/default" `
        --ignore-conflicts

配置2:单次执行带--code-coverage参数的测试,但JSON解析失败

stages:
  - build-testing
  - prod-deploy

variables:
  SF_INSTANCE_URL: "https://test.salesforce.com"

default:
  tags: [meh-playground]
  before_script:
    - '$ErrorActionPreference = "Stop"'
    - 'Write-Host "Using PowerShell on Windows Runner"'
    - 'if (!(Test-Path "assets")) { New-Item -ItemType Directory -Path "assets" | Out-Null }'
    - 'if (!(Test-Path $Env:SERVER_KEY)) { throw "SERVER_KEY File Variable not found!" }'
    - 'Copy-Item -Path $Env:SERVER_KEY -Destination "server.key"'
    - 'npx -y @salesforce/cli --version'
    - 'npx -y @salesforce/cli plugins --core'

build-testing:
  stage: build-testing
  script:
    - |
      # region PowerShell
      $ScratchAlias = if ($Env:CI_PIPELINE_ID) { "ApexHours_{0}" -f $Env:CI_PIPELINE_ID } elseif ($Env:CI_JOB_ID) { "ApexHours_{0}" -f $Env:CI_JOB_ID } else { "ApexHours_ci" }
      $ScratchCreated = $false

      try {
        Write-Host "JWT login to $Env:SF_USERNAME_SANDBOX via $Env:SF_INSTANCE_URL"
        & npx -y @salesforce/cli org login jwt `
          --instance-url "$Env:SF_INSTANCE_URL" `
          --client-id "$Env:SF_CONSUMER_KEY_SANDBOX" `
          --jwt-key-file "server.key" `
          --username "$Env:SF_USERNAME_SANDBOX" `
          --alias HubOrg `
          --set-default-dev-hub
        if ($LASTEXITCODE -ne 0) { throw "JWT login failed (exit $LASTEXITCODE)" }

        # --- Scratch-Org erstellen ---
        $createOut = (& npx -y @salesforce/cli org create scratch `
          --target-dev-hub HubOrg `
          --definition-file "config/project-scratch-def.json" `
          --alias $ScratchAlias `
          --set-default `
          --wait 10 `
          --duration-days 7 `
          --json) 2>&1
        $createCode = $LASTEXITCODE

        if ($createCode -ne 0) {
          $createTxt = $createOut | Out-String
          if ($createTxt -match 'LIMIT_EXCEEDED') {
            Write-Warning "Daily scratch org signup limit reached. Skipping deploy & tests for this run."
            # Wenn du hart fehlschlagen willst, ersetze die nächste Zeile durch: throw "Scratch org daily limit reached"
            exit 0
          } else {
            Write-Error $createTxt
            throw "Scratch org creation failed (exit $createCode)"
          }
        } else {
          $ScratchCreated = $true
        }

        if ($ScratchCreated) {
          & npx -y @salesforce/cli org display `
            --target-org $ScratchAlias
          if ($LASTEXITCODE -ne 0) { throw "org display failed (exit $LASTEXITCODE)" }

          & npx -y @salesforce/cli project deploy start `
            --target-org $ScratchAlias `
            --source-dir "force-app"
          if ($LASTEXITCODE -ne 0) { throw "project deploy failed (exit $LASTEXITCODE)" }

          $testResult = (& npx -y @salesforce/cli apex test run `
            --target-org $ScratchAlias `
            --wait 10 `
            --code-coverage `
            --json) 2>&1
          if ($LASTEXITCODE -ne 0) {
            Write-Error ($testResult | Out-String)
            throw "Apex test run failed (exit $LASTEXITCODE)"
          }

          if (-not $testResult) { throw "Apex test run produced no output." }
          $json = $testResult | ConvertFrom-Json
          if (-not $json -or -not $json.result) { throw "Failed to parse Apex test JSON." }

          $coverageItems = $json.result.codeCoverage
          if ($coverageItems) {
            $coverage = ($coverageItems | Measure-Object -Property coveragePercent -Average).Average
            Write-Host ("Average Code Coverage: {0}%" -f [math]::Round($coverage,2))
            if ($coverage -lt 80) { throw ("Code coverage is below 80%. Actual: {0}%" -f $coverage) }
          } else {
            Write-Warning "No code coverage details returned."
          }
        }
      }
      finally {
        try {
          if ($ScratchCreated) {
            & npx -y @salesforce/cli org delete scratch `
              --target-org $ScratchAlias `
              --no-prompt
          } else {
            Write-Host "No scratch org was created; skipping deletion."
          }
        } catch {
          Write-Host "Scratch org cleanup skipped/failed: $($_.Exception.Message)"
        }

        if (Test-Path "server.key") {
          Remove-Item "server.key" -Force -ErrorAction SilentlyContinue
        }
      }
      # endregion

prod-deploy:
  stage: prod-deploy
  when: manual
  needs: ["build-testing"]
  script:
    - 'Write-Host "JWT login to $Env:SF_USERNAME_SANDBOX via $Env:SF_INSTANCE_URL"'
    - |
      & npx -y @salesforce/cli org login jwt `
        --instance-url "$Env:SF_INSTANCE_URL" `
        --client-id "$Env:SF_CONSUMER_KEY_SANDBOX" `
        --jwt-key-file "server.key" `
        --username "$Env:SF_USERNAME_SANDBOX" `
        --alias HubOrg `
        --set-default-dev-hub
      if ($LASTEXITCODE -ne 0) { throw "JWT login failed (exit $LASTEXITCODE)" }
    - |
      & npx -y @salesforce/cli project deploy start `
        --target-org HubOrg `
        --source-dir "force-app/main/default"
      if ($LASTEXITCODE -ne 0) { throw "project deploy failed (exit $LASTEXITCODE)" }
  after_script:
    - 'if (Test-Path "server.key") { Remove-Item "server.key" -Force -ErrorAction SilentlyContinue }'

求助

寻求针对GitLab+Windows Runner+Apex场景的解决方案,希望能正确实现代码覆盖率低于80%时流水线自动失败的质量门禁。


内容的提问来源于stack exchange,提问作者Moritz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 06:45:55