How To Write An ABAP Test Cockpit (ATC) Check
How To Write An ABAP Test Cockpit (ATC) Check
A brief introduction.
PUBLIC
The ATC is an important framework for carrying out static checks of ABAP source code and ABAP
dictionary objects. The remote scenario allows a check system running the newest release to run
its checks against a system running an older release.
In the following, you will learn how to write your own check to analyze source code and how to
ensure it is able to be carried out remotely.
TABLE OF CONTENTS
Preliminaries .................................................................................................................................................................. 4
Creating a check class ................................................................................................................................................. 4
1. Checks based on CL_CI_TEST_ABAP_COMP_PROCS ...................................................................... 5
2. Checks based on CL_CI_TEST_ROOT .............................................................................................. 5
3. Checks based on CL_CI_TEST_SCAN .............................................................................................. 5
Implementing your check .......................................................................................................................................... 6
Setting up attributes of your check ................................................................................................................... 6
Registering checked object types ...................................................................................................................... 7
Defining the messages of your check ................................................................................................................ 8
Statically unknown T100 messages ................................................................................................................... 8
The RUN method ................................................................................................................................................ 9
The ANALYZE_PROC method .......................................................................................................................... 10
Reporting a finding .......................................................................................................................................... 11
Reporting a finding directly via INFORM.......................................................................................................... 13
Reporting a finding for statically unknown T100 messages ............................................................................ 14
Verification of test prerequisites ..................................................................................................................... 15
Activating your check ...................................................................................................................................... 15
www.sap.com/contactsap
Allowing configurable settings for your check ................................................................................................ 15
Results and result classES ........................................................................................................................................ 17
Short texts and long texts ................................................................................................................................ 17
Navigation and stacks ...................................................................................................................................... 17
Unit tests for your check .......................................................................................................................................... 18
Running your check during a unit test............................................................................................................. 18
Checking your findings against your expectations .......................................................................................... 19
Creating documentation .......................................................................................................................................... 19
Custom documentation access........................................................................................................................ 20
Ensuring remote ability of your check ................................................................................................................. 20
Error handling for RFC calls ............................................................................................................................. 20
Pseudo-remote checks .................................................................................................................................... 21
Creating Quickfixes .................................................................................................................................................... 22
Creating a set of quickfixes.............................................................................................................................. 23
Defining the actions of a quickfix .................................................................................................................... 23
Defining the display text of a quickfix ............................................................................................................. 24
Unit testing your quickfix ................................................................................................................................ 24
An example: Detecting obsolete KEYWORDS .................................................................................................... 25
Requirements .................................................................................................................................................. 25
Defining the check meta data.......................................................................................................................... 25
Test cases......................................................................................................................................................... 26
The check logic ................................................................................................................................................ 26
Building quickfixes ........................................................................................................................................... 27
Unit tests ......................................................................................................................................................... 29
Appendix ....................................................................................................................................................................... 32
Full example code ............................................................................................................................................ 32
Class CL_CI_TEST_DOCU_EXAMPLE .................................................................................................................................. 32
Message class CI_DOCU_EXAMPLE .................................................................................................................................. 47
First test report ................................................................................................................................................................. 47
Second test report ............................................................................................................................................................ 49
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 3 / 49
Preliminaries
Please note that this guide assumes you are working with a system with SAP_BASIS release 7.55 or higher. Some of
the features and specifics described work differently or are not present at all in lower releases.
The first chapters of this guide document various aspects of creating an ATC check. If you prefer learning by example,
skip to the chapter An example: Detecting obsolete KEYWORDS.
CL_CI_TEST_ROOT
CL_CI_TEST_PROGRAM
CL_CI_TEST_INCLUDE CL_CI_TEST_ABAP_COMPILER
CL_CI_TEST_SCAN
CL_CI_TEST_ABAP_COMP_PROCS
Figure 1: Class hierarchy of standad check classes. All check classes must inherit from CL_CI_TEST_ROOT. To reuse
existing data collection routines, tests may inherit from CL_CI_TEST_SCAN or CL_CI_TEST_ABAP_COMP_PROCS to
analyze ABAP source code.
When asked to carry out your check, the framework will instantiate an object of your check class and then call its
methods RUN_BEGIN, RUN and RUN_END. Therefore, the main logic of your check needs to be implemented in these
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 4 / 49
methods. The way this implementation proceeds depends on what kind of check exactly you have. Your three main
options are:
1. A subclass of CL_CI_TEST_ABAP_COMP_PROCS
2. A subclass of CL_CI_TEST_ROOT
3. A subclass of CL_CI_TEST_SCAN
While a lot of implementation details depend on which of these options you have chosen, there are a few things that
always work in the same manner.
A programming block is a logically coherent unit of source code. In the context of ABAP Objects, this mainly refers to
the declaration and implementation blocks of classes, with each implementation block itself split up into one block for
each method implementation. For reports, a programming block is basically equivalent to an event block, i.e. the set
of statements following a START-OF-SELECTION, AT-SELECTION-SCREEN or any other event reporting keyword. If
a report contains multiple START-OF-SELECTION words, this will lead to multiple programming blocks in the
CL_ABAP_COMP_PROCS output, even though logically these form a single set of statements all executed when the
event occurs.
We generally do not recommend directly inheriting from CL_CI_TEST_ROOT. However, if your test does not check
ABAP source code and there is no more suitable superclass for the objects you want to check, this is a valid option. If
you are writing several checks that are based on the same information, consider writing a common superclass that
streamlines the collection of this information. (Of course, it is not forbidden to use composition instead of inheritance
to re-use data collection routines.)
Moreover, the book ''SAP Code Inspector'' published by Galileo Press contains relevant information for checks based
on CL_CI_TEST_SCAN (ISBN: 978-3-8362-1706-4).
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 5 / 49
Implementing your check
To obtain a check that can be executed, there are a few minimal implementation steps required so that the
framework knows how to execute your check and display its results:
METHOD constructor .
super->constructor( ).
description = 'My class description'(001).
category = 'CL_CI_CATEGORY_MY_CATEGORY'.
position = '001'.
version = '000'.
has_attributes = abap_true.
attributes_ok = abap_true.
has_documentation = abap_true.
remote_enabled = abap_true.
remote_rfc_enabled = abap_true.
pseudo_remote_enabled = abap_true.
uses_checksum = abap_true.
check_scope_enabled = abap_true.
• DESCRIPTION contains a short text description of the purpose of your check, which will be displayed in the
SCI transaction when creating a check variant.
• CATEGORY is the technical name of the category (e.g. performance checks, S/4HANA readiness checks, etc.)
your check falls into. A category is technically a class that inherits from CL_CI_CATEGORY_ROOT. To get the
technical names of an already existing category, go to the dynpro for creating and displaying check variants in
the SCI transaction (reached via the bottom-most of the three entry fields on the main screen of the
transaction) and choose the menu item Checkvariant->Display->Technical Names there.
• POSITION is a numeric string that controls at which position your check is displayed within its category. The
topmost check will be the one with the lowest (positive) value for position, the bottommost the one with the
largest. If two checks have the same value for position, it is undefined which of them will be displayed first.
• VERSION is a numeric string that allows both you and the framework to identify if a check has changed
substantially. Whenever you make an edit to your check that changes it in an incompatible way, you should
increase this number. Check variants that include a test whose version has changed since it was created
cannot be executed anymore. Consequently, the version should not be changed too often since check
variants need to be redefined after each version change. But it is a advisable to increase the test version if the
results of the check are significantly different. In that case, increasing the version forces all users to re-
evaluate whether they want to run the check.
• HAS_ATTRIBUTES informs the framework if your check has attributes that a user can or must set before
running it.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 6 / 49
• ATTRIBUTES_OK tells the framework whether or not the default values for the settings of your check
are suitable for its execution. If false, the framework will reject attempts to execute the check in a
variant without its settings being adjusted manually.
• HAS_DOCUMENTATION informs the framework if your check has attached documentation that can be
displayed. You should always document your check! This documentation should be created via the SE61
transaction, see the section “Creating documentation” below.
• REMOTE_ENABLED allows to check with an outdated scenario (which uses a push approach rather than a pull
approach by manually uploading the extracted source code to be checked). If your check is based
CL_CI_TEST_ABAP_COMP_PROCS and the flag REMOTE_RFC_ENABLED is set to ABAP_TRUE, you can also
set REMOTE_ENABLED to ABAP_TRUE.
• REMOTE_RFC_ENABLED indicates that your check can be carried out remotely. Note that it is your
responsibility to ensure that your check can run remotely if you set these attributes as true – the attributes
themselves are merely of an organizational nature and do not enable your test to run remotely! Additionally,
beware that these attributes might be inherited from the superclass if you do not set them yourself, even if
the classification does not apply to your check.
• PSEUDO_REMOTE_ENABLED indicates whether the check uses the pseudo-remote method of
execution. See the chapter on pseudo-remote checks.
• USES_CHECKSUM means that your check will generate a checksum for each finding to identify its location.
This is relevant, for instance, when you want to create ATC exemptions for a finding: The checksum is used to
ascertain that the environment of the finding has not changed compared to the state when the exemption
was issued. When setting this attribute to ABAP_TRUE, you are entering a binding contract: You promise that
your check will not emit any findings that do not have a checksum attached (the values 0 and -1 for the
checksum correspond to “no checksum”). The framework will generally react to a violation of this contract by
aborting the check run (directly, via an exception or via an assertion).
• CHECK_SCOPE_ENABLED The default setting should be CHECK_SCOPE_ENABLED = ABAP_FALSE. In this
case, findings in SAP code will not be reported in customer systems (but findings in modifications of SAP code
will, if the corresponding ATC settings are activated). If you set this parameter to true, you claim that your
check already evaluates whether the findings are “in scope”, i.e. caused by customer code and not SAP code
or generated code.
Attributes you do not set in the constructor are inherited from its superclasses, if any of them sets the attribute in
question in its own constructor. If no superclass sets an attribute, it defaults to its initial value.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 7 / 49
Defining the messages of your check
The next important thing you need to do in the instance constructor is to create and register the messages your check
will output when it encounters a finding. These messages are stored in the instance attribute SCIMESSAGES, and you
register them simply by inserting them into this table. Here is a sample insertion of a message:
The definition of the constant passed as the CODE parameter deserves special attention: If you want to display
documentation specific to each of these codes, you must name the constant the same as its content, or the same as
its content with MCODE_ prepended. For example, the constant holding the error code ERROR must be named ERROR
or MCODE_ERROR.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 8 / 49
METHODS register_dyn_t100_message_code FINAL
IMPORTING p_test TYPE sci_chk
p_code TYPE sci_errc
p_title TYPE string
p_kind TYPE sci_errty
p_pcom TYPE sci_pcom OPTIONAL
p_pragma TYPE sci_pragma OPTIONAL
p_pseudo_remote TYPE abap_bool OPTIONAL.
The parameters of this method correspond exactly to the components of a SCIMESSAGE, with the exceptions that the
TEXT is missing and an additional parameter P_PSEUDO_REMOTE is present. TEXT is missing because the text will only
be known at run time for individual messages, and if the P_PSEUDO_REMOTE parameter is set to true, the text of the
T100 message associated with a specific finding will be loaded from the checked system, not the central check
system. (The name of the parameter refers to the idea that the check is “faking” being remote, and in reality just gets
its results from the checked system regardless of the type of (non-)remote scenario it is being executed in.)
In order to correctly associate T100 messages with your findings, you also need to report them in a specific way, see
further below.
Note that the framework re-uses the same instance of your check to run on multiple objects in succession. If your
check carries object-specific state in its instance and class attributes, you need to reset these to their initial states
either at the beginning of RUN or in a redefinition of the inherited method CLEAR, which is called once for every check
after RUN has been called on all of them.
If your check is not meant for source code analysis, then you can skip the rest of this section, since it focuses on
particular patterns to be followed when inheriting from CL_CI_TEST_ABAP_COMP_PROCS.
The start of the standard RUN method of CL_CI_TEST_ABAP_COMP_PROCS looks like this:
METHOD run.
DATA:
l_refs TYPE t_infos.
IF get( ) = abap_false.
RETURN.
ENDIF.
The GET method is supposed to initialize the data collection routines of your check and should return ABAP_FALSE if
any part of this initialization failed. If you want to gather different or additional data compared to your superclass,
then you should redefine the GET method, too. In that case, you should always call SUPER->GET( ) at the beginning
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 9 / 49
of your redefinition. Note that if the only information your check uses is already provided by
CL_CI_TEST_ABAP_COMP_PROCS, it is automatically able to be executed remotely.
ANALYZE_START is where the bulk of the check starts. The class now initiates the analysis of the individual procs. In
general, most checks you want to write will need to redefine the ANALYZE_PROC method to implement their logic.
You can and should assume that the code you write in ANALYZE_PROC will run at least once over every proc in the
code you are checking, i.e. over the entire source code. The analysis will not proceed in any guaranteed order.
METHODS analyze_proc
IMPORTING
p_proc TYPE cl_abap_comp_procs=>t_proc_entry
p_from TYPE i DEFAULT 1
p_proc_ids TYPE t_proc_ids OPTIONAL
VALUE(p_level) TYPE i DEFAULT level
p_params TYPE t_params OPTIONAL
CHANGING
p_collect TYPE t_collect.
• P_FROM contains the number of the statement from which the analysis is supposed to begin (this is e.g.
useful if you aborted the analysis of this proc earlier and now want to resume it where you left off)
• P_PROC_IDS contains a list of T_PROC_ID that you can use for various purposes; it has no dedicated
purpose by default.
• P_LEVEL contains the number of levels left until the analysis is aborted, e.g. a value of 5 means that the
analysis will resolve calls of methods and other reusable components up to a depth of five before aborting
with an exception.
• P_PARAMS contains parameters that might have been passed to the proc, e.g. it may contain the parameters
of a method call.
• P_COLLECT contains various information that persists at a level coarser than individual procs, e.g. it contains
a call stack.
The main information you likely want to use in your source code analysis is contained in the procedure entry itself.
The most used field of the type T_PROC_ENTRY is STMTS, which contains the table of all tokenized statements within
the current proc. The statement type is one of the fundamental types you will encounter in this context:
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 10 / 49
BEGIN OF t_stmt,
keyword TYPE string,
tokens TYPE t_tokens,
comments TYPE t_comments,
include TYPE program,
line TYPE i,
column TYPE i,
non_buf_db_op TYPE abap_bool,
idx TYPE i,
links_origins TYPE SORTED TABLE OF i WITH UNIQUE KEY table_line,
link_blocks TYPE i,
END OF t_stmt .
-
• KEYWORD is the keyword associated with the statement. This is often, but not always, the first token of the
statement, but there are statements that do not begin with a keyword. Most relevant among these are
ordinary assignments, which implicitly begin with the superfluous keyword COMPUTE, and static functional
method calls that do not assign a returning parameter, which implicitly begin with the fake keyword
+CALL_METHOD
• LINE and COLUMN are the statement’s position within its include
In many use cases, a large part of your ANALYZE_PROC method (or its called methods) will consist of iterating over
the statements of the current proc and detecting those you are interested in by examining their keywords, e.g. via a
multi-pronged “CASE <STMT>-KEYWORD” statement. If what you want to do does not fit within a few lines of code for
each WHEN statement, it is highly advisable to extract such code into its own methods, since otherwise the
ANALYZE_PROC method rapidly becomes cluttered and difficult to read.
If you need to analyze actual comments in addition to the statements themselves and their pseudo comments, then
you need to set the SCAN_NEEDED attribute to ABAP_TRUE in the constructor of your check class and use the SCANS
table inherited from CL_CI_TEST_ABAP_COMP_PROCS to obtain comments from the raw output of SCAN ABAP-
SOURCE. See the ABAP Keyword Documentation for more information on the format used there.
Reporting a finding
CL_CI_TEST_ABAP_COMP_PROCS uses a two-step approach to findings. If you wish to report finding while not
inheriting from this class, please skip to the next section. During the analysis, each finding is first registered via the
ADD_INFO method. After the analysis and additional optional processing of the registered findings, the findings are
reported to the end user as in all other cases.
These findings are stored in the instance attribute infos and returned as the P_REF parameter of the
ANALYZE_START method. The standard implementation of run in CL_CI_TEST_ABAP_COMP_PROCS simply iterates
over all findings and calls inform as described above on them, so if you want to change the information that’s passed
onto inform or change the way the findings are displayed, you will need to redefine the run method in your check
class.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 11 / 49
The signature of the ADD_INFO method is as follows:
METHODS add_info
IMPORTING
p_include TYPE program OPTIONAL
p_line TYPE i OPTIONAL
p_column TYPE i OPTIONAL
p_kind TYPE sci_errc
p_source TYPE csequence OPTIONAL
p_name TYPE csequence OPTIONAL
p_stack TYPE t_stack_entries OPTIONAL
p_stack_of_var TYPE t_stack_entries OPTIONAL
p_proc_pos TYPE scis_proc_pos OPTIONAL
p_no_moves TYPE sci_no_moves OPTIONAL
p_comments TYPE cl_abap_comp_procs=>t_comments OPTIONAL
p_program TYPE program OPTIONAL
p_checksum TYPE sci_crc64 OPTIONAL
p_proc TYPE cl_abap_comp_procs=>t_proc_entry OPTIONAL
p_stmt TYPE cl_abap_comp_procs=>t_stmt OPTIONAL
p_stmt_supplied TYPE abap_bool DEFAULT abap_true.
• Once again, P_INCLUDE, P_LINE AND P_COLUMN indicate the position of the finding in the source code.
• P_KIND denotes the 10-character error code you have already used in the instance constructor.
• P_STACK and P_STACK_OF_VAR are two stacks you can attach to your finding. The default expectation is
that P_STACK contains a call stack that leads from the tested program to the actual finding.
• P_COMMENTS contains a list of comments belonging to the finding. The main purpose is to store all pseudo
comments related to the finding to examine them in a next step for pseudo comments that would suppress
this finding.
• P_CHECKSUM is a checksum generated from the finding’s surroundings to recognize in later runs of the same
test whether the surrounding code (or even the location of the finding itself) was changed.
• P_PROC and P_STMT are again indicators of the position of the finding. The default expectation is that a
finding corresponds to a single statement. If you have findings that do not relate to a specific statement and
therefore do not want to pass a statement as an argument, you need to set the next parameter,
P_STMT_SUPPLIED, to ABAP_FALSE.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 12 / 49
Reporting a finding directly via INFORM
If you do not want to sort your findings, or if you are not implementing a subclass of
CL_CI_TEST_ABAP_COMP_PROCS you can directly call the INFORM method to report a finding. Findings reported this
way will show up in whatever UI the end user is using to display the result. The INFORM method is redefined in
CL_CI_TEST_ABAP_COMP_PROCS and behaves slightly differently from the general case:
methods INFORM
importing
P_SUB_OBJ_TYPE type TROBJTYPE optional
P_SUB_OBJ_NAME type SOBJ_NAME optional
P_POSITION type INT4 optional
P_LINE type TOKEN_ROW optional
P_COLUMN type TOKEN_COL optional
P_ERRCNT type SCI_ERRCNT optional
value(P_KIND) type SYCHAR01 optional
P_TEST type SCI_CHK
P_CODE type SCI_ERRC
P_SUPPRESS type SCI_PCOM optional
P_PARAM_1 type CSEQUENCE optional
P_PARAM_2 type CSEQUENCE optional
P_PARAM_3 type CSEQUENCE optional
P_PARAM_4 type CSEQUENCE optional
P_INCLSPEC type SCI_INCLSPEC optional
P_DETAIL type XSTRING optional
P_CHECKSUM_1 type INT4 optional
P_COMMENTS type T_COMMENTS optional
P_FINDING_ORIGINS type CL_CI_SCAN=>T_ORIGIN_TAB optional .
• P_SUB_OBJ_TYPE and P_SUB_OBJ_NAME are the location of the finding. Usually you do not need to worry
about these parameters since using the ADD_INFO method described below will automatically generate the
correct values. If you need to set this information manually, though, you need to be rather diligent about the
value of P_SUB_OBJ_NAME, e.g. for a method of a class this will contain the name of the automatically
generated include this method appears in instead of the class name.
• P_POSITION is obsolete and does nothing unless you are inheriting from CL_CI_TEST_SCAN. If you are
inheriting from CL_CI_TEST_SCAN, then it is the index of the statement the finding refers to in the table of
statements of the currently shared instance of CL_CI_SCAN (static attribute REF_SCAN) and not passing
this parameter correctly is an error.
• P_LINE and P_COLUMN specify the position of the finding within the sub-object.
• P_KIND is an obsolete parameter with which you can override the priority set for the finding’s error code
passed in P_CODE. Use different error codes for findings with different priorities instead.
• P_TEST is the name of the check that has raised the finding, i.e. you should only in exceptional cases pass
something different than your check class’s own name here.
• P_CODE is one of the 10-character codes you defined in the instance constructor
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 13 / 49
• P_SUPPRESS is an obsolete parameter to which you can pass a pseudo comment that can suppress this
finding. Use the PCOM and PCOM_ALT fields of the corresponding error code’s entry in SCIMESSAGES instead.
• P_PARAM_<N> are parameters for use with error codes whose messages contain placeholders of the format
&<N>. The method will automatically substitute &<N> with the content of P_PARAM_<N> when displaying the
findings.
• P_DETAIL is where you put any additional information not covered by other parameters. EXPORT any
additional information to this raw byte string. Note that unless you also write code that reads this
information at another place, e.g. in the result class discussed below, it will never be accessed by the default
behavior of the framework.
• P_CHECKSUM_1 is a checksum by which you can recognize changes to the finding’s surroundings. If you want
to manually compute a checksum, you should use the utility class CL_CI_PROVIDE_CHECKSUM, but the
inform method of CL_CI_TEST_ABAP_COMP_PROCS also already does this automatically for you if you do
not pass a value here.
• P_COMMENTS is a table of pseudo comments which is checked against the pseudo comments defined in the
SCIMESSAGES table of the test. The comments are provided in the same tabular format in the PROC_DEFS[
X ]-STMTS[ Y ]-COMMENTS. Note that due to restrictions in the parser the framework is not able to
process more than one comment per line.
Caution: If your test class is inheriting from CL_CI_TEST_SCAN, this field is obsolete and has no effect, but
the position specification is used to search for pseudo comments instead in the SCAN output.
• P_FINDING_ORIGINS is a table related to the classification of the code and is automatically generated from
the statement and stack of a finding reported via ADD_INFO. In detail, it is a machine-readable table of all
classifications (SAP code, customer code, automatically generated code…) applying to code involved in this
finding. Usually, it should contain the classifications applying to all statements involved. The classification of a
statement is encoded in its LINKS_ORIGINS component, which is the index for the classification in the
ORIGINS table component of the proc the statement belongs to.
• In the standard configuration, the end user will see the finding together with the message you defined for
this error code in the instance constructor and can navigate to the position specified by the SUBOBJECT,
LINE and COLUMN. To replace this with custom behavior, you need to implement a custom result class, see
below.
There is no P_DETAIL parameter and instead there is a P_T100_KEY parameter with a structure corresponding to
the message class and number of a T100 message. Pass the key for the message of the finding here.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 14 / 49
Verification of test prerequisites
Your check may require certain prerequisites in order to be executed correctly. If these only depend on the system
and not on the specific objects used, you can emit these messages not as a finding, but as a verification message by
implementing the method VERIFY_TEST. If you emit a verification message with a priority higher than that of an
information – i.e. a message with the attribute KIND set to CL_CI_TEST_ROOT=>C_ERROR or
CL_CI_TEST_ROOT=>C_WARNING – then the inspection will terminate and no check in the current check variant will
be executed at all.
Most issues detected during the verification phase should not be reported as ordinary findings, but as check failures
instead, which will not show up in the ordinary results of an ATC run but instead in their own place. To mark a finding
as a check failure, the entry of its message code in the SCIMESSAGES table needs to have the CATEGORY attribute set
to one of the following constants (all are members of CL_CI_TEST_ROOT):
Constant Meaning
If you have defined a new category for your check, you need to also activate it on this screen in the same way.
If you want to disable your check again, visit the same management screen and uncheck the check box again.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 15 / 49
The latter two methods typically just require you to export respectively import the configurable attributes of your
check to/from a byte string that is passed in their sole parameter P_ATTRIBUTES.
The QUERY_ATTRIBUTES method, however, implements the dialog that is shown to the user so that they can
configure the attributes manually.
As a first step, you need to define a table of type SCI_ATTTAB that will hold one row for each configurable attribute.
Of the column, three are relevant for you: ref expects a reference to the parameter that should be configured, text is
the text shown to the user in the configuration dialog and kind is a one-character flag indicating the type of the
attribute. If you leave it initial, the user will see a standard text box to enter arbitrary strings for this attribute. A
typical statement for an attribute might look like this:
INSERT VALUE #( ref = REF #( level ) text = 'External analysis depth'(002)
kind = ' ' ) INTO TABLE attributes.
Note that the logic that automatically generates the settings dialog from this table only works for DDIC types, i.e. the
variable whose reference you pass in REF must have a type from the dictionary, and not a locally defined type or a
type from a type group.
In this case, level could be an instance attribute of the check class that controls how deep the check descends into
method, function or perform calls into other programs.
The different possible values for the KIND property are available as fields of the constant structure
CL_CI_QUERY_ATTRIBUTES=>C_ATTRIBUTE_KINDS together with a short ABAP Doc description of what types
they are suited for and what effect they have on the displayed screen.
After filling the attribute table with all attributes, you want the user to be able to configure in this way, you need to
display the configuration dialog to the user by calling the class method CL_CI_QUERY_ATTRIBUTES=>GENERIC,
which has the following signature:
class-methods GENERIC
importing
P_NAME type SCI_CHK
P_TITLE type C
P_ATTRIBUTES type SCI_ATTTAB
P_MESSAGE type C optional
P_DISPLAY type FLAG
returning
value(P_BREAK) type SYCHAR01 .
• P_MESSAGE is a message that is displayed to the user in case their entries are erroneous
The return value of this function becomes ABAP_TRUE if the user cancels the dialogue. Be aware that calling this
method will overwrite the contents of whatever was in the parameters referenced by the rows of P_ATTRIBUTES, so
it is not recommended to directly pass references to your instance attributes unless you are fine with potentially
arbitrary user input ending up in them.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 16 / 49
Of course, if you did not pass references to the instance attributes but to local variables you need to write the content
of the local variables to the instance attributes after GENERIC has finished without the user cancelling.
If the standard values of the attributes of your check after instantiation are not suitable for its execution, then you
should set the ATTRIBUTES_OK variable to ABAP_FALSE in its constructor.
To achieve this, you should export a table of type SCIT_WB_NAVIGATION under the name WB_NAVIGATION to the
details buffer of the finding. The row type SCIS_WB_NAVIGATION has the following components:
POSITION is a line number in the source code-like object given by the triple OBJECT_TYPE, OBJECT_NAME,
ENCLOSING_OBJECT. In this case, OBJECT_TYPE and OBJECT_NAME refer to the include, and ENCLOSING_OBJECT
to the TADIR object the include belongs to. DESCRIPTION is the text that will be displayed as the text of the link that
leads to the position specified by the other components.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 17 / 49
Alternatively, you can override the entire logic of creating the workbench navigation by implementing the method
GET_WB_NAVIGATION for your result class.
Caution: Result classes also have an IF_CI_TEST~NAVIGATE method. This method is a legacy functionality that only
steers navigation in the SCI transaction, it has no effect on navigation in the ATC.
class-methods RUN
importing
P_VARIANT type SCI_CHKV
P_OBJ_TYPE type TROBJTYPE
P_OBJ_NAME type SOBJ_NAME
P_OBJ_PARAMS type SCIT_OBJ_PAR optional
P_NOSUPPRESS type SCI_NOSUP optional
P_SYSID type SYST_SYSID optional
P_DESTINATION type RFCDEST optional
P_ALLOW_EXCEPTIONS type ABAP_BOOL default ABAP_FALSE
raising
CX_CI_CHECK_ERROR .
• P_OBJ_TYPE and P_OBJ_NAME are the TADIR keys of the object you wish to check
• P_OBJ_PARAMS is an optional parameter in which you can pass the parameters the framework would
normally determine by itself during an ordinary inspection, such as if the object to be checked is an SAP
object, or which namespace it belongs to. For the kinds of allowed parameters refer to the definition of
C_OBJ_PARAM_KINDS in CL_CI_OBJECTSET.
• P_NOSUPPRESS is a flag to deactivate the suppression of findings via pseudo comments for this check run.
• P_SYSID is the RFC source in case you want to test RFC functionality. The same caveats as for
P_DESTINATION below apply.
• P_DESTINATION is the RFC destination in case you want to test RFC functionality. This is generally advised
against for elementary unit testing since you make yourself dependent on the remote availability of the
destination but may be useful in some cases. If you do want to include remote executions of your check in
your unit tests, consider making their failure tolerable so that the unavailability of the remote system does
not prevent the execution of other tests and is not seen as a fatal error.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 18 / 49
• P_ALLOW_EXCEPTIONS determines if the exceptions for a missing check variant or a missing object to be
checked cause a failure of the unit test (this happens if this parameter is ABAP_FALSE) or the corresponding
exceptions are merely propagated by the method.
Usually you only need to pass the required parameters to do useful unit testing. After this method has been called, the
results of the check will be stored in the instance attribute RESULT_LIST of your check class.
class-methods CHECK
importing
P_CODE type SCI_ERRC
P_SOBJTYPE type TROBJTYPE
P_SOBJNNAME type SOBJ_NAME
P_LINE type I
P_COL type I
P_PARAM_1 type STRING optional
P_PARAM_2 type STRING optional
P_PARAM_3 type STRING optional
P_PARAM_4 type STRING optional
P_DETAIL type SCIT_DETAIL optional
P_CHECKSUM1 type I optional
P_TEST type SCI_CHK optional
returning
value(P_MESSAGE) type STRING .
You will certainly note that, not coincidentally, these parameters are the same as those for the INFORM method.
Simply pass the values you expect for your findings to this method. If the actual finding matches the expected finding,
the returned P_MESSAGE string will be empty, otherwise it will contain information about the mismatch, usually the
data of the finding that was expected.
Pay attention to the unusually spelled parameter P_SOBJNNAME – preserved this way for backward-compatibility – as
well as once again to the fact that this SUBOBJECT identifier refers to the full name of the actual include, not e.g. the
class, a finding was reported in.
Creating documentation
You can create documentation that will automatically be displayed in both the SCI transaction and the ATC, regardless
of whether it is being used through SAPGUI or the ADT. There are two basic types of documentation, the
documentation of the check itself and the documentation of each error code of a check. Note that you need to set the
attribute HAS_DOCUMENTATION to ABAP_TRUE for any documentation to be displayed.
The documentation of the check itself is always created in the SE61 transaction by documenting the dummy class
attribute 0000 of your check class. This documentation will appear when a user clicks on the blue information icon in
the SCI transaction next to your check and will also be displayed by the ATC for each finding of your check.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 19 / 49
You can furthermore document each error code of your check by documenting class attributes in SE61 whose names
agree with the literal values of your error code constants, not with these constants’ names. Alternatively, you can
document an attribute named MCODE_<LITERAL>. Note that this means that your error code constants must be
named exactly like their values, or like their values with MCODE_ prepended, otherwise SE61 will not allow you to
create documentation – you can’t document class attributes that do not exist. This documentation – if necessary –
should contain e.g. the reason this finding is being displayed and advice on how to fix this specific finding.
You should consider the documentation of the check itself mandatory, since otherwise only you will be able to know
what the check even is supposed to do. In contrast, documentation for individual error codes should only be
considered in cases where the short text of the finding is not self-explanatory or where the course of action to fix the
finding is not obvious. You do not need to explicitly document any pseudo comments that suppress a finding; the
framework will automatically display up to two pseudo comments that can suppress the finding in its description.
There is a series of blog entries on how to perform remote analysis via ATC once you have a remote-enabled check.
For your check to be able to run successfully in a remote scenario, you need to avoid any explicit dependence on local
data of the check system. For instance, a common pitfall is to rely on reading the TADIR or other tables with object
information – of course that will only get you information about the objects in your check system, not about the
objects in the checked system. You should also be wary of using kernel methods to provide information or using the
RTTI (Run Time Type Information) functionality.
If you use only the information and methods provided by CL_CI_TEST_ABAP_COMP_PROCS, you can be sure that all
relevant data is acquired from the checked system. However, if you require additional data that is not yet provided by
this class, you currently need to write your own RFC function module that will make the necessary data available to
your check class.
Of course, when you manually call an RFC module in your test, you need to ensure that the destination is correct. The
destination is not directly passed to your check, but in an enriched format called the SCR_SOURCE_ID. The currently
valid source ID is held globally in the class attribute CL_CI_TEST_ROOT=>SRCID. To translate this into an RFC
destination, you should call the method CL_ABAP_SOURCE_ID=>GET_DESTINATION.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 20 / 49
Documentation for more information) you can obtain an error text for these exceptions. If they occur and this means
that your check cannot be executed correctly, you should raise – via INFORM - a Code Inspector finding with the
predefined (in CL_CI_TEST_ROOT) message codes C_CODE_RFC_SYSTEM_FAILURE and
C_CODE_RFC_COMMUNICATION_FAILURE. These codes expect the error message as the first parameter of the
finding and the name of the destination as the second.
Pseudo-remote checks
With SAP_BASIS release 7.55 and with note 2916724 in lower releases, the framework also supports the so-called
pseudo-remote execution of checks.
While it is recommended that checks retrieve the necessary data from the checked system and perform the actual
check logic in the central system, there are cases where this is infeasible. For example, especially for checks of objects
that do not consist of ABAP source code, there are checks that do not contain any logic beyond a simple evaluation of
database entries, or that call external APIs that do not allow such a separation of data retrieval and check logic.
For these checks, the pseudo-remote scenario is better suited, but it has several restrictions compared to a true
remote check. The most severe is that it requires the check class to also exist in the checked system, i.e. the main
advantage of having to maintain the check only in the central system’s release is lost.
If you choose this variant, you need to set the attribute PSEUDO_REMOTE_ENABLED of your check class to
ABAP_TRUE, as described in the section on setting up the attributes of your check. Furthermore, to execute your
check pseudo-remotely, you should call the function module SCI_EXECUTE_TEST during the RUN step when
CL_CI_TEST_ROOT=>SRCID is not empty. A typical call would look like this:
l_attributes = get_attributes( ).
DESTINATION l_destination
EXPORTING
p_test = me->myname
p_object_type = object_type
p_object_name = object_name
p_program_name = program_name
p_object_params = object_params
p_attributes = attributes
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 21 / 49
IMPORTING
p_result = result
EXCEPTIONS
OTHERS = 3.
IF sy-subrc <> 0.
ENDIF.
inform(
p_kind = <result>-kind
p_test = <result>-test
p_code = <result>-code
p_param_1 = <result>-param1
p_param_2 = <result>-param2
p_param_3 = <result>-param3
p_param_4 = <result>-param4
p_detail = <result>-detail ).
ENDLOOP.
Creating Quickfixes
The following section is only relevant if your check reports findings in ABAP source code.
In many cases, findings have an easy and obvious solution, such as appending a particular addition to an ABAP
statement, deleting an unused variable or other minor modifications of the source code. In cases where it is possible
to statically determine the correct solution to the problem reported by the finding, it is natural to want to offer this
solution programmatically to the user.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 22 / 49
Quickfixes in the ABAP Development Tools (ADT) make this possible: You can attach arbitrarily many so-called
quickfixes to a finding, which can then be executed by the developer in the IDE. For a set of findings, it is even possible
to execute a quickfix for each finding at once, vastly reducing the time spent by developers compared to manually
applying the same solution to each finding individually.
This container now allows you to create individual quickfixes by calling CREATE_QUICKFIX. If you create more than
one potential fix for a single finding and you want to make it possible to identify which fix is which later on, you should
pass the optional parameter P_QUICKFIX_CODE with a character sequence of your choosing that identifies the type
of fix being offered.
• ADD_PSEUDO_COMMENT
• INSERT_AFTER
• INSERT_BEFORE
• REPLACE_BY
All actions have in common that they rely on the notion of a context, which needs to be passed as the P_CONTEXT
parameter of the actions.
While you do not need to worry about the technical underpinnings of this notion, you should think of a context as
something that uniquely identifies the place in an ABAP program where the quickfix should take place. So regardless
of which of the above actions you wish to perform, you must first create a context that specifies where this action
should be performed. Contexts can be created from a line/column specification in an include, a collection of SCAN
tokens or statements, or a collection of COMP_PROCS tokens or statements via the static factory methods in
CL_CI_QUICKFIX_ABAP_CONTEXT, starting with CREATE_FROM_SCAN_ and CREATE_FROM_COMP_PROCS_,
respectively.
This context determines where the action you specify is performed. ADD_PSEUDO_COMMENT adds the comment at a
valid position for every statement in the context, INSERT_AFTER inserts code at the end of the context,
INSERT_BEFORE inserts code at the beginning of the context and REPLACE_BY replaces the whole context by
entirely new code.
Since you can create contexts both from statements and from tokens, you are therefore able to delete, insert or
replace entire statements as well as individual tokens. The example class CL_CI_TEST_DOCU_EXAMPLE
demonstrates this: The quickfix that transforms a MOVE into an assignment via = acts on the scope of the whole
statement and creates its context as
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 23 / 49
p_context = cl_ci_quickfix_abap_context=>create_from_comp_procs_stmt(
p_action_stmt = statement
p_proc_def = procedure
p_proc_defs = proc_defs
p_with_end_point = abap_false
)
while the quickfix that deletes the COMPUTE keyword acts on the scope of the COMPUTE token and creates its context
as
p_context = cl_ci_quickfix_abap_context=>create_from_comp_procs_tokens(
p_action_stmt = statement
p_proc_def = procedure
p_proc_defs = proc_defs
p_from_token = 1
)
so that the context is only the first token of the COMPUTE statement.
Quickfixes can be enabled for automatic execution, so that the ADT will apply them when the user tells it to perform
all recommended quickfixes. To do so, simply call the method ENABLE_AUTOMATIC_EXECUTION on the quickfix
object.
To attach the short and long text of a message class to a quickfix, call the ADD_DOCU_FROM_MSGCLASS method on it.
Figure 2 The texts associated with a quickfix. Left: Short text. Right: Long text.
The screenshot below shows where these texts then appear in the IDE.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 24 / 49
Quickfixes are tested via an instance of CL_CI_QUICKFIX_TESTING. You set the finding whose quickfix you want to
test by calling SET, and you start the check via either CHECK_QUICKFIX or CHECK_QUICKFIXES methods. There are
two independent parts to this check: First, you can execute a syntax check for the result of the quickfix. This option is
on by default and is recommended in almost all cases to detect quickfixes that produce incorrect code. In rare
situations it may be unavoidable to produce errors, but in most cases you should ensure that your quickfix never
produces syntax errors. Second, you can test whether the content of the fix is the code you expect by passing the
P_EXPECTED_QUICKFIXES parameter of CHECK_QUICKFIXES.
Requirements
The new check is aimed at modernizing old code. It should detect MOVE and COMPUTE statements and build a quickfix
that replaces them with their non-obsolete form, i.e. a keyword-less assignment using the = operator. We start with
an empty class CL_CI_TEST_DOCU_EXAMPLE inheriting from CL_CI_TEST_ABAP_COMP_PROCS since we want to
analyze ABAP source code.
METHOD constructor.
super->constructor( ).
category = 'CL_CI_CATEGORY_INTERNAL'.
position = '005'.
remote_rfc_enabled = abap_true.
check_scope_enabled = abap_false.
has_attributes = abap_false.
ENDMETHOD.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 25 / 49
Since we only intend to use the source code information provided by CL_CI_TEST_ABAP_COMP_PROCS, the check is
REMOTE_RFC_ENABLED, and since we do not intend to provide any special handling of generated or SAP code, it is
not CHECK_SCOPE_ENABLED. We also define two finding codes for the check – one for MOVE statements and the
other for COMPUTE statements. Since these statements are not functional errors, but merely cosmetic annoyances, we
give the findings the lowest possible priority as C_NOTE. Note also that all texts that will be displayed to the user are
text elements, so that they can be translated.
Test cases
Before implementing the check logic, we want to have some simple test cases for us to test the check against. We
create the reports RS_CI_TEST_DOCU_EXAMPLE_1 and RS_CI_TEST_DOCU_EXAMPLE_2 containing some MOVE and
COMPUTE statements respectively.
After activating our new check class in the SCI transaction via the menu item Code Inspector->Management of->Checks
(Ctrl+Shift+F5), we define a global check variant containing only this check called DOCU_EXAMPLE. Of course, running
this variant gives us no findings yet since we haven’t implemented any logic in our check.
CASE <statement>-keyword.
emit_finding(
procedure = p_proc
statement = <statement>
code = finding_codes-simple_move
).
WHEN 'COMPUTE'.
emit_finding(
procedure = p_proc
statement = <statement>
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 26 / 49
code = finding_codes-compute
).
ENDIF.
ENDCASE.
ENDLOOP.
Building quickfixes
We define the BUILD_MOVE_QUICKFIXES and BUILD_COMPUTE_QUICKFIXES methods that take the current
PROCEDURE and STATEMENT and return an instance of CL_CI_QUICKFIX_CREATION, the container type for a set of
quickfixes. The essence of BUILD_MOVE_QUICKFIXES is the following logic:
METHOD add_move_to_assignment_op_fix.
fix->replace_by(
p_context = cl_ci_quickfix_abap_context=>create_from_comp_procs_stmt(
p_action_stmt = statement
p_proc_def = procedure
p_proc_defs = proc_defs
p_with_end_point = abap_false
).
p_msg_class = quickfix_docu_message_class
p_msg_number = 001
).
ENDMETHOD.
PARSE_MOVE obtains relevant info about the statement by detecting whether it contains EXACT or is a MOVE-
CORRESPONDING or whether it is a cast of object references (i.e. contains ?TO instead of TO). It also separates the
statement into its SOURCE and TARGET, according to MOVE SOURCE TO TARGET.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 27 / 49
The actual quickfix now consists of two parts: the code that shall replace the MOVE statement, which we construct in
CONSTRUCT_EQUIV_ASSIGNMENT and the context that specifies which part of the code we want to replace. Since we
want to replace the entire statement, we create the context directly from the statement. See the appendix for the
implementation of methods like PARSE_MOVE or CONSTRUCT_EQUIV_ASSIGNMENT if you are interested.
We are confident that this quickfix cannot break anything, so we call ENABLE_AUTOMATIC_EXECUTION on it,
meaning that users can carry out this quickfix in bulk. We also define a short and long text in a message class, leading
to the texts in Figure 2 above being shown to the user.
to the INFORM method. This finishes the fix for the MOVE statement.
The second fix is very easy at first sight: When we see a compute, we delete it and leave the rest of the statement
untouched:
METHOD add_delete_compute_fix.
fix->replace_by(
p_new_code = ``
p_context = cl_ci_quickfix_abap_context=>create_from_comp_procs_tokens(
p_action_stmt = statement
p_proc_def = procedure
p_proc_defs = proc_defs
p_from_token = 1
).
Since there is no explicit delete action, deletion of tokens is represented by an empty replace action on the context
that just consists of the first token of the statement.
For simple COMPUTE statements, this is already the entire fix. However, there is a variant of the compute statement
with the second token being an EXACT keyword, where COMPUTE EXACT A = B is equivalent to the statement A =
EXACT #( B ). So if there is an EXACT, we need to delete the EXACT and surround the right hand side of the
assignment with the EXACT operator:
fix->replace_by(
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 28 / 49
p_new_code = ``
p_context = cl_ci_quickfix_abap_context=>create_from_comp_procs_tokens(
p_action_stmt = statement
p_proc_def = procedure
p_proc_defs = proc_defs
p_from_token = 2
).
DATA(rhs_context) = cl_ci_quickfix_abap_context=>create_from_comp_procs_tokens(
p_proc_defs = proc_defs
p_proc_def = procedure
p_action_stmt = statement
p_from_token = 5
).
fix->insert_before(
p_context = rhs_context
).
fix->insert_after(
p_new_code = `)`
p_context = rhs_context
).
ENDIF.
Since we can create fine-grained contexts on the level of individual tokens, we represent our fix by three distinct
actions: Deleting the EXACT, inserting the token sequence EXACT #( in front of the right hand side and inserting the
token ) behind the right hand side. The insertions are performed by the quickfix actions INSERT_BEFORE and
INSERT_AFTER, and we can always let the context start at the fifth token of the statement because COMPUTE
statements only allow single tokens as the left hand side of the assignment (i.e. no table expressions). This completes
the quickfix for COMPUTE statements.
Unit tests
At some point during the development of our check, we of course want to add unit tests to it (this being the last
section in this example is not supposed to be a suggestion to only add tests at the end). Since the signatures of the
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 29 / 49
generic unit test methods provided by the framework are rather unwieldy for our specific use case, we define a
custom assertion method in our test class inheriting from CL_CI_VERIFY_TEST:
METHODS assert_finding
RAISING cx_static_check.
METHOD assert_finding.
DATA(error_message) = check(
p_test = cut_name
p_code = finding_code
p_sobjtype = 'PROG'
p_sobjnname = position-include
p_line = position-line
p_col = position-column
).
cl_abap_unit_assert=>fail(
msg = error_message
quit = if_abap_unit_constant=>quit-no
).
ELSE.
sobjtype = 'PROG'
sobjname = position-include
]-detail ).
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 30 / 49
DATA(quickfix_okay) = quickfix_tester->check_quickfixes(
EXPORTING
p_program = position-include
p_expected_quickfixes = VALUE #( (
qf_code = 'QFIX1'
replacements = VALUE #( (
include = position-include
from_line = position-line
by = CONV #( expected_quickfixed_code )
) )
) )
IMPORTING
p_err_msgs = errors
p_result_sources = quickfixed_sources
).
IF quickfix_okay = abap_false.
DATA(first_error) = errors[ 1 ].
cl_abap_unit_assert=>assert_true(
act = quickfix_okay
quit = if_abap_unit_constant=>quit-no
).
ENDIF.
ENDIF.
ENDMETHOD.
This method first checks whether there exists a finding with the intended code and location at all in the check result. If
it does, then we instantiate the quickfix test tool, pass the finding to it via SET and then construct the correct
expected replacement from the expected raw code we take as input. If the test tool reports an error, we show the
first error message and the corresponding line in the code with the quickfix applied to it.
While this method may look a bit unwieldy, it makes for readable assertions in our actual test methods: After running
our check against our test objects as
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 31 / 49
DATA(test_report) = CONV sobj_name( 'RS_CI_TEST_DOCU_EXAMPLE_1' ).
run(
p_variant = 'DOCU_EXAMPLE'
p_obj_type = 'PROG'
p_obj_name = test_report
p_create_quickfixes = abap_true
).
assert_finding(
finding_code = cl_ci_test_docu_example=>finding_codes-simple_move
expected_quickfixed_code = VALUE #(
( `lhs = rhs.` )
).
which corresponds to a line MOVE RHS TO LHS. in our test code. After writing an assertion for every expected
finding, we can quickly check whether our check produces the quickfixes we expect and that they produce
syntactically correct ABAP code when applied just by running the unit tests.
Appendix
Class CL_CI_TEST_DOCU_EXAMPLE
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
TYPES:
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 32 / 49
BEGIN OF ty_position,
line TYPE i,
column TYPE i,
END OF ty_position.
CONSTANTS:
BEGIN OF finding_codes,
END OF finding_codes.
METHODS constructor.
PROTECTED SECTION.
PRIVATE SECTION.
TYPES:
BEGIN OF ty_move_info,
END OF ty_move_info.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 33 / 49
DATA analyzed_proc_ids TYPE HASHED TABLE OF cl_abap_comp_procs=>t_proc_id WITH UNIQUE
KEY table_line.
METHODS emit_finding
METHODS build_move_quickfixes
METHODS build_compute_quickfixes
METHODS add_move_to_assignment_op_fix
METHODS parse_move
METHODS flatten_tokens
METHODS compute_statement_checksum
METHODS construct_equiv_assignment
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 34 / 49
METHODS break_into_lines
METHODS add_delete_compute_fix
ENDCLASS.
METHOD constructor.
super->constructor( ).
category = 'CL_CI_CATEGORY_INTERNAL'.
position = '005'.
remote_rfc_enabled = abap_true.
check_scope_enabled = abap_false.
has_attributes = abap_false.
ENDMETHOD.
METHOD analyze_proc.
RETURN.
ELSE.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 35 / 49
ENDIF.
CASE <statement>-keyword.
emit_finding(
procedure = p_proc
statement = <statement>
code = finding_codes-simple_move
procedure = p_proc
statement = <statement>
) )
).
WHEN 'COMPUTE'.
emit_finding(
procedure = p_proc
statement = <statement>
code = finding_codes-compute
procedure = p_proc
statement = <statement>
) )
).
ENDIF.
ENDCASE.
ENDLOOP.
ENDMETHOD.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 36 / 49
METHOD emit_finding.
inform(
p_test = my_name
p_code = code
p_sub_obj_type = 'PROG'
p_sub_obj_name = statement-include
p_line = statement-line
p_checksum_1 = compute_statement_checksum(
procedure = procedure
statement = statement
)-i1
).
ENDMETHOD.
METHOD build_move_quickfixes.
quickfixes = cl_ci_quickfix_creation=>create_quickfix_alternatives( ).
add_move_to_assignment_op_fix(
statement = statement
).
ENDMETHOD.
METHOD add_move_to_assignment_op_fix.
fix->replace_by(
p_context = cl_ci_quickfix_abap_context=>create_from_comp_procs_stmt(
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 37 / 49
p_action_stmt = statement
p_proc_def = procedure
p_proc_defs = proc_defs
p_with_end_point = abap_false
).
p_msg_class = quickfix_docu_message_class
p_msg_number = 001
).
ENDMETHOD.
METHOD compute_statement_checksum.
get_stmt_checksum(
p_index_stmt = statement-idx
).
ENDMETHOD.
METHOD flatten_tokens.
code = REDUCE #( INIT str = `` FOR tok IN tokens NEXT str = |{ str }{ tok-str } | ).
ENDMETHOD.
METHOD parse_move.
DATA(found_to) = abap_false.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 38 / 49
found_to = abap_true.
move_info-is_cast = abap_true.
found_to = abap_true.
ELSE.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD construct_equiv_assignment.
DATA(operator) = COND #(
).
DATA(flat_new_statement) = COND #(
ENDMETHOD.
METHOD break_into_lines.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 39 / 49
DATA(remaining_chunk) = strlen( code ).
remaining_chunk -= chars_to_chop.
ENDWHILE.
ENDMETHOD.
METHOD build_compute_quickfixes.
quickfixes = cl_ci_quickfix_creation=>create_quickfix_alternatives( ).
add_delete_compute_fix(
statement = statement
).
ENDMETHOD.
METHOD add_delete_compute_fix.
fix->replace_by(
p_new_code = ``
p_context = cl_ci_quickfix_abap_context=>create_from_comp_procs_tokens(
p_action_stmt = statement
p_proc_def = procedure
p_proc_defs = proc_defs
p_from_token = 1
).
fix->replace_by(
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 40 / 49
p_new_code = ``
p_context = cl_ci_quickfix_abap_context=>create_from_comp_procs_tokens(
p_action_stmt = statement
p_proc_def = procedure
p_proc_defs = proc_defs
p_from_token = 2
).
DATA(rhs_context) = cl_ci_quickfix_abap_context=>create_from_comp_procs_tokens(
p_proc_defs = proc_defs
p_proc_def = procedure
p_action_stmt = statement
p_from_token = 5
).
fix->insert_before(
p_context = rhs_context
).
fix->insert_after(
p_new_code = `)`
p_context = rhs_context
).
ENDIF.
p_msg_class = quickfix_docu_message_class
p_msg_number = 002
).
ENDMETHOD.
ENDCLASS.
Unit tests:
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 41 / 49
CLASS simple_moves DEFINITION FINAL FOR TESTING
DURATION SHORT
PRIVATE SECTION.
METHODS assert_finding
RAISING cx_static_check.
ENDCLASS.
METHOD move_report.
run(
p_variant = 'DOCU_EXAMPLE'
p_obj_type = 'PROG'
p_obj_name = test_report
p_create_quickfixes = abap_true
).
assert_finding(
finding_code = cl_ci_test_docu_example=>finding_codes-simple_move
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 42 / 49
expected_quickfixed_code = VALUE #(
( `lhs = rhs.` )
).
assert_finding(
finding_code = cl_ci_test_docu_example=>finding_codes-simple_move
expected_quickfixed_code = VALUE #(
).
assert_finding(
finding_code = cl_ci_test_docu_example=>finding_codes-simple_move
expected_quickfixed_code = VALUE #(
).
assert_finding(
finding_code = cl_ci_test_docu_example=>finding_codes-simple_move
expected_quickfixed_code = VALUE #(
).
assert_finding(
finding_code = cl_ci_test_docu_example=>finding_codes-simple_move
expected_quickfixed_code = VALUE #(
).
assert_finding(
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 43 / 49
finding_code = cl_ci_test_docu_example=>finding_codes-simple_move
expected_quickfixed_code = VALUE #(
).
assert_finding(
finding_code = cl_ci_test_docu_example=>finding_codes-simple_move
expected_quickfixed_code = VALUE #(
).
assert_finding(
finding_code = cl_ci_test_docu_example=>finding_codes-simple_move
expected_quickfixed_code = VALUE #(
).
ENDMETHOD.
METHOD compute_report.
run(
p_variant = 'DOCU_EXAMPLE'
p_obj_type = 'PROG'
p_obj_name = test_report
p_create_quickfixes = abap_true
).
assert_finding(
finding_code = cl_ci_test_docu_example=>finding_codes-compute
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 44 / 49
expected_quickfixed_code = VALUE #(
).
assert_finding(
finding_code = cl_ci_test_docu_example=>finding_codes-compute
expected_quickfixed_code = VALUE #(
).
assert_finding(
finding_code = cl_ci_test_docu_example=>finding_codes-compute
expected_quickfixed_code = VALUE #(
( `sub ?= sup.` )
).
ENDMETHOD.
METHOD assert_finding.
DATA(error_message) = check(
p_test = cut_name
p_code = finding_code
p_sobjtype = 'PROG'
p_sobjnname = position-include
p_line = position-line
p_col = position-column
).
cl_abap_unit_assert=>fail(
msg = error_message
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 45 / 49
quit = if_abap_unit_constant=>quit-no
).
ELSE.
sobjtype = 'PROG'
sobjname = position-include
]-detail ).
DATA(quickfix_okay) = quickfix_tester->check_quickfixes(
EXPORTING
p_program = position-include
p_expected_quickfixes = VALUE #( (
qf_code = 'QFIX1'
replacements = VALUE #( (
include = position-include
from_line = position-line
by = CONV #( expected_quickfixed_code )
) )
) )
IMPORTING
p_err_msgs = errors
p_result_sources = quickfixed_sources
).
IF quickfix_okay = abap_false.
DATA(first_error) = errors[ 1 ].
cl_abap_unit_assert=>assert_true(
act = quickfix_okay
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 46 / 49
msg = |Quickfix error: { first_error-msg }. Erroneous line: {
quickfixed_sources[ include = first_error-include ]-source[ first_error-line ] }|
quit = if_abap_unit_constant=>quit-no
).
ENDIF.
ENDIF.
ENDMETHOD.
ENDCLASS.
REPORT rs_ci_test_docu_example_1.
PUBLIC SECTION.
CLASS-METHODS return_int
CLASS-METHODS return_int_from_two_pars
par_2 TYPE i
ENDCLASS.
METHOD return_int.
ENDMETHOD.
METHOD return_int_from_two_pars.
ENDMETHOD.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 47 / 49
ENDCLASS.
START-OF-SELECTION.
TYPES:
BEGIN OF ty_struct,
comp_1 TYPE i,
comp_2 TYPE i,
END OF ty_struct.
ENDCLASS.
START-OF-SELECTION.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 48 / 49
Second test report
REPORT rs_ci_test_docu_example_2.
START-OF-SELECTION.
ENDCLASS.
ENDCLASS.
END-OF-SELECTION.
© 2024 SAP SE or an SAP affiliate company. All rights reserved. See Legal Notice on www.sap.com/legal-notice for use terms, disclaimers, disclosures, or restrictions related to this material. 49 / 49