--- name: generate-cds-unit-test description: Generate an ABAP Unit test class for a CDS entity using the CDS Test Double Framework — replicates SAP Joule's "CDS Unit Test Generation" capability. Use when asked to "generate CDS unit tests", "test this CDS view", "create CDSTDF tests", or "write unit tests for ZI_SALESORDER". --- # Generate CDS Unit Test Generate an ABAP Unit test class for a CDS entity using the CDS Test Double Framework. This skill replicates SAP Joule's "CDS Unit Test Generation" capability by combining ARC-1 (SAP system access) with mcp-sap-docs (documentation & best practices). ## Smart Defaults (apply silently, do NOT ask) | Setting | Default | Rationale | |---|---|---| | Test class name | `ZCL_TEST_` | Standard convention | | Package | `$TMP` | Fast prototyping | | Test scope | All testable semantics | User can narrow after seeing the list | | Risk level | `HARMLESS` | CDS test doubles don't modify real data | | Duration | `SHORT` | Unit tests should be fast | ## Input The user provides a CDS entity name (e.g., `ZI_SALESORDER`, `ZC_TRAVEL`). Only the **CDS entity name** is required. If the user provides just an entity name, apply Smart Defaults and proceed immediately. Optionally, the user may specify: - **Test class name** (default: `ZCL_TEST_`) - **Package** (default: `$TMP`) - **Transport request** (only needed for non-`$TMP` packages) ## Step 1: Gather CDS Entity Context Read the CDS entity, its field structure, and all dependencies in just 3 tool calls. ### 1a. Read the CDS DDL source ``` SAPRead(type="DDLS", name="") ``` ### 1b. Get structured field list ``` SAPRead(type="DDLS", name="", include="elements") ``` Returns a formatted listing of all fields with key markers, aliases, association references, and expression types (calculated, case, cast, coalesce). Use this to understand the entity's structure without parsing raw DDL. ### 1c. Get dependency context (tables, views, associations) ``` SAPContext(type="DDLS", name="") ``` Automatically extracts all data sources (FROM, JOIN), associations, compositions, and projection bases from the CDS DDL. For each dependency, fetches the full source with type fallback (DDLS → TABL → STRU). This gives you: - Underlying table definitions with field types (needed for correctly-typed test data) - Other CDS view sources (needed to understand transitive logic) - Association targets For deeper dependency graphs (e.g., a consumption view → interface view → table), use `depth=2`: ``` SAPContext(type="DDLS", name="", depth=2) ``` ### 1d. (Optional) Read metadata extensions and behavior definition ``` SAPRead(type="DDLX", name="") SAPRead(type="BDEF", name="") ``` These may fail if no DDLX/BDEF exists — that's fine, skip them. Only needed if the entity has UI annotations or RAP behavior. ## Step 2: Analyze CDS Semantics and Propose Test Cases **Preferred (SAP_BASIS 8.16+ — ABAP Platform 2025 / S/4HANA 2025): ask SAP directly.** SAP analyzes the entity for you and returns the authoritative list of testable semantics — the same CDS Test Double Framework engine behind SAP Joule's CDS unit-test generation: ``` SAPDiagnose(action="cds_testcases", name="") ``` Each suggestion carries a ready `testMethod` name, a `semanticType` (`NONE` = whole view, `CALCULATION`, `CAST`, `JOIN`, `CASE`, …), a `description`, and — for calculated/cast cases — the `calculatedField`. **Use this as your proposed test-case list** for the "Output to User" block below: it is SAP's own analysis, so prefer it over hand-parsing the DDL. Map each `semanticType` onto the category table and each `testMethod` onto a `FOR TESTING` method in Step 4. If the tool returns a "requires SAP_BASIS 8.16+" message (older releases / NW 7.5x don't expose the endpoint), or you want to cross-check coverage, use the manual analysis below. **Fallback / cross-check — analyze the DDL yourself.** Analyze the CDS DDL source you read in Step 1 and identify all testable semantics. Group them by category: ### Semantic Categories to Look For | Category | What to Look For in DDL | Example Test Case | |---|---|---| | **Calculations** | Arithmetic expressions (`field1 * field2`, `field1 - field2`) | Insert known values, verify calculated result | | **CASE expressions** | `case when ... then ... else ... end` | One test per branch | | **Type casts** | `cast(field as abap.dec(15,2))`, `cast(field as abap.char(10))` | Insert value, verify cast output | | **WHERE filters** | `where status = 'A'` or `where amount > 0` | Insert matching + non-matching rows, verify only matching appear | | **HAVING clauses** | `having sum(amount) > 1000` | Insert data that triggers and doesn't trigger the filter | | **JOINs** | `inner join`, `left outer join`, `association` | Insert matching keys in both tables; test with non-matching keys for outer joins | | **Aggregations** | `sum()`, `count()`, `avg()`, `min()`, `max()` with `group by` | Insert multiple rows, verify aggregated result | | **Null handling** | `coalesce(field, 'default')`, null-safe expressions | Insert row with null field, verify default | | **Parameters** | `with parameters p_param : type` | Call view with parameter, verify filtering | | **Currency/Unit conversion** | `currency_conversion()`, `unit_conversion()` | Insert conversion data, verify result | | **String operations** | `concat()`, `substring()`, `length()` | Insert string values, verify output | | **Associations** | `association [0..*] to ...` | Insert data in both sides, verify navigation | ### Output to User Present the identified test cases as a numbered list: ``` Identified test cases for ZI_SALESORDER: 1. [CALCULATION] test_net_amount_calculation — Verify net_amount = gross_amount - discount 2. [CASE] test_status_text_open — Verify status 'O' maps to 'Open' 3. [CASE] test_status_text_closed — Verify status 'C' maps to 'Closed' 4. [JOIN] test_customer_join — Verify customer data is correctly joined 5. [WHERE] test_active_orders_filter — Verify only active orders are returned 6. [NULL] test_description_coalesce — Verify null description gets default value ``` Ask the user: **"Which test cases should I generate? (all / specific numbers / skip any?)"** If the user says "all" or doesn't respond with preferences, generate all. ## Step 3: Fetch CDS Test Double Framework Reference Use mcp-sap-docs to get the latest CDS Test Double Framework patterns. This ensures generated code follows SAP best practices. ``` search("CDS test double framework cl_cds_test_environment") ``` Also fetch: ``` search("ABAP Unit cl_abap_unit_assert") ``` Use the returned documentation to inform the generated code patterns. Key things to verify: - Correct `cl_cds_test_environment=>create()` signature - Correct `get_double()` / `insert()` pattern - Correct assertion method names ## Step 4: Generate the Test Class Generate a complete ABAP test class following this structure: ### Template ```abap "! @testing CLASS DEFINITION PUBLIC FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PUBLIC SECTION. PROTECTED SECTION. PRIVATE SECTION. CLASS-DATA environment TYPE REF TO if_cds_test_environment. CLASS-METHODS class_setup RAISING cx_static_check. CLASS-METHODS class_teardown. METHODS setup. "! METHODS FOR TESTING RAISING cx_static_check. "! METHODS FOR TESTING RAISING cx_static_check. " ... one method per selected test case ENDCLASS. CLASS IMPLEMENTATION. METHOD class_setup. environment = cl_cds_test_environment=>create( i_for_entity = '' ). ENDMETHOD. METHOD class_teardown. environment->destroy( ). ENDMETHOD. METHOD setup. environment->clear_doubles( ). ENDMETHOD. METHOD . " Arrange — prepare test data for underlying tables DATA lt_ TYPE STANDARD TABLE OF WITH EMPTY KEY. lt_
= VALUE #( ( = '' = '' ... ) ). environment->get_double( '' )->insert( lt_
). " Act — SELECT from the CDS entity under test SELECT * FROM INTO TABLE @DATA(lt_result). " Assert — verify expected behavior cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = msg = |Expected { } rows| ). cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]- exp = '' msg = '' ). ENDMETHOD. " ... more test methods ENDCLASS. ``` ### Test Data Generation Rules Follow these principles when generating test data: 1. **Minimal rows**: Use the fewest rows needed to exercise the semantic. Typically 1-3 rows per test. 2. **Obvious values**: Choose values where the expected result is trivially verifiable: - For `gross - discount = net`: use 100.00 - 10.00 = 90.00, not 37.83 - 4.27 - For CASE branches: use exactly the values from the CASE expression - For WHERE filters: one row that passes, one that doesn't 3. **Type-correct**: Match ABAP types exactly: - Use `'001'` for NUMC fields (string with leading zeros) - Use `'100.00'` for DEC/CURR fields - Use `'20260101'` for DATS fields - Use `'EUR'` for CUKY fields 4. **Key fields populated**: Always fill key fields even if not directly tested 5. **Deterministic**: Never use random or date-dependent values 6. **Isolated**: Each test method should be self-contained (clear_doubles in setup ensures this) ### Naming Conventions - Test class: `ZCL_TEST_` or `ZCL__TEST` - Test methods: `test__` (e.g., `test_status_case_open`, `test_net_amount_calc`) - Use snake_case for method names (ABAP convention for test methods) - Keep method names under 30 characters ## Step 5: Preview and Confirm Show the user the complete generated test class source code and ask: **"Here's the generated test class. Should I create it on the SAP system? (yes / edit first / cancel)"** If the user wants edits, incorporate them before proceeding. ## Step 6: Create, Activate, and Test ### 6-pre. Lint-check generated code (optional) Before writing, validate the generated code against lint rules: ``` SAPLint(action="lint", source="", name="") ``` Fix any lint findings before proceeding. Pre-write lint validation also runs automatically when enabled (default: on). ### 6a. Create the test class ``` SAPWrite(action="create", type="CLAS", name="", source="", package="", transport="") ``` ### 6b. Update the source (if create only scaffolds) ``` SAPWrite(action="update", type="CLAS", name="", source="", transport="") ``` ### 6c. Activate the test class ``` SAPActivate(type="CLAS", name="") ``` Activation returns structured responses with detailed error/warning messages including line numbers. Use these to pinpoint exact issues. ### 6d. Run the unit tests ``` SAPDiagnose(action="unittest", type="CLAS", name="") ``` ### 6e. Report results Show the user: - Number of tests passed / failed / skipped - For any failures: the assertion message and which test method failed - Suggested fixes for any failures If tests fail: 1. Analyze the failure message 2. Determine if it's a test data issue, assertion issue, or CDS logic issue 3. Fix the test method using `SAPWrite(action="edit_method", ...)` 4. Re-activate and re-run ## Error Handling ### Common Issues and Fixes | Error | Cause | Fix | |---|---|---| | `CX_CDS_TEST_ENVIRONMENT` during class_setup | Entity name wrong or not active | Verify entity name, check activation status | | Type mismatch in VALUE constructor | Wrong field type in test data | Check DD03L or table structure for correct types | | `GET_DOUBLE` fails for a data source | Data source name doesn't match | Check CDS DDL for exact data source names (case-sensitive!) | | Assertion fails with unexpected values | Test data or expected result wrong | Re-check CDS logic, adjust test data or assertion | | Activation error: "test relation not found" | `@testing` annotation points to wrong entity | Fix entity name in `"! @testing` comment | | Class exists already | Test class already created | Use `SAPWrite(action="update", ...)` instead | ## Notes ### BTP vs On-Premise Differences - **BTP**: Only Z*/Y* classes can be created. Standard SAP tables cannot be queried via DD03L. Use released CDS entities for type reference. - **On-Premise**: Full access to DD03L, DD02L for field catalogs. Can query TADIR for object existence checks. ### What This Skill Does NOT Do - **Performance testing**: CDS Test Doubles are for logic testing, not performance - **Authorization testing**: DCL/CDS access control testing requires separate patterns - **Integration testing**: This generates isolated unit tests, not end-to-end flows - **RAP behavior testing**: For validations/actions/determinations, use the RAP BO Test Double Framework instead ### When to Use This Skill - When a CDS entity has non-trivial logic (calculations, CASE, filters, joins) - When the user wants basic test coverage as a starting point - When refactoring CDS logic and wanting regression tests - NOT for trivial projections that just rename fields — if analysis in Step 2 finds no testable semantics (just field renames with no calculations, CASE, WHERE, JOINs, or aggregations), warn the user: *"This CDS view is a simple projection with no testable logic. Unit tests would only verify that fields are correctly aliased, which provides minimal value. Consider testing the underlying view instead."*