I recently received e-mail request from a developer, because how he could test it with ABAP units, whether a particular error message that he goes through in its business logic, not be sent or received is. He had already tried in various ways, was at this point but no further.
If something turns out to be testable to be complicated, it is often not the unit test, or even, as has geargwöhnt indirectly, the Unit Test Framework (between the lines I read "Pah! With ABAP Unit can not even be an Error message intercept or . query "), but usually the problem is to test the code itself The same here.
Suppose there is a plausibility test requires that a user enter prices exceed a certain maximum amount must not be. In the transaction bound to the class that checks the input values, we may find the following Code:
if iv_input_price> gv_max_price.
* The input price & 1 is too high (maximum: & 2)go_validator-> check_price (gc_very_high_price).
message e100 with iv_input_price gv_max_price.
endif.
This works just fine in the GUI completely. But the automatic testing of code there are complications. The unit test, which simulates an excess of the maximum amount, can not run up to the end. When he meets with the error message that passes the test, the control unit bound to the GUI to display the error. But this means that the execution of unit tests stops here. Both - the display of the error and the impossibility of the Unit testing should be completed correctly - of course, is undesirable.
Where is the problem here?
Although the code seems so productive working properly and the problem only occurs when unit test, the problem is not the unit test and certainly not in the Unit Test Framework. The problem is that the above code binds the test logic in the existence of a GUI: the message
command is a statement that is passed to the GUI to run. It is not possible to test independently run by the GUI. This is harmful in many ways:
The code can not run in the background, eg in a Job: An error message will result in immediate termination of the job. But if such a number of documents in the background should be recorded, this is not the desired behavior: So normally, only the processing of the current
document is canceled and must proceed to the next document.
It is not possible to use the class with this assessment in a web application or with a completely different user interface: Since the message
statement can not run, there is an interruption of the process with a short dump - So the Web (HTTP 500) to an "internal server error".
It is not possible, the logic of this offer class as API function, since the failure leads to a situation that is not controlled by the interface.
All problems in computer science can be solved by introducing a new level of indirection be
, a famous quote by Butler Lampson
is. [1] This is also in this case Sun Just separate the treatment of an error by the detection, we invented the exception. Instead of an error to report the situation directly in the GUI, the program should raise an exception, which might best like a class-based. This exception is declared in the interface of the test method. The consumer can then use this method
a try ... catch ...
block to decide what he wants to do in exceptional cases. After the introduction of such exception, the code looks something like this for example: if
iv_input_price> gv_max_price.
RaiseException type zcx_price_exceeds_bound
exporting
price = iv_input_price
bound = gv_max_price.
endif.
The test unit designed according to this logic is simple - and simple unit tests are usually a sign that the productive code has come very good. Here is the test that the test detects a limit violation and raises the expected exception:
test_bound_detected method.
try.
* Not OK - exception was not triggered: fail ('is too high a price trigger exception! "). catch zcx_price_exceeds_bound into lo_ex.
* OK - Check the option still the exception parameters:
assert_equal ( act = lo_ex-> price exp = gc_very_high_price msg = 'exception object parameterized false'). assert_equal (
act = lo_ex-> bound = exp
go_validator-> gv_max_price msg = 'exception object parameterized wrong' ). ENDTRY. ENDMETHOD.
The reverse test can be useful, if a sufficiently low price (eg 1) is input to
no exception:
test_no_ex_for_price_in_range method. go_validator-> check_price (1).
ENDMETHOD.
not missing anything else? No! After catching an exception, we can safely leave the Unit Test Framework: If there is one exception, this is a bug in the program. So the unit test framework already responded correctly by the test
test_no_exc_for_price_in_range in the overview as incorrect mark is. See also my blog
Unhandled exceptions as assurance
.
We could consider the case as settled:
Error Messages in the test logic should be avoided.- if we are ready to introduce another level of indirection! While I run, how to do that, I certainly do not advise the use of error messages. It could be, however, that knowledge of this possibility in some cases even useful.
End of story.
Nevertheless, the underlying complaint that the Unit Test Framework could not catch error messages (regardless of whether it makes sense or not). The answer is: It can
fact ABAP offers a way to intercept error messages: function modules always have the built-in Exception error_message
. If this exception is caught explicitly by calling a function module, it is triggered when an error message in the past or from the function module called by code. So you can retrieve by calling the function module as usual the
sy-subrc
to recognize that an error message was triggered. Now there are methods for good reason (see above) no such mechanism. However, we can introduce a function module, the dynamic methods a given object at run time with a given parameter list at run time calls:
- *"----------------------------------------------- ----------------------- * "*" Local interface: * "IMPORTING *" REFERENCE (IO_OBJECT) TYPE REF TO OBJECT
- * "REFERENCE (IT_PARAMETERS) TYPE ABAP_PARMBIND_TAB *"--------------------------------- ------------------------------------- call method io_object-> (iv_method)
- ENDFUNCTION.
FUNCTION Z_TEST_E_MESSAGE.
* "REFERENCE (IV_METHOD) TYPE CSEQUENCE
parameter- it_parameters table.
The following example program displays the using a mini object lcl_testee , generated on the request Error Messages, such as the occurrence or non-occurrence of Error Message in the unit test can be considered:
* ---
z_test_e_message report.
importing iv_error type flag.
* --- How to test for error messages within unit tests
* ---
lcl_testee class definition.
public section.
methods unwise_check
endclass. "Lcl_testee DEFINITION
* ---
lcl_testee class implementation.
method unwise_check.importing iv_error type flag
if iv_error eq 'X'.
message E001 (bl)
with 'Never issue e-message in a validator'.
endif.
endmethod. "unwise_check
endclass. "lcl_testee IMPLEMENTATION
* ---
class lcl_test definition
for testing " #AU Risk_Level Harmless
inheriting from cl_aunit_assert. " #AU Duration Short
private section.
data go_testee type ref to lcl_testee.
methods:
setup,
prepare_parameters
exporting et_parameters type abap_parmbind_tab,
test_error for testing, test_no_error for testing.
endclass. "lcl_test DEFINITION
method setup.
*
class lcl_test implementation.
*
create object go_testee.
endmethod. "setup * method test_error. data: lt_parameters type abap_parmbind_tab, lv_subrc type i.
call method prepare_parameters
exporting iv_error = 'X' importing et_parameters = lt_parameters. call function 'Z_TEST_E_MESSAGE'
exporting
io_object = go_testee iv_method = 'UNWISE_CHECK' it_parameters = lt_parameters
exceptions
error_message = 1. lv_subrc = sy-subrc. assert_not_initial( act = lv_subrc msg = 'Method should issue an error message' ).
endmethod. "test
*
method test_no_error.
data: lt_parameters type abap_parmbind_tab.
call method prepare_parametersdata: ls_parameter type abap_parmbind,
exporting iv_error = space
importing et_parameters = lt_parameters.
call function 'Z_TEST_E_MESSAGE'
exporting
io_object = go_testee
iv_method = 'UNWISE_CHECK'
it_parameters = lt_parameters
exceptions
error_message = 1.
assert_subrc( sy-subrc ).
endmethod. "test_no_error
*
method prepare_parameters.
lv_error type ref to flag.
et_parameters clear.
CreateDataSource lv_error.
lv_error-> * = iv_error.
ls_parameter-name = 'IV_ERROR'.
ls_parameter-kind = cl_abap_objectdescr => exporting.
ls_parameter-value = lv_error.
ls_parameter insert into table et_parameters.
ENDMETHOD.
endclass. "Lcl_test IMPLEMENTATION
This program shows that it is quite possible error messages in unit tests to catch. The significant amount of test code for such a simple thing like Throwing an error message, however, an unmistakable "
Code Smell" - is used in this case, ensure that the
command message in an inappropriate area.
[1] Quoted from Greg Wilson, Andy Oram [ed]:
Beautiful Code
, O'Reilly, Sebastopol (CA), June 2007, p.279.
0 comments:
Post a Comment