ABAP Development for SAP S4HANA (SAP PRESS) (第13页)

第 372 页 / 共 465 页

10 Automated Testing

Technique Description

Object factory class The class uses an object factory to fetch its dependencies. At

test runtime, the object factory can be configured to return test

doubles instead of the actual dependencies.

Table 10.1 Dependency Injection Techniques (Cont.)

In our example, we’ll use the object factory class dependency injection technique

because it provides a single point of injection for test doubles in unit and integration

tests, for instance, on the package level. Figure 10.13 gives an overview of the pattern

in the form of a UML class diagram. Instead of directly instantiating the vendor ser-

vice (zcl_vendor_service), the calculate() method fetches an instance of the service

via the zcl_object_factory class’s static get_vendor_service() method. The zcl_ven-

dor_service will only be instantiable by the zcl_object_factory class. We therefore

must make its creation private and add the factory class as a friend. Additionally, we

introduce an interface for our vendor rating class (zif_vendor_service), which makes

its concrete implementation replaceable as the calculate() method no longer

depends on a concrete implementation but on an abstraction (dependency inversion

principle). At test runtime, we can then use an injector class (ztcl_object_injector),

which is a friend of the object factory (zcl_object_factory), to inject a test double for

the vendor service (ztd_vendor_service) by overwriting its private static go_vendor_

service attribute with the test double.

In Listing 10.2, you can see the full implementation of the factory class for our exam-

ple. The get_vendor_service() creates a new instance, if not yet done, and saves it in

the static go_vendor_service class attribute. Note also that we don’t return a refer-

ence to the concrete vendor service implementation but a reference to its interface.

CLASS zcl_object_factory DEFINITION

PUBLIC

FINAL

CREATE PRIVATE

GLOBAL FRIENDS ztcl_object_injector.

PUBLIC SECTION.

CLASS-METHODS get_vendor_service

RETURNING VALUE(ro_vendor_service) TYPE REF TO zif_vendor_service.

PROTECTED SECTION.

PRIVATE SECTION.

CLASS-DATA go_vendor_service TYPE REF TO zif_vendor_service.

372

Technique Description
Object factory class The class uses an object factory to fetch its dependencies. At test runtime, the object factory can be configured to return test doubles instead of the actual dependencies.

第 373 页 / 共 465 页

10.1 Backend Test Automation

ENDCLASS.

CLASS zcl_object_factory IMPLEMENTATION.

METHOD get_vendor_service.

IF go_vendor_service ISN’T BOUND.

go_vendor_service = NEW zcl_vendor_service( ).

ENDIF.

ro_vendor_service = go_vendor_service.

ENDMETHOD.

ENDCLASS.

Listing 10.2 Object Factory Class Implementation

<<interface>> Create Private and

if_sadl_exit_calc_element_read ztcl_object_injector is Friend

+ get_calculation_info()

+ calculate()

zcl_object_factory

Inject test - go_vendor_service: zif_vendor_service

double as

+ get_vendor_service(): zif_vendor_service

Friend

zcl_vendor_rating_calc_exit

+ get_calculation_info()

+ calculate()

At test runtime

<<interface>>

zif_vendor_service

+ get_vendor_rating(vendor)

ztcl_object_injector

+ inject_vendor_service(vendorService)

Create

Private and zcl_vendor_service

For Testing

+ get_vendor_rating(vendor)

ztd_vendor_service

+ get_vendor_rating(vendor)

Create Private and

zcl_object_factory is Friend

Figure 10.13 Object Factory Class Dependency Injection Technique for Our Vendor Rating

Example

373

<<interface>> if_sadl_exit_calc_element_read
+ get_calculation_info() + calculate()
zcl_object_factory
- go_vendor_service: zif_vendor_service
+ get_vendor_service(): zif_vendor_service
zcl_vendor_rating_calc_exit
+ get_calculation_info() + calculate()
<<interface>> zif_vendor_service
+ get_vendor_rating(vendor)
ztcl_object_injector
+ inject_vendor_service(vendorService)
zcl_vendor_service
+ get_vendor_rating(vendor)
ztd_vendor_service
+ get_vendor_rating(vendor)

第 374 页 / 共 465 页

10 Automated Testing

Listing 10.3 shows the full implementation of the test double injector class we want to

use at test runtime to replace the actual vendor service with a test double vendor ser-

vice. For that purpose, it provides the inject_vendor_service() method to which we

can pass a test double object (io_vendor_service_td). As the injector and factory class

are friends, it can overwrite the private static go_vendor_service attribute of the fac-

tory class. This makes the factory class return the test double on future requests

instead of the actual implementation of the vendor service.

CLASS ztcl_object_injector DEFINITION

PUBLIC

FOR TESTING

FINAL

CREATE PRIVATE .

