Abap Future
Abap Future
Abap Future
Reading Sample
This sample chapter describes how to use ABAP Unit for test-driven
development (TDD) when creating and changing custom programs
to make your changes with minimal risk to the business. This chap-
ter explains what TDD is and how to enable it via the ABAP Unit
framework.
Contents
Index
The Author
Paul Hardy
ABAP to the Future
801 Pages, 2016, $79.95
ISBN 978-1-4932-1410-5
www.sap-press.com/4161
Code without tests is bad code. It doesnt matter how well-written it is; it
Chapter 3
Nothing is more important, during the process of creating and changing custom
programs, than figuring out how to make such changes with minimal risk to the
business. The way to do this is via test-driven development (TDD), and the tool to
use is ABAP Unit. This chapter explains what TDD is and how to enable it via the
ABAP Unit framework.
The whole aim of creating your tests first is to make it so that, once the tests have
all been written, as you createand more importantly changeyour application,
you can follow the menu path Program Test Unit Test at any given instant to
see a graphical display of whats currently working in your program and whats
either not yet created or broken (Figure 3.1). This way, when the time comes to
move the created or changed code into test, you can be confident that its correct.
159
3 ABAP Unit and Test-Driven Development Eliminating Dependencies 3.1
Game show host: Something that stops you dead in your tracks when you want to
write a test.
Figure 3.1 ABAP Unit Results Screen
Contestant: What is a dependency?
Because thats a highly desirable outcome from everybodys perspective, this The contestant is correct. But to go into a little more detail, a dependency is what
chapter explains how to transform your existing code into test-driven code via you have when you want to do a test for your productive code but you cant
three main steps: because the productive code reads from the database or writes to the database or
accesses some external system or sends an email or needs some input from a user
1. Eliminating dependencies in your existing programs
or one of a million other things you cannot or do not want to do in the develop-
2. Implementing mock objects in your existing programs
ment environment.
3. Using ABAP Unit to write and implement test classes
As an example, consider a monolithic SAP program that schedules feeding time at
Each step will be covered in detail. After that, some additional advice on how to the zoo and makes sure all the animals get the right amount and type of food.
improve TDD via automated frameworks will be presented. Finally, the chapter Everything works fine. The keepers have some sort of mobile device that they can
will conclude with a look at how to run large numbers of unit tests on different query to see what animals need feeding next, and theres some sort of feedback
data sets without having to hard-code them all. they have to give after feeding the animals. They want to keep track of the food
inventory, and so on; none of the details are important for this example.
Unit Testing Procedural Programs
All is well until one day when they get two pandas on loan from China, and the
The examples in this chapter deal with testing object-oriented (OO) code (i.e., meth-
programmer has to make some changes to accommodate their specific panda-type
ods), and most of the examples of ABAP Unit you will find in books or on the web will
also focus on OO programming. This doesnt mean that you cant test procedural pro- needs (e.g., bamboo shoots) without messing up all the other animals. The pro-
grams; its far easier to test an OO program, but its not the end of the world to add unit grammer can do a unit test on the panda-specific changes he has made, but how
tests to a function module or a procedural program. can he be sure that an unforeseen side effect will not starve the lions, causing
You most likely have huge monolithic procedural programs in your system that it would them to break out and eat all the birds and all the monkeys before breaking into
be too difficult to rewrite in an OO fashion, because doing so would take a lot of time the insect house and crushing and eating the beehive?
and not provide any new functionality. In such cases, whenever you are called on to
maintain a section of that programfixing a bug or adding new functionalityyou can Normally, theres no way to do it; there are just too many dependencies. The
make relatively minor changes to break up the dependencies as described in this chap- program needs to read the system time, read the configuration details on what
ter, and then slowly add test methods to bring little islands of the program under test. animal gets fed when, read and write inventory levels, send messages to the zoo-
As time goes by, more and more islands of the program will have tests, making it more keepers, receive and process input from those same zookeepers, interface with
and more stable, until one day the whole archipelago of the program will be covered.
assorted external systems, and so on.
However, you really dont want to risk the existing system breaking, leading the
lions to dine on finch, chimps, and mushy bees, so how can we enable tests?
160 161
3 ABAP Unit and Test-Driven Development Eliminating Dependencies 3.1
The first step is to break up the dependencies. In this section, youll learn how to WHEN 'Y'.
"Off we go!
do exactly that, a process that involves two basic steps:
WHEN 'N'.
RETURN.
1. Look at existing programs to identify sections of code that are dependencies
WHEN OTHERS.
(and are thus candidates to be replaced by mock objects during a test). RETURN.
ENDCASE.
2. Change your production code to separate out the concerns into smaller classes
that deal with each different type of dependency (well look at two ways of * Fire Missile
doing so: one quick and dirty, the other slower and more time-intensive but CALL FUNCTION 'TELL_PI_PROXY_TO_FIRE_MISSILE'.
ultimately more rewarding).
* Print Results
CALL FUNCTION 'PRINT_NUCLEAR_SMARTFORM'.
good old friend the baron (remember him from the Introduction?) doesnt want
any neighboring mad scientists building monsters and thus encroaching on his In this code, you want to test two things:
market, so as soon as hears about such a competitor he drops a nuclear bomb on That you direct the missile to the correct target
him (as any good mad scientist would). Listing 3.1 illustrates this by first getting
That if the user aborts halfway through, the missile does not actually fire
the customizing settings for the missiles and making sure theyre ready to fire,
then confirming with the operator that he really wants to fire the missile, then fir- However, because of the way the routine is written, you cant do a meaningful
ing the missile, and finally printing out a statement saying that the missile was test unless the following points are true:
fired. At almost every point in the code, youll deal with something or somebody
You have an actual database with proper customizing data.
external to the SAP system: the database, the operator (user), the missile firing
system, and the printer. You have a link to the sensor on the missile.
You have a user to say yes or no.
FORM fire_nuclear_missile.
You actually fire the missile to see what happens (possibly resulting in the
* Read Database
world being destroyed).
CALL FUNCTION 'READ_CUSTOMIZING'.
* Query Missile Sensor You have a printer hooked up.
CALL FUNCTION 'GET_NUCLEAR_MISSILE_STATUS'.
These are all examples of dependencies. As long as theyre part of your code, you
* Business Logic
cant implement TDD.
IF something.
"We fire the missile here
Unfortunately, most ABAP code traditionally looks like the example in Listing
ELSEIF something_else.
"We fire the missile there Section 3.1. Thus, when preparing an existing program to be unit tested, the first
ENDIF. thing to do is make a list of anything that is not pure business logic (i.e., calls to
the database, user input, calls to external systems, etc.), exactly as in the preced-
* Ask user if he wants to fire the missile
CALL FUNCTION 'POPUP_TO_CONFIRM'. ing bullet list.
* Business Logic
CASE user_answer.
162 163
3 ABAP Unit and Test-Driven Development Eliminating Dependencies 3.1
The TEST-SEAM concept should never be used in new programs, because its a dirty IF sy-subrc <> 0.
workaround that horrifies all serious programmers (they would say production code RETURN.
ENDIF.
should be unaware what parts of it are going to be tested) and is designed purely as an
interim measure to use while redesigning old, badly written programs.
END-TEST-SEAM.
164 165
3 ABAP Unit and Test-Driven Development Eliminating Dependencies 3.1
Highlighting the dependencies in this way not only allows unit testing (as covered The next step is to change the production code to make calls to methods of these
in Section 3.3) but also saysin letters of fire a thousand miles highthat the newly created classes. The end result will look like Listing 3.3.
code is badly designed and needs changing, which, as mentioned earlier, youre
FORM fire_nuclear_missile.
often not allowed to do because the powers that be wrongly think such changes
are risky when they are the exact reverse. * Read Database
mo_database_access->read_customising( ).
mo_missile_interface->get_nuclear_missile_status( ).
3.1.3 Breaking Up Dependencies Properly * Business Logic
The correct way (once theyre identified) of breaking up dependencieswhich IF something.
"We fire the missile here
takes a lot of effortis redesigning your program so that it adopts a separation of
ELSEIF something_else.
concerns approach. This approach dictates that you have one class for database "We fire the missile there
access, one for the user interface layer, and one for talking to an external system; ENDIF.
that is, each class does one thing only and does it well. This is known as the single * Ask user if they want to fire the missile
responsibility principle. Designing an application this way enables you to change mo_user_interface->popup_to_confirm( ).
the implementation of, say, your user interface layer without affecting anything
* Business Logic
else. This type of breakup is vital for unit tests. CASE user_answer.
WHEN 'Y'.
Warning: Houston, We Have a Problem "Off we go!
WHEN 'N'.
As an aside and a warning, Ive seen a great example in which someone split out all the RETURN.
database access into its own class, presumably following the separation of concerns WHEN OTHERS.
model. However, that person also made every single variable and method staticand RETURN.
you cant subclass static methods. As a result, the end program still wasnt eligible for ENDCASE.
unit testing.
* Fire Missile
mo_missile_interface->tell_pi_proxy_to_fire_missile( ).
To illustrate the separation of concerns approach, take database access as an
* Print Results
example. This means that you would go through your program looking for every mo_printer->print_nuclear_smartform( )..
SELECT statement and extract them out in a method of a separate database access
class. Repeat the process for other functions of the programs, such as processing ENDFORM."Fire Nuclear Missile
user input, communicating with external systems, and anything else you can Listing 3.3 Calling Methods of Classes
identify as a dependency, each having one specific class that serves each such pur-
pose. (You may be wondering how to decide which functions need to be split into Note that the functionality has not been changed at all; the only difference is that
their own class. Luckily, this is an iterative process; when you start writing tests the calls to various external systems (the dependencies) are now handled by
and the test fails because it does not really have access to proper database entries, classes as opposed to functions or FORM routines. All that remains untouched is
user input, or an external system, it will become clear what is a dependency and the business logic.
thus needs to be isolated into its own class.) With the dependencies successfully eliminated, you can now implement mock
objects.
166 167
3 ABAP Unit and Test-Driven Development Implementing Mock Objects 3.2
2. Make sure that all the classes under tests (often a unit test will use several ENDMETHOD.
classes, but there is always one main one that you are testingthe class under Listing 3.4 Test Injections for Test Seams
test) are able to use these mock objects instead of the real objects, but only
when a test is underway. This is known as injection. This works fine; a test injection can be empty and so no code is executed during
the test, so no database data is read, no missile is fired, and all is well.
Mock Objects vs. Stub Objects
This is all well and good but dont do it; its more trouble than its worth, and if
When talking about mock objects, the terms stub and mock are often used interchange-
ably; technically, though, there is a difference. If youre testing how your class affects an proper programmers catch you, theyll make you stand in the corner with a dunce
external system, then the fake external system is a mock, and if youre testing how the cap on your head. Instead, proceed according to the next section.
fake external system affects your class, then the fake external system is a stub. (Either
way, the point is that you use a fake external system for testing.)
3.2.2 Creating Mock Objects
Before jumping into creating mock objects and injection, lets first take a quick For testing purposes, what you actually want is to define mock classes and mock
look at test injection, introduced with test seams. This is not how you should objects. Mock classes are classes that run in the development environment. They
implement mock objects, but you should see it in action at least once before dis- dont really try to read and write to the database, send emails, fire nuclear mis-
missing it. siles, and so on, but they test the business logic nonetheless. Mock objects follow
the same principles as regular objects; that is, in the same way that a monster
object is an instance of the real monster class, a mock monster object is an
3.2.1 Test Injection for Test Seams instance of a mock monster class.
Test injection for test seams is the poor mans version of implementing mock
This is where the basic features of OO programming come into play: subclasses
objects. Instead of replacing entire routines with a duplicate inside a mock class,
and interfaces. To continue the previous example (about firing nuclear missiles),
you flag sections (one or more lines of code) inside of such routines as shown in
youll next create a subclass of the database access class that doesnt actually read
Section 3.1.2 so they can be replaced with different code during a test. In other
the database but instead redefines the database access methods to return hard-
words, you have the option to surround sections of production code with test
coded values based upon the values passed in. In Listing 3.5, youll see some pos-
seams. When the program runs in production, the actual code within the test
sible redefined implementations of methods in mock subclasses that could
seam is executed. When running a test, you define some bogus code that runs
replace the real classes in the example.
instead, the format of which is as shown in Listing 3.4.
METHOD read_customising. "mock database implementation
*--------------------------------------------------------------*
* IMPORTING input_value
168 169
3 ABAP Unit and Test-Driven Development Implementing Mock Objects 3.2
170 171
3 ABAP Unit and Test-Driven Development Writing and Implementing Unit Tests 3.3
now you make these into optional import parameters of the constructor. The con-
you pass in a fake object that gives hard-coded values. Now, say a requirement comes
structor definition and implementation now looks like the code in Listing 3.8. in that the users want to change some of the configuration values on screen and run a
PUBLIC SECTION. what-if analysis before saving the changes. One way to do that is to have a subclass that
METHODS: constructor IMPORTING uses the internal tables in memory as opposed to the ones in the database, and you pass
io_pers_layer TYPE REF TO zcl_monster_pers_layer OPTIONAL that class into the constructor when running your simulator program in what-if mode.
io_logger TYPE REF TO zcl_logger OPTIONAL.
METHOD constructor."Implementation At this point, you now have mock objects and a way to pass them into your pro-
gram. This means that youre ready to write the unit tests.
IF io_logger IS SUPPLIED.
mo_logger = io_logger.
ELSE.
CREATE OBJECT mo_logger.
ENDIF.
3.3 Writing and Implementing Unit Tests
IF io_pers_layer IS SUPPLIED. At last, the time has come to talk about actually writing the test classes and how
mo_pers_layer = io_pers_layer. to use the ABAP Unit framework. In this section, youll walk through this process,
ELSE.
which involves two main steps:
CREATE OBJECT mo_pers_layer
EXPORTING
1. Set up the definition of a test class. The definition section of a class, as always,
io_logger = mo_logger " Logging Class
id_valid_on = sy-datum. " Validity Date is concerned with the what, as in, What should a test class do? This is covered
ENDIF. in Section 3.3.1.
ENDMETHOD."constructor implementation 2. Implement a test class. Once you know what a test class is supposed to do, you
Listing 3.8 Constructor Definition and Implementation can go into the detail of how its achieved technically. This is covered in Section
3.3.2.
The whole idea here is that the constructor has optional parameters for the vari-
ous classes. The main class needs these parameters in order to read the database, Executable Specifications
write to a log, or communicate with any other external party thats needed. When Some people in the IT world like to call unit tests executable specifications, because they
running a unit test, you pass in (inject) mock objects into the constructor that sim- involve the process of taking the specification document, breaking it down into tests,
ply pass back hard-coded values of some sort or dont do anything at all. (In the and then, when the program is finished, executing these tests. If they pass, then you are
proving beyond doubt that the finished program agrees with the specification. If you
real production code, you dont pass anything into the optional parameters of the
cant break the specification into tests, then it means that the specification is not clear
constructor, so the real objects that do real work are created.) enough to turn into a program. (Actually, most specifications I get fall into that cate-
gory. But to be fair to the business analysts, there is only so much that you can write on
Arguments Against Constructor Injection the back of a post-it note.)
Some people have complained that the whole idea of constructor injection is horrible,
because a program can pass in other database access subclasses when executing the
3.3.1 Defining Test Classes
code for real outside of the testing framework. However, I disagree with that argument,
because constructor injection can give you benefits outside of unit testing. There are several things you need to define in a test class, and the following sub-
As an example, consider a case in which a program usually reads the entire monster- sections will go through them one by one. Broadly, the main steps in defining
making configuration from the databaseunless youre performing unit testing, when your test class are as follows:
172 173
3 ABAP Unit and Test-Driven Development Writing and Implementing Unit Tests 3.3
174 175
3 ABAP Unit and Test-Driven Development Writing and Implementing Unit Tests 3.3
PRIVATE SECTION. This method cant have any parametersyoull get an error if you try to give it
DATA: mo_class_under_test TYPE REF TO zcl_monster_simulator, anybecause it must perform the exact same task each time it runs and importing
mo_mock_pers_layer TYPE REF TO zcl_mock_pers_layer,
mo_logger TYPE REF TO zcl_mock_logger. parameters might change its behavior. The code for defining the SETUP method is
Listing 3.11 Defining Mock Classes for Injecting into Test Class very simple:
METHODS: setup,
In Listing 3.8, you saw how in the constructor in production, the class would cre-
ate the real classes, but during a unit test mock classes are passed into the con-
Defining the Test Methods
structor of the class under test by the SETUP method, which runs at the start of
each test method. After defining the SETUP method, youll move onto defining the actual test meth-
ods. At this stage, you havent actually written any production code (i.e., the code
The full list of the data definitions in the test class are shown in Listing 3.12. In that will run in the real application), and all you have is the specification. There-
addition to the mock classes, there are some global (to a class instance) variables fore, next youre going to create some method definitions with names that will be
for things such as the input data and the result. Its good to set things up this way recognizable to the person who wrote the specification (Listing 3.13). The FOR
because passing parameters in and out of test methods can distract someone look- TESTING addition after the method definition says that this method will be run
ing at the code (e.g., a business expert) from making sure that the names of the every time you want to run automated tests against your application.
test methods reflect whats supposed to be tested.
return_a_bom_for_a_monster FOR TESTING
PRIVATE SECTION. make_the_monster_sing FOR TESTING
DATA: mo_class_under_test TYPE REF TO zcl_monster_simulator, make_the_monster_dance FOR TESTING
mo_mock_pers_layer TYPE REF TO zcl_mock_monster_pers_layer, make_the_monster_go_to_france FOR TESTING
mo_mock_logger TYPE REF TO zcl_mock_logger, Listing 3.13 Unit Test Methods
176 177
3 ABAP Unit and Test-Driven Development Writing and Implementing Unit Tests 3.3
These are the aims of the program to be written (sometimes these are called use really long test method names, like It Should Return a BOM for a Purple Monster,
cases); you want to be sure the application can perform every one of these func- but in ABAP you cant afford to add the extra characters of IT SHOULD to the
tions and perform them without errors. This is why you need unit tests. method name. Instead, you can put the IT SHOULD in a comment line with dots
after it, padding the comment line out to thirty characters to make it really obvi-
ous what the maximum permitted length of the names of the test methods are.
Implementing Helper Methods
You will then declare the test methods underneath the dotted line, being aware of
The last step in defining the test class is to implement helper methods (i.e., meth- when youre running out of space for the name. An example is shown in Listing
ods in your test class definition without the FOR TESTING addition). These are nor- 3.14.
mal private methods that are called by the test methods.
*--------------------------------------------------------------*
The purpose of helper methods is to perform low-level tasks for one or more of * Specifications
*--------------------------------------------------------------*
the actual test methods; in normal classes, public methods usually contain several
"IT SHOULD.....................
small private methods for the same reason. Helper methods usually fall into two "User Acceptance Tests
categories: return_a_bom_for_a_monster FOR TESTING,
make_the_monster_sing FOR TESTING
1. Helper methods that contain boilerplate code that you want to hide away make_the_monster_dance FOR TESTING
make_the_monster_go_to_france FOR TESTING
(because although you need this code to make the test work, it could be a dis-
Listing 3.14 Test Methods That Describe What an Application Should Do
traction to someone trying to understand the test)
2. Helper methods that call one or more ABAP statements for the sole purpose of
Other Names for Behavior-Driven Development
making the core test method read like plain English
Sometimes, these sorts of behavior-driven development unit tests are described as user
Inside each unit test method (the methods that end with FOR TESTING), you will acceptance tests. Although there is no actual user involved, the reason for the terminol-
have several helper methods with names that have come straight out of the spec- ogy is that this sort of test simulates the program exhibiting a behavior that the user
ification. As an example, the specification document says that the main purpose would expect when he performs a certain action within the program. Outside of SAP,
of the program is to return a bill of materials (BOM) for a monster, and you do the testing framework called FitNesse describes itself as such an automated user accep-
tance testing framework.
that by having the user enter various desired monster criteria, which are then
used to calculate the BOM according to certain rules. You may also see behavior-driven development referred to as the assemble/act/assert
way of testing (which doesnt read as much like natural language, but it makes some
Helper methods have names that adhere to a concept known as behavior-driven people very happy, because every word starts with the same letter).
development, the idea that tests should be managed by both business experts and
developers. When using behavior-driven development, the general recommenda- Whatever you want to call these types of automated tests, they usually involve
tion is to start all the test methods with IT SHOULD, with the IT referring to the several methodsand often several classes as wellall working together, as
application being tested (the class under test). Thus, you would have names such shown in Listing 3.15.
as IT SHOULD FIRE A NUCLEAR MISSILE, such names coming straight out of the spec-
*--------------------------------------------------------------*
ification that describes what the program is supposed to achieve. * Low-Level Test Implementation Methods
*--------------------------------------------------------------*
In ABAP, you are limited to thirty characters for names of methods, variables, "GIVEN.........................
database tables, and so onso you have to use abbreviations, which potentially given_monster_details_entered,
makes ABAP less readable than languages like Java. In Java, you would have
178 179
3 ABAP Unit and Test-Driven Development Writing and Implementing Unit Tests 3.3
"WHEN.......................... The steps for implementing the test classes are as follows:
when_bom_is_calculated,
"THEN.......................... 1. Setting up the test
then_resulting_bom_is_correct,
2. Preparing the test data
Listing 3.15 The GIVEN/WHEN/THEN Pattern for Unit Tests
3. Calling the production code to be tested
As you can see in the preceding code, unit test methods that follow the behavior- 4. Evaluating the test result
driven development approach have three parts:
1. GIVEN describes the state of the system just before the test to be run. Step 1: Setting Up the Test
2. WHEN calls the actual production code you want to test. Our class under test has lots of small objects that need to be passed into it. For the
3. THEN uses ASSERT methods to test the state of the system after the class under first example, youll manually create all those small objects and pass them into
test has been run. the constructor method of the class under test to get the general idea of construc-
tor injection. Later on, youll find out how to reduce the amount of code needed
Use Natural Language to do this.
A test method is supposed to be able to be viewed by business experts to see if the test As theres no guarantee about the order in which the test methods will run, you
matches their specifications, so it has to read like natural language. Often, if business
want every test method to run as if it were the first test method run, to avoid tests
experts see even one line of ABAP, their eyes glaze over and youve lost them for good.
affecting each other. Therefore, when setting up the test, you create each object
instance anew and clear all the horrible global variables, as shown in Listing 3.17.
3.3.2 Implementing Test Classes
METHOD: setup.
Now that youve defined the test class, you can go ahead with the process of *-------------------------------------------------------*
implementing it. At the end of this step, youll be able to show the business * Called before every test
*-------------------------------------------------------*
expert who wrote the initial specification that youve made the specification into CREATE OBJECT mo_mock_logger.
a program that does what it says on the side of the box. CREATE OBJECT mo_mock_monster_pers_layer
EXPORTING
Given this, each implementation of a test method should look like it jumped io_logger = mo_logger
straight out of the pages of the specification and landed inside the ABAP Editor id_valid_on = sy-datum.
180 181
3 ABAP Unit and Test-Driven Development Writing and Implementing Unit Tests 3.3
At this point, you can be sure that during the test you wont actually read the "WHEN..........................
METHOD when_bom_is_calculated.
database or output any sort of log, so you can proceed with the guts of the actual
tests, which can be divided into the three remaining steps: preparing the test mo_class_under_test->simulate_monster_bom(
data, calling the production code to be tested, and evaluating the test result. EXPORTING is_bom_input_data = ms_input_data
IMPORTING et_bom_data = mt_bom_data ).
ENDMETHOD."when_bom_is_calculated
Step 2: Preparing the Test Data
Listing 3.19 Calling Production Code to be Tested
You now want to create some test data to be used by the method being tested. In
real life, these values could come from user input or an external system. Here, The method that calls the code to be tested should be very shortfor example, a
youll just hard-code them (Listing 3.18). Such input data is often taken from real call to a single function module or a methodfor two reasons:
problems that actually occurred in production; for example, a user might have
said, When I created a monster using these values, everything broke down. 1. Clarity
There could be a large number of values, which is why you hide away the details Anyone reading the test code should be able to tell exactly what the input data
of the data preparation in a separate method, to avoid distracting anybody read- is, what routine processes this data, and what form the result data comes back
ing the main test method. in. Calling several methods in a row distracts someone reading the code and
makes them have to spend extra time working out whats going on.
METHOD given_monster_details_entered.
2. Ease of maintenance
ms_input_data-monster_strength = 'HIGH'. You want to hide the implementation details of whats being tested from the
ms_input_data-monster_brain_size = 'SMALL'.
test method; this way, even if those implementation details change, the test
ms_input_data-monster_sanity = 0.
ms_input_data-monster_height_in_feet = 9. method doesnt need to change.
md_creator = 'BARON FRANKENSTEIN'. For example, in a procedural program, you might call two or three PERFORM state-
ments in a row when it would be better to call a single FORM routineso that if
ENDMETHOD."Monster Details Entered Implementation
you were to add another FORM routine in the real program, you wouldnt have to
Listing 3.18 Preparing Test Data by Simulating External Input
go and add it to the test method as well. With procedural programs, it would be
good to have a signature with the input and output values, but a lot of procedural
Step 3: Calling the Production Code to be Tested programs work by having all the data in global variables. Such a program can still
The time has come to actually invoke the code to be tested; youre calling pre- benefit from unit testing; it just requires more effort (possibly a lot more effort) in
cisely one method (or other type of routine), into which you pass in your hard- setting up the test to make sure the global variables are in the correct state before
coded dummy values and (usually) get some sort of result back. the test is run.
The important thing is that the routine being called doesnt know its being called
from a test method; the business logic behaves exactly as it would in production, Step 4: Evaluating the Test Result
with the exception that when it interacts with the database or another external Once you have some results back, youll want to see if they are correct or not.
system its really dealing with mock classes. There are generally two types of tests:
In this example, when you pass in the hard-coded input data, the real business 1. Absolutely basic tests
logic will be executed, and a list of monster components is passed back (Listing In Chapter 6, Section 6.3, youll read about design by contract, which says what
3.19). a method absolutely needs before it can work and what it absolutely must do.
182 183
3 ABAP Unit and Test-Driven Development Writing and Implementing Unit Tests 3.3
In their book The Pragmatic Programmer, Andrew Hunt and David Thomas state
when_bom_is_calculated( ).
that unit tests should test for method failures in terms of contract violations;
for example, do you pass a negative number into a method to calculate a square then_resulting_bom_is_correct( ).
root of that number (which is silly) or pass a monster with no head into a
CATCH zcx_violated_precondition.
method to evaluate hat size (a more realistic example)?
cl_abap_unit_assert=>fail( 'Violated Contract Precondition' ).
2. Data validation tests CATCH zcx_violated_postcondition.
cl_abap_unit_assert=>fail( 'Violated Contract Postcondition' ).
This is what most people would call the normal type of unit test in which you
ENDTRY.
have an expected result given a known set of inputs; for example, a method to
calculate the square root of sixteen should return four or (back in the real ENDMETHOD."Return a BOM for a Monster (Test Class)
world), when calling a method to supply monster hats, the HATS_RECIEVED Listing 3.20 Unit Test to Check for Basic Errors
returning parameter should be seven when the MONSTER_HEADS importing
parameter is seven, as demonstrated in the famous movie Seven Heads for The structure in Listing 3.20 is totally generic. In TDD, you create the structure
Seven Monsters. before writing the actual code, so the nature of the pre- and postconditions will
only be determined later. In this example, the precondition could be that the
Next, youll learn how to test for really basic failures, the standard way of evalu-
input data should be asking for sensible values, like wanting a murderous evil
ating test results, and how you can enhance the standard framework when the
monster as opposed to a cute fluffy one, and the postcondition should be that the
standard mechanism doesnt do everything you want. Finally, youll see how to
resulting monster is scary and not colored pink.
achieve 100% test coverage.
Evaluating Test Results in the Normal Way
Testing for Really Basic Failures
Moving on to looking at return values, when you ran the test method that called
In Chapter 6, youll see that each routine in a program has a contract with the
the production code, either you got a result backtable MT_BOM_DATA in the exam-
code that calls it, and there are only two ways of violating that contract: Either the
ple in Listing 3.21or some sort of publicly accessible member variable of the
calling program is at fault because the input data is wrong (violated precondition)
class under test was updateda status variable, perhaps. Next, youll want to run
or the routine itself is at fault because the data returned is wrong (violated post-
one or more queries to see if the new state of the application is what you expect
condition).
it to be in the scenario youre testing. This is done by looking at one or more vari-
The idea is that this contract is used to define unit tests for the routine, and those able values and performing an evaluation (called an assertion) to compare the
tests are used to verify that the routine is behaving correctly; two sides of the actual value with the expected value. If the two values dont match, then the test
same coin. fails, and you specify the error message to be shown to the person running the
test (Listing 3.21).
A failure of such a test indicates a really serious, fatal, end of the universe as we
know it type of bug, which needs to be addressed before dealing with minor (in "THEN..........................
METHOD then_resulting_bom_is_correct.
comparison) matters, like your program adding up one and one and getting three.
Listing 3.20 shows how to code such tests. DATA(bom_item_details) = mt_bom_data[ 1 ].
184 185
3 ABAP Unit and Test-Driven Development Writing and Implementing Unit Tests 3.3
METHOD if_constraint~is_valid.
Multiple Evaluations (Assertions) in One Test Method
*--------------------------------------------------------*
Many authors writing about TDD have stated that you should have only one ASSERT * IMPORTING data_object TYPE data
* RETURNING result TYPE abap_bool
statement per test. As an example, you shouldnt have a test method that tests that the
*--------------------------------------------------------*
correct day of the week is calculated for a given date and at the same time tests whether
* Local Variables
an error is raised if you dont supply a date. By using only one ASSERT statement per DATA: monster_description TYPE string.
test, its easier for you to quickly drill down into whats going wrong.
I would change that rule slightly, so that youre only testing one outcome per test. monster_description = data_object.
Although your test might need several ASSERT statements to make sure its correct, the
result = abap_false.
fact that you are only testing one outcome will still make it easy to figure out whats
wrong. The default behavior in a unit test in ABAP, however, is to stop the test method CHECK monster_description CS 'SCARY'.
at the first failed assertion and not even execute subsequent assertions within the same CHECK strlen( monster_description ) GT 5.
method. Every test method will be called, even if some failbut only the first assertion CHECK monster_description NS 'FLUFFY'.
in each method will be checked.
result = abap_true.
You can avoid this problem by setting an input parameter. In Listing 3.21, there are
three assertions. By adding the following code, you can make the method continue with ENDMETHOD. "IF_CONSTRAINT~is_valid
subsequent assertions even if one fails:
quit = if_aunit_constants=>no
186 187
3 ABAP Unit and Test-Driven Development Automating the Test Process 3.4
ENDMETHOD. "IF_CONSTRAINT~get_description If you think about what youve read above, youll see that this can lead to quite a
large effort in writing the test code, in two areas:
ENDCLASS."My Constraint - Implementation
Listing 3.23 Implementation of a Custom Constraint Class 1. Setting up the class under test via the SETUP method
Well-designed OO programs use lots of small reusable classes, and these often
All that remains is to call the assertion in the THEN part of a test method, as shown need to be inserted into the main class during construction. The smaller classes
in Listing 3.24. often need still smaller classes inserted into them during their construction,
and so on. In practical terms, this can mean a lot of lines of code to build up
DATA(custom_constraint) = NEW lcl_my_constraint( ).
your test class, passing in assorted mock doubles and elementary data parame-
cl_abap_unit_assert=>assert_that( exp = custom_constraint ters, such as date, and organizational elements, such as plant or sales organiza-
act = scariness_description ).
tion.
Listing 3.24 Call Assertion
2. Creating mock objects
As you can see, the only limit on what sort of evaluations you can run on the test Although you need mock objects to take the place of real objects during a test
results is your own imagination. in order to avoid actual database access and the like, it can take a good deal of
effort to create these and to write the logic inside them to return hard-coded
Achieving 100 Percent Test Coverage values. Even worse, if youre passing in constructor objects as interfaces as
When evaluating your results, you want to make sure that youve achieved 100% opposed to actual classes (which all the OO experts recommend), then you
test coverage. In the same way that the US army wants no man left behind, you need to expend even more effort, because you have to create an implementa-
want no line of code to remain untested. That is, if you have an IF statement with tion for every method in your mock object that uses the interface, including the
lots of ELSE clauses or a CASE statement with 10 different branches, then you want methods youre not interested in.
your tests to ensure that every possible path is followed at some point during the Luckily, there are solutions for both problems and ahead, youll read about two
execution of the test classes to be sure that nothing ends in tears by causing an frameworks to solve those problems:
error of some sort.
A framework to automate dependency injection (which I created myself)
Thats not as easy as it sounds. Aside from the fact that you need a lot of tests,
A framework to automate creating mock objects (SAP standard)
how can you be sure you havent forgotten some branches? Luckily, theres tool
support for this. As of release 7.31 of ABAP, you can follow the menu path Local Then, youll see how to combine both techniques. The section will end with a
Test Classes Execute Execute Tests With Code Coverage. As mentioned look at what to do when you have a really large number of situations to test for,
earlier in the book, the same feature is available through ABAP in Eclipse. which is often the case.
188 189
3 ABAP Unit and Test-Driven Development Automating the Test Process 3.4
Mock Objects Framework class needs to create itself and by creating and passing those objects in all at once.
This will drastically reduce the amount of CREATE OBJECT statementsand
Prior to ABAP 7.4 SPS 9, the only framework available to automate mock object cre-
because youre going to be using a program to dynamically determine what
ation was the open-source framework MockA (see Recommended Reading at the end of
the chapter). objects need to be created, that same program might as well also dynamically
Since then, SAP has introduced its own standard framework to fill that gap, which is
determine what type (subclass) those created objects should be.
described in this chapter. Listing 3.25 is an example of this approach; this code attempts to achieve the
same thing as the SETUP method in Listing 3.17, but with fewer CREATE OBJECT
statements. If you look at the definition of the SETUP method earlier in the chap-
3.4.1 Automating Dependency Injection ter, youll see three CREATE OBJECT statements, and in each case the object youre
Lets say, for the sake of example, that the main class under test needs two object creating has to be defined by a DATA statement. (Some objects also need data
instances to be passed into it during construction, and one of those objects needs parameters that are elementary, my dear Watson.)
some parameters itself. In other words, the main class has dependencies: The main
Listing 3.25 rewrites the same SETUP method, using a Z class created to use depen-
class depends on the fact that those object instances need to be created before it
dency injection. Look at this like a newspaper: Look at the four high-level method
itself can be created.
calls first (which represent the headlines), and then dive into the implementation
In real life, this could lead to dozens of lines of code in your SETUP method, cre- of each component method (representing the actual newspaper article) one at a
ating objects all over the place. This is bad, because the more CREATE OBJECT state- time.
ments you have in your program, the less flexible it becomes. The logic goes as
First, set some values for elementary data parameters. Then, specify any sub-
follows: Creating an object via CREATE OBJECT forces the resulting instance to be
classes you want to substitute for real classes. Finally, create an instance of the
of a specific class. Having objects of a specific class makes your program less resis-
class under test.
tant to change. If your main class contains lots of little helper classesas it
shouldthen you may need to have a great deal of CREATE OBJECT statements, "Set values for any elementary parameters that the objects
meaning that your program is full of rigid components. "being created need
zcl_bc_injector=>during_construction( :
This leads to two problems: First, you have to clutter up your code with a large for_parameter = 'ID_CREATOR' use_value = md_creator ),
for_parameter = 'ID_VALID_ON' use_value = sy-datum ).
number of CREATE OBJECT statements. Second, those CREATE OBJECT statements
usually create instances of a hard-coded class type rather than a dynamically "We want to use a test double for the database object
determined subclass. zcl_bc_injector=>instead_of(
using_main_class = 'ZCL_MONSTER_SIM_PERS_LAYER'
Now, youre always going to have to state the specific subclass you want some- use_sub_class = 'ZCL_MOCK_MONSTER_PERS_LAYER' ).
where. However, youll see that its possible to decouple this from the exact "Same deal for the logger
instant you call the CREATE OBJECT statement and, as a beneficial by-product, have zcl_bc_injector=>instead_of(
fewer CREATE OBJECT statements in the first place. using_main_class = 'ZCL_MONSTER_LOGGER'
use_sub_class = 'ZCL_MOCK_LOGGER' ).
The process of passing the objects a class needs to create itself is known as depen-
"Off we go!
dency injection; you saw this at work in Section 3.2.3 when you manually passed zcl_bc_injector=>create_via_injection(
mock objects into a constructor method. Here, you seek to automate the process CHANGING co_object = mo_class_under_test ).
by dynamically working out what objects (dependencies) an instance of the main Listing 3.25 SETUP Method Rewritten Using Z Statement
190 191
3 ABAP Unit and Test-Driven Development Automating the Test Process 3.4
As you can see, when using this approach you dont need DATA statements to INSERT parameter_value_information INTO TABLE mt_parameter_values.
declare the database access object or the logging object. In addition, you only
ENDMETHOD.
have one CREATE statement. This may not seem like much in this simple example,
Listing 3.26 DURING CONSTRUCTION Method
but the advantage increases proportionally with the complexity of the class being
tested.
The next method called as part of the rewritten SETUP method is the INSTEAD_OF
method (Listing 3.27). This method takes in as parameters the subclasses you
Note
want to create instead of a superclass, and that relation is stored in a hashed table.
You can also use this same methodology if the importing parameter of the object con-
structor is an interface. You just pass the interface name in to the INSTEAD_OF method METHOD instead_of.
* Local Variables
rather than the main class name.
DATA: sub_class_to_use_info LIKE LINE OF mt_sub_classes_to_use,
created_objects_info LIKE LINE OF mt_created_objects.
The first method called in Listing 3.25 is the DURING_CONSTRUCTION method. This
sub_class_to_use_info-main_class = using_main_class.
is shown in greater detail in Listing 3.26; it analyzes elementary parameters and sub_class_to_use_info-sub_class = use_sub_class.
then does nothing fancier than adding entries to an internal table.
"Add entry at the start, so it takes priority over previous
METHOD during_construction. "similar entries
* Local Variables INSERT sub_class_to_use_info INTO mt_sub_classes_to_use INDEX 1.
DATA: dummy_string TYPE string ##needed,
data_element_name TYPE string, "A specific object instance can be passed in, sometimes
parameter_value_information LIKE LINE OF mt_parameter_values. "a generated instance created via a framework
CHECK with_specific_instance IS SUPPLIED.
parameter_value_information-identifier = for_parameter. CHECK with_specific_instance IS BOUND.
parameter_value_information-do_value = REF #( use_value ).
created_objects_info-clsname = use_sub_class.
CHECK sy-subrc = 0. created_objects_info-object = with_specific_instance.
INSERT created_objects_info INTO TABLE mt_created_objects.
CALL METHOD cl_abap_structdescr=>describe_by_data_ref
EXPORTING ENDMETHOD.
p_data_ref = parameter_value_information-do_value
Listing 3.27 INSTEAD_OF Method
RECEIVING
p_descr_ref = DATA(type_description)
EXCEPTIONS The last part of the rewritten SETUP method is the CREATE_BY_INJECTION method
reference_is_initial = 1
(Listing 3.28). This is written as close to plain English as possible so that the code
OTHERS = 2.
is more or less self-explanatory. In essence, youre passing the input values you
IF sy-subrc <> 0. just stored into the constructor method when creating your class under test and
RETURN.
any smaller classes it requires.
ENDIF.
METHOD create_via_injection.
SPLIT type_description->absolute_name AT '=' INTO dummy_string data_ * Local Variables
element_name. DATA: class_in_reference_details TYPE REF TO cl_abap_refdescr.
parameter_value_information-rollname = data_element_name. * Determine the class type of the reference object passed in
class_in_reference_details ?=
cl_abap_refdescr=>describe_by_data( co_object ).
192 193
3 ABAP Unit and Test-Driven Development Automating the Test Process 3.4
194 195
3 ABAP Unit and Test-Driven Development Automating the Test Process 3.4
concepts; lets examine them one at a time before looking at the listing as a Its fairly obvious what the result is going to be, but the test method ends with
whole. two assertions: one to see if the correct result has been returned and one to see if
the method was called once and only once as expected.
First, you create the mock object instance, which is an instance of a (nonexistent)
class that implements the chosen interface. This dummy class has empty imple- Listing 3.29 combines these various lines of code. When you put them all
mentations for every method defined in the interface, as follows: together, what have you got? A lovely unit test!
Now is the time to overcomplicate things and say that, in this unit test, you expect "What result do we expect back from the called method?
cl_abap_testdouble=>configure_call( mock_monster_simulator )-
the method to be called once and once only. The previous line of code is modified >returning( 'REALLY SCARY' )->and_expect( )->is_called_times( 1 ).
as follows:
"Prepare the simulated input details e.g. monster strength
cl_abap_testdouble=>configure_call( mock_monster_simulator )- given_monster_details_entered( ).
>returning( 'REALLY SCARY' )->and_expect( )->is_called_times( 1 ).
"Say what method we are mocking and the input values
The names of the standard methods make the code read almost like English, mock_monster_simulator->calculate_scariness( is_bom_input_data = ms_
which is a Good Thing. input_data ).
Next, you set up the input data. As mentioned earlier in Listing 3.15, we have a "Invoke the production code to be tested
special helper method for this called GIVEN_MONSTER_DETAILS_ENTERED, which fills scariness_description = mock_monster_simulator->calculate_
scariness( ms_input_data ).
in the values for the input structure, because there could be quite a few such val-
ues. Now, you can finally say (1) which method it is you want to mock, and (2) "Was the correct value returned?
what input values should give the result you just specified (i.e., REALLY SCARY), as cl_abap_unit_assert=>assert_equals(
exp = 'REALLY SCARY'
follows: act = scariness_description
msg = 'Monster is not scary enough' ).
mock_monster_simulator->calculate_scariness( is_bom_input_data = ms_
input_data ).
"Listen very carefully - was the method only called once?
From now on, calls to methods of our mock instance will be indistinguishable cl_abap_testdouble=>verify_expectations( mock_monster_simulator ).
from calls to an instance of an actual class. To prove this, perform a real call to the ENDMETHOD."Mocking Framework Test
same method (that may seem pointless now, but just you wait and see) to fill a Listing 3.29 Coding Unit Test without Needing Definitions and Implementations
variable with the scariness description, as follows:
scariness_description = mock_monster_simulator->calculate_ As you can see, the ATDF does away with the need to create definitions and
scariness( ms_input_data ). implementations of the class you want to mock. You only need to focus on what
output values should be returned for what input values for what class. This
196 197
3 ABAP Unit and Test-Driven Development Automating the Test Process 3.4
methodology uses ABAPs ability to generate temporary programs that live only "What result do we expect back from the called method?
DATA(lo_violation) = NEW zcx_violated_precondition_stat( ).
in memory and only exist as long as the mother program is running. In effect,
cl_abap_testdouble=>configure_call( mock_monster_simulator )->raise_
the framework writes the method definitions and implementations for you at exception( lo_violation ).
runtime.
"Prepare the simulated input details e.g. monster strength
Note also that during the test a check is performed to see if the CALCULATE_ CLEAR ms_input_data.
SCARINESS method was in fact called in the preceding code. Even if a mock
"Say what method we are mocking and the input values
method doesnt do anything at all in a test situation, you still want to be sure that TRY.
its been called. mock_monster_simulator->calculate_scariness( is_bom_input_data = ms_
input_data ).
Method Chaining
"Invoke the production code to be tested
Listing 3.29 also uses method chaining, a feature we discussed in Chapter 2. Four meth- scariness_description = mock_monster_simulator->calculate_
scariness( ms_input_data ).
ods in a row are called on CL_ABAP_TESTDOUBLE: CONFIGURE_CALL, RETURNING, AND_
EXPECT, and IS_CALLED_TIMES. Before release 7.02 of ABAP, you would have had to
CATCH zcx_violated_precondition_stat.
use four lines here, create a helper variable on the first line, and use that variable on "All is well, we wanted the exception to be raised
each subsequent line. RETURN.
ENDTRY.
198 199
3 ABAP Unit and Test-Driven Development Automating the Test Process 3.4
200 201
3 ABAP Unit and Test-Driven Development Automating the Test Process 3.4
"A Monster Creator (e.g., the baron ) needs a speciality method, hard-coded data was used. The rest of the book goes on and on about
zcl_bc_injector=>during_construction( :
how hard-coded values are the work of the devil, so theres some disparity here
for_parameter = 'ID_SPECIALITY' use_value = speciality ).
a circle that needs to be squared.
"A complicated laboratory needs a Monster Creator object but this will
be created automatically by the injector In most cases, you want to test your method with a wide variety of possible
inputs to make sure the correct result is returned in every case. One way to do
"Pass in the instance of the simulator we have mocked up
this is to code one unit test with one set of inputs to ensure it comes back with the
zcl_bc_injector=>for_the(
interface_or_class = monster_simulator_interface correct result, and then move the transport into test (or production) and wait for
use_specific_instance = mock_monster_simulator ). people to tell you everything falls apart when you input a different set of results.
(Hopefully, you can see that might not be the ideal way to go about things.) It
"During injection the mocked up object gets passed in with
"all the other data we have set up would be so much better if you started off with a wide range of scenarios, ran
zcl_bc_injector=>create_via_injection( tests for all of them, made sure they all worked, and then moved the program to
CHANGING co_object = laboratory ).
test. Everyone would be a lot happierespecially you.
DATA(is_the_monster_ok) = laboratory->evaluate_monster( ms_input_
Getting a list of scenarios was easy: I went to the business users (Igor and his
data ).
hunchback mates) and asked for a list of a hundred sets of monster requirements
cl_abap_unit_assert=>assert_equals( and their monster BOMs. Before I could say Jack Robinson, I had a spreadsheet
exp = abap_true
in my hot little hands. Wonderful! Now, should I manually code one hundred dif-
act = is_the_monster_ok
msg = 'Monster is just not good enough' ). ferent test methods, each with the same method call with a different set of inputs
Listing 3.32 Combining ATDF with Dependency Injection followed by assertions with a different set of results? Doesnt sound like much
fun.
In Listing 3.32, youre passing in a generated object that will be used when the
You could create a database table (but you might have to create different ones for
injection class creates the class under test.
different programs) or store the test data in the standard ECATT automated tests
What the examples that culminate in Listing 3.32 show is that its possible to script system. My favorite solution to this problem, however, is an open-source
simplify the SETUP method dramatically when creating the class under test. You project created by a programmer called Alexander Tsybulsky and his colleagues,
can use ATDF to set up test doubles with a lot less effort and use injection to who came up with a framework called the Mockup Loader, which lives on the
avoid having to code long strings of CREATE OBJECT statements that pass the GitHub site and can be downloaded to your system via the link found in the Rec-
results into each other before handing the end result into the class under test ommended Reading box at the end of this chapter.
when its finally created. The two frameworks werent created with the intention
of working together, but by a happy accident they fit together like the pieces of ABAP Open-Source Projects
a jigsaw puzzle. Several times throughout this book, well refer to open-source ABAP projects, which
started life in the SAP Code Exchange section of the SAP Community Network website
but nowadays live on sites like GitHub. The obvious benefit is that these are free. Some
3.4.4 Unit Tests with Massive Amounts of Data development departments have rules against installing such things, but I feel theyre
In the United Kingdom, children can buy I-Spy books, in which they have to spot just cutting off their nose to spite their face.
various things. Once theyve spotted them all, they send the completed list to Big The important point to note is that these are not finished products, so installing them is
not like installing the sort of SAP add-on you pay for. Its highly likely youll encounter
Chief I-Spy, who sends them a feather in return. If you were on the lookout for a
bugs and that the tool wont do 100% of what you want it to do. In both cases, I
feather, you have may have spied that in all the preceding examples, regardless of
202 203
3 ABAP Unit and Test-Driven Development Automating the Test Process 3.4
Then, create an instance of your mockup loader. If you spelled the name of the
strongly encourage you to fix the bug or add the new feature, and then update the
open-source project so that the whole SAP community benefits.
MIME object incorrectly, this is where youll find out in a hurry, due to a fatal
error message. Next, load the MIME object into an internal table based on the
The very first open-source project youll need to install is SAPlink so that you can down-
load any others you need easily. You can get SAPlink from www.saplink.org. structure you declared earlier. At this point, if the columns in the structure dont
match the columns in the spreadsheet, an exception is raised and the test fails.
This is good: Making sure the test data format is correct is just another step in get-
Before you begin, you should have the test data loaded inside the SAP develop-
ting the unit tests to pass.
ment system (where the tests will run, of course). Thats much better than having
the test data on some sort of shared directory or, worse, on a developers laptop. The rest is plain sailing: Loop through the test cases, call the method being tested
each time with the specific test case input data, and see if the result matches the
The GitHub page for the Mockup Loader gives detailed instructions for storing a
specific test case result data. As mentioned earlier, you can use the QUIT parame-
spreadsheet inside the MIME repository, which allows you to store various files
ter to determine if you want to see all the results at once or stop at the first failure;
(like spreadsheets) inside SAP. You can upload as many spreadsheets as you want:
the code in Listing 3.33 stops at the first failure.
one for input data, one for output data, or both in one sheet, as in the following
example. Even betterif you have different types of data, you can store each one METHOD mockup_loader.
as a sheet inside the one big worksheet, keeping everything in the same place. * Local Variables
TYPES: BEGIN OF l_typ_monster_test_data,
Once the test data in the spreadsheet is uploaded into SAP, the fun begins. Listing strength TYPE zde_monster_strength,
brain_size TYPE zde_monster_brain_size,
3.33 demonstrates a test method that evaluates lots of test cases at once, without sanity TYPE zde_monster_sanity,
any of the fancy things mentioned elsewhere in the chapter so as not to distract ssatn TYPE zde_component_type_percentage,
from whats being demonstrated. sspdt TYPE zde_component_type_percentage,
END OF l_typ_monster_test_data.
In this example, the idea is to loop through different sets of customer require-
* Need to specify the type of the table, to make sure
ments to make sure the correct component split is returned. For now, you can * correct tests are done on the data loaded from MIME
ignore SSATN and SSPDT and the percentage split; those elements will be DATA test_cases_table TYPE TABLE OF l_typ_monster_test_data.
detailed in Chapter 8.
"Name of Entry in SMW0
Start with a spreadsheet with five columns; the first three are customer require- zcl_mockup_loader=>class_set_source(
i_type = 'MIME'
ments (e.g., what the customer desires in a monster) and the last two are result i_path = 'ZMONSTER_TEST_DATA' ).
columns (percentages of SSATN and SSPDT, respectively). At the start of the test
method, declare a structure that exactly matches the columns in the spreadsheet; TRY.
DATA(mockup_loader) = zcl_mockup_loader=>get_instance( ).
the spreadsheet has to have a header row that exactly matches the names of the CATCH zcx_mockup_loader_error INTO DATA(loader_exception).
fields in this structure. cl_abap_unit_assert=>fail( loader_exception->get_text( ) ).
ENDTRY.
When you upload your spreadsheet to the MIME repository using Transaction
SMW0, you give the file a name. In the test method, you must also specify the fact TRY.
"Load test cases. The format is SPREADSHEET NAME/Sheet Name
that this is a MIME object and the name of that object. You could specify FILE and mockup_loader->load_data(
a directory path, but that would be uncool; youd never be invited to parties EXPORTING i_obj = 'MONSTER_TEST_DATA/monster_tests'
again. IMPORTING e_container = test_cases_table ).
204 205
3 ABAP Unit and Test-Driven Development Summary 3.5
cl_abap_unit_assert=>assert_equals( One question that hasnt come up yet is this: What if someone else comes along
exp = test_case-sspdt and changes your program by adding a new feature, but accidently breaks some-
act = actual_percentage_sspdt
msg = |{ test_case-strength } + { test_case-brain_ thing else and doesnt bother to run the unit tests, and thus doesnt realize that
size } + { test_case-sanity } gets incorrect SSPDT %age| ). hes broken something? Clearly, you somehow need to embed the automated
unit tests into the whole change control procedure. Conveniently, this leads
ENDLOOP."Test Cases
nicely to the subject of the next chapter: ABAP Test Cockpit.
ENDMETHOD."Mockup Loader
Listing 3.33 Test Method to Load Multiple Test Cases Recommended Reading
206 207
3 ABAP Unit and Test-Driven Development
208
Contents
Foreword ......................................................................................................... 19
Acknowledgments ............................................................................................ 21
Introduction ..................................................................................................... 23
9
Contents Contents
2.1.2 Creating While Reading .................................................... 95 2.6.9 Grouping Internal Tables .................................................. 140
2.1.3 Buffering Improvements ................................................... 96 2.6.10 Extracting One Table from Another .................................. 143
2.1.4 Inner Join Improvements .................................................. 98 2.7 Object-Oriented Programming ...................................................... 145
2.1.5 UNION ............................................................................ 100 2.7.1 Upcasting/Downcasting with CAST ................................... 146
2.1.6 Code Completion in SELECT Statements ........................... 101 2.7.2 Finding the Subclass of an Object Instance ....................... 147
2.1.7 Filling a Database Table with Summarized Data ................ 101 2.7.3 CHANGING and EXPORTING Parameters ......................... 148
2.2 Declaring and Creating Variables ................................................... 102 2.7.4 Changes to Interfaces ....................................................... 149
2.2.1 Omitting the Declaration of TYPE POOL Statements ........ 103 2.8 Search Helps .................................................................................. 151
2.2.2 Omitting Data Type Declarations ...................................... 104 2.8.1 Predictive Search Helps .................................................... 151
2.2.3 Creating Objects Using NEW ............................................ 105 2.8.2 Search Help in SE80 ......................................................... 152
2.2.4 Filling Structures and Internal Tables While Creating 2.9 Unit Testing ................................................................................... 153
Them Using VALUE .......................................................... 106 2.9.1 Creating Test Doubles Relating to Interfaces ..................... 153
2.2.5 Filling Internal Tables from Other Tables Using FOR ......... 107 2.9.2 Coding Return Values from Test Doubles ......................... 154
2.2.6 Creating Short-Lived Variables Using LET ......................... 108 2.9.3 Creating Test Doubles Related to Complex Objects .......... 155
2.3 String Processing ........................................................................... 109 2.10 Summary ....................................................................................... 156
2.3.1 New String Features in Release 7.02 ................................. 109
2.3.2 New String Features in Release 7.4 ................................... 110 3 ABAP Unit and Test-Driven Development ................................ 159
2.4 Calling Functions ........................................................................... 111
2.4.1 Method Chaining ............................................................. 111 3.1 Eliminating Dependencies ............................................................. 161
2.4.2 Avoiding Type Mismatch Dumps When Calling 3.1.1 Identifying Dependencies ................................................. 162
Functions ......................................................................... 112 3.1.2 Breaking Up Dependencies Using Test Seams ................... 164
2.4.3 Using Constructor Operators to Convert Strings ............... 114 3.1.3 Breaking Up Dependencies Properly ................................. 166
2.4.4 Functions That Expect TYPE REF TO DATA ....................... 115 3.2 Implementing Mock Objects ......................................................... 168
2.5 Conditional Logic .......................................................................... 116 3.2.1 Test Injection for Test Seams ............................................ 168
2.5.1 Using Functional Methods in Logical Expressions ............. 116 3.2.2 Creating Mock Objects ..................................................... 169
2.5.2 Omitting ABAP_TRUE ...................................................... 117 3.2.3 Proper Injection ............................................................... 171
2.5.3 Using XSDBOOL as a Workaround for BOOLC .................. 119 3.3 Writing and Implementing Unit Tests ............................................ 173
2.5.4 The SWITCH Statement as a Replacement for CASE .......... 120 3.3.1 Defining Test Classes ........................................................ 173
2.5.5 The COND Statement as a Replacement for IF/ELSE ......... 122 3.3.2 Implementing Test Classes ............................................... 180
2.6 Internal Tables ............................................................................... 124 3.4 Automating the Test Process ......................................................... 189
2.6.1 Using Secondary Keys to Access the Same Internal Table 3.4.1 Automating Dependency Injection ................................... 190
in Different Ways ............................................................. 124 3.4.2 Automating Mock Object Creation: Test Double
2.6.2 Table Work Areas ............................................................. 127 Framework ....................................................................... 195
2.6.3 Reading from a Table ....................................................... 128 3.4.3 Combining Dependency Injection and the ABAP
2.6.4 CORRESPONDING for Normal Internal Tables .................. 130 Test Double Framework ................................................... 200
2.6.5 MOVE-CORRESPONDING for Internal Tables with 3.4.4 Unit Tests with Massive Amounts of Data ........................ 202
Deep Structures ............................................................... 131 3.5 Summary ....................................................................................... 207
2.6.6 Dynamic MOVE-CORRESPONDING ................................. 135
2.6.7 New Functions for Common Internal Table Tasks ............. 137
2.6.8 Internal Table Queries with REDUCE ................................ 139
10 11
Contents Contents
12 13
Contents Contents
7.3.5 Setting Display Text Using Determinations ....................... 371 9.2.3 Report Flow Step 3: Making Application-Specific
7.3.6 Disabling Certain Commands Using Validations ................ 384 Changes (Specific) ............................................................ 494
7.3.7 Checking Data Integrity Using Validations ........................ 386 9.2.4 Report Flow Step 4: Displaying the Report (Generic) ........ 507
7.3.8 Responding to User Input via Actions ............................... 392 9.3 Adding Custom Command Icons with Programming ...................... 512
7.3.9 Saving to the Database ..................................................... 404 9.3.1 Creating a Method to Automatically Create a Container ... 514
7.3.10 Tracking Changes in BOPF Objects ................................... 411 9.3.2 Changing ZCL_BC_VIEW_SALV_TABLE to Fill the
7.4 Custom Enhancements .................................................................. 420 Container ......................................................................... 514
7.4.1 Enhancing Standard SAP Objects ...................................... 420 9.3.3 Changing the INITIALIZE Method .................................... 516
7.4.2 Using a Custom Interface (Wrapper) ................................. 423 9.3.4 Adding the Custom Commands to the Toolbar ................. 517
7.5 Summary ....................................................................................... 425 9.3.5 Sending User Commands from the Calling Program .......... 518
9.4 Editing Data .................................................................................. 519
8 BRFplus ..................................................................................... 427 9.4.1 Creating a Custom Class to Hold the Standard SALV
Model Class ..................................................................... 520
8.1 The Historic Location of Rules ....................................................... 430 9.4.2 Changing the Initialization Method of
8.1.1 Rules in Peoples Heads .................................................... 430 ZCL_BC_VIEW_SALV_TABLE ............................................ 521
8.1.2 Rules in Customizing Tables ............................................. 432 9.4.3 Adding a Method to Retrieve the Underlying
8.1.3 Rules in ABAP .................................................................. 434 Grid Object ...................................................................... 525
8.2 Creating Rules in BRFplus: Basic Example ...................................... 435 9.4.4 Changing the Calling Program .......................................... 527
8.2.1 Creating a BRFplus Application ......................................... 435 9.4.5 Coding User Command Handling ...................................... 528
8.2.2 Adding Rule Logic ............................................................ 444 9.5 Handling Large Internal Tables with CL_SALV_GUI_TABLE_IDA ..... 531
8.2.3 BRFplus Rules in ABAP ..................................................... 456 9.6 Open-Source Fast ALV Grid Object ................................................ 534
8.3 Creating Rules in BRFplus: Complicated Example ........................... 458 9.7 Summary ....................................................................................... 535
8.4 Simulations .................................................................................... 465
8.5 SAP Business Workflow Integration ............................................... 467 10 ABAP2XLSX and Beyond ........................................................... 537
8.6 Options for Enhancements ............................................................ 472
8.6.1 Procedure Expressions ...................................................... 472 10.1 The Basics ...................................................................................... 539
8.6.2 Application Exits .............................................................. 473 10.1.1 How XLSX Files Are Stored ............................................... 539
8.6.3 Custom Frontends ............................................................ 473 10.1.2 Downloading ABAP2XLSX ................................................ 541
8.6.4 Custom Extensions ........................................................... 474 10.1.3 Creating XLSX Files Using ABAP ....................................... 541
8.7 SAP HANA Rules Framework ......................................................... 474 10.2 Enhancing Custom Reports with ABAP2XLSX ................................ 546
8.8 Summary ....................................................................................... 475 10.2.1 Converting an ALV Object to an Excel Object ................... 546
10.2.2 Changing Number and Text Formats ................................ 548
9 ALV SALV Reporting Framework .............................................. 477 10.2.3 Establishing Printer Settings ............................................. 551
10.2.4 Using Conditional Formatting ........................................... 554
9.1 Getting Started .............................................................................. 480 10.2.5 Creating Spreadsheets with Multiple Worksheets ............. 563
9.1.1 Defining a SALV-Specific (Concrete) Class ......................... 481 10.2.6 Using Graphs and Pie Charts ............................................. 565
9.1.2 Coding a Program to Call a Report ................................... 482 10.2.7 Embedding Macros .......................................................... 568
9.2 Designing a Report Interface ......................................................... 484 10.2.8 Emailing the Result .......................................................... 574
9.2.1 Report Flow Step 1: Creating a Container 10.2.9 Adding Hyperlinks to SAP Transactions ............................ 577
(Generic/Optional) ........................................................... 486 10.3 Tips and Tricks ............................................................................... 582
9.2.2 Report Flow Step 2: Initializing a Report (Generic) ........... 487 10.3.1 Using the Enhancement Framework for Your Own Fixes ... 583
14 15
Contents Contents
10.3.2 Creating a Reusable Custom Framework ........................... 585 12.4 Backend Tasks: Automatically Generating the Model ..................... 691
10.4 Beyond Spreadsheets: Microsoft Word Documents ........................ 586 12.4.1 BOPF/SAP Gateway Integration ........................................ 691
10.4.1 Installing the Tool ............................................................ 587 12.4.2 CDS View/SAP Gateway Integration ................................. 694
10.4.2 Creating a Template ......................................................... 588 12.5 Frontend Tasks: Creating the View and Controller Using SAPUI5 ... 699
10.4.3 Filling the Template ......................................................... 589 12.5.1 First Steps ........................................................................ 699
10.5 Summary ....................................................................................... 596 12.5.2 View ................................................................................ 702
12.5.3 Controller ......................................................................... 715
11 Web Dynpro ABAP and Floorplan Manager ............................. 599 12.5.4 Testing Your Application .................................................. 721
12.6 Generating SAPUI5 Applications from SAP Web IDE Templates ..... 723
11.1 The Model-View-Controller Concept ............................................. 600 12.7 Generating SAPUI5 Applications from the BUILD Tool ................... 728
11.1.1 Model .............................................................................. 601 12.8 Adding Elements with OpenUI5 .................................................... 737
11.1.2 View ................................................................................ 603 12.9 Importing SAPUI5 Applications to SAP ERP ................................... 741
11.1.3 Controller ......................................................................... 606 12.9.1 Storing the Application in Releases Lower Than 7.31 ....... 742
11.2 Building the WDA Application ....................................................... 607 12.9.2 Storing the Application in Releases 7.31 and Above ......... 744
11.2.1 Creating a Web Dynpro Component ................................. 609 12.9.3 Testing the SAPUI5 Application from within SAP ERP ...... 745
11.2.2 Declaring Data Structures for the Controller ..................... 611 12.10 SAPUI5 vs. SAP Fiori ...................................................................... 747
11.2.3 Establishing View Settings ................................................ 613 12.11 Summary ....................................................................................... 748
11.2.4 Defining the Windows ..................................................... 623
11.2.5 Navigating between Views inside the Window ................. 624 13 ABAP Channels .......................................................................... 751
11.2.6 Enabling the Application to be Called ............................... 627
11.3 Coding the WDA Application ........................................................ 628 13.1 General Concept ............................................................................ 752
11.3.1 Linking the Controller to the Model ................................. 629 13.1.1 ABAP Messaging Channels ............................................... 753
11.3.2 Selecting Monster Records ............................................... 629 13.1.2 ABAP Push Channels ........................................................ 754
11.3.3 Navigating to the Single-Record View .............................. 635 13.2 ABAP Messaging Channels: SAP GUI Example ............................... 755
11.4 Using Floorplan Manager to Create WDA Applications .................. 639 13.2.1 Coding the Sending Application ....................................... 758
11.4.1 Creating an Application Using Floorplan Manager ............ 640 13.2.2 Coding the Receiving Application ..................................... 764
11.4.2 Integrating BOPF with Floorplan Manager ........................ 651 13.2.3 Watching the Applications Communicate ......................... 768
11.5 Summary ....................................................................................... 656 13.3 ABAP Push Channels: SAPUI5 Example .......................................... 771
13.3.1 Coding the Receiving (Backend) Components ................... 772
12 SAPUI5 ...................................................................................... 659 13.3.2 Coding the Sending (Frontend) Application ...................... 780
13.4 Internet of Things Relevance ......................................................... 782
12.1 Architecture .................................................................................. 661 13.5 Summary ....................................................................................... 783
12.1.1 Frontend: What SAPUI5 Is ............................................... 662
12.1.2 Backend: What SAP Gateway Is ........................................ 663 Appendices ....................................................................................... 785
12.2 Prerequisites .................................................................................. 664
12.2.1 Requirements in SAP ........................................................ 664 A Conclusion ............................................................................................... 785
12.2.2 Requirements on Your Local Machine .............................. 665 B The Author .............................................................................................. 789
12.3 Backend Tasks: Creating the Model Manually Using
SAP Gateway ................................................................................. 665 Index ............................................................................................................... 791
12.3.1 Configuration ................................................................... 666
12.3.2 Coding ............................................................................. 680
16 17
Index
A ABAP2XLSX, 537
conditional formatting, 554
ABAP download, 541
constructs, 47 email, 574
development system, 45 enhancement framework, 583
event mechanism, 509 enhancing custom reports, 546
Quick Assist, 53 example programs, 545
ABAP 7.02, 103, 109, 111, 116, 124 hyperlinks, 577, 579
ABAP 7.31, 188 macros, 568
ABAP 7.4, 93, 110, 116, 129, 149, 153 multiple worksheets, 563
new features, 89 printer settings, 551
recommended reading, 157 recommended reading, 597
ABAP 7.5, 53, 89, 94 templates, 571
ABAP Channels, 751 testing, 558
general concept, 752 Access condition parameter, 276
ABAP Extended Program Check, 222, 238 Adapter pattern, 425, 510
ABAP in Eclipse SAP NetWeaver Agile development, 729
Development Tools for ABAP (ADT) Alias, 266
ABAP Managed Database Procedures (ADMP), ALPHA formatting option, 111
254, 257, 279 ALV, 546
Eclipse, 281 application, 756
ABAP Messaging Channels, 752, 753, 756 function modules, 507
coding the receiving application, 764 grid, 607
coding the sending application, 758 interface, 761
example, 768 list program, 317
framework, 761 report, 251, 420, 697, 751
SAP GUI, 755 SALV, 477
warning, 758 screen, 706
ABAP Push Channels, 752, 754, 771 Annotation, 62, 262, 349, 696
coding the sending application, 780 ANSI-standard SQL, 282
SAPUI5, 771 Application model, 488
ABAP Test Cockpit (ATC), 33, 145, 209, 210, Application settings, 437
289, 290, 305 Application-defined function, 493
recommended reading, 252 Artifacts, 47
SAP HANA, 211 Assemble/act/assert test, 179
ABAP Test Double Framework (ATDF), 195, ASSERT, 186, 334
197, 200 Association, 261, 654
ABAP to Word, 589 Asterisks, 99
ABAP Unit, 159, 211 Authority checks, 275, 371
ABAP Workbench, 33, 34, 52, 72, 429, 681
ABAP_TRUE, 117, 118
791
Index Index
792 793
Index Index
794 795
Index Index
796 797
Index Index
SAP GUI, 77, 151, 222, 477, 728, 753 SAPUI5 (Cont.) SPLASH BUILD The Pragmatic Programmer, 184
embedded, 75 architecture, 661 SQL, 686 Tooltip, 498, 504
Screen Painter, 733 browser support, 679 calculations, 93 Trace file, 76
SAP HANA, 101, 211, 253, 475, 534, 662, buttons, 719 queries, 92 Transaction, 627
686, 758 controller, 715 SQL for the web, 686 (/BOBF/TEST_UI, 384
ABAP table design, 299 Developer Guide, 738 SQL Monitor, 289 /BOBF/CONF_UI, 414
AMDP, 257, 279 fragment XML file, 710 SQL Performance Tuning Worklist, 289, 290 /BOBF/TEST_UI, 398
CDS views, 256, 257 function for testing, 721 SQL view, 262 /IWFND/ERROR_LOG, 688
code pushdown, 254, 288 functions, 717, 718 SQL-92 standard, 91 /IWFND/MAINT_SERVICE, 675, 677, 687
database views, 255 HTML file, 703 SQLScript, 257, 279, 282, 283 /SDF/CD_CCA, 226
database-specific features, 303 importing applications, 741 Stateful, 773 ATC, 216, 217
DDL, 258 JavaScript, 662 Stateless, 773 BOB, 343, 358, 393
Eclipse, 258 prerequisites, 664 Static check, 314 BOBF, 343
recommended reading, 309 recommended reading, 749 Static code check, 211, 228 BOBF/TEST_UI, 419
redundant storage, 299 storing applications, 744 Static method, 287, 765 BOBX, 343
secondary indexes, 301 testing, 721, 745 Stored procedure, 255 BOPF_EWB, 421
SELECT coding, 304 view, 702 String processing, 109 FPM_WB, 641, 651
stored procedure, 279 view and controller, 699 Structured variable, 397 IWFND/MAINT_SERVICE, 698
SAP HANA Cloud Connector, 724 XML file, 704 Stub objects, 168 MRRL, 101
SAP HANA Cloud Platform, 735 Search helps, 151 Subnodes, 349 RSSCD100, 420
SAP HANA Cloud Platform Cockpit, 725 predictive, 151 SWITCH, 120 SALV, 760
SAP HANA Rules Framework, 474 Sbastien Hermann, 587 Syntax check, 314 SAMC, 762
SAP Logon Pad, 85 SELECT, 234 System alias, 673 SAPC, 778
SAP Messaging Channels SELECT *, 230 SY-TABIX, 140 SAT, 75
activity scope, 760 SELECT statement, 101, 256 SATC, 289
receiver object, 766 Separation of concerns, 166, 210 SCDO, 412
subscriber object, 766 Service adaptation definition language T SCI, 209, 211
SAP NetWeaver Development Tools for ABAP (SADL), 695 SE03, 65
(ADT), 34, 695 Service Builder, 666 Table join, 101 SE11, 61, 151, 257, 265
SAP Process Integration (SAP PI), 347, service_manager, 356 Table work areas, 127 SE24, 85, 146, 320, 321, 429, 672
663, 765 SET_COLUMN_ATTRIBUTES_METHOD, 497 TCP protocol, 783 SE37, 85, 429
SAP Push Channels SETUP, 177 Technical columns, 498 SE38, 44
code for incoming messages, 774 Short dump, 64, 112, 113 Template SE80, 33, 41, 43, 71, 75, 152, 429, 609, 645
coding receiving components, 772 SICF framework, 691 customization, 726 SEGW, 666, 674, 691
testing the APC service, 778 SICF service node, 682 views, 261 SICF, 80, 578, 672, 678, 679, 772, 778
SAP S/4HANA, 248, 251, 253 Signature definition, 113 Word, 590 SIMGH, 675
SAP Screen Personas, 748 Simplification database, 249 Test class, 173, 180 SLIN, 58
SAP Solution Manager, 226 Single responsibility principle, 166 definition, 174 SLIN (ABAP Extended Program Check), 209
SAP Web IDE, 732, 748 Smart Forms, 587 Test code, 189 SM12, 370
cloud version, 724 Smart templates, 723 Test data, 182 SMW0, 204
menu, 728 SNOTE, 249 Test doubles, 153 SQLM, 289
templates, 723 Sort order, 506 Test injection, 168 SRTCM, 307
SAPlink, 80, 541 SORT statement, 239 Test methods, 177 ST05, 96, 276, 289
SAPscript, 247, 589 Sorted key, 144 Test seams, 164 ST22, 321
SAPUI5, 33, 296, 349, 420, 475, 600, 659, Sorted table, 126 Test value, 466 SWLT, 289
753, 781 Source code view, 44 Test-driven development (TDD), 69, 70, SWO1, 667
and Eclipse, 665, 699 Source mode, 705 159, 185 XLST_TOOL, 81
transaction_manager, 356
798 799
Index Index
Validation, 386
coding, 388 X
creation, 387
Validation logic, 401 XML, 540, 543, 700, 702
VALUE, 106 code, 558
Variables, 102 files, 540
Variant configuration, 224 tree, 345
VBA (Visual Basic for Applications), 568 XSDBOOL, 119
800 801
First-hand knowledge.
Paul can regularly be found blogging away on the SCN site and presen-
ting at SAP conferences in Australia (Mastering SAP Technology and the
SAP Australian User Group annual conference). If you happen to ever be
at one of these conferences, Paul invites you to come and have a drink
with him at the networking event in the evening and to ask him the
most difficult questions you can think of (preferably SAP-related).
Paul Hardy
ABAP to the Future
801 Pages, 2016, $79.95 We hope you have enjoyed this reading sample. You may recommend
ISBN 978-1-4932-1410-5 or pass it on to others, but only in its entirety, including all pages. This
reading sample and all its parts are protected by copyright law. All usage
www.sap-press.com/4161 and exploitation rights are reserved by the author and the publisher.