Las verificaciones ATC automatizadas son indispensables para la calidad continua del codigo en SAP BTP ABAP Environment. Mientras en el dia a dia de desarrollo ejecutas ATC manualmente en ADT, la API ATC permite la integracion en pipelines CI/CD para Quality Gates automatizados.
Por que automatizar ATC?
La ejecucion manual de verificaciones ATC tiene limites:
| Aspecto | Manual | Automatizado |
|---|---|---|
| Timing | Antes de transporte | En cada commit |
| Consistencia | Depende del desarrollador | Siempre los mismos checks |
| Olvido | Posible | Imposible |
| Reporting | Puntual | Continuo |
| Analisis de tendencias | No posible | Datos historicos |
| Estandar de equipo | Dificil de imponer | Impuesto automaticamente |
+----------------------------------------------------------------+| Pipeline CI/CD con ATC Quality Gate |+----------------------------------------------------------------+| || Git Push --> Build --> ABAP Unit --> ATC API --> Deploy || Tests Checks || | || v || +--------------+ || | Quality | || | Gate | || | ------------ | || | Errors: 0 | || | Warnings: <5 | || +--------------+ || |+----------------------------------------------------------------+Requisitos previos
Antes de poder usar la API ATC necesitas:
- SAP BTP ABAP Environment (instancia Steampunk)
- Communication Arrangement para servicios ADT (SAP_COM_0763)
- Communication User con permisos correspondientes
- Check Variant para la ejecucion automatizada
Configurar Communication Arrangement
El Communication Arrangement del ABAP Unit Runner aplica tambien para ATC:
+----------------------------------------------------------------+| Communication Arrangement: SAP_COM_0763 |+----------------------------------------------------------------+| || Inbound Services (activados): || [x] ADT Core Services || [x] ADT ABAP Unit || [x] ADT ATC (ABAP Test Cockpit) || || Permisos requeridos: || * S_DEVELOP (Leer objetos de desarrollo) || * S_ATC_ADM (Administracion ATC) || |+----------------------------------------------------------------+API ATC en BTP
La API ATC se basa en el framework ADT REST y permite la ejecucion programatica de verificaciones ATC.
Endpoint API
POST https://<system-url>/sap/bc/adt/atc/runsFormato del Request
<?xml version="1.0" encoding="UTF-8"?><atc:run xmlns:atc="http://www.sap.com/adt/atc" xmlns:adtcore="http://www.sap.com/adt/core" maximumVerdicts="100"> <objectSets> <objectSet kind="inclusive"> <adtcore:objectReferences> <adtcore:objectReference adtcore:uri="/sap/bc/adt/vit/wb/object_type/devck/object_name/Z_FLIGHT_BOOKING"/> </adtcore:objectReferences> </objectSet> </objectSets> <checkVariant> <atc:name>ABAP_CLOUD_READINESS</atc:name> </checkVariant></atc:run>Parametros importantes del Request
| Parametro | Descripcion | Ejemplo |
|---|---|---|
| maximumVerdicts | Max. cantidad de findings | 100, 500, 1000 |
| objectReference | Objeto a verificar (paquete, clase) | devck/Z_FLIGHT_BOOKING |
| checkVariant | Variante de check | ABAP_CLOUD_READINESS |
Ejemplo cURL
# Iniciar verificacion ATC para un paquetecurl -X POST \ "https://my-system.abap.eu10.hana.ondemand.com/sap/bc/adt/atc/runs" \ -u "CICD_USER:password" \ -H "Content-Type: application/vnd.sap.atc.run.v1+xml" \ -H "Accept: application/vnd.sap.atc.run.v1+xml" \ -d @atc_config.xml \ -o atc_results.xmlDefinir variantes de check propias
Las variantes de check determinan que verificaciones se ejecutan con que prioridad.
Variantes de check estandar en BTP
| Variante | Descripcion | Recomendado para |
|---|---|---|
| ABAP_CLOUD_READINESS | Verifica compatibilidad Cloud | Migracion a ABAP Cloud |
| DEFAULT | Todos los checks estandar | Calidad general |
| SAP_CP | SAP Cloud Platform Checks | Desarrollo BTP |
Obtener variante de check via API
# Consultar variantes de check disponiblescurl -X GET \ "https://my-system.abap.eu10.hana.ondemand.com/sap/bc/adt/atc/customizing/variants" \ -u "CICD_USER:password" \ -H "Accept: application/vnd.sap.atc.customizing.v1+xml"Crear variante de check propia
En SAP BTP ABAP Environment creas variantes de check a traves de la app Fiori Maintain ATC Check Variants:
+----------------------------------------------------------------+| Check Variant: Z_CI_PIPELINE |+----------------------------------------------------------------+| || Descripcion: Check Variant para pipeline CI/CD || || +------------------------------------------------------------+ || | Check | Prioridad | Activo | || +--------------------------------+-----------+---------------+ || | SLIN_CHECK (Syntax Check) | 1 | [x] | || | SEC_CHECK (Security) | 1 | [x] | || | PERF_CHECK (Performance) | 2 | [x] | || | CONV_CHECK (Conventions) | 3 | [x] | || | CLOUD_CHECK (Cloud Readiness) | 1 | [x] | || | UNIT_TEST (Unit Tests) | 2 | [ ] | || +--------------------------------+-----------+---------------+ || || Configuracion de prioridades: || * Prioridad 1 (Error) -> Build falla || * Prioridad 2 (Warning) -> Build con advertencia || * Prioridad 3 (Info) -> Solo protocolado || |+----------------------------------------------------------------+Categorias de check en detalle
" Las categorias de check mas importantes para ABAP Cloud:
" 1. Cloud Readiness Checks" - Uso de APIs no liberadas" - Accesos directos a DB (SELECT en tablas estandar)" - Uso de sentencias Dynpro
" 2. Security Checks" - Riesgos de SQL Injection" - Cross-Site Scripting (XSS)" - Authority Checks faltantes" - Credenciales hardcodeadas
" 3. Performance Checks" - Evitar SELECT *" - SELECT en bucles" - Clausulas WHERE faltantes" - LOOP AT sin alternativa READ TABLE
" 4. Naming Convention Checks" - Prefijo Z/Y para objetos custom" - Directrices de nombres para variables" - Asignacion a paqueteConfigurar ejecucion automatica
Proceso ATC en dos pasos
La API ATC trabaja de forma asincrona - inicias un run y consultas los resultados:
+-----------------------------------------------------------------+| Workflow API ATC |+-----------------------------------------------------------------+| || 1. POST /sap/bc/adt/atc/runs || +-- Request: Configuracion de check || +-- Response: Run-ID (Header Location) || || 2. Polling: GET /sap/bc/adt/atc/runs/{run-id} || +-- Status: running -> esperar || +-- Status: finished -> obtener resultados || || 3. GET /sap/bc/adt/atc/runs/{run-id}/results || +-- Response: Findings como XML || |+-----------------------------------------------------------------+Script Bash completo para ejecucion ATC
#!/bin/bash# run_atc.sh - Ejecutar verificacion ATC en BTP
set -e
# ConfiguracionSYSTEM_URL="${ABAP_ENDPOINT}"USER="${ABAP_USER}"PASSWORD="${ABAP_PASSWORD}"PACKAGE="${1:-Z_FLIGHT_BOOKING}"CHECK_VARIANT="${2:-ABAP_CLOUD_READINESS}"MAX_VERDICTS="${3:-500}"
echo "=== Starting ATC Check ==="echo "Package: $PACKAGE"echo "Variant: $CHECK_VARIANT"
# 1. Iniciar ATC Runcat > atc_request.xml << EOF<?xml version="1.0" encoding="UTF-8"?><atc:run xmlns:atc="http://www.sap.com/adt/atc" xmlns:adtcore="http://www.sap.com/adt/core" maximumVerdicts="$MAX_VERDICTS"> <objectSets> <objectSet kind="inclusive"> <adtcore:objectReferences> <adtcore:objectReference adtcore:uri="/sap/bc/adt/vit/wb/object_type/devck/object_name/$PACKAGE"/> </adtcore:objectReferences> </objectSet> </objectSets> <checkVariant> <atc:name>$CHECK_VARIANT</atc:name> </checkVariant></atc:run>EOF
# Iniciar run y extraer header LocationRESPONSE=$(curl -s -D - \ -X POST \ "$SYSTEM_URL/sap/bc/adt/atc/runs" \ -u "$USER:$PASSWORD" \ -H "Content-Type: application/vnd.sap.atc.run.v1+xml" \ -H "Accept: application/vnd.sap.atc.run.v1+xml" \ -d @atc_request.xml)
RUN_ID=$(echo "$RESPONSE" | grep -i "location:" | sed 's/.*runs\///' | tr -d '\r\n')
if [ -z "$RUN_ID" ]; then echo "::error::Failed to start ATC run" echo "$RESPONSE" exit 1fi
echo "ATC Run started: $RUN_ID"
# 2. Esperar finalizacion (Polling)MAX_ATTEMPTS=60ATTEMPT=0
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do STATUS_RESPONSE=$(curl -s \ -X GET \ "$SYSTEM_URL/sap/bc/adt/atc/runs/$RUN_ID" \ -u "$USER:$PASSWORD" \ -H "Accept: application/vnd.sap.atc.run.v1+xml")
STATUS=$(echo "$STATUS_RESPONSE" | grep -oP 'status="\K[^"]+' || echo "unknown")
echo "Status: $STATUS (Attempt $((ATTEMPT+1))/$MAX_ATTEMPTS)"
if [ "$STATUS" = "finished" ]; then break fi
sleep 5 ATTEMPT=$((ATTEMPT+1))done
if [ "$STATUS" != "finished" ]; then echo "::error::ATC run timed out" exit 1fi
# 3. Obtener resultadoscurl -s \ -X GET \ "$SYSTEM_URL/sap/bc/adt/atc/runs/$RUN_ID/results" \ -u "$USER:$PASSWORD" \ -H "Accept: application/vnd.sap.atc.worklist.v1+xml" \ -o atc_results.xml
echo "=== ATC Results saved to atc_results.xml ==="Evaluar resultados y reportar
Formato del Response ATC
<?xml version="1.0" encoding="UTF-8"?><atc:worklist xmlns:atc="http://www.sap.com/adt/atc" xmlns:adtcore="http://www.sap.com/adt/core" timestamp="2026-02-15T10:30:00Z" usedCheckVariant="Z_CI_PIPELINE"> <objects> <object adtcore:uri="/sap/bc/adt/oo/classes/zcl_flight_booking" adtcore:name="ZCL_FLIGHT_BOOKING" adtcore:type="CLAS"> <findings> <finding checkId="CL_CI_CRITICAL_API_USE" checkTitle="Non-released API usage" messageId="001" messageTitle="Usage of non-released API detected" priority="1" location="/sap/bc/adt/oo/classes/zcl_flight_booking/source/main#start=45,10"> <atc:quickfixes count="1"/> </finding> <finding checkId="CL_CI_SEC_INJECTION" checkTitle="SQL Injection Risk" messageId="002" messageTitle="Potential SQL injection vulnerability" priority="1" location="/sap/bc/adt/oo/classes/zcl_flight_booking/source/main#start=78,5"> <atc:quickfixes count="0"/> </finding> <finding checkId="CL_CI_CONV_NAMING" checkTitle="Naming Convention" messageId="003" messageTitle="Variable naming does not follow convention" priority="3" location="/sap/bc/adt/oo/classes/zcl_flight_booking/source/main#start=12,8"> <atc:quickfixes count="1"/> </finding> </findings> </object> </objects> <statistics> <totalFindings>3</totalFindings> <findingsByPriority> <priority1>2</priority1> <priority2>0</priority2> <priority3>1</priority3> </findingsByPriority> </statistics></atc:worklist>Script Python para parsing de resultados
import xml.etree.ElementTree as ETimport sysimport jsonfrom dataclasses import dataclass, asdictfrom typing import List
@dataclassclass Finding: check_id: str check_title: str message: str priority: int object_name: str location: str has_quickfix: bool
def parse_atc_results(xml_file: str) -> dict: tree = ET.parse(xml_file) root = tree.getroot()
ns = { 'atc': 'http://www.sap.com/adt/atc', 'adtcore': 'http://www.sap.com/adt/core' }
findings: List[Finding] = [] priority_counts = {1: 0, 2: 0, 3: 0}
for obj in root.findall('.//atc:object', ns): obj_name = obj.get('{http://www.sap.com/adt/core}name', 'Unknown')
for finding in obj.findall('.//atc:finding', ns): priority = int(finding.get('priority', 3)) priority_counts[priority] = priority_counts.get(priority, 0) + 1
quickfixes = finding.find('atc:quickfixes', ns) has_quickfix = quickfixes is not None and int(quickfixes.get('count', 0)) > 0
findings.append(Finding( check_id=finding.get('checkId', ''), check_title=finding.get('checkTitle', ''), message=finding.get('messageTitle', ''), priority=priority, object_name=obj_name, location=finding.get('location', ''), has_quickfix=has_quickfix ))
return { 'total': len(findings), 'errors': priority_counts.get(1, 0), 'warnings': priority_counts.get(2, 0), 'info': priority_counts.get(3, 0), 'findings': [asdict(f) for f in findings] }
def print_report(results: dict, error_threshold: int = 0, warning_threshold: int = 5): print("=" * 60) print("ATC QUALITY REPORT") print("=" * 60) print(f"Total Findings: {results['total']}") print(f" Errors (P1): {results['errors']}") print(f" Warnings (P2): {results['warnings']}") print(f" Info (P3): {results['info']}") print("=" * 60)
# Findings agrupados por prioridad for priority in [1, 2, 3]: priority_findings = [f for f in results['findings'] if f['priority'] == priority] if priority_findings: print(f"\nPriority {priority} Findings:") print("-" * 40) for f in priority_findings: print(f" [{f['check_id']}] {f['object_name']}") print(f" {f['message']}") quickfix = "[OK] Quickfix disponible" if f['has_quickfix'] else "" print(f" {f['location']} {quickfix}")
print("\n" + "=" * 60)
# Verificar thresholds exit_code = 0 if results['errors'] > error_threshold: print(f"::error::Found {results['errors']} error(s) (threshold: {error_threshold})") exit_code = 1
if results['warnings'] > warning_threshold: print(f"::warning::Found {results['warnings']} warning(s) (threshold: {warning_threshold})")
return exit_code
if __name__ == '__main__': if len(sys.argv) < 2: print("Usage: python parse_atc_results.py <xml_file> [--json]") sys.exit(1)
results = parse_atc_results(sys.argv[1])
if '--json' in sys.argv: print(json.dumps(results, indent=2)) else: exit_code = print_report(results) sys.exit(exit_code)Integracion con GitHub/GitLab
Workflow GitHub Actions
name: ATC Quality Gate
on: push: branches: [ main, develop ] pull_request: branches: [ main ]
env: PACKAGE: Z_FLIGHT_BOOKING CHECK_VARIANT: ABAP_CLOUD_READINESS ERROR_THRESHOLD: 0 WARNING_THRESHOLD: 5
jobs: atc-check: name: ATC Quality Check runs-on: ubuntu-latest
steps: - name: Checkout uses: actions/checkout@v4
- name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11'
- name: Create ATC Configuration run: | cat > atc_config.xml << 'EOF' <?xml version="1.0" encoding="UTF-8"?> <atc:run xmlns:atc="http://www.sap.com/adt/atc" xmlns:adtcore="http://www.sap.com/adt/core" maximumVerdicts="500"> <objectSets> <objectSet kind="inclusive"> <adtcore:objectReferences> <adtcore:objectReference adtcore:uri="/sap/bc/adt/vit/wb/object_type/devck/object_name/${{ env.PACKAGE }}"/> </adtcore:objectReferences> </objectSet> </objectSets> <checkVariant> <atc:name>${{ env.CHECK_VARIANT }}</atc:name> </checkVariant> </atc:run> EOF
- name: Start ATC Run id: start-atc run: | RESPONSE=$(curl -s -D - \ -X POST \ "${{ secrets.ABAP_ENDPOINT }}/sap/bc/adt/atc/runs" \ -u "${{ secrets.ABAP_USER }}:${{ secrets.ABAP_PASSWORD }}" \ -H "Content-Type: application/vnd.sap.atc.run.v1+xml" \ -H "Accept: application/vnd.sap.atc.run.v1+xml" \ -d @atc_config.xml)
RUN_ID=$(echo "$RESPONSE" | grep -i "location:" | sed 's/.*runs\///' | tr -d '\r\n')
if [ -z "$RUN_ID" ]; then echo "::error::Failed to start ATC run" exit 1 fi
echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT echo "Started ATC run: $RUN_ID"
- name: Wait for ATC Completion run: | RUN_ID="${{ steps.start-atc.outputs.run_id }}" MAX_ATTEMPTS=60
for i in $(seq 1 $MAX_ATTEMPTS); do STATUS=$(curl -s \ -X GET \ "${{ secrets.ABAP_ENDPOINT }}/sap/bc/adt/atc/runs/$RUN_ID" \ -u "${{ secrets.ABAP_USER }}:${{ secrets.ABAP_PASSWORD }}" \ -H "Accept: application/vnd.sap.atc.run.v1+xml" \ | grep -oP 'status="\K[^"]+' || echo "unknown")
echo "Status: $STATUS (Attempt $i/$MAX_ATTEMPTS)"
if [ "$STATUS" = "finished" ]; then break fi
sleep 5 done
if [ "$STATUS" != "finished" ]; then echo "::error::ATC run timed out" exit 1 fi
- name: Fetch ATC Results run: | RUN_ID="${{ steps.start-atc.outputs.run_id }}"
curl -s \ -X GET \ "${{ secrets.ABAP_ENDPOINT }}/sap/bc/adt/atc/runs/$RUN_ID/results" \ -u "${{ secrets.ABAP_USER }}:${{ secrets.ABAP_PASSWORD }}" \ -H "Accept: application/vnd.sap.atc.worklist.v1+xml" \ -o atc_results.xml
- name: Parse and Evaluate Results id: parse run: | python << 'PYTHON_SCRIPT' import xml.etree.ElementTree as ET import os
tree = ET.parse('atc_results.xml') root = tree.getroot() ns = {'atc': 'http://www.sap.com/adt/atc', 'adtcore': 'http://www.sap.com/adt/core'}
errors = warnings = info = 0
for finding in root.findall('.//atc:finding', ns): priority = int(finding.get('priority', 3)) if priority == 1: errors += 1 elif priority == 2: warnings += 1 else: info += 1
with open(os.environ['GITHUB_OUTPUT'], 'a') as f: f.write(f"errors={errors}\n") f.write(f"warnings={warnings}\n") f.write(f"info={info}\n") f.write(f"total={errors + warnings + info}\n")
print(f"Errors: {errors}, Warnings: {warnings}, Info: {info}")
# Verificar Quality Gate if errors > int(os.environ.get('ERROR_THRESHOLD', 0)): print(f"::error::Quality Gate failed: {errors} error(s) found") exit(1) PYTHON_SCRIPT
- name: Upload ATC Results uses: actions/upload-artifact@v4 with: name: atc-results path: atc_results.xml
- name: Create Summary if: always() run: | cat >> $GITHUB_STEP_SUMMARY << EOF ## ATC Quality Gate Results
| Metric | Value | Threshold | |--------|-------|-----------| | Errors (P1) | ${{ steps.parse.outputs.errors }} | ${{ env.ERROR_THRESHOLD }} | | Warnings (P2) | ${{ steps.parse.outputs.warnings }} | ${{ env.WARNING_THRESHOLD }} | | Info (P3) | ${{ steps.parse.outputs.info }} | - | | **Total** | ${{ steps.parse.outputs.total }} | |
**Status:** ${{ steps.parse.outputs.errors == 0 && '[OK] Passed' || '[X] Failed' }} EOFPipeline GitLab CI/CD
stages: - quality
variables: PACKAGE: Z_FLIGHT_BOOKING CHECK_VARIANT: ABAP_CLOUD_READINESS ERROR_THRESHOLD: "0"
atc-quality-gate: stage: quality image: python:3.11-slim before_script: - apt-get update && apt-get install -y curl script: # Iniciar ATC Run - | RESPONSE=$(curl -s -D - \ -X POST \ "${ABAP_ENDPOINT}/sap/bc/adt/atc/runs" \ -u "${ABAP_USER}:${ABAP_PASSWORD}" \ -H "Content-Type: application/vnd.sap.atc.run.v1+xml" \ -H "Accept: application/vnd.sap.atc.run.v1+xml" \ -d "<atc:run xmlns:atc=\"http://www.sap.com/adt/atc\" xmlns:adtcore=\"http://www.sap.com/adt/core\" maximumVerdicts=\"500\"> <objectSets> <objectSet kind=\"inclusive\"> <adtcore:objectReferences> <adtcore:objectReference adtcore:uri=\"/sap/bc/adt/vit/wb/object_type/devck/object_name/${PACKAGE}\"/> </adtcore:objectReferences> </objectSet> </objectSets> <checkVariant> <atc:name>${CHECK_VARIANT}</atc:name> </checkVariant> </atc:run>")
RUN_ID=$(echo "$RESPONSE" | grep -i "location:" | sed 's/.*runs\///' | tr -d '\r\n') echo "ATC Run ID: $RUN_ID"
# Esperar finalizacion - | for i in $(seq 1 60); do STATUS=$(curl -s \ "${ABAP_ENDPOINT}/sap/bc/adt/atc/runs/${RUN_ID}" \ -u "${ABAP_USER}:${ABAP_PASSWORD}" \ -H "Accept: application/vnd.sap.atc.run.v1+xml" \ | grep -oP 'status="\K[^"]+' || echo "unknown")
echo "Attempt $i: Status = $STATUS" [ "$STATUS" = "finished" ] && break sleep 5 done
# Obtener resultados - | curl -s \ "${ABAP_ENDPOINT}/sap/bc/adt/atc/runs/${RUN_ID}/results" \ -u "${ABAP_USER}:${ABAP_PASSWORD}" \ -H "Accept: application/vnd.sap.atc.worklist.v1+xml" \ -o atc_results.xml
# Verificar resultados - | python3 << 'EOF' import xml.etree.ElementTree as ET import sys import os
tree = ET.parse('atc_results.xml') root = tree.getroot() ns = {'atc': 'http://www.sap.com/adt/atc'}
errors = sum(1 for f in root.findall('.//atc:finding', ns) if f.get('priority') == '1') print(f"Found {errors} error(s)")
if errors > int(os.environ.get('ERROR_THRESHOLD', 0)): sys.exit(1) EOF artifacts: paths: - atc_results.xml reports: junit: atc_results.xml when: always rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == "main"Mejores practicas
| Aspecto | Recomendacion |
|---|---|
| Check Variant | Variante propia para CI/CD con prioridades claras |
| Error-Threshold | 0 para Priority 1 (no se permiten errores) |
| Warning-Threshold | Establecer de forma realista, reducir gradualmente |
| Timeout | Max. 5 minutos para paquetes grandes |
| Quickfixes | Mostrar en reporte, no aplicar automaticamente |
| Trend-Tracking | Protocolar findings a lo largo del tiempo |
| Estandares de equipo | Acordar variante de check en el equipo |
| Manejo de excepciones | Documentar excepciones justificadas |
Troubleshooting
| Problema | Posible causa | Solucion |
|---|---|---|
| 401 Unauthorized | Credenciales incorrectas | Verificar user/password |
| 403 Forbidden | Permiso ATC faltante | Verificar S_ATC_ADM |
| 404 Not Found | Paquete/variante no existe | Verificar nombres |
| Timeout | Paquete muy grande | Dividir en paquetes mas pequenios |
| Sin Findings | Check variant vacia | Activar checks en variante |
| Run permanece “running” | Sistema sobrecargado | Reintentar mas tarde |
Recursos adicionales
- ABAP Test Cockpit - Fundamentos ATC y uso manual
- ABAP Unit Runner en BTP - Ejecutar Unit Tests automatizados
- CI/CD con ABAP Cloud - Conceptos completos de pipeline
- Estrategia Clean Core - Estandares de calidad para ABAP Cloud