PUBLIC SECTION.

CLASS-METHODS inject_vendor_service

IMPORTING

io_vendor_service_td

TYPE REF TO zif_vendor_service.

PROTECTED SECTION.

PRIVATE SECTION.

ENDCLASS.

CLASS ztcl_object_injector IMPLEMENTATION.

METHOD inject_vendor_service.

zcl_object_factory=>go_vendor_service =

io_vendor_service_td.

ENDMETHOD.

ENDCLASS.

Listing 10.3 Test Double Injector Class Implementation

To create a test double, we can either use the ABAP Test Double Framework or cre-

ate our own local test double class that implements the zif_vendor_service inter-

face. In general, test doubles can be classified as one of the test double types listed in

Table 10.2.

Test Double Type Description

Dummy A dummy satisfies syntax requirements, for instance, implements the

required interface, but does nothing else.

Table 10.2 Different Test Double Types

374

Test Double Type Description
Dummy A dummy satisfies syntax requirements, for instance, implements the required interface, but does nothing else.

第 375 页 / 共 465 页

10.1 Backend Test Automation

Test Double Type Description

Fake A fake is a simple lightweight implementation of a DOC.

Stub A stub replaces a DOC and provides test-specific indirect input to the

CUT, for instance, returns predefined values that affect the behavior

of the CUT in a certain way.

Spy A spy captures indirect output calls to other components for later veri-

fication, for instance, if you want to make sure that a certain method

is called by the CUT.

Mock A mock is an extension of fake, spy, and stub. It verifies indirect input

and/or output according to given expectations.

Table 10.2 Different Test Double Types (Cont.)

Listing 10.4 shows how we can create a stub for the vendor service using the ABAP

Test Double Framework and inject it into the object factory. The code must be exe-

cuted before we call the CUT. In our case, this is the calculate() method of the test. If

we look back to our initial test method in Figure 10.11, the code sequence must be

inserted directly before the Act comment.

" (1) Create the test double of interface type

DATA tdo_vendor_service

TYPE REF TO zif_vendor_service.

tdo_vendor_service ?= cl_abap_testdouble=>create(

'zif_vendor_service' ).

" (2) Configure the test double behavior

cl_abap_testdouble=>configure_call(

tdo_vendor_service )->returning( 5 ).

" (3) Configure the test double method

tdo_vendor_service->get_vendor_rating(

iv_vendor = 'VendorA' ).

" (4) Inject the test double

ztcl_object_injector=>inject_vendor_service(

tdo_vendor_service ).

Listing 10.4 Create a Stub for the Vendor Service DOC Using the ABAP Test Double Frame-

work and Inject It into the Object Factory

375

Test Double Type Description
Fake A fake is a simple lightweight implementation of a DOC.
Stub A stub replaces a DOC and provides test-specific indirect input to the CUT, for instance, returns predefined values that affect the behavior of the CUT in a certain way.
Spy A spy captures indirect output calls to other components for later veri- fication, for instance, if you want to make sure that a certain method is called by the CUT.
Mock A mock is an extension of fake, spy, and stub. It verifies indirect input and/or output according to given expectations.

第 376 页 / 共 465 页

10 Automated Testing

In Figure 10.14, you can see the final implementation of the calculate() method,

which passes our test and fetches an instance of the vendor service (zif_vendor_ser-

vice) via the object factory (zcl_object_factory).

Figure 10.14 Final Implementation of the calculate() Method

10.1.2 Unit Testing (Core Data Services Test Double Framework)

We’ve already seen examples of the CDS unit test in Chapter 4 where we wrote tests

for several calculated fields contained in our purchase document and item CDS

views. In general, you should write unit tests for CDS views that contain any kind of

calculation logic, for instance, calculations, conversions, or Boolean expressions. You

shouldn’t write tests for views that simply do projections on database tables and

don’t contain any logic. Unit tests for CDS views are also based on ABAP Unit and the

ABAP Unit Runner. However, to cut and replace first-level DOC CDS views or database

tables of a CDS entity CUT, we can’t use conventional ABAP dependency injection

methods because CDS view logic is executed in the underlying database and there-

fore also the DOCs must be replaced inside the database (Figure 10.15). Moreover, we

must also be able to insert test-specific data into the test doubles so that this data is

returned when executing the CUT.

These challenges are addressed by the CDS Test Double Framework, which has been

available since SAP S/4HANA OP1610 SPS 03. The CDS Test Double Framework

enables unit testing of CDS views by doing the following:

(cid:2) Creating updatable doubles for each DOC of the CDS entity CUT

(cid:2) Creating a copy of the CDS entity CUT and replacing its DOC with the previously

created updatable doubles while preserving the CUT’s actual logic

It’s also possible to turn on/off the corresponding data control language (DCL) of the

CDS entity CUT using the CDS Test Double Framework. Unless you really want to

376

第 377 页 / 共 465 页

10.1 Backend Test Automation

implement authorization tests, it’s recommended to switch it off for unit testing to

truly isolate the CUT.

case when OverallPrice > 1000

then 'X' else '' end

as IsApprovalRequired

input output

CDS View Under Test

Dependent CDS Dependent CDS

… DCL

View 1 View 2

Figure 10.15 Cutting Dependencies to DOC CDS Views and Replacing Them with Test Doubles

We’ve already shown you in Chapter 4 how you can create a unit test for a CDS view

using the wizard approach: right-click on a CDS view in the Project Explorer, and

choose New Test Class. Another, more manual, approach is to use the Eclipse quick

assist. You first must create a global class, including a local test class, which is linked

to the CDS view using a test reference (! @testing) by pressing (Ctrl)+(1). You can

then trigger the quick assist popover and selectively generate the different parts of

the CDS unit test, for instance, the CDS test fixtures (see Figure 10.16).

Figure 10.16 Linking the Test Class to the CDS Repository Object Using a Test

Reference (! @testing) and Triggering the Quick Assist Using a Keyboard Shortcut

377

第 378 页 / 共 465 页

10 Automated Testing

In Listing 10.5, you can see a minimal CDS unit test skeleton for a CDS entity or view

CUT (<CDS_ENTITY_UNDER_TEST>). The cl_cds_test_environment=>create( '<CDS_ENTITY_

UNDER_TEST>' ) call inside the class_setup method will create the test environment by

first creating the updatable test doubles for the first-level dependencies of the CUT.

Then it will clone the CUT itself and replace its dependencies with the test doubles.

Before each test method, the data contained in the test doubles is cleared in the setup

test fixture, which is called before each test method. After all tests have been exe-

cuted, the test doubles and the clone of the CUT will be destroyed using the static

class_teardown fixture method. The actual test methods (some_test_method) always

follow the same pattern:

1. Given/Arrange: Insert data into the test doubles (only as much as required for the

test).

2. When/Act: Do an OpenSQL select on the CDS entity and store the result in an inter-

nal ABAP table.

3. Then/Assert: Assert that the actual result (act_results) is as expected by using one

of the methods provided by the CL_ABAP_UNIT_ASSERT class.

"! @testing <CDS_ENTITY_UNDER_TEST>

CLASS ltcl_<CDS_ENTITY_UNDER_TEST> DEFINITION FINAL FOR TESTING

DURATION SHORT

RISK LEVEL HARMLESS.

PRIVATE SECTION.

CLASS-DATA: environment TYPE REF TO if_cds_test_environment.

CLASS-METHODS: class_setup RAISING cx_static_check, class_teardown.

METHODS setup.

METHODS some_test_method FOR TESTING RAISING cx_static_check.

ENDCLASS.

CLASS ltcl_<CDS_VIEW_UNDER_TEST> IMPLEMENTATION.

METHOD class_setup.

environment = cl_cds_test_environment=>create( '<CDS_ENTITY_UNDER_TEST>' ).

ENDMETHOD.

METHOD setup.

environment->clear_doubles( ).

ENDMETHOD.

378

第 379 页 / 共 465 页

10.1 Backend Test Automation

METHOD class_teardown.

environment->destroy( ).

ENDMETHOD.

METHOD some_test_method.

" given

lt_table_stub = VALUE #( ( ) ). “ TODO: Specify test data

environment->insert_test_data( i_data = lt_table_stub ).

" when

SELECT * FROM <CDS_ENTITY_UNDER_TEST> INTO TABLE

@DATA(act_results).

" then

cl_abap_unit_assert=>fail(

msg = 'Place your assertions here' ).

ENDMETHOD.

ENDCLASS.

Listing 10.5 Minimal CDS Unit Test Skeleton for a CDS Entity CUT

It’s also possible to write hierarchy tests for CDS views where the first-level depen-

dencies of the CDS entity CUT are no longer automatically identified and replaced

with test doubles, but developers can specify views or tables deeper in the CDS hier-

archy to be replaced with test doubles. Figure 10.17 shows an example; instead of

replacing the first-level DOC CDS views DOC 1 and 2 automatically, CDS hierarchy

tests allow us to replace the DB Tables 1 and 2 to also include the DOC views in our

test. This is then rather an integration test than a unit test but allows us to test a

defined part of the CDS view stack instead of a single CDS view.

In addition to the CUT, a hierarchy test requires the specification of a dependency list

(i_dependency_list), which must contain a view or table for each hierarchy path (see

Listing 10.6) or an exception is thrown. The idea is that the test should never hit the

actual database to isolate the tests from any real data. Updatable test doubles will be

created for the selected dependencies, and entities above the selected dependencies

are cloned to select from the test doubles while preserving their logic. The rest of the

test structure is similar to standard CDS unit tests, so we’re not going to repeat that

here.

cl_cds_test_environment=>create( i_for_entity = '<CDS_ENTITY_UNDER_TEST>' i_

dependency_list =

379

第 380 页 / 共 465 页

10 Automated Testing

VALUE #( ( name = '<DB Table 1>' type = 'TABLE' )( name =

'<DB Table 2>' type = 'TABLE' ) ) ).

Listing 10.6 Add Dependency List to Environment Creation

CDS entity under

test (CUT)

Depended-on Depended-on

CDS view (DOC) 1 CDS view (DOC) 2

DB Table 1 DB Table 2

Figure 10.17 Replacing DB Tables 1 and 2 to Include DOC 1 and 2 in the CDS Hierarchy Test

To test code-pushdowns to SAP HANA using OpenSQL, you can use the OpenSQL Test

Double Framework (Listing 10.7). Like the hierarchy CDS Test Double Framework, you

must specify a list of dependencies you want to create updatable test doubles for.

cl_osql_test_environment=>create( i_dependency_list = VALUE #

( ( '<View1>' ) ( '<View2>' ) ) ).

Listing 10.7 Create Test Doubles Using the OpenSQL Test Double Framework

10.2 Frontend Test Automation

The UI part of SAP S/4HANA applications is based on the SAPUI5 JavaScript frame-

work and requires JavaScript-compatible unit and integration testing frameworks for

writing automated tests. Consequently, the SAPUI5 SDK comes with QUnit and

Sinon.JS for writing unit tests and OPA5 accompanied by a mock server for writing

UI integration tests. In the following, you’ll learn how to write a UI unit test for a

380

第 381 页 / 共 465 页

10.2 Frontend Test Automation

JavaScript method using QUnit and isolate it from a dependency using Sinon.JS.

Additionally, we’ll look at OPA5 for writing integration tests.

10.2.1 Unit Testing (QUnit)

Let’s say we want to implement a new column in our SAP Fiori elements list report

application from Chapter 4 that displays the total delivery time for a purchase docu-

ment in days (as shown in Figure 10.18). We’ll define the total delivery time for a pur-

chase document as the delivery time of its purchase document item with the longest

delivery time; for instance, if a purchase document has two items with delivery dates

20 and 30 days from now, we’ll consider 30 days as the total delivery time of the pur-

chase document.

Figure 10.18 New Delivery Time Column Displaying the Total Delivery Time of All Purchase

Document Items

We can implement the new Delivery Time column as a new UI column extension just

like we implemented the Budget Share column in Chapter 4, Section 4.7. We’ll add the

new field to our already-existing column and cell fragment definitions and calculate

its value using the formatter (formatter.js). This time, we won’t do the calculation

directly in the formatter, but the formatter will delegate the calculation to the getDe-

liveryTime() method of a DeliveryTimeHelper.js class with a dependency to a deliv-

eryDateService.js object (see Figure 10.20 later in this section for the final

implementation). The deliveryDateService.js dependency is injected using con-

structor injection. We treat the service like a black box. We just pass the purchase doc-

ument ID to its getDeliveryDatesOfItems() method, and it will return the delivery

dates of all purchase document items as an array of date objects. The service itself

might therefore call another REST service that is providing this data. In the Delivery-

TimeHelper.js, we then must identify the max delivery date among the item delivery

381

第 382 页 / 共 465 页

10 Automated Testing

dates, calculate the days between today and the max delivery date, and format the

result as a string, for instance, 18 days will be 18d.

In Figure 10.19, you can see the unit test for the delivery time calculation. Like ABAP

unit, QUnit also provides test fixtures or hooks (before,beforeEach,afterEach,after)

that are executed before and after the test suite or each unit test execution. In the

beforeEach method, we’re using Sinon.JS to create a stub for the deliveryDateSer-

vice.js object’s getDeliveryDatesOfItems method.

Figure 10.19 Isolated Unit Test for the DeliveryTimeHelper.js Class’s getDeliveryTime

Method

We’ll make it return two dates (today + 5 days, today + 6 days) in an array. In the

afterEach method, we’ll restore the stubbed method after each test execution. In

the actual test case, we’ll create an instance of the DeliveryTimeHelper.js class and

inject the deliveryDateService.js object (with the stubbed getDeliveryDatesOfItems

382

第 383 页 / 共 465 页

10.2 Frontend Test Automation

method) using constructor injection. Then we call the getDeliveryTime method,

which is the CUT and make sure that the returned string equals 6d as the total deliv-

ery time as the purchase document should be equal to the maximum delivery time

among its items.

In the preceding example, you’ve seen how you can use Sinon.JS to isolate CUT from

dependencies in JavaScript using its stub application programming interface (API). In

addition to its stub API, Sinon.JS also provides an API for creating mocks and spies.

Note

For further information concerning Sinon.JS, refer to the official documentation at

https://sinonjs.org.

Figure 10.20 Final Implementation of the DeliveryTimeHelper.js class. Integration Testing

(OPA5)

383

Note For further information concerning Sinon.JS, refer to the official documentation at
https://sinonjs.org.

第 384 页 / 共 465 页

10 Automated Testing

The OPA5 framework enables us to write integration tests for the UI part of our SAP

S/4HANA application (see Figure 10.20). You’ve already seen an example of an OPA5

test in Chapter 7, Section 7.2.4, where we wrote a test for the creation of a purchase

document. If the test needs to be isolated from the backend OData service, we can

additionally use the sap.ui.core.util.MockServer, which will interfere with the stan-

dard OData requests sent from the frontend and return mock data instead.

Figure 10.21 shows the overall structure of an OPA5 test suite. OPA5 can use QUnit as

a test runner, and the usual entry point for an OPA5 integration test suite is therefore

an opaTests.qunit.html file that will load QUnit, run the tests, and display the result.

The tests will be organized in journeys that will be collected in an allJourneys.js file,

which is then the only file the opaTests.qunit.html file needs to load. Journeys

describe tests in a behavior-driven development (BDD) style, which nondevelopers

will be able to understand. The concrete implementations of these behavior specifi-

cations are delegated to the page objects.

Common Page Object

opaTests.qunit.html

(Common.js)

allJourneys.js

Create Purchase Document Journey Create Purchase Document Page Object

(createPurchaseDocumentJourney.js) (CreatePurchaseDocument.js)

…

Display Purchase Documents

(DisplayPurchaseDocuments.js)

Figure 10.21 Structure of OPA5 Integration Tests

Journeys

A journey describes the test in an understandable high-level form and is always orga-

nized according to the Given-When-Then pattern, as shown in Listing 10.8. The

iStartTheApp() arrangement is usually implemented in the common page object

(Common.js) as it will be used in all journeys. The app can either be started in a UICom-

ponent (recommended when using a mock server) or in an IFrame. The When object

provides access to the actions of loaded page objects, and the Then object provides

access to their assertions.

384

第 385 页 / 共 465 页

10.2 Frontend Test Automation

sap.ui.define([

"sap/ui/test/opaQunit"

], function (opaTest) {

"use strict";

QUnit.module("<MODULE_DESCRIPTION>");

opaTest("<TEST_DESCRIPTION>", function (Given, When, Then) {

Given.iStartTheApp();

When.onThe<PAGE_OBJECT>.<iDoSomething>;

Then.onThe<PAGE_OBJECT>.<iCheckSomething>;

});

Listing 10.8 Basic Structure of an OPA5 Journey

Page Objects

Page objects contain the actual implementation of arrangements, actions, and asser-

tions used in journeys. Listing 10.9 shows the basic structure of a page object. It has a

baseClass property to inherit arrangements, actions, and assertions needed within

more than one test case from a common base object. The actions object contains

arrangements and actions, and the assertions object contains assertions. In action

functions, we’ll usually first try to find a certain UI element and then trigger an

action. Predefined matchers and actions can be found below the sap.ui.test.match-

ers and sap.ui.test.actions namespaces, for instance, sap.ui.test.matchers.Prop-

erties or sap.ui.test.actions.Press. In assertion functions, we’ll make sure that a

certain element is present (or not) on the UI and/or has certain properties. If the UI

doesn’t look like as expected, the assertion will time out and fail.

sap.ui.define([

"sap/ui/test/Opa5",

"./Common",

"sap/ui/test/actions/Press",

"sap/ui/test/matchers/Properties",

], function (Opa5, Common, Press, Properties) {

"use strict";

Opa5.createPageObjects({

onThe<PAGE_OBJECT>: {

baseClass: Common,

actions: {

385

第 386 页 / 共 465 页

10 Automated Testing

<iDoSomething>: function(){

return this.waitFor({

id:”<CONTROL_ID>”,

viewName:”<VIEW_NAME>”,

actions: new Press(),

errorMessage: "<SOME_ERROR_MESSAGE>"

});

}

},

assertions: {

<iCheckSomething>: function(){

return this.waitFor({

id:”<CONTROL_ID>”,

viewName:”<VIEW_NAME>”,

matchers : new Properties({

isSelected: true

}),

success: function () {

Opa5.assert.ok("true", "<SOME_SUCCESS_MESSAGE>");

},

errorMessage: "<SOME_ERROR_MESSAGE>"

});

}

}

}

});

});

Listing 10.9 Structure of a Page Object Containing Actions and Assertions

10.3 End-to-End Test Automation Tools

To test the interaction and functioning of all components of software ranging from the

UI to the database access, it’s necessary to provide a small set of meaningful end-to-end

(E2E) tests covering the core processes of applications. As the UI part of SAP S/4HANA

applications runs in browsers, we can make use of standard browser testing tools to cre-

ate E2E tests. The de facto standard toolset for automating browsers is Selenium. In the

following we’re going to create an E2E test for the same scenario as in Chapter 7 where

386

第 387 页 / 共 465 页

10.3 End-to-End Test Automation Tools

we developed an OPA5 integration test to create a purchase document and verify its

successful creation in the subsequently displayed table showing all available purchase

documents. However, in this case, we’re not going to use a mock server because we’re

writing an E2E test and want to verify that everything is working, which also includes

our OData backend service.

10.3.1 Setting Up Nightwatch.js

We’ll create our E2E tests using Nightwatch.js, which is a wrapper around Selenium/

Web Driver that allows us to write the tests in JavaScript (Node.js). We can install

Nightwatch.js via node package manager (npm) and save its dependency in our pack-

age.json file using the following command: npm install –save nightwatch. Addition-

ally, we must download the Selenium server Java Archive (.jar) (http://selenium-

release.storage.googleapis.com/index.html), which Nightwatch uses to connect to the

various browsers. In our case, we also must download the ChromeDriver (https://

sites.google.com/a/chromium.org/chromedriver/downloads) as we want to run our

tests in the Chrome browser.

The Nightwatch.js test runner by default expects a nightwatch.json configuration

file from the current folder. Listing 10.10 shows our simple configuration file. The

src_folders property specifies that our tests can be found in the tests folder. In the

selenium object, we tell Nightwatch from where to start the Selenium server pro-

cess (server_path) and the browser driver Selenium will use, in our case, the

ChromeDriver (webdriver.chrome.driver), and where to find it.

{

"src_folders" : ["tests"],

"output_folder" : "reports",

"selenium" : {

"start_process" : true,

"server_path" : "./bin/selenium-server-standalone-3.9.1.jar",

"port" : 4444,

"cli_args" : {

"webdriver.chrome.driver" : "./bin/chromedriver.exe"

}

},

"test_settings" : {

"default" : {

"launch_url" : "http://localhost",

387

第 388 页 / 共 465 页

10 Automated Testing

"selenium_port" : 4444,

"selenium_host" : "localhost",

"silent": true,

"screenshots" : {

"enabled" : false,

"path" : ""

},

"desiredCapabilities": {

"browserName": "chrome"

}

}

}

}

Listing 10.10 Our nightwatch.json Configuration File

Note

For more detailed instructions on how to set up Nightwatch.js, refer to the official

Getting Started guide at http://nightwatchjs.org/gettingstarted.

10.3.2 Creating the Create Purchase Document End-to-End Nightwatch.js Test

We can then create a Nightwatch test that we’ll call createPurchaseDocument.js as

shown in Figure 10.22. As in our OPA5 test developed in Chapter 7, Section 7.2.4, we

first navigate to the create screen of our freestyle application. Then we wait for 6 sec-

onds (6000 milliseconds) for the appearance of the Purchase Document ID input

field. If it shows up, we enter data in our mandatory ID and description fields and

click the Save button. So far, we’ve used Cascading Style Sheet (CSS) selectors to select

HTML elements. By calling the useXpath() method, we switch to XPath selector usage

and check whether the previously entered Nightwatch.js test doc description is pres-

ent in a link (a) inside a div with a sapMObjectIdentifierTitle class attribute value. As

a reminder, when we save a purchase document in our freestyle application, we’re

immediately forwarded to a list report displaying all purchase documents in a table.

By looking for the description inside an object identifier (sap.m.ObjectIdentifier)

control, which is supposed to be present in the first column of the table, we make

388

Note For more detailed instructions on how to set up Nightwatch.js, refer to the official
Getting Started guide at http://nightwatchjs.org/gettingstarted.

第 389 页 / 共 465 页

10.3 End-to-End Test Automation Tools

sure that the creation was successful. If the Nightwatch.js test doc description doesn’t

become visible within 6 seconds, the test will time out and fail.

Figure 10.22 Creating a Purchase Document Using Nightwatch.js and Verifying Its Success-

ful Creation by Checking That It’s Displayed in the Smart Table on the Next Page

10.3.3 Running the Create Purchase Document End-to-End Nightwatch.js Test

As we haven’t installed Nightwatch.js globally, the easiest way to execute the test is to

add an entry to the scripts object in our package.json file, as shown in Listing 10.11.

The npm test command will trigger the nightwatch command without any arguments

as Nightwatch knows, due to our nightwatch.json configuration file, from where to

start the selenium server and where to find our tests (tests folder).

389

第 390 页 / 共 465 页

10 Automated Testing

"scripts": {

"test": "nightwatch"

}

Listing 10.11 Test Scripts Entry in the package.json File for Our Nightwatch Test

We can then execute the test by executing npm test on the command line from within

the root folder of our E2E tests. In Figure 10.23, you can see the output of the test run

on the console. The test will make sure that the elements want to wait for using the

waitForElementPresent method are present on the page within a certain time frame

(6 seconds); otherwise, it will fail. We should make sure that the second argument spec-

ifying the timeout in milliseconds isn’t too low or the tests might fail due to temporary

system performance issues.

Figure 10.23 Execution of the Create Purchase Document E2E Test

10.4 Summary

In this chapter, we introduced you to the testing pyramid and its different layers

from an SAP S/4HANA perspective. In general, a good functional test coverage of soft-

ware can only be achieved by implementing all layers of the testing pyramid. Conse-

quently, frameworks and tools exist to cover all layers of the testing pyramid when

developing applications for SAP S/4HANA. We showed you in detail how to write unit

390

第 391 页 / 共 465 页

10.4 Summary

tests for the backend (ABAP Unit) and frontend (QUnit) parts of your application that

form the foundation of the testing pyramid and how to decouple your code to make

it testable using dependency injection techniques. Unit tests outnumber other test

forms by far and when done right, and early use of TDD positively influences the

overall code quality by enforcing decoupling. Unit tests are therefore the most criti-

cal layer of the testing pyramid and deserve the most attention and effort.

Integration tests test several components together and can be developed in the back-

end using ABAP Unit and in the frontend using OPA5, including a mock server to iso-

late them from the backend OData service. E2E tests give us the most confidence that

our software is working as a whole by automating tests for the most common pro-

cesses or steps. However, their overall number should be kept relatively small as they

are usually slow and require a stable test environment. A commonly used toolset in

this respect is Selenium, which we demonstrated using Nightwatch.js that serves as a

Node.js wrapper for Selenium. A high degree of test automation according to the test-

ing pyramid is also a prerequisite for continuous integration and continuous deliv-

ery, which we’ll explore in the next chapter.

391

第 392 页 / 共 465 页

第 393 页 / 共 465 页

Chapter 11

Continuous Integration

Continuously integrating code changes into a main code line

accompanied by automated tests provides immediate feedback to

developers and prevents integration problems and regression bugs.

Inthis chapter we’ll look at tools and practices enabling continuous

integration for SAP S/4HANA applications.

Shipping software to customers fast and frequently is one of the core principles of

agile software development and fosters faster customer feedback and satisfaction.

Especially in cloud environments, the release cycles are getting shorter and shorter

or are even completely removed by the principle of Continuous Deployment (CD)

where new features are immediately deployed to production in a completely auto-

mated way as soon as they are finished. Delivering software to customers in short

cycles requires a high degree of automation where steps that had to be done manu-

ally in the past, for instance, uploading a software artifact to a certain system or

repository, must be automated completely. Moreover, test automation plays a cru-

cial role in automating deployments as usually a lot of time must be invested in test-

ing software for bugs and regressions introduced with new features before it can be

released to customers with manageable risk. Therefore, a high degree of test coverage

is of utmost importance to being able to deliver software frequently and safely to end

users. Being able to ship or deploy bug-free and regression-free software fast is a com-

petitive advantage for any company, organization, or development team.

In the following, we’ll give an overview of practices and tools that support faster release

cycles while still ensuring quality when developing applications for SAP S/4HANA.

We’ll start with a general introduction to continuous integration (CI), continuous deliv-

ery, and continuous deployment. The rest of the chapter is split into separate frontend

and backend parts due to their usage of completely different technology stacks. In the

frontend, which consists of our JavaScript-based SAPUI5 freestyle or SAP Fiori elements

application developed in the SAP Web IDE full-stack full-stack, we can make use of

widely adopted tools and technologies, such as GitHub, ESLint, Grunt, Jenkins, Docker,

393

第 394 页 / 共 465 页

11 Continuous Integration

and Node.js, to build the stages and steps of our CI/CD pipelines. Therefore, you’ll learn

in this chapter how you can setup a simple CI pipeline for a frontend SAPUI5 project

using the previously mentioned tools and technologies.

In contrast, in the backend, we still completely rely on the classic ABAP technology

stack and the Change and Transport System (CTS) for transporting SAP S/4HANA rel-

evant backend development objects, such as Core Data Services views or ABAP

classes, between the different SAP systems in the system landscape. Nevertheless,

several tools, such as the ABAP Test Cockpit (ATC) or the ABAP Unit Runner, support

faster release cycles by ensuring code quality through continuous checks and testing.

Consequently, we’ll show you how you can set up kind of a CI/CD pipeline in the

ABAP backend as well by using the ATC and the ABAP Unit Runner.

11.1 Introduction

In the following we’ll give a quick introduction to CI, continuous delivery, and contin-

uous deployment and outline their differences. Continuous delivery and continuous

deployment (CD) are often confused although they can be clearly distinguished. Con-

sequently, by reading this introductory section, you should be able to clearly distin-

guish these three different software engineering approaches.

11.1.1 Continuous Integration

CI is one of the 12 original practices of the Extreme Programming (XP) software devel-

opment methodology that originated in the late 1990s. The main aim of CI is to pre-

vent integration problems by integrating changes to software frequently (multiple

times per day) into a central main code line hosted in a Source Code Management

(SCM) system, for instance, Git. The SCMs represent the single source of truth of the

software. Following the principle of CI reduces the overall risk and uncertainty of a

software project as the individual parts of the software are constantly integrated into

the main code line, so it’s always clear where the project stands. Bug and integration

problems are detected at earlier stages and not cumulated until the end of the proj-

ect, resulting in integration hell. Additionally, CI is the foundation for customer feed-

back throughout the development project as it fosters runnable and presentable

software.

Figure 11.1 illustrates a simple CI pipeline. As soon as a new feature is committed to

the SCM system, the Build stage is triggered, which in turn might consist of several

394

第 395 页 / 共 465 页

11.1 Introduction

steps ensuring that the software can be compiled and that the different parts fit

together. If this stage is successful, a set of tests is executed in the subsequent Test

stage, for instance, static code checks and unit and integration tests. Like this, devel-

opers get early feedback about their change and can fix potential issues when they

are still fresh in their minds.

Commit Source Control Start CI

Develop Build

Feedback Test

Test

Figure 11.1 Simple CI Pipeline Consisting of Build and Test Stages

11.1.2 Continuous Delivery

Continuous delivery takes CI one step further. As soon as the CI process is finished

successfully, it’s possible to reliably ship the software to customers or deploy it to

production at any time (see Figure 11.2). Whether the software is shipped or deployed

is decided by the development team or a delivery manager. This requires that the

software is built, tested, and available in an immediately shippable or deployable for-

mat as the output of the CI process. The actual delivery to end users must also be

automated and should not require more effort than pressing a button.

395

第 396 页 / 共 465 页

11 Continuous Integration

Feedback

Automatic Automatic At will

Develop Build Test Deploy

Figure 11.2 Simple Continuous Delivery Pipeline Where a Built and Tested Software Artifact

Can Be Deployed at Will

11.1.3 Continuous Deployment

Continuous deployment (CD) extends continuous delivery further and eliminates

any manual steps and decisions to be taken before the software artifact is shipped or

deployed to production. As soon as the software is built and tested successfully, it’s

automatically shipped to customers or deployed to production without any further

human interaction (as shown in Figure 11.3). This requires the automation of every

single step of the deployment pipeline and a comprehensive automated test suite

that covers all aspects of the software ranging from unit and integration tests over to

system testing to performance and security testing. Out of CI, continuous delivery,

and continuous deployment, the latter has the highest velocity and shortest cus-

tomer feedback loop.

Feedback

Automatic Automatic Automatic

Develop Build Test Deploy

Figure 11.3 Simple Continuous Deployment Pipeline in Which the Build, Test, and Deploy

Stages Are Completely Automated

396

第 397 页 / 共 465 页

11.2 Setting Up a Continuous Integration Pipeline for SAPUI5 on the ABAP Server

11.2 Setting Up a Continuous Integration Pipeline for

SAPUI5 on the ABAP Server

In the following, we’ll set up a simple CI pipeline that will make a SAPUI5 project,

developed in the SAP Web IDE full-stack and stored in GitHub, ready for deployment

on the SAP Gateway hub system (see Figure 11.4). We’ll use the Jenkins automation

server to create a CI pipeline consisting of 1 build, 2 test, and 3 deploy stages. The

pipeline will be triggered on each push to the remote Git SCM system. In the build

stage, we’ll create a new separate deployment-ready version of our project, for

instance, by minifying the project’s files and doing other optimizations. We’ll also do

static code checks in this stage using ESLint, which will analyze our code for common

mistakes or bad practices.

Commit and

Develop Push

SAP Web IDE

Git Hub SCM

Full Stack

Start CI/CD

Pipeline RFC

3.2 Trigger

Download

SAP Gateway hub

Jenkins system

R

ABAP backend

Build Test Deploy

3.1 Upload 3.3 Download

JFrog

Artifact Repository

Figure 11.4 Simple CI/CD Pipeline Taking a SAPUI5 Application from the SAP Web IDE Full-

Stack and GitHub over a Jenkins Automation Server to the SAP Gateway Hub System

397

返回ABAP参考资料列表