GitLab CI/CD集成Salesforce Apex代码覆盖率校验问题求助
GitLab Windows Runner下Apex代码覆盖率质量门禁实现问题
已完成工作
- 部署了可用的Windows环境GitLab Runner
- 本地Apex类及对应测试类代码覆盖率可达100%
- CI/CD流水线已能成功运行Apex测试
需求
实现代码覆盖率质量门禁:当Apex代码覆盖率低于80%时,流水线自动失败。
遇到的困境
- 无法在流水线中正确获取并评估Apex代码覆盖率数据
- 不清楚如何将覆盖率校验逻辑正确集成到
.gitlab-ci.yml配置中 - 不确定是否需要使用特殊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
相关产品推荐
相关产品推荐

