In the previous exercise, you've implemented the dynamic instance feature control for some of the standard and non-standard operations of the Travel entity. (see Exercise 7).
In the present exercise, you will write scenario tests for your business object to verify its transactional behavior as a whole. You will write an ABAP Unit test with the Entity Manipulation Language (EML). The scenario to be tested in the present exercise is the creation and manipulation of a new travel instance.
- 8.1 - Create the ABAP Unit Test Class
- 8.2 - Enhance the Test Class Definition
- 8.3 - Implement the Special Methods
- 8.4 - Implement the Test Method
create_with_action
- 8.5 - Run Your ABAP Unit Test
- Summary
Reminder: Do not forget to replace the suffix placeholder
###
with your choosen or assigned group ID in the exercise steps below.
Ensuring the high quality of applications has a very high priority in the whole software development lifecycle. As an application developer, you want to be able to write, for example, unit, scenario and integration tests to verify the application behavior as a whole. The ABAP platform offers different mechanisms and frameworks to achieve this. The main options here are ABAP Unit Tests und the ABAP Test Cockpit.
Writing ABAP Unit tests is the way to provide high quality software, which can be easily evolved over time without introducing regressions. In methodologies, like extreme programming and test driven development, the role of unit testing is even more important.
ABAP Unit is the state-of-the-art unit testing framework for ABAP. It's embedded into the ABAP programming language which supports you in writing unit tests. In ADT you have various possibilities to execute the unit tests and to evaluate the results concerning functional correctness and code coverage.
Further reading : Test@RAP Development Guide | Testing the RAP Business Object | Ensuring Quality of ABAP Code
Click to expand!
The ABAP Unit framework has standard special methods which needs to be implemented to set up and destroy a given test cofiguration.
- The standard special static methods of the test configuration
class_setup()
andclass_teardown()
.class_setup()
: This setup method is executed once before all tests of the class. It is used to setup the test environment.class_teardown()
: This teardown method is executed once after all tests of the test class are executed. It is used to destroy the test environment.
- The standard special instance methods of the test configuration:
setup()
andteardown()
.setup()
: This method is executed before each individual test or before each execution of a test method. It is used, for example, to reset the test doubles before the execution of each test method.teardown()
: This method is executed after each individual test or after each execution of a test method. It will be used to rollback any changes in involved entities
Below is a simplified representation of the runtime flow of ABAP Unit test classes.
ABAP Unit test methods: <test method>()
represents each unit test method for a given code under test (CUT). Such method is identifiable by the addition FOR TESTING
in the method interface.
Create the test class
ZRAP100_TC_TRAVEL_EML_###
in your previously exercise package.
🔵 Click to expand!
-
Right-click on your package
ZRAP100_###
and choose New > ABAP Class from the context menu. -
Maintain the required information (
###
is your group ID) and click Next >.- Name:
ZRAP100_TC_TRAVEL_EML_###
- Description:
BO Test with EML
- Name:
-
Assign a transport request and choose Finish to create the new ABAP class.
-
Specify the new global ABAP class as an ABAP Unit test class and also specify the test relation to the behavior definition (
BDEF
) of your Travel BO entityZRAP100_R_TravelTP_###
.For that, insert the addition
FOR TESTING RISK LEVEL HARMLESS DURATION SHORT
after the additionCREATE PPUBLIC
(just before the.
) of the class definition to enable the class for ABAP Unit testing.FOR TESTING RISK LEVEL HARMLESS DURATION SHORT
Then add the ABAP Doc comment provided as code snippet below at the top of class editor to specify the test relation with your behavior definition
ZRAP100_R_TravelTP_###
(TADIR entry:R3TR BDEF
). Replace the placeholder###
with your group ID.Info: In ABAP Unit testing, test relations allow the tests of a given object to be executed from the referenced object. In the present example, you will execute the test for your RAP BO in the separate test class. It is also possible to write ABAP Unit tests directly in the behavior implementation classes.
"! @testing BDEF:ZRAP100_R_TravelTP_###
Your source code should look like this:
Enhance the ABAP Unit test class definition with all the needed methods.
You will declare the needed special ABAP unit instance and static methods, and the test method for the code under test (CUT).
🔵 Click to expand!
- Insert the code snippet provided below under the
PRIVATE SECTION.
statement in the class definition.
Mock data for the Agency and Customer entities are needed in the present test scenario.
CLASS-DATA:
cds_test_environment TYPE REF TO if_cds_test_environment,
sql_test_environment TYPE REF TO if_osql_test_environment,
begin_date TYPE /dmo/begin_date,
end_date TYPE /dmo/end_date,
agency_mock_data TYPE STANDARD TABLE OF /dmo/agency,
customer_mock_data TYPE STANDARD TABLE OF /dmo/customer.
CLASS-METHODS:
class_setup, " setup test double framework
class_teardown. " stop test doubles
METHODS:
setup, " reset test doubles
teardown. " rollback any changes
METHODS:
" CUT: create with action call and commit
create_with_action FOR TESTING RAISING cx_static_check.
An error will be displayed in the ABAP editor due to the missing method bodies in the class implementations.
-
Add the method bodies via ADT quick fix.
For that, set your cursor on the method name
class_setup
, press Ctrl+1 to display the Quick Assist view, and select the entry+ Add 5 unimplemented methods
from the pop-up menu to add the empty method bodies in the class implementation section.Your source code should now look like:
ℹ️ Brief Explanation - Click to expand!
- Various static attributes for the test doubles and the mock data
cds_test_environment
: The reference object for the CDS TDF (if_cds_test_environment
) which is used to provide test doubles for the travel CDS entity of the base BO view. The CDS test doubles will be used for read operations.sql_test_environment
: The reference object for the ABAP SQL TDF (if_osql_test_environment
) is used for stubing the additional needed database tables. The database test doubles will be used for write operations.- The ABAP unit framework standard special methods of the test configuration are specified:
setup
,teardown
,class_setup
, andclass_teardown
. - The method
create_with_action()
is the unit test method for our CUT. Test methods are easily identifiable by the additionFOR TESTING
in the method signature.
Implement the special static methods
class_setup
andclass_teardown
, and the special instance methodsetup
required by the ABAP unit framework.
🔵 Click to expand!
-
Implement the static method
class_setup
which is used to setup the test doubles environment and prepare the test data.Replace the method body with the code snippet below and replace all occurrences of the placeholder
###
with your group ID.Use the Pretty Printer (Shift+F1) to format your source code and save the changes.
METHOD class_setup. " create the test doubles for the underlying CDS entities cds_test_environment = cl_cds_test_environment=>create_for_multiple_cds( i_for_entities = VALUE #( ( i_for_entity = 'ZRAP100_R_TravelTP_###' ) ) ). " create test doubles for additional used tables. sql_test_environment = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( '/DMO/AGENCY' ) ( '/DMO/CUSTOMER' ) ) ). " prepare the test data begin_date = cl_abap_context_info=>get_system_date( ) + 10. end_date = cl_abap_context_info=>get_system_date( ) + 30. agency_mock_data = VALUE #( ( agency_id = '070041' name = 'Agency 070041' ) ). customer_mock_data = VALUE #( ( customer_id = '000093' last_name = 'Customer 000093' ) ). ENDMETHOD.
Your source code should look like this:
-
Implement the ABAP Unit framework standard static method
class_teardown
which is used to stop the test doubles at the end of the test class execution.Replace the method body with the code snippet provided below, format your code your source code, and save the changes.
METHOD class_teardown. " remove test doubles cds_test_environment->destroy( ). sql_test_environment->destroy( ). ENDMETHOD.
Your source code should look like this:
-
Implement the special instance method
setup
which is used to reset the test doubles and insert the test data before the execution of the test method - or of each test method of a test class in general.Insert the code snippet below in the appropriate method implementation as shown on the screenshot.
METHOD setup. " clear the test doubles per test cds_test_environment->clear_doubles( ). sql_test_environment->clear_doubles( ). " insert test data into test doubles sql_test_environment->insert_test_data( agency_mock_data ). sql_test_environment->insert_test_data( customer_mock_data ). ENDMETHOD.
The source code should look like this:
-
Implement the special instance method
teardown
which is used to rollback any changes in the involved entities after the execution the test method.Replace the method body with the code snippet provided below and format your code your source code.
METHOD teardown. " clean up any involved entity ROLLBACK ENTITIES. ENDMETHOD.
The source code should look like follows.
Implement your BO test.
The present code under test (CUT) is an EML statement that will create a Travel instance and execute the actionacceptTravel
on it. This scenario will includeCREATE
,EXECUTE
andCOMMIT
EML statements.
🔵 Click to expand!
-
Implement the test method
create_with_action
.Insert the code snippet provided below in the method body and replace all occurrences of
###
with your group ID.⚠Attention⚠:
In case you've defined and implemented the instance actionsacceptTravel
in the Exercise 6_), then uncomment the six (6) related code lines in the inserted source code.METHOD create_with_action. " create a complete composition: Travel (root) MODIFY ENTITIES OF ZRAP100_R_TravelTP_### ENTITY Travel CREATE FIELDS ( AgencyID CustomerID BeginDate EndDate Description TotalPrice BookingFee CurrencyCode ) WITH VALUE #( ( %cid = 'ROOT1' AgencyID = agency_mock_data[ 1 ]-agency_id CustomerID = customer_mock_data[ 1 ]-customer_id BeginDate = begin_date EndDate = end_date Description = 'TestTravel 1' TotalPrice = '1100' BookingFee = '20' CurrencyCode = 'EUR' ) ) * " execute action `acceptTravel` * ENTITY Travel * EXECUTE acceptTravel * FROM VALUE #( ( %cid_ref = 'ROOT1' ) ) " execute action `deductDiscount` ENTITY Travel EXECUTE deductDiscount FROM VALUE #( ( %cid_ref = 'ROOT1' %param-discount_percent = '20' ) ) "=> 20% " result parameters MAPPED DATA(mapped) FAILED DATA(failed) REPORTED DATA(reported). " expect no failures and messages cl_abap_unit_assert=>assert_initial( msg = 'failed' act = failed ). cl_abap_unit_assert=>assert_initial( msg = 'reported' act = reported ). " expect a newly created record in mapped tables cl_abap_unit_assert=>assert_not_initial( msg = 'mapped-travel' act = mapped-travel ). " persist changes into the database (using the test doubles) COMMIT ENTITIES RESPONSES FAILED DATA(commit_failed) REPORTED DATA(commit_reported). " no failures expected cl_abap_unit_assert=>assert_initial( msg = 'commit_failed' act = commit_failed ). cl_abap_unit_assert=>assert_initial( msg = 'commit_reported' act = commit_reported ). " read the data from the persisted travel entity (using the test doubles) SELECT * FROM ZRAP100_R_TravelTP_### INTO TABLE @DATA(lt_travel). "#EC CI_NOWHERE " assert the existence of the persisted travel entity cl_abap_unit_assert=>assert_not_initial( msg = 'travel from db' act = lt_travel ). " assert the generation of a travel ID (key) at creation cl_abap_unit_assert=>assert_not_initial( msg = 'travel-id' act = lt_travel[ 1 ]-TravelID ). * " assert that the action has changed the overall status * cl_abap_unit_assert=>assert_equals( msg = 'overall status' exp = 'A' act = lt_travel[ 1 ]-OverallStatus ). " assert the discounted booking_fee cl_abap_unit_assert=>assert_equals( msg = 'discounted booking_fee' exp = '16' act = lt_travel[ 1 ]-BookingFee ). ENDMETHOD.
The source code should look like this:
ℹ️ Brief Explanation - Click to expand!
- The complete CUT is a complex EML Statement comprising a
MODIFY ENTITIES
statementCREATE
andEXECUTE
additions, and aCOMMIT ENTITIES
statement. - The class
CL_ABAP_UNIT_ASSERT
is used in test method implementations to check/assert the test assumptions. It offers various static methods for the purposes - e.g.assert_equals()
,assert_initial()
,assert_not_initial()
, andassert_differs()
. - Block (1)
- CUT:
MODIFY ENTITIES
statement, including the following- a
CREATE
addition which creates a travel instance (root). - an
EXECUTE
addition which calls the actionacceptTravel
on the travel instance. - an
EXECUTE
addition which calls the actiondeductDiscount
on the travel instance.
- a
- First assertion block on the result parameters - before committing the changes
- An assertion used to check for eventual failures and messages during the creation of the instances and the execution of the action.
- An assertion used to check the returned
mapped
table for the created travel and booking instances.
- CUT:
- Block (2)
- CUT:
COMMIT ENTITIES
statement which persists the changes to the database (test doubles). - The second assertion block checks for an eventual failure of the performed commit by examinating the return parameters
commit_failed
andcommit_reported
.
- CUT:
- Block (3)
- Third assertion block to check the successful persistence of the data in the test doubles.
- For that, the committed data is first read via a
SELECT
statement on the base BO viewZRAP100_R_TravelTP_####
; The data is read from the configure test double. - Various assertion checks are performed. Please read the comment lines in the code snippet for more explanation.
- The complete CUT is a complex EML Statement comprising a
You are through with writing your BO test. Go ahead and execute it.
Run your implemented ABAP Unit test.
🔵 Click to expand!
-
Execute the implemented unit test.
For that, right-click on either the test method name
create_with_action
or the test class name in the Outline or Project Explorer view, and select Run as > ABAP Unit Test from the context menu or simply press Ctrl+Shift+F10.Your result should look like this:
-
Now, run the unit test from the related behavior definition
ZRAP100_R_TravelTP_###
.In the 8.1 - Create the ABAP Unit Test Class of this document, you've specified the test relation to your behavior definition
ZRAP100_R_TravelTP_###
using the special ABAP Doc comment"! @testing BDEF:ZRAP100_R_TravelTP_###
, where###
is your group ID.This means that every ABAP Unit tests implemented in this test class can be executed from the behavior definition
ZRAP100_R_TravelTP_####
.Select your BO behavior definition
ZRAP100_R_TravelTP_###
in the Project Explorer, right-click on it, and select Run as > ABAP Unit Test (Ctrl+Shift+F10) from the context menu to executed all its related unit tests; Currently only one :-).All unit tests related to this behavior definition are executed and displayed under Foreign Tests in the ABAP Unit view.
That's it for this exercise!
In this exercise you have...
- written an ABAP unit test for your Travel BO entity with EML
- run the implemented ABAP Unit test.
Further reading: You can learn more on writing ABAP Unit tests for apps built with RAP here: RAP400
You can continue with the next exercise – [Explore] Exercises 9: External API-based Access to the RAP BO with EML
ℹ Info:
The solution comprises the implementation of a unit test for the actions, i.e.deductDiscount
andacceptTravel
.