Você está na página 1de 11

Getting Started Newsletters Store

Products Services & Support About SCN Downloads


Industries Training & Education Partnership Developer Center
Lines of Business University Alliances Events & Webinars Innovation
Log On Join Us Hi, Guest Search the Community
Activity Communications Actions
Browse
ABAP Testing and Troubleshooting
Previous
post
Next
post
0 Tweet 0
Hi

In this blog I would like to present you technique which is used for business logic testing without database
dependency. It is implemented with object oriented design. Many developers complain that they cannot write too
much unit tests because their reports use database and tables content may easily change. Removing database from
testing is the key factor to have successful unit tests.

Just to remind, a good unit tests:
Always give same result.
The order of tests is not important each can be run independently and must work.

Lets imagine that there is test that uses database:
Creates new row.
Runs business logic which reads that row.
Checks result .
Deletes the row at the end.

And this test can work fine. But it may not always give same result. In case if someone else will manually create row,
or change/delete it during test runtime, we can have error that will interrupt our test or invalid results finally.

That is why good unit tests:
Do not use database.
Do not rely on network calls or files.

I think that it is really bad thing to have randomized test failures. It means that logic of program and test is correct, but
accidentally test is failing because of environment set up or other factors. We need to eliminate this, because unit test
failure must notify about defect in business logic and not in testing environment.

Technique that I present is called dependency injection. In general we need to replace database queries with
something that pretends (mocks) database. We inject new object with its new behavior to the test framework, so
finally we are not using database queries that is why it is called dependency injection.
There are many ways of doing it, like using interfaces or inheritance.

I want to recommend one approach that uses inheritance, because:
It is simple.
It does not require separate interface creation.
We only extend test code to pretend database, not influence the production code.

We need to make distinction between:
Production code business logic executed by real program in production system. It is usually global class,
report or include.
Testing code used only for testing, never run in production. Test code cannot be put in the production code
even if it is unused, so production global class should not have methods like set_customer_for_test_only( ).

There is a design template that we can use for testing database dependent logic with dependency injection. If you
follow this approach, it is easy to extend production code, database queries and testing in the future.


1. Build Data Access Object (DAO) which will be single access point to database.

Create class method get_instance( ) which returns singleton instance of object.
Create class method set_instance( ) that makes it possible to inject mock instance if we need it.
Each business domain should have own DAO, like ZCL_CUSTOMER_DAO, ZCL_CONTRACT_DAO,
ABAP Unit Tests without database dependency - DAO
concept
Posted by Adam Krawczyk in ABAP Testing and Troubleshooting on Mar 21, 2013 11:31:51 AM
Share 2
2 Like
ZCL_WORK_ORDER_DAO etc. Initially we can have one DAO for report, but if there are too many queries for
different tables there, complexity increases. It is better to split it logically into separate DAO units that everyone
can use, so try to make DAOs domain specific and not report oriented. Keep it simple.
Methods in DAO should suit our program need, especially for performance reasons. If our program reads table
many times and require only single column values, then build method that returns table of that column values
only. However if program reads table just few times, you can build method that returns full rows content and
extract column in your program.
Methods in DAO are mainly database queries, but also function/bapi/objects calls that use database internally.
Database logic is extracted and separated from business logic.
There is only one access point to database queries because singleton pattern is used.



2. Global class (production code) keeps attribute of mo_dao_instance, which is initialized in constructor.


3. All database operations from production code must be delegated to DAO instance.

Global class never runs direct queries on database inside own methods.
Instead, all queries are delegated to dao instance, for example:



4. For testing scenarios, we create new class that pretends real DAO, but has predefined results for each
method.

It may be local class if we need to pretend results only for local program, or global class if we want to share it
wider.
The class extends ZCL_EMPLOYEE_DAO, inheritance is used here.
I use _mock addition to the name to identify that this is mocking class (convention from Java development).


We need to redefine only methods that will be used in testing scenario.
However if we do not redefine some method and they are used during test, the real database access will be
performed so just watch out on that.
Optionally all methods can be implemented with empty content (by default empty result returned from methods),
then write implementation for methods that we need for test scenarios.


In lcl_employee_dao_mock methods implementation we create fixed values that we assume should be
returned from database. We can program conditions to have different results for different input parameters.

