Files
gh-secondsky-sap-skills-ski…/references/testing.md
2025-11-30 08:55:02 +08:00

8.4 KiB

SAP BTP Testing Reference

Overview

Quality assurance requires comprehensive testing across multiple dimensions including UI, usability, performance, and unit testing.

Testing Strategy

Test Pyramid

        /\
       /  \     E2E/UI Tests (few, slow)
      /----\
     /      \   Integration Tests (some)
    /--------\
   /          \  Unit Tests (many, fast)
  /------------\

Benefits of Unit Testing

  • Detect issues fast
  • Better maintainable code
  • More understandable code
  • CI/CD integration
  • Regression prevention

CAP Testing

Jest Setup

// package.json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  },
  "devDependencies": {
    "jest": "^29.0.0",
    "@sap/cds-dk": "^7.0.0"
  }
}

Unit Test Example

// test/unit/catalog-service.test.js
const cds = require('@sap/cds');

describe('CatalogService', () => {
  let srv;

  beforeAll(async () => {
    srv = await cds.connect.to('CatalogService');
  });

  afterAll(async () => {
    await cds.disconnect();
  });

  describe('READ Books', () => {
    it('should return all books', async () => {
      const books = await srv.read('Books');
      expect(books).toBeDefined();
      expect(Array.isArray(books)).toBe(true);
    });

    it('should filter by title', async () => {
      const books = await srv.read('Books').where({ title: 'Test Book' });
      expect(books.length).toBeLessThanOrEqual(1);
    });
  });

  describe('CREATE Books', () => {
    it('should create a book', async () => {
      const book = await srv.create('Books', {
        title: 'New Book',
        stock: 10
      });
      expect(book.ID).toBeDefined();
      expect(book.title).toBe('New Book');
    });

    it('should reject invalid data', async () => {
      await expect(srv.create('Books', {}))
        .rejects.toThrow();
    });
  });
});

Integration Test Example

// test/integration/api.test.js
const cds = require('@sap/cds');
const { GET, POST, PATCH, DELETE } = cds.test(__dirname + '/..');

describe('API Integration', () => {
  it('GET /catalog/Books', async () => {
    const { status, data } = await GET('/catalog/Books');
    expect(status).toBe(200);
    expect(data.value).toBeDefined();
  });

  it('POST /catalog/Books', async () => {
    const { status, data } = await POST('/catalog/Books', {
      title: 'Test Book',
      stock: 5
    });
    expect(status).toBe(201);
    expect(data.ID).toBeDefined();
  });

  it('GET /catalog/Books/:id', async () => {
    const { data: created } = await POST('/catalog/Books', { title: 'Test' });
    const { status, data } = await GET(`/catalog/Books(${created.ID})`);
    expect(status).toBe(200);
    expect(data.title).toBe('Test');
  });
});

SAPUI5 Testing

QUnit Framework

Best Practice: Write small, focused tests

// webapp/test/unit/model/formatter.js
sap.ui.define([
  "my/app/model/formatter"
], function(formatter) {
  "use strict";

  QUnit.module("formatter");

  QUnit.test("formatStatus", function(assert) {
    assert.strictEqual(
      formatter.formatStatus("OPEN"),
      "Open",
      "Status formatted correctly"
    );
  });

  QUnit.test("formatDate", function(assert) {
    const date = new Date("2024-01-15");
    assert.strictEqual(
      formatter.formatDate(date),
      "Jan 15, 2024",
      "Date formatted correctly"
    );
  });
});

OPA5 Integration Tests

// webapp/test/integration/pages/List.js
sap.ui.define([
  "sap/ui/test/Opa5",
  "sap/ui/test/actions/Press"
], function(Opa5, Press) {
  "use strict";

  Opa5.createPageObjects({
    onTheListPage: {
      actions: {
        iPressOnFirstItem: function() {
          return this.waitFor({
            controlType: "sap.m.ObjectListItem",
            success: function(aItems) {
              new Press().executeOn(aItems[0]);
            }
          });
        }
      },
      assertions: {
        iShouldSeeTheList: function() {
          return this.waitFor({
            id: "list",
            success: function() {
              Opa5.assert.ok(true, "List is visible");
            }
          });
        }
      }
    }
  });
});

OPA5 Journey

// webapp/test/integration/AllJourneys.js
sap.ui.define([
  "sap/ui/test/Opa5",
  "./pages/List",
  "./ListJourney"
], function(Opa5) {
  "use strict";

  Opa5.extendConfig({
    viewNamespace: "my.app.view.",
    autoWait: true
  });
});

// webapp/test/integration/ListJourney.js
sap.ui.define([
  "sap/ui/test/opaQunit"
], function(opaTest) {
  "use strict";

  QUnit.module("List Journey");

  opaTest("Should see the list", function(Given, When, Then) {
    Given.iStartMyApp();
    Then.onTheListPage.iShouldSeeTheList();
    Given.iTeardownMyApp();
  });

  opaTest("Should navigate to detail", function(Given, When, Then) {
    Given.iStartMyApp();
    When.onTheListPage.iPressOnFirstItem();
    Then.onTheDetailPage.iShouldSeeTheDetail();
    Given.iTeardownMyApp();
  });
});

ABAP Testing

ABAP Unit

CLASS ltcl_travel DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    CLASS-DATA: mo_environment TYPE REF TO if_cds_test_environment.
    DATA: mo_cut TYPE REF TO zcl_travel_handler.

    CLASS-METHODS class_setup.
    CLASS-METHODS class_teardown.
    METHODS setup.
    METHODS test_validate_dates FOR TESTING.
    METHODS test_calculate_total FOR TESTING.
ENDCLASS.

CLASS ltcl_travel IMPLEMENTATION.

  METHOD class_setup.
    mo_environment = cl_cds_test_environment=>create(
      i_for_entity = 'ZI_TRAVEL'
    ).
  ENDMETHOD.

  METHOD class_teardown.
    mo_environment->destroy( ).
  ENDMETHOD.

  METHOD setup.
    mo_environment->clear_doubles( ).
    mo_cut = NEW #( ).
  ENDMETHOD.

  METHOD test_validate_dates.
    " Given
    DATA(lv_begin) = cl_abap_context_info=>get_system_date( ).
    DATA(lv_end) = lv_begin + 7.

    " When
    DATA(lv_valid) = mo_cut->validate_dates(
      iv_begin = lv_begin
      iv_end = lv_end
    ).

    " Then
    cl_abap_unit_assert=>assert_true( lv_valid ).
  ENDMETHOD.

  METHOD test_calculate_total.
    " Given
    DATA(lv_booking_fee) = CONV decfloat16( '50.00' ).
    DATA(lv_price) = CONV decfloat16( '500.00' ).

    " When
    DATA(lv_total) = mo_cut->calculate_total(
      iv_booking_fee = lv_booking_fee
      iv_price = lv_price
    ).

    " Then
    cl_abap_unit_assert=>assert_equals(
      exp = '550.00'
      act = lv_total
    ).
  ENDMETHOD.

ENDCLASS.

CDS Test Double Framework

" Create test doubles for CDS entities
DATA(lo_env) = cl_cds_test_environment=>create(
  i_for_entity = 'ZI_TRAVEL'
).

" Insert test data
DATA: lt_travel TYPE TABLE OF ztravel.
lt_travel = VALUE #(
  ( travel_uuid = cl_uuid_factory=>create_uuid_c32( )
    travel_id = '1'
    customer_id = 'CUST001'
    begin_date = '20240101'
    end_date = '20240108' )
).
lo_env->insert_test_data( lt_travel ).

" Run tests...

" Clear after tests
lo_env->clear_doubles( ).

Test Automation

CI/CD Integration

# .pipeline/config.yml
stages:
  Additional Unit Tests:
    npmExecuteScripts: true
    npmScripts:
      - test

  Integration Tests:
    npmExecuteScripts: true
    npmScripts:
      - test:integration

  UI Tests:
    karmaExecuteTests: true

Code Coverage

// jest.config.js
module.exports = {
  collectCoverage: true,
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
};

Best Practices

Unit Tests

  1. Test one thing at a time
  2. Use descriptive names
  3. Follow AAA pattern (Arrange, Act, Assert)
  4. Mock external dependencies
  5. Keep tests fast

Integration Tests

  1. Test real interactions
  2. Use test data that represents production
  3. Clean up after tests
  4. Test error paths

UI Tests

  1. Test user journeys
  2. Use page objects pattern
  3. Keep tests maintainable
  4. Test accessibility

Source Documentation