第 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