8.4 KiB
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
- Test one thing at a time
- Use descriptive names
- Follow AAA pattern (Arrange, Act, Assert)
- Mock external dependencies
- Keep tests fast
Integration Tests
- Test real interactions
- Use test data that represents production
- Clean up after tests
- Test error paths
UI Tests
- Test user journeys
- Use page objects pattern
- Keep tests maintainable
- Test accessibility