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
1---2name: sap-testing-quality3description: 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.4license: MIT5---67# SAP Testing & Quality Assurance89## Related Skills10- `sap-rap-comprehensive` — RAP BO testing patterns, EML-based test assertions11- `sap-devops-cicd` — Integrating tests into CI/CD pipelines, ATC in CI12- `sap-fiori-testing` — wdi5/OPA5 E2E tests for Fiori/UI5 apps13- `sap-abap-advanced` — ABAP Cloud test patterns with released API constraints14- `sap-cloud-alm` — Cloud ALM Test Management for enterprise test orchestration1516## Quick Start1718**Choose your testing approach:**1920| Layer | Framework | Tool |21|-------|-----------|------|22| ABAP backend logic | ABAP Unit | ADT Test Runner / ATC |23| ABAP SQL / CDS | CDS Test Double Framework | `CL_CDS_TEST_ENVIRONMENT` |24| RAP Business Objects | RAP BO Test | EML + `CL_BOTD_TXBUFDBL_BO_TEST_ENV` |25| CAP Node.js services | cds.test + Jest/Mocha | `cds test` CLI |26| CAP Java services | JUnit 5 + Spring Test | `mvn test` |27| UI5/Fiori frontend | OPA5 + QUnit | UI5 Test Runner |28| API contract testing | REST/OData assertions | Postman / Newman / custom |29| Performance | JMeter / k6 | Load test scripts |3031## Core Concepts3233### ABAP Unit Architecture34- **Test classes**: Local class with `FOR TESTING` addition35- **Risk levels**: `HARMLESS`, `DANGEROUS`, `CRITICAL` — controls what DB changes are allowed36- **Duration**: `SHORT` (<1s), `MEDIUM` (<10s), `LONG` (>10s)37- **Test isolation**: Use test doubles to isolate unit under test from DB, authority checks, etc.38- **Test relations**: `FOR TESTING RISK LEVEL` annotations on test class definition3940### Test Double Frameworks4142| Framework | Class | Purpose |43|-----------|-------|---------|44| ABAP Test Double Framework | `CL_ABAP_TESTDOUBLE` | Mock any interface |45| SQL Test Environment | `CL_OSQL_TEST_ENVIRONMENT` | Redirect DB tables to test data |46| CDS Test Double | `CL_CDS_TEST_ENVIRONMENT` | Test CDS views with mock data |47| Authority Check Double | `CL_AUNIT_AUTHORITY_CHECK` | Stub authority checks |48| RAP BO Test Double | `CL_BOTD_TXBUFDBL_BO_TEST_ENV` | Test RAP BOs without DB |4950### ATC (ABAP Test Cockpit)51- Centralized quality gate for ABAP code52- Runs: syntax check, naming conventions, performance patterns, security checks, custom checks53- Integrable into CI/CD via `ABAP Environment Pipeline` or API54- Exemptions managed via `ATC Exemption` workflow5556## Common Patterns5758### Pattern 1: ABAP Unit with Test Doubles5960```abap61" Production class62CLASS zcl_order_validator DEFINITION PUBLIC.63 PUBLIC SECTION.64 INTERFACES zif_order_validator.65 METHODS constructor66 IMPORTING io_order_repo TYPE REF TO zif_order_repository.67 PRIVATE SECTION.68 DATA mo_repo TYPE REF TO zif_order_repository.69ENDCLASS.7071CLASS zcl_order_validator IMPLEMENTATION.72 METHOD constructor.73 mo_repo = io_order_repo.74 ENDMETHOD.75 METHOD zif_order_validator~validate.76 DATA(ls_order) = mo_repo->get_order( iv_order_id ).77 IF ls_order-amount <= 0.78 APPEND VALUE #( type = 'E' message = 'Amount must be positive' ) TO rt_messages.79 ENDIF.80 ENDMETHOD.81ENDCLASS.8283" Test class84CLASS ltcl_order_validator DEFINITION FINAL FOR TESTING85 DURATION SHORT RISK LEVEL HARMLESS.86 PRIVATE SECTION.87 DATA mo_cut TYPE REF TO zcl_order_validator.88 DATA mo_repo_mock TYPE REF TO zif_order_repository.89 METHODS setup.90 METHODS invalid_amount FOR TESTING.91 METHODS valid_order FOR TESTING.92ENDCLASS.9394CLASS ltcl_order_validator IMPLEMENTATION.95 METHOD setup.96 mo_repo_mock = CAST #( cl_abap_testdouble=>create( 'ZIF_ORDER_REPOSITORY' ) ).97 mo_cut = NEW #( io_order_repo = mo_repo_mock ).98 ENDMETHOD.99100 METHOD invalid_amount.101 " Arrange102 DATA(ls_order) = VALUE zs_order( order_id = 'ORD001' amount = -100 ).103 cl_abap_testdouble=>configure_call( mo_repo_mock104 )->returning( ls_order105 )->and_expect( )->is_called_once( ).106 mo_repo_mock->get_order( 'ORD001' ).107108 " Act109 DATA(lt_messages) = mo_cut->zif_order_validator~validate( 'ORD001' ).110111 " Assert112 cl_abap_unit_assert=>assert_not_initial( lt_messages ).113 cl_abap_unit_assert=>assert_equals( exp = 'E' act = lt_messages[ 1 ]-type ).114115 " Verify mock116 cl_abap_testdouble=>verify_expectations( mo_repo_mock ).117 ENDMETHOD.118119 METHOD valid_order.120 DATA(ls_order) = VALUE zs_order( order_id = 'ORD002' amount = 500 ).121 cl_abap_testdouble=>configure_call( mo_repo_mock122 )->returning( ls_order ).123 mo_repo_mock->get_order( 'ORD002' ).124125 DATA(lt_messages) = mo_cut->zif_order_validator~validate( 'ORD002' ).126 cl_abap_unit_assert=>assert_initial( lt_messages ).127 ENDMETHOD.128ENDCLASS.129```130131### Pattern 2: SQL Test Environment132133```abap134CLASS ltcl_order_report DEFINITION FINAL FOR TESTING135 DURATION SHORT RISK LEVEL HARMLESS.136 PRIVATE SECTION.137 CLASS-DATA go_sql_env TYPE REF TO if_osql_test_environment.138 CLASS-METHODS class_setup.139 CLASS-METHODS class_teardown.140 METHODS total_by_customer FOR TESTING.141ENDCLASS.142143CLASS ltcl_order_report IMPLEMENTATION.144 METHOD class_setup.145 go_sql_env = cl_osql_test_environment=>create( i_dependency_list = VALUE #(146 ( 'ZORDERS' )147 ( 'ZCUSTOMERS' )148 ) ).149 ENDMETHOD.150151 METHOD class_teardown.152 go_sql_env->destroy( ).153 ENDMETHOD.154155 METHOD total_by_customer.156 " Arrange — insert test data into double157 go_sql_env->clear_doubles( ).158 go_sql_env->insert_test_data( EXPORTING i_data = VALUE zt_orders(159 ( order_id = 'O1' customer_id = 'C1' amount = '100.00' )160 ( order_id = 'O2' customer_id = 'C1' amount = '250.00' )161 ( order_id = 'O3' customer_id = 'C2' amount = '75.00' )162 ) ).163164 " Act165 DATA(lt_result) = NEW zcl_order_report( )->get_totals_by_customer( ).166167 " Assert168 cl_abap_unit_assert=>assert_equals( exp = 2 act = lines( lt_result ) ).169 READ TABLE lt_result INTO DATA(ls_c1) WITH KEY customer_id = 'C1'.170 cl_abap_unit_assert=>assert_equals( exp = '350.00' act = ls_c1-total ).171 ENDMETHOD.172ENDCLASS.173```174175### Pattern 3: CDS Test Double Framework176177```abap178CLASS ltcl_cds_view DEFINITION FINAL FOR TESTING179 DURATION SHORT RISK LEVEL HARMLESS.180 PRIVATE SECTION.181 CLASS-DATA go_cds_env TYPE REF TO if_cds_test_environment.182 CLASS-METHODS class_setup.183 CLASS-METHODS class_teardown.184 METHODS active_orders_only FOR TESTING.185ENDCLASS.186187CLASS ltcl_cds_view IMPLEMENTATION.188 METHOD class_setup.189 go_cds_env = cl_cds_test_environment=>create( i_for_entity = 'ZI_ORDER' ).190 go_cds_env->enable_double_redirection( ).191 ENDMETHOD.192193 METHOD class_teardown.194 go_cds_env->destroy( ).195 ENDMETHOD.196197 METHOD active_orders_only.198 " Insert test data into underlying tables via SQL doubles199 DATA lt_orders TYPE TABLE OF zorders.200 lt_orders = VALUE #(201 ( order_id = 'O1' status = 'ACTIVE' amount = '100.00' )202 ( order_id = 'O2' status = 'CLOSED' amount = '200.00' )203 ).204 go_cds_env->insert_test_data( i_data = lt_orders ).205206 " Execute CDS view207 SELECT * FROM zi_order INTO TABLE @DATA(lt_result).208209 " Assert: only active orders returned (CDS has WHERE status = 'ACTIVE')210 cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_result ) ).211 cl_abap_unit_assert=>assert_equals( exp = 'O1' act = lt_result[ 1 ]-OrderId ).212 ENDMETHOD.213ENDCLASS.214```215216### Pattern 4: CAP Node.js Test (cds.test)217218```javascript219const cds = require('@sap/cds');220const { expect } = cds.test('serve', '--project', __dirname + '/..');221222describe('OrderService', () => {223 it('should create an order', async () => {224 const { data } = await cds.run(INSERT.into('Orders').entries({225 ID: 'uuid-001', item: 'Laptop', quantity: 2, amount: 2000226 }));227 expect(data).to.exist;228 });229230 it('should reject negative amount', async () => {231 try {232 await cds.run(INSERT.into('Orders').entries({233 ID: 'uuid-002', item: 'Phone', quantity: 1, amount: -100234 }));235 expect.fail('Should have thrown');236 } catch (e) {237 expect(e.code).to.equal(400);238 expect(e.message).to.include('amount');239 }240 });241242 it('should return only active orders via API', async () => {243 const response = await cds.test.get('/odata/v4/OrderService/Orders?$filter=status eq 'ACTIVE'');244 expect(response.status).to.equal(200);245 response.data.value.forEach(order => {246 expect(order.status).to.equal('ACTIVE');247 });248 });249});250```251252### Pattern 5: OPA5 UI5 Integration Test253254```javascript255sap.ui.define([256 "sap/ui/test/opaQunit",257 "sap/ui/test/Opa5",258 "sap/ui/test/matchers/Properties",259 "sap/ui/test/actions/Press"260], function (opaTest, Opa5, Properties, Press) {261 "use strict";262263 opaTest("Should display order list", function (Given, When, Then) {264 Given.iStartMyApp();265 Then.onTheOrderList.iShouldSeeTheTable();266 });267268 opaTest("Should navigate to detail on press", function (Given, When, Then) {269 When.onTheOrderList.iPressOnFirstItem();270 Then.onTheOrderDetail.iShouldSeeTheObjectHeader();271 Then.iTeardownMyApp();272 });273274 Opa5.createPageObjects({275 onTheOrderList: {276 actions: {277 iPressOnFirstItem: function () {278 return this.waitFor({279 controlType: "sap.m.ColumnListItem",280 matchers: new Properties({ type: "Navigation" }),281 actions: new Press(),282 success: function () { Opa5.assert.ok(true, "Pressed first item"); }283 });284 }285 },286 assertions: {287 iShouldSeeTheTable: function () {288 return this.waitFor({289 id: "orderTable",290 success: function () { Opa5.assert.ok(true, "Table visible"); }291 });292 }293 }294 }295 });296});297```298299## Error Catalog300301| Error | Context | Root Cause | Fix |302|-------|---------|------------|-----|303| `CX_AUNIT_ASSERT_FAILED` | ABAP Unit assertion | Expected ≠ actual value | Check test data setup; verify production logic |304| `CL_OSQL_TEST_ENVIRONMENT CREATE failed` | SQL double | Table name wrong or not accessible | Use exact DB table name (not CDS entity name) |305| `CDS test: no data returned` | CDS test double | Redirection not enabled or wrong entity | Call `enable_double_redirection( )` after create |306| `cds.test timeout` | CAP test | Service startup slow or DB connection issue | Increase timeout; check `cds.requires` for test profile |307| `OPA5 waitFor timeout` | UI5 test | Control not rendered or wrong matcher | Increase timeout; verify control ID or matcher config |308| `ATC: check cannot be suppressed` | ATC exemption | Priority-1 findings block transport | Fix the code; P1 findings cannot be exempted |309310## Performance Tips3113121. **Test isolation** — Each test method independent; use `setup` for fresh state3132. **`RISK LEVEL HARMLESS`** — Fastest execution; no DB rollback overhead3143. **Minimize test doubles** — Only double external dependencies; test real logic3154. **Parallel ATC** — Run ATC checks in parallel in CI; each object type independently3165. **CAP test profiles** — Use `[test]` profile in `.cdsrc.json` with SQLite for speed3176. **OPA5 `autoWait`** — Enable `autoWait: true` in OPA config to avoid flaky tests3187. **Test data builders** — Create helper methods for test data; reduce duplication across test methods319320## Gotchas321322- **ABAP test double limitations**: Only works with interfaces, not concrete classes — design for dependency injection323- **SQL double scope**: `CL_OSQL_TEST_ENVIRONMENT` doubles apply to the entire test class, not per method — use `clear_doubles( )` in setup324- **CDS entity vs. DB table**: `CL_CDS_TEST_ENVIRONMENT` takes the CDS entity name; `CL_OSQL_TEST_ENVIRONMENT` takes the DB table name325- **ATC in ABAP Cloud**: Some classic ATC checks don't apply; ABAP Cloud has its own check set326- **OPA5 async pitfalls**: All OPA assertions are async; never use synchronous checks after `waitFor`327- **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