| name | sap-testing-quality |
| description | SAP testing and quality assurance skill. Use when writing ABAP Unit tests, implementing test doubles (CL_OSQL/CL_CDS/CL_BOTD), setting up CAP tests, working with ATC/Code Inspector, or building CI/CD test pipelines. If the user mentions ABAP Unit, test double, ATC check, SAP test automation, or TDD in SAP, use this skill.
|
| license | MIT |
| metadata | {"author":"SAP Skills Community","version":"1.0.0","last_verified":"2026-03-23"} |
SAP Testing & Quality Assurance
Related Skills
sap-rap-comprehensive — RAP BO testing patterns, EML-based test assertions
sap-devops-cicd — Integrating tests into CI/CD pipelines, ATC in CI
sap-fiori-testing — wdi5/OPA5 E2E tests for Fiori/UI5 apps
sap-abap-advanced — ABAP Cloud test patterns with released API constraints
sap-cloud-alm — Cloud ALM Test Management for enterprise test orchestration
Quick Start
Choose your testing approach:
| Layer | Framework | Tool |
|---|
| ABAP backend logic | ABAP Unit | ADT Test Runner / ATC |
| ABAP SQL / CDS | CDS Test Double Framework | CL_CDS_TEST_ENVIRONMENT |
| RAP Business Objects | RAP BO Test | EML + CL_BOTD_TXBUFDBL_BO_TEST_ENV |
| CAP Node.js services | cds.test + Jest/Mocha | cds test CLI |
| CAP Java services | JUnit 5 + Spring Test | mvn test |
| UI5/Fiori frontend | OPA5 + QUnit | UI5 Test Runner |
| API contract testing | REST/OData assertions | Postman / Newman / custom |
| Performance | JMeter / k6 | Load test scripts |
Core Concepts
ABAP Unit Architecture
- Test classes: Local class with
FOR TESTING addition
- Risk levels:
HARMLESS, DANGEROUS, CRITICAL — controls what DB changes are allowed
- Duration:
SHORT (<1s), MEDIUM (<10s), LONG (>10s)
- Test isolation: Use test doubles to isolate unit under test from DB, authority checks, etc.
- Test relations:
FOR TESTING RISK LEVEL annotations on test class definition
Test Double Frameworks
| Framework | Class | Purpose |
|---|
| ABAP Test Double Framework | CL_ABAP_TESTDOUBLE | Mock any interface |
| SQL Test Environment | CL_OSQL_TEST_ENVIRONMENT | Redirect DB tables to test data |
| CDS Test Double | CL_CDS_TEST_ENVIRONMENT | Test CDS views with mock data |
| Authority Check Double | CL_AUNIT_AUTHORITY_CHECK | Stub authority checks |
| RAP BO Test Double | CL_BOTD_TXBUFDBL_BO_TEST_ENV | Test RAP BOs without DB |
ATC (ABAP Test Cockpit)
- Centralized quality gate for ABAP code
- Runs: syntax check, naming conventions, performance patterns, security checks, custom checks
- Integrable into CI/CD via
ABAP Environment Pipeline or API
- Exemptions managed via
ATC Exemption workflow
Common Patterns
Pattern 1: ABAP Unit with Test Doubles
" Production class
CLASS zcl_order_validator DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES zif_order_validator.
METHODS constructor
IMPORTING io_order_repo TYPE REF TO zif_order_repository.
PRIVATE SECTION.
DATA mo_repo TYPE REF TO zif_order_repository.
ENDCLASS.
CLASS zcl_order_validator IMPLEMENTATION.
METHOD constructor.
mo_repo = io_order_repo.
ENDMETHOD.
METHOD zif_order_validator~validate.
DATA(ls_order) = mo_repo->get_order( iv_order_id ).
IF ls_order-amount <= 0.
APPEND VALUE #( type = 'E' message = 'Amount must be positive' ) TO rt_messages.
ENDIF.
ENDMETHOD.
ENDCLASS.
" Test class
CLASS ltcl_order_validator DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO zcl_order_validator.
DATA mo_repo_mock TYPE REF TO zif_order_repository.
METHODS setup.
METHODS invalid_amount FOR TESTING.
METHODS valid_order FOR TESTING.
ENDCLASS.
CLASS ltcl_order_validator IMPLEMENTATION.
METHOD setup.
mo_repo_mock = CAST #( cl_abap_testdouble=>create( 'ZIF_ORDER_REPOSITORY' ) ).
mo_cut = NEW #( io_order_repo = mo_repo_mock ).
ENDMETHOD.
METHOD invalid_amount.
" Arrange
DATA(ls_order) = VALUE zs_order( order_id = 'ORD001' amount = -100 ).
cl_abap_testdouble=>configure_call( mo_repo_mock
)->returning( ls_order
)->and_expect( )->is_called_once( ).
mo_repo_mock->get_order( 'ORD001' ).
" Act
DATA(lt_messages) = mo_cut->zif_order_validator~validate( 'ORD001' ).
" Assert
cl_abap_unit_assert=>assert_not_initial( lt_messages ).
cl_abap_unit_assert=>assert_equals( exp = 'E' act = lt_messages[ 1 ]-type ).
" Verify mock
cl_abap_testdouble=>verify_expectations( mo_repo_mock ).
ENDMETHOD.
METHOD valid_order.
DATA(ls_order) = VALUE zs_order( order_id = 'ORD002' amount = 500 ).
cl_abap_testdouble=>configure_call( mo_repo_mock
)->returning( ls_order ).
mo_repo_mock->get_order( 'ORD002' ).
DATA(lt_messages) = mo_cut->zif_order_validator~validate( 'ORD002' ).
cl_abap_unit_assert=>assert_initial( lt_messages ).
ENDMETHOD.
ENDCLASS.
Pattern 2: SQL Test Environment
CLASS ltcl_order_report DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA go_sql_env TYPE REF TO if_osql_test_environment.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
METHODS total_by_customer FOR TESTING.
ENDCLASS.
CLASS ltcl_order_report IMPLEMENTATION.
METHOD class_setup.
go_sql_env = cl_osql_test_environment=>create( i_dependency_list = VALUE #(
( 'ZORDERS' )
( 'ZCUSTOMERS' )
) ).
ENDMETHOD.
METHOD class_teardown.
go_sql_env->destroy( ).
ENDMETHOD.
METHOD total_by_customer.
" Arrange — insert test data into double
go_sql_env->clear_doubles( ).
go_sql_env->insert_test_data( EXPORTING i_data = VALUE zt_orders(
( order_id = 'O1' customer_id = 'C1' amount = '100.00' )
( order_id = 'O2' customer_id = 'C1' amount = '250.00' )
( order_id = 'O3' customer_id = 'C2' amount = '75.00' )
) ).
" Act
DATA(lt_result) = NEW zcl_order_report( )->get_totals_by_customer( ).
" Assert
cl_abap_unit_assert=>assert_equals( exp = 2 act = lines( lt_result ) ).
READ TABLE lt_result INTO DATA(ls_c1) WITH KEY customer_id = 'C1'.
cl_abap_unit_assert=>assert_equals( exp = '350.00' act = ls_c1-total ).
ENDMETHOD.
ENDCLASS.
Pattern 3: CDS Test Double Framework
CLASS ltcl_cds_view DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA go_cds_env TYPE REF TO if_cds_test_environment.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
METHODS active_orders_only FOR TESTING.
ENDCLASS.
CLASS ltcl_cds_view IMPLEMENTATION.
METHOD class_setup.
go_cds_env = cl_cds_test_environment=>create( i_for_entity = 'ZI_ORDER' ).
go_cds_env->enable_double_redirection( ).
ENDMETHOD.
METHOD class_teardown.
go_cds_env->destroy( ).
ENDMETHOD.
METHOD active_orders_only.
" Insert test data into underlying tables via SQL doubles
DATA lt_orders TYPE TABLE OF zorders.
lt_orders = VALUE #(
( order_id = 'O1' status = 'ACTIVE' amount = '100.00' )
( order_id = 'O2' status = 'CLOSED' amount = '200.00' )
).
go_cds_env->insert_test_data( i_data = lt_orders ).
" Execute CDS view
SELECT * FROM zi_order INTO TABLE @DATA(lt_result).
" Assert: only active orders returned (CDS has WHERE status = 'ACTIVE')
cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_result ) ).
cl_abap_unit_assert=>assert_equals( exp = 'O1' act = lt_result[ 1 ]-OrderId ).
ENDMETHOD.
ENDCLASS.
Pattern 4: CAP Node.js Test (cds.test)
const cds = require('@sap/cds');
const { expect } = cds.test('serve', '--project', __dirname + '/..');
describe('OrderService', () => {
it('should create an order', async () => {
const { data } = await cds.run(INSERT.into('Orders').entries({
ID: 'uuid-001', item: 'Laptop', quantity: 2, amount: 2000
}));
expect(data).to.exist;
});
it('should reject negative amount', async () => {
try {
await cds.run(INSERT.into('Orders').entries({
ID: 'uuid-002', item: 'Phone', quantity: 1, amount: -100
}));
expect.fail('Should have thrown');
} catch (e) {
expect(e.code).to.equal(400);
expect(e.message).to.include('amount');
}
});
it('should return only active orders via API', async () => {
const response = await cds.test.get('/odata/v4/OrderService/Orders?$filter=status eq \'ACTIVE\'');
expect(response.status).to.equal(200);
response.data.value.forEach(order => {
expect(order.status).to.equal('ACTIVE');
});
});
});
Pattern 5: OPA5 UI5 Integration Test
sap.ui.define([
"sap/ui/test/opaQunit",
"sap/ui/test/Opa5",
"sap/ui/test/matchers/Properties",
"sap/ui/test/actions/Press"
], function (opaTest, Opa5, Properties, Press) {
"use strict";
opaTest("Should display order list", function (Given, When, Then) {
Given.iStartMyApp();
Then.onTheOrderList.iShouldSeeTheTable();
});
opaTest("Should navigate to detail on press", function (Given, When, Then) {
When.onTheOrderList.iPressOnFirstItem();
Then.onTheOrderDetail.iShouldSeeTheObjectHeader();
Then.iTeardownMyApp();
});
Opa5.createPageObjects({
onTheOrderList: {
actions: {
iPressOnFirstItem: function () {
return this.waitFor({
controlType: "sap.m.ColumnListItem",
matchers: new Properties({ type: "Navigation" }),
actions: new Press(),
success: function () { Opa5.assert.ok(true, "Pressed first item"); }
});
}
},
assertions: {
iShouldSeeTheTable: function () {
return this.waitFor({
id: "orderTable",
success: function () { Opa5.assert.ok(true, "Table visible"); }
});
}
}
}
});
});
Error Catalog
| Error | Context | Root Cause | Fix |
|---|
CX_AUNIT_ASSERT_FAILED | ABAP Unit assertion | Expected ≠ actual value | Check test data setup; verify production logic |
CL_OSQL_TEST_ENVIRONMENT CREATE failed | SQL double | Table name wrong or not accessible | Use exact DB table name (not CDS entity name) |
CDS test: no data returned | CDS test double | Redirection not enabled or wrong entity | Call enable_double_redirection( ) after create |
cds.test timeout | CAP test | Service startup slow or DB connection issue | Increase timeout; check cds.requires for test profile |
OPA5 waitFor timeout | UI5 test | Control not rendered or wrong matcher | Increase timeout; verify control ID or matcher config |
ATC: check cannot be suppressed | ATC exemption | Priority-1 findings block transport | Fix the code; P1 findings cannot be exempted |
Performance Tips
- Test isolation — Each test method independent; use
setup for fresh state
RISK LEVEL HARMLESS — Fastest execution; no DB rollback overhead
- Minimize test doubles — Only double external dependencies; test real logic
- Parallel ATC — Run ATC checks in parallel in CI; each object type independently
- CAP test profiles — Use
[test] profile in .cdsrc.json with SQLite for speed
- OPA5
autoWait — Enable autoWait: true in OPA config to avoid flaky tests
- Test data builders — Create helper methods for test data; reduce duplication across test methods
Gotchas
- ABAP test double limitations: Only works with interfaces, not concrete classes — design for dependency injection
- SQL double scope:
CL_OSQL_TEST_ENVIRONMENT doubles apply to the entire test class, not per method — use clear_doubles( ) in setup
- CDS entity vs. DB table:
CL_CDS_TEST_ENVIRONMENT takes the CDS entity name; CL_OSQL_TEST_ENVIRONMENT takes the DB table name
- ATC in ABAP Cloud: Some classic ATC checks don't apply; ABAP Cloud has its own check set
- OPA5 async pitfalls: All OPA assertions are async; never use synchronous checks after
waitFor
- CAP test DB: By default
cds test uses in-memory SQLite; HANA-specific SQL won't work — use [test] profile with HANA for integration tests