ABAP Unit Tests without database dependency – DAO concept
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.
Let’s 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, 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.
DEFINITION: CLASS-DATA mo_dao_instance TYPE REF TO zcl_employee_dao. CLASS-METHODS get_instance RETURNING VALUE(ro_instance) TYPE REF TO zcl_employee_dao. IMPLEMENTATION: METHOD get_instance. IF ( mo_dao_instance IS INITIAL ). CREATE OBJECT mo_dao_instance. ENDIF. ro_instance = mo_dao_instance. ENDMETHOD.
2. Global class (production code) keeps attribute of mo_dao_instance, which is initialized in constructor.
METHOD constructor. me->mo_employee_dao = zcl_employee_dao=>get_instance( ). ENDMETHOD.
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:
ls_employee = mo_employee_dao->get_employee_by_id( i_employee_id ). lt_employees = mo_employee_dao->get_employees_from_department( i_department ).
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).
CLASS lcl_employee_dao_mock DEFINITION INHERITING FROM zcl_employee_dao.
- 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.
DEFINITION: METHODS get_employee_by_id FINAL REDEFINITION. METHODS get_employees_from_department FINAL REDEFINITION. FINAL REDEFINITION.
- 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.
METHOD get_employee_by_id. DATA ls_employee TYPE zemployee_s. IF ( i_employee_id = '00001' ). rs_employee-id = '000001'. rs_employee-name = 'Adam Krawczyk'. rs_employee-age = 29. rs_employee-department = 'ERP_DEV'. rs_employee-salary = 10000. ELSEIF ( i_employee_id = '00002' ). rs_employee-id = '000002'. rs_employee-name = 'Julia Elbow'. rs_employee-age = 35. rs_employee-department = 'ERP_DEV'. rs_employee-salary = 15000. ENDIF. ENDMETHOD. "get_employee_by_id
- 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:
DEFINITION. CLASS-METHODS class_setup. IMPLEMENTATION. METHOD class_setup. DATA lo_employee_dao_mock TYPE REF TO lcl_employee_dao_mock. CREATE OBJECT lo_employee_dao_mock. ZCL_EMPLOYEE_DAO=>set_instance( lo_employee_dao_mock ). ENDMETHOD.
- 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.
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 itis 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.
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.
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! 🙂
Really good job 🙂
Excellent stuff! I might come back and post more comments when I am sure I understand it (and live it!) all! 🙂
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.
You are right, persistance class is one way to implement DAO for simple queries and new 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.
I would like to use persistence class to implement DAO and transient class to implement mock but I have a problem during the execution of my unit tests.
In fact, in the setup method of my local test class, transient objects are not cleared. I have no idea how to delete them. During the first execution of the setup method, there is no problem because no transient object exists but during the second execution I have an exception because the transient object already exists. Do you have any idea how to initialize the transient objects during the execution of the setup method ?
Thanks in advance,
Could you explain to me how you can wrap DAO over a persistence class (maybe with an UML diagram) ?
Thanks in advance.
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!
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.
Great article. Let's hope it fosters the coding with unit tests ( in best case TDD )! It can be so helpful.
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 line will really consume alot of time?
do you really need that many test data records in your unit-tests?
What do you want to test?
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! 🙂
Unfortunately I can`t find you txt file wich you attach to this article. Above you say about this "I am attaching also text file with all code from presented example so you can use it for testing."
Can you help me find this file or reload?
Nicely put through. Easy to understand. Thanks 🙂
Very nice and clear article, thanks for posting it...
One question though comes into my mind: as I know, the Singleton pattern is to be avoided.
Is it really needed here? Which are the risks here in case of using a non-singleton pattern? Is there a risk of instantiating a class more than once during the runtime, and therefore having the risk of creating different data objects with different selection criteria?
Thank you for a short clarification
The attached file seems to have disappear.