01. DEFINITION:
02. CLASS-DATA mo_dao_instance TYPE REF TO zcl_employee_dao.
03. CLASS-METHODS get_instance
04. RETURNING VALUE(ro_instance) TYPE REF TO zcl_employee_dao.
05. IMPLEMENTATION:
06. METHOD get_instance.
07. IF ( mo_dao_instance IS INITIAL ).
08. CREATE OBJECT mo_dao_instance.
09. ENDIF.
10. ro_instance = mo_dao_instance.
11. ENDMETHOD.
01. METHOD constructor.
02. me->mo_employee_dao = zcl_employee_dao=>get_instance( ).
03. ENDMETHOD.
01. ls_employee = mo_employee_dao->get_employee_by_id( i_employee_id ).
02. lt_employees = mo_employee_dao->get_employees_from_department( i_department ).
01. CLASS lcl_employee_dao_mock DEFINITION INHERITING FROM zcl_employee_dao.
01. DEFINITION:
02. METHODS get_employee_by_id FINAL REDEFINITION.
03. METHODS get_employees_from_department FINAL REDEFINITION.
04. FINAL REDEFINITION.
01. METHOD get_employee_by_id.
02. DATA ls_employee TYPE zemployee_s.
03.
04. IF ( i_employee_id = '00001' ).
05. rs_employee-id = '000001'.
06. rs_employee-name = 'Adam Krawczyk'.
07. rs_employee-age = 29.
08. rs_employee-department = 'ERP_DEV'.
09. rs_employee-salary = 10000.
10. ELSEIF ( i_employee_id = '00002' ).
11. rs_employee-id = '000002'.
12. rs_employee-name = 'Julia Elbow'.
13. rs_employee-age = 35.
14. rs_employee-department = 'ERP_DEV'.

Implementing methods requires knowledge of database content. When I do development, I often take real
database values found during debugging/manual queries and prepare test case. In this way, you show in code
what can be actually expected from database, not fake but real possible values. That helps others to understand
the logic as well.
We must know possible input values and expected results. Otherwise if we do not know it, how can we be sure
that our production code logic works fine? Not knowing business domain and lack of testing data cannot be
excuse for not having unit tests.


6. After we have everything above set up, we can easily inject mock DAO to unit tests.

In the class_setup method of Unit Test class which is run once before each tests are executed, insert mock
DAO into real DAO:

And that is it. Now mock dao will be used and predefined result set is returned during all tests from our own
implementation in LCL_EMPLOYEE_DAO_MOCK.
Initially I used to also set original instance of DAO in the tear_down method, which is run after all tests are
finished. However this is not needed.
ABAP specification is that singleton instance defined as in point 1, works only within one session. It means that
mock DAO instance will be injected to ZCL_EMPLOYEE_DAO only during Unit Tests execution. Even if Unit
Tests are lasting longer, and in the same time I will run production code in parallel from new session (like new
transaction or program run F8), because this is separate session, real DAO will be used there.

Below is the summary of all described steps, showing end to end example of production code and test code.

1. Types definition used in classes.
Let's define type that will be used in below example.
Structure represents basic data of employee.
Hashed table of employees with unique ID key.
2. DAO class for employee - definition.
Get_instance( ) and set_instance( ) create according to described template.
3 methods for database queries.
15. rs_employee-salary = 15000.
16. ENDIF.
17. ENDMETHOD. "get_employee_by_id
01. DEFINITION.
02. CLASS-METHODS class_setup.
03. IMPLEMENTATION.
04. METHOD class_setup.
05. DATA lo_employee_dao_mock TYPE REF TO lcl_employee_dao_mock.
06. CREATE OBJECT lo_employee_dao_mock.
07. ZCL_EMPLOYEE_DAO=>set_instance( lo_employee_dao_mock ).
08. ENDMETHOD.

3. DAO class for employee - implementation.
get_average_age is specialized method which moves logic of average calculation to database.
get_employees_from_department method returns table of employees, that will be used for other statistics
calculations.
For test purpose, zemployee_db_table is used and we assume that it contains same columns as structure.
4. Business class - employee statistics - definition.
Example production code which reads employee statistics: employee data, average age of all employees and
average salary in specific department.
5. Business class - employee statistics - implementation.
mo_employee_dao is initialized in constructor and this is access point to database for business logic.
No database direct access.
All queries to database are done through mo_employee_dao object.
It is simple example for demo purpose. In real life logic can be more complicated, but still only single queries
are used to database, then program logic is processing results.
Average age is read directly from database through dao.
Average salary in department is calculated by program. For demonstration purpose, DAO is returning list of
employees from department, then program calculates average. In reality it would be easier to program it as well
in DAO as single database query.
6. Mock DAO - definition.
Mock DAO extends real DAO, so it has same methods available.
All methods from real dao are redefined in this case.
FINAL REDEFINITION points that we do not want anyone to extend lcl_employee_dao_mock class methods, but
as well we could use REDEFINITION keyword only.
In point 7 and 8 you will se different ways of implementing testing data, for demonstration purpose. In reality it is
better to keep one convention in the mock DAO class.
7. Mock DAO - implementation part 1.
One way of test data preparation.
There is internal table that corresponds to real database table.
In constructor of mock DAO we initialize table like we would prepare real database table before test.
In mock DAO methods, we use internal table to find results rather than real database table.


8. Mock DAO - implementation part 2
If we do not need to simulate full table content we can implement testing data directly in methods.
Based on input parameters conditions, we define received results.
It is easy to extend testing data in the future, just build own data for new input parameter designed for new test
scenario.
Sometimes we can also hardcode database values as result of method, like in case of get_average_age.
9. Unit Test class definition.
class_setup needed - will be run once before all tests. We need to replace real DAO with mock DAO there.
setup method will be run before each test. New fresh instance of object to test will be created.
lo_employee_statistics is the business logic object, that we want to test.
3 methods tested, two of them are tested with found and not found values.

10. Unit Test class implementation - part 1.
It is enough to replace instance in ZCL_EMPLOYEE_DAO with mock DAO instance before all tests are started.
This is the key point of dependency injection used.
Any further call during tests execution, by production code (example constructor in lo_employee_statistics) that
tries to get instance of DAO by ZCL_EMPLOYEE_DAO=>GET_INSTANCE( ) will now get our fake prepared
instance of MOCK DAO.
It is safe to inject fake DAO as this affects only current user session that will finish after tests are executed. Any
other session that calls ZCL_EMPLOYEE_DAO=>GET_INSTANCE( ) will get real DAO.

11. Unit Test class implementation - part 2.


I am attaching also text file with all code from presented example so you can use it for testing.

I hope that after reading this blog you will see how easy it is to write unit tests even for logic that requires access to
database. If it looks like much code for such simple example, believe me that it is worth to spent time and create unit
tests anyhow. Even and especially complex reports need it, where simple change in the future may impact behavior
and non-author is not sure if he can add new line there or not. If code is well tested, there is less chance for
unexpected errors. Lately there are tools that allows you to easily execute unit tests and measure code coverage but
that is another story.

Keep in mind that Unit Tests that skips database by pretending it are verifying business logic but not end to end
program behavior. If there is error in real DAO method, in select statement for example, our tests will not discover it.
That is why end user tests are important as well. But users have less to test or less probably will discover logic bug
after code was already tested on unit level. Of course it is also possible to write unit tests for DAO class itself, by
inserting, reading, validating results and deleting rows for example. But I mentioned at the beginning that this are not
pure unit tests, but may be helpful anyhow. Just group them in category "may have randomize fail".

There is one more advantage of using DAO concept. If we delegate all database operations to DAO classes in our
development, they can be reused by anyone. Additionally class can be tested by F8, and single methods may be
executed. In this way we can check database statements (or function methods) results that are already implemented
in DAO, no need to implement temporal code or thinking how to query table or execute join statement.

I recommend you to use DAO concept and always extract database logic from business layer. I strongly encourage to
write unit tests whenever it is possible. - try and see long term benefits.

Average User Rating
(11 ratings)
0 Tweet 0
Good luck!
2418 Views
Topics: abap Tags: object_oriented, unit_tests, unit_testing, database_access_object, dao, dependency_injection
DAO_Unit_Tests_ABAP_code.txt.zip(2.2 K)
Share 2
2 Like
12 Comments
Like (0)
Nathan Jones Apr 3, 2013 6:07 PM
Great article! I'm still learing with abap unit testing and this has been most helpful. I have started to
use unit tests but only in limited areas - this provides me with a good starter to keep me going.

Thank you once again.

Nath
Like (0)
Adam Krawczyk Apr 4, 2013 9:41 AM (in response to Nathan Jones)
Hi Nathan,

I am glad I can help. Even if this article is long (I tried to put all details), writing good unit
tests is really simple and presented technique easy to learn. Good luck with your clean
code journery!

Regards,
Adam
Like (0)
Abderrahman SOUIDI Apr 17, 2013 6:15 PM
Really good job
Like (0)
Otto Gold Apr 29, 2013 11:04 AM
Excellent stuff! I might come back and post more comments when I am sure I understand it (and live
it!) all!
Cheers Otto
Like (1)
Suhas Saha Apr 29, 2013 11:31 AM
Hello Adam,

I'm a big fan of separation-of-concerns(SoC) and i think DAO conforms to this as well.

I can very well wrap DAO over a persistence class & make it cooler But persistence classes give
up when handling huge data, sigh

Anyway i've itching to use ABAP unit, but unfortunately have not been able to do so. But when i start
using it DAO is the thing to look upto.

Cheers,
Suhas
Adam Krawczyk Apr 29, 2013 1:26 PM (in response to Suhas Saha)
Hi Suhas,

You are right, persistance class is one way to implement DAO for simple queries and new
Like (0)
development. If we need to work with already existing tables, using manual queries and
predefined BAPIs/functions is a better choice. Moreover you control performance, can
provide different variants of methods - single item details or massive queries with range
etc.

I encourage you to practice unit tests as it improves quality and gives fun as well.

Regards
Adam
Like (1)
Frank Stdle May 28, 2013 11:16 PM
Hi Adam, thanks for sharing your insight on unit testing with us. I am a little confused about one
thing, hopefully you can help me out a bit. You use the DAO class to access the database (and
perhaps other dependencies). You inject the DAO into the business class so that the DAO is used by
the business class, and then you call the business class methods from your application code. But
you can also call the DAO methods directly from the application code. For instance, you can either
call ZCL_EMPLOYEE_STATISTICS->GET_BY_ID( ) or you can use ZCL_EMPLOYEE_DAO-
>GET_EMPLOYEE_BY_ID( ) from your application? Both end up calling the DAO method eventually,
but when should you use one over the other? Does it make sense to have to accesses to the same
logic?

As an alternative pattern, have you considered passing the business class into a DAO class instead
of injecting the DAO into the business class? Then it would look something like this:
ZCL_EMPLOYEE_DAO->SAVE_EMPLOYEE( LO_EMPLOYEE ) and LO_EMPLOYEE =
ZCL_EMPLOYEE_DAO->GET_EMPLOYEE_BY_ID( LV_ID) and so on ?

I am not sure which is the better approach, but perhaps you have some thoughts on this,

Cheers, and see you soon!
Frank
Like (1)
Adam Krawczyk Jun 7, 2013 12:14 PM (in response to Frank Stdle)
Hi Frank,

Nice to see you here. Sorry for late answer, but my son Alexander was born in the
meantime, I am again happy father now

With pattern I presented above, DAO instance is always present in business class,
initialized in constructor during object creation. For testing purpose we inject fake DAO to
production DAO singleton instance, and not to the business class itself. Thanks to that it
does not matter if in production code we use ZCL_EMPLOYEE_STATISTICS-
>GEWT_BY_ID( ) or ZCL_EMPLOYEE_DAO->GET_EMPLOYEE_BY_ID( ) - unit tests and
production code will still work.

Actually the method ZCL_EMPLOYEE_STATISTICS->GET_EMPLOYEE_BY_ID( ) is not
needed so much as you could use directly DAO method, that is correct. If we want we can
keep it there just for grouping purposes. It gets something from fascade pattern, where we
choose only subset of methods from other class for simplification - actually
ZCL_EMPLOYEE_STATISTICS can use only few methods from ZCL_EMPLOYEE_DAO
which has many others as well. Then we can have other class like
ZCL_EMPLOYEE_SALARY which will also use same ZCL_EMPLOYEE_DAO, but different
subset of methods. That is the point. But there is nothing against using
ZCL_EMPLOYEE_DAO->GET_BY_ID( ) directly anywhere in the production code.

Your last example with ZCL_EMPLOYEE_DAO->SAVE_EMPLOYEE( lo_employee) is ok but
I would probably not use it. If you do it, you make dependency in both directions - DAO uses
business class and business class uses DAO (in different methods). To keep it simple I
would prefer to have method ZCL_EMPLOYEE->SAVE( ) which inside uses DAO to update
database. In this way there is only one way dependency - business class is using DAO but
DAO is not even aware that ZCL_EMPLOYEE class exists.

Regards,
Adam
Like (0)
Dirk Wittenberg Jul 11, 2013 9:40 PM
Great article. Let's hope it fosters the coding with unit tests ( in best case TDD )! It can be so helpful.

Regards,
Dirk
Hsia-Liang Tan Sep 12, 2013 6:00 AM
Hi Adam,

Great article

Would like to check if we are going to add in test doable class for the DAO and have expected to
return > 300 lines of database lines? What will be the best way to do that as coding single line by
Follow SCN
Site Index Contact Us SAP Help Portal
Privacy Terms of Use Legal Disclosure Copyright
Like (0)
line will really consume alot of time?
Like (0)
Dirk Wittenberg Sep 12, 2013 11:08 AM (in response to Hsia-Liang Tan)
Hi,

do you really need that many test data records in your unit-tests?
What do you want to test?

Regards,
Dirk
Like (1)
Adam Krawczyk Sep 13, 2013 1:31 PM (in response to Hsia-Liang Tan)
Hi Hsia-Liang,

It depends on case, but I would rather not created test for 300 lines. As Dirk already
mentioned below, do you really need so many?

Unit Tests should consider boundary conditions. For numbers it will be negative, 0, 1, 5 and
big number 1000000. For table contents it will be like: no rows, row with empty/unexpected
values, 1 row and 3 rows. If logic of program works for 3 rows, why it should not work for 10,
100 or 1000? This is how I would approach it.

If you really want to see how program behaves with 300 lines (because you experienced
that something is going wrong with many lines only), I would simulate it with LOOP
statement so that few lines of code would create 300 entries and assertions verify results.
For sure I would not copy paste 300 lines.

We need to be smart! :-)

Regards,
Adam

Você também pode gostar