Re: [389-devel] Proof of concept: mocking DS in lib389
Hello Thierry, I am not rewriting ldapadd,... methods of real DS class, I am in fact creating MockDS class with custom ldapadd,... methods, _just_ like you suggest :) Furthermore, you can view it as a subclass of real_ds - even though it is not a proper Python subclass, it inherits all functions from repl module just like real_ds would (again through ModuleProxy mechanism). So, methods that are defined in repl are the same for real_ds class and for MockDS class, but ldap.. methods are different. So, basically exactly what you suggest :) Code of the whole class along with all methods is in file tests/test_dsmodules/conftest.py line 7. Thank you, Jan On 10/28/2013 12:02 PM, thierry bordaz wrote: Hi Jan, That is very impressive POC, far above my skill in python. Thanks for sharing this. I have a novice question. This implementation overwrites the basic ldapadd,ldapsearch... function of the real DS. An other approach is to write a 'mock_ds' class being a subclass of 'real_ds' and to overwrite the ldapadd,ldapsearch in mock_ds class (to store data into a dict). What would be the advantages of your approach ? best regards thierry On 10/25/2013 09:36 PM, Jan Rusnacko wrote: Hello Roberto and Thierry, as I promised, I am sending you a proof-of-concept code that demonstrates, how we can mock DS in unit tests for library function (see attachment). You can run tests just by executing py.test in tests directory. Only 3 files are of interest here: lib389/dsmodules/repl.py - this is a Python module with functions - they expect DS instance as the first argument. Since they are functions, not methods, I can just mock DS and pass that fake one as the first argument to them in unit tests. tests/test_dsmodules/conftest.py - this file contains definition of mock DS class along with py.test fixture, that returns it. tests/test_dsmodules/test_repl.py - this contains unit tests for functions from repl.py. What I do is quite simple - I override ldapadd, ldapdelete .. methods of mock DS class, so that instead of sending command to real DS instance, they just store the data in 'dit' dictionary (which represents content stored in DS). This way, I can check that when I call e.g. function enable_changelog(..), in the end DS will have correct changelog entry. To put it very bluntly - enable_changelog(..) function just adds correct changelog entry to whatever is passed to it as the first argument. In unit tests, it is mock DS, otherwise it would be real DS class that sends real ldap commands to real DS instance behind. Now I can successfully test that enable_changelog really works, without going into trouble defining DSInstance or ldap calls at all. Also, I believe this approach would work for 95% of all functions in lib389. Another benefit is that unit tests are much faster, than on real DS instance. Sidenote: even though everything is defined in separate namespace of 'repl' module as function, in runtime they can be used as normal methods of class DSInstance. That is handled by DSModuleProxy. We already went through this, but not with Roberto. Hopefully, now with some code in our hands, we will be able to understand each other on this 'mocking' issue and come to conclusions more quickly. Let me know what you think. Thank you, Jan -- 389-devel mailing list 389-de...@lists.fedoraproject.org https://admin.fedoraproject.org/mailman/listinfo/389-devel
Re: [389-devel] Proof of concept: mocking DS in lib389
On 10/28/2013 02:52 PM, Roberto Polli wrote: Hi @all, Jan wrote: as I promised, I am sending you a proof-of-concept code that demonstrates, how we can mock DS in unit tests for library function Ok, that's clear. instead of sending command to real DS instance, they just store the data in 'dit' dictionary We could use some monkeypatching lib. https://pypi.python.org/pypi/fakeldap/0.5.1 You are correct, we could ! That was Thierry`s question - we can rewrite ldapadd,.. methods of DSInstance (monkeypatching), OR define MockDS class with fake ldapadd,.. and use that one. Now I can successfully test that enable_changelog really works, _really_ is not the right word, right : I think it is. The unit test for enable_changelog really does test the code of enable_changelog(). It does not test ldapadd, ldapmodify ... methods (that should be a job for ldapadd`s unit test) and it does not test how real 389 DS would respond to such request (that is job for 389 DS acceptance, or as you refer to them, integration tests). It is a unit test. this approach would work for 95% of all functions in lib389. I agree that many functions are just ADD/DELETE, and this will work for that. Mocking functions involving MOD, default values, simulating errors co will be complex though: that's the reason why - even after adding unit tests - I will leave the integration testings in the same repo. They will be part of 389-ds-base repo. If we want to keep them together with lib389, then lib389 would be merged to 389-ds-base repo. That`s where developers would like to keep these. DSInstance. That is handled by DSModuleProxy. We already went through this, but not with Roberto. Saw the code, it just imports all files in the folder, right? It's a nice trick, even if it makes the design a bit complex. Correct ! Takes functions from modules and makes them methods of DSInstance. Peace, R. -- 389-devel mailing list 389-de...@lists.fedoraproject.org https://admin.fedoraproject.org/mailman/listinfo/389-devel
Re: [389-devel] Proof of concept: mocking DS in lib389
On 10/29/2013 03:30 PM, thierry bordaz wrote: On 10/29/2013 02:18 PM, Jan Rusnacko wrote: Hello Thierry, I am not rewriting ldapadd,... methods of real DS class, I am in fact creating MockDS class with custom ldapadd,... methods, _just_ like you suggest :) Furthermore, you can view it as a subclass of real_ds - even though it is not a proper Python subclass, it inherits all functions from repl module just like real_ds would (again through ModuleProxy mechanism). So, methods that are defined in repl are the same for real_ds class and for MockDS class, but ldap.. methods are different. So, basically exactly what you suggest :) Hi Jan, Sorry my question was not clear. For example an other approach could be Class DSInstance (object): def __init__(self): ... def ldapadd_r(self, input): # real call to pythonldap.add_s Class MockDSInstance(DSInstance): def __init__(self): ... def ldapadd_r(self, input): input = input.strip() entry = dict(e.strip().split(': ') for e in input.split('\n')) self.dit[entry['dn']] = entry My understanding is that both approach would allow us to call inherited methods, just ldap method are different. What are the advantages of the approach you described compare to the one above ? Oh, ok. Not sure what would be advantage/disadvantage. Result of both approaches is basically the same ... We could do it this way too. best regards thierry Code of the whole class along with all methods is in file tests/test_dsmodules/conftest.py line 7. Thank you, Jan On 10/28/2013 12:02 PM, thierry bordaz wrote: Hi Jan, That is very impressive POC, far above my skill in python. Thanks for sharing this. I have a novice question. This implementation overwrites the basic ldapadd,ldapsearch... function of the real DS. An other approach is to write a 'mock_ds' class being a subclass of 'real_ds' and to overwrite the ldapadd,ldapsearch in mock_ds class (to store data into a dict). What would be the advantages of your approach ? best regards thierry On 10/25/2013 09:36 PM, Jan Rusnacko wrote: Hello Roberto and Thierry, as I promised, I am sending you a proof-of-concept code that demonstrates, how we can mock DS in unit tests for library function (see attachment). You can run tests just by executing py.test in tests directory. Only 3 files are of interest here: lib389/dsmodules/repl.py - this is a Python module with functions - they expect DS instance as the first argument. Since they are functions, not methods, I can just mock DS and pass that fake one as the first argument to them in unit tests. tests/test_dsmodules/conftest.py - this file contains definition of mock DS class along with py.test fixture, that returns it. tests/test_dsmodules/test_repl.py - this contains unit tests for functions from repl.py. What I do is quite simple - I override ldapadd, ldapdelete .. methods of mock DS class, so that instead of sending command to real DS instance, they just store the data in 'dit' dictionary (which represents content stored in DS). This way, I can check that when I call e.g. function enable_changelog(..), in the end DS will have correct changelog entry. To put it very bluntly - enable_changelog(..) function just adds correct changelog entry to whatever is passed to it as the first argument. In unit tests, it is mock DS, otherwise it would be real DS class that sends real ldap commands to real DS instance behind. Now I can successfully test that enable_changelog really works, without going into trouble defining DSInstance or ldap calls at all. Also, I believe this approach would work for 95% of all functions in lib389. Another benefit is that unit tests are much faster, than on real DS instance. Sidenote: even though everything is defined in separate namespace of 'repl' module as function, in runtime they can be used as normal methods of class DSInstance. That is handled by DSModuleProxy. We already went through this, but not with Roberto. Hopefully, now with some code in our hands, we will be able to understand each other on this 'mocking' issue and come to conclusions more quickly. Let me know what you think. Thank you, Jan -- 389-devel mailing list 389-de...@lists.fedoraproject.org https://admin.fedoraproject.org/mailman/listinfo/389-devel
Re: [389-devel] Proof of concept: mocking DS in lib389
On 10/29/2013 02:18 PM, Jan Rusnacko wrote: Hello Thierry, I am not rewriting ldapadd,... methods of real DS class, I am in fact creating MockDS class with custom ldapadd,... methods, _just_ like you suggest :) Furthermore, you can view it as a subclass of real_ds - even though it is not a proper Python subclass, it inherits all functions from repl module just like real_ds would (again through ModuleProxy mechanism). So, methods that are defined in repl are the same for real_ds class and for MockDS class, but ldap.. methods are different. So, basically exactly what you suggest :) Hi Jan, Sorry my question was not clear. For example an other approach could be Class DSInstance (object): def __init__(self): ... def ldapadd_r(self, input): # real call to pythonldap.add_s Class MockDSInstance(DSInstance): def __init__(self): ... def ldapadd_r(self, input): input = input.strip() entry = dict(e.strip().split(': ') for e in input.split('\n')) self.dit[entry['dn']] = entry My understanding is that both approach would allow us to call inherited methods, just ldap method are different. What are the advantages of the approach you described compare to the one above ? best regards thierry Code of the whole class along with all methods is in file tests/test_dsmodules/conftest.py line 7. Thank you, Jan On 10/28/2013 12:02 PM, thierry bordaz wrote: Hi Jan, That is very impressive POC, far above my skill in python. Thanks for sharing this. I have a novice question. This implementation overwrites the basic ldapadd,ldapsearch... function of the real DS. An other approach is to write a 'mock_ds' class being a subclass of 'real_ds' and to overwrite the ldapadd,ldapsearch in mock_ds class (to store data into a dict). What would be the advantages of your approach ? best regards thierry On 10/25/2013 09:36 PM, Jan Rusnacko wrote: Hello Roberto and Thierry, as I promised, I am sending you a proof-of-concept code that demonstrates, how we can mock DS in unit tests for library function (see attachment). You can run tests just by executing py.test in tests directory. Only 3 files are of interest here: lib389/dsmodules/repl.py - this is a Python module with functions - they expect DS instance as the first argument. Since they are functions, not methods, I can just mock DS and pass that fake one as the first argument to them in unit tests. tests/test_dsmodules/conftest.py - this file contains definition of mock DS class along with py.test fixture, that returns it. tests/test_dsmodules/test_repl.py - this contains unit tests for functions from repl.py. What I do is quite simple - I override ldapadd, ldapdelete .. methods of mock DS class, so that instead of sending command to real DS instance, they just store the data in 'dit' dictionary (which represents content stored in DS). This way, I can check that when I call e.g. function enable_changelog(..), in the end DS will have correct changelog entry. To put it very bluntly - enable_changelog(..) function just adds correct changelog entry to whatever is passed to it as the first argument. In unit tests, it is mock DS, otherwise it would be real DS class that sends real ldap commands to real DS instance behind. Now I can successfully test that enable_changelog really works, without going into trouble defining DSInstance or ldap calls at all. Also, I believe this approach would work for 95% of all functions in lib389. Another benefit is that unit tests are much faster, than on real DS instance. Sidenote: even though everything is defined in separate namespace of 'repl' module as function, in runtime they can be used as normal methods of class DSInstance. That is handled by DSModuleProxy. We already went through this, but not with Roberto. Hopefully, now with some code in our hands, we will be able to understand each other on this 'mocking' issue and come to conclusions more quickly. Let me know what you think. Thank you, Jan -- 389-devel mailing list 389-de...@lists.fedoraproject.org https://admin.fedoraproject.org/mailman/listinfo/389-devel
Re: [389-devel] Proof of concept: mocking DS in lib389
On 10/28/2013 02:31 PM, Rich Megginson wrote: On 10/26/2013 12:49 AM, Jan Rusnacko wrote: On 10/25/2013 11:00 PM, Rich Megginson wrote: On 10/25/2013 01:36 PM, Jan Rusnacko wrote: Hello Roberto and Thierry, as I promised, I am sending you a proof-of-concept code that demonstrates, how we can mock DS in unit tests for library function (see attachment). You can run tests just by executing py.test in tests directory. Only 3 files are of interest here: lib389/dsmodules/repl.py - this is a Python module with functions - they expect DS instance as the first argument. Since they are functions, not methods, I can just mock DS and pass that fake one as the first argument to them in unit tests. tests/test_dsmodules/conftest.py - this file contains definition of mock DS class along with py.test fixture, that returns it. tests/test_dsmodules/test_repl.py - this contains unit tests for functions from repl.py. What I do is quite simple - I override ldapadd, ldapdelete .. methods of mock DS class, so that instead of sending command to real DS instance, they just store the data in 'dit' dictionary (which represents content stored in DS). This way, I can check that when I call e.g. function enable_changelog(..), in the end DS will have correct changelog entry. To put it very bluntly - enable_changelog(..) function just adds correct changelog entry to whatever is passed to it as the first argument. In unit tests, it is mock DS, otherwise it would be real DS class that sends real ldap commands to real DS instance behind. def test_add_repl_manager(fake_ds_inst_with_repl): ds_inst = fake_ds_inst_with_repl ds_inst.repl.add_repl_manager(cn=replication manager, cn=config, Secret123) assert ds_inst.dit[cn=replication manager, cn=config][userPassword] == Secret123 assert ds_inst.dit[cn=replication manager, cn=config][nsIdleTimeout] == 0 assert ds_inst.dit[cn=replication manager, cn=config][cn] == replication manager If you are using a real directory server instance, doing add_repl_manager() is going to make a real LDAP ADD request, right? Correct. If you pass DS with real ldapadd method that makes real reqests, its going to use that. Will it still update the ds_inst.dit dict? ds_inst.dit is updated in mocked ldapadd. So in real ldapadd, no. Wouldn't you have to do a real LDAP Search request to get the actual values? Yes, correct. ds_inst.dit[] .. call is specific to mocked DS. But you are right - I could add fake ldapsearch method, that would return entries from 'dit' dictionary and use that to retrieve entries from mocked DS. Because, otherwise, you have separate tests for mock DS and real DS? Or perhaps I'm missing something? I dont understand the question, but let me answer something :) Class DSInstance would have ~10 methods that make real requests to real DS instance (setup, remove, ldapadd, ldapmodify, .., start, stop ..). All other methods would probably depend on ldapadd, ldapmodify.. We can tests these 10 methods either using real DS, or somehow faking the traffic, or just review them manually .. As for other 95 % of library functions that depend on those, we can use MockDS with fake ldapadd, ldapmodify to make unit tests. These unit tests will verify that *assuming* they receive OK ldapmodify method, they will use it to perform correct calls to set up the thing they are supposed to. To be concrete, unit test above, test_add_repl_manager(fake_ds_inst_with_repl), verifies that method ds_inst.repl.add_repl_manager() will add replication manager with expected fields using ldapadd method of object that was passed to it. It will *not* verify the correctness of ldapadd method of real DSInstance class (that is the job of ldapadd`s unit test), nor will it verify that real DS instance would accept such call or behave correctly (that is the job of DS acceptance tests). Hence, a unit test. Now I can successfully test that enable_changelog really works, without going into trouble defining DSInstance or ldap calls at all. Also, I believe this approach would work for 95% of all functions in lib389. Another benefit is that unit tests are much faster, than on real DS instance. Sidenote: even though everything is defined in separate namespace of 'repl' module as function, in runtime they can be used as normal methods of class DSInstance. That is handled by DSModuleProxy. We already went through this, but not with Roberto. Hopefully, now with some code in our hands, we will be able to understand each other on this 'mocking' issue and come to conclusions more quickly. Let me know what you think. Thank you, Jan -- 389-devel mailing list 389-devel@lists.fedoraproject.org https://admin.fedoraproject.org/mailman/listinfo/389-devel
Re: [389-devel] Proof of concept: mocking DS in lib389
Hi @all, Jan wrote: I am in fact creating MockDS class with custom ldapadd, actually I agree with Jan about how the mocking process works: 1- reuse/create an ldap mocking class; 2- rewire existing tests on the mock; Hence, a unit test. Ok, I agree to add unit-testing. Just I don't want to move integration tests (the one against a real instance) in another repo. I'd leave the integration tests there so that whenever somebody clones he can test if the lib works for his installation. It will *not* verify the correctness of ldapadd method of real DSInstance class (that is the job of ldapadd`s unit test About testing ldapadd co, we should consider that - as of now - lib389 wraps some python-ldap methods with *args, **kwds. We should even be more consistent in parameter names between wrapping and wrapped method . Peace, R. On Tuesday 29 October 2013 14:18:59 Jan Rusnacko wrote: Hello Thierry, I am not rewriting ldapadd,... methods of real DS class, I am in fact creating MockDS class with custom ldapadd,... methods, _just_ like you suggest :) Furthermore, you can view it as a subclass of real_ds - even though it is not a proper Python subclass, it inherits all functions from repl module just like real_ds would (again through ModuleProxy mechanism). So, methods that are defined in repl are the same for real_ds class and for MockDS class, but ldap.. methods are different. So, basically exactly what you suggest :) Code of the whole class along with all methods is in file tests/test_dsmodules/conftest.py line 7. Thank you, Jan On 10/28/2013 12:02 PM, thierry bordaz wrote: Hi Jan, That is very impressive POC, far above my skill in python. Thanks for sharing this. I have a novice question. This implementation overwrites the basic ldapadd,ldapsearch... function of the real DS. An other approach is to write a 'mock_ds' class being a subclass of 'real_ds' and to overwrite the ldapadd,ldapsearch in mock_ds class (to store data into a dict). What would be the advantages of your approach ? best regards thierry On 10/25/2013 09:36 PM, Jan Rusnacko wrote: Hello Roberto and Thierry, as I promised, I am sending you a proof-of-concept code that demonstrates, how we can mock DS in unit tests for library function (see attachment). You can run tests just by executing py.test in tests directory. Only 3 files are of interest here: lib389/dsmodules/repl.py - this is a Python module with functions - they expect DS instance as the first argument. Since they are functions, not methods, I can just mock DS and pass that fake one as the first argument to them in unit tests. tests/test_dsmodules/conftest.py - this file contains definition of mock DS class along with py.test fixture, that returns it. tests/test_dsmodules/test_repl.py - this contains unit tests for functions from repl.py. What I do is quite simple - I override ldapadd, ldapdelete .. methods of mock DS class, so that instead of sending command to real DS instance, they just store the data in 'dit' dictionary (which represents content stored in DS). This way, I can check that when I call e.g. function enable_changelog(..), in the end DS will have correct changelog entry. To put it very bluntly - enable_changelog(..) function just adds correct changelog entry to whatever is passed to it as the first argument. In unit tests, it is mock DS, otherwise it would be real DS class that sends real ldap commands to real DS instance behind. Now I can successfully test that enable_changelog really works, without going into trouble defining DSInstance or ldap calls at all. Also, I believe this approach would work for 95% of all functions in lib389. Another benefit is that unit tests are much faster, than on real DS instance. Sidenote: even though everything is defined in separate namespace of 'repl' module as function, in runtime they can be used as normal methods of class DSInstance. That is handled by DSModuleProxy. We already went through this, but not with Roberto. Hopefully, now with some code in our hands, we will be able to understand each other on this 'mocking' issue and come to conclusions more quickly. Let me know what you think. Thank you, Jan -- Roberto Polli Community Manager Babel S.r.l. - http://www.babel.it T: +39.06.9826.9651 M: +39.340.652.2736 F: +39.06.9826.9680 P.zza S.Benedetto da Norcia, 33 - 00040 Pomezia (Roma) CONFIDENZIALE: Questo messaggio ed i suoi allegati sono di carattere confidenziale per i destinatari in indirizzo. E' vietato l'inoltro non autorizzato a destinatari diversi da quelli indicati nel messaggio originale. Se ricevuto per errore, l'uso del contenuto e' proibito; si prega di comunicarlo al mittente e cancellarlo immediatamente. -- 389-devel mailing list 389-devel@lists.fedoraproject.org
Re: [389-devel] Proof of concept: mocking DS in lib389
On 10/26/2013 12:49 AM, Jan Rusnacko wrote: On 10/25/2013 11:00 PM, Rich Megginson wrote: On 10/25/2013 01:36 PM, Jan Rusnacko wrote: Hello Roberto and Thierry, as I promised, I am sending you a proof-of-concept code that demonstrates, how we can mock DS in unit tests for library function (see attachment). You can run tests just by executing py.test in tests directory. Only 3 files are of interest here: lib389/dsmodules/repl.py - this is a Python module with functions - they expect DS instance as the first argument. Since they are functions, not methods, I can just mock DS and pass that fake one as the first argument to them in unit tests. tests/test_dsmodules/conftest.py - this file contains definition of mock DS class along with py.test fixture, that returns it. tests/test_dsmodules/test_repl.py - this contains unit tests for functions from repl.py. What I do is quite simple - I override ldapadd, ldapdelete .. methods of mock DS class, so that instead of sending command to real DS instance, they just store the data in 'dit' dictionary (which represents content stored in DS). This way, I can check that when I call e.g. function enable_changelog(..), in the end DS will have correct changelog entry. To put it very bluntly - enable_changelog(..) function just adds correct changelog entry to whatever is passed to it as the first argument. In unit tests, it is mock DS, otherwise it would be real DS class that sends real ldap commands to real DS instance behind. def test_add_repl_manager(fake_ds_inst_with_repl): ds_inst = fake_ds_inst_with_repl ds_inst.repl.add_repl_manager(cn=replication manager, cn=config, Secret123) assert ds_inst.dit[cn=replication manager, cn=config][userPassword] == Secret123 assert ds_inst.dit[cn=replication manager, cn=config][nsIdleTimeout] == 0 assert ds_inst.dit[cn=replication manager, cn=config][cn] == replication manager If you are using a real directory server instance, doing add_repl_manager() is going to make a real LDAP ADD request, right? Correct. If you pass DS with real ldapadd method that makes real reqests, its going to use that. Will it still update the ds_inst.dit dict? ds_inst.dit is updated in mocked ldapadd. So in real ldapadd, no. Wouldn't you have to do a real LDAP Search request to get the actual values? Yes, correct. ds_inst.dit[] .. call is specific to mocked DS. But you are right - I could add fake ldapsearch method, that would return entries from 'dit' dictionary and use that to retrieve entries from mocked DS. Because, otherwise, you have separate tests for mock DS and real DS? Or perhaps I'm missing something? Now I can successfully test that enable_changelog really works, without going into trouble defining DSInstance or ldap calls at all. Also, I believe this approach would work for 95% of all functions in lib389. Another benefit is that unit tests are much faster, than on real DS instance. Sidenote: even though everything is defined in separate namespace of 'repl' module as function, in runtime they can be used as normal methods of class DSInstance. That is handled by DSModuleProxy. We already went through this, but not with Roberto. Hopefully, now with some code in our hands, we will be able to understand each other on this 'mocking' issue and come to conclusions more quickly. Let me know what you think. Thank you, Jan -- 389-devel mailing list 389-devel@lists.fedoraproject.org https://admin.fedoraproject.org/mailman/listinfo/389-devel
Re: [389-devel] Proof of concept: mocking DS in lib389
Hi @all, Jan wrote: as I promised, I am sending you a proof-of-concept code that demonstrates, how we can mock DS in unit tests for library function Ok, that's clear. instead of sending command to real DS instance, they just store the data in 'dit' dictionary We could use some monkeypatching lib. https://pypi.python.org/pypi/fakeldap/0.5.1 Now I can successfully test that enable_changelog really works, _really_ is not the right word, right : this approach would work for 95% of all functions in lib389. I agree that many functions are just ADD/DELETE, and this will work for that. Mocking functions involving MOD, default values, simulating errors co will be complex though: that's the reason why - even after adding unit tests - I will leave the integration testings in the same repo. DSInstance. That is handled by DSModuleProxy. We already went through this, but not with Roberto. Saw the code, it just imports all files in the folder, right? It's a nice trick, even if it makes the design a bit complex. Peace, R. -- Roberto Polli Community Manager Babel S.r.l. - http://www.babel.it T: +39.06.9826.9651 M: +39.340.652.2736 F: +39.06.9826.9680 P.zza S.Benedetto da Norcia, 33 - 00040 Pomezia (Roma) CONFIDENZIALE: Questo messaggio ed i suoi allegati sono di carattere confidenziale per i destinatari in indirizzo. E' vietato l'inoltro non autorizzato a destinatari diversi da quelli indicati nel messaggio originale. Se ricevuto per errore, l'uso del contenuto e' proibito; si prega di comunicarlo al mittente e cancellarlo immediatamente. -- 389-devel mailing list 389-devel@lists.fedoraproject.org https://admin.fedoraproject.org/mailman/listinfo/389-devel
Re: [389-devel] Proof of concept: mocking DS in lib389
On 10/25/2013 11:00 PM, Rich Megginson wrote: On 10/25/2013 01:36 PM, Jan Rusnacko wrote: Hello Roberto and Thierry, as I promised, I am sending you a proof-of-concept code that demonstrates, how we can mock DS in unit tests for library function (see attachment). You can run tests just by executing py.test in tests directory. Only 3 files are of interest here: lib389/dsmodules/repl.py - this is a Python module with functions - they expect DS instance as the first argument. Since they are functions, not methods, I can just mock DS and pass that fake one as the first argument to them in unit tests. tests/test_dsmodules/conftest.py - this file contains definition of mock DS class along with py.test fixture, that returns it. tests/test_dsmodules/test_repl.py - this contains unit tests for functions from repl.py. What I do is quite simple - I override ldapadd, ldapdelete .. methods of mock DS class, so that instead of sending command to real DS instance, they just store the data in 'dit' dictionary (which represents content stored in DS). This way, I can check that when I call e.g. function enable_changelog(..), in the end DS will have correct changelog entry. To put it very bluntly - enable_changelog(..) function just adds correct changelog entry to whatever is passed to it as the first argument. In unit tests, it is mock DS, otherwise it would be real DS class that sends real ldap commands to real DS instance behind. def test_add_repl_manager(fake_ds_inst_with_repl): ds_inst = fake_ds_inst_with_repl ds_inst.repl.add_repl_manager(cn=replication manager, cn=config, Secret123) assert ds_inst.dit[cn=replication manager, cn=config][userPassword] == Secret123 assert ds_inst.dit[cn=replication manager, cn=config][nsIdleTimeout] == 0 assert ds_inst.dit[cn=replication manager, cn=config][cn] == replication manager If you are using a real directory server instance, doing add_repl_manager() is going to make a real LDAP ADD request, right? Correct. If you pass DS with real ldapadd method that makes real reqests, its going to use that. Will it still update the ds_inst.dit dict? ds_inst.dit is updated in mocked ldapadd. So in real ldapadd, no. Wouldn't you have to do a real LDAP Search request to get the actual values? Yes, correct. ds_inst.dit[] .. call is specific to mocked DS. But you are right - I could add fake ldapsearch method, that would return entries from 'dit' dictionary and use that to retrieve entries from mocked DS. Now I can successfully test that enable_changelog really works, without going into trouble defining DSInstance or ldap calls at all. Also, I believe this approach would work for 95% of all functions in lib389. Another benefit is that unit tests are much faster, than on real DS instance. Sidenote: even though everything is defined in separate namespace of 'repl' module as function, in runtime they can be used as normal methods of class DSInstance. That is handled by DSModuleProxy. We already went through this, but not with Roberto. Hopefully, now with some code in our hands, we will be able to understand each other on this 'mocking' issue and come to conclusions more quickly. Let me know what you think. Thank you, Jan -- 389-devel mailing list 389-devel@lists.fedoraproject.org https://admin.fedoraproject.org/mailman/listinfo/389-devel
Re: [389-devel] Proof of concept: mocking DS in lib389
On 10/25/2013 01:36 PM, Jan Rusnacko wrote: Hello Roberto and Thierry, as I promised, I am sending you a proof-of-concept code that demonstrates, how we can mock DS in unit tests for library function (see attachment). You can run tests just by executing py.test in tests directory. Only 3 files are of interest here: lib389/dsmodules/repl.py - this is a Python module with functions - they expect DS instance as the first argument. Since they are functions, not methods, I can just mock DS and pass that fake one as the first argument to them in unit tests. tests/test_dsmodules/conftest.py - this file contains definition of mock DS class along with py.test fixture, that returns it. tests/test_dsmodules/test_repl.py - this contains unit tests for functions from repl.py. What I do is quite simple - I override ldapadd, ldapdelete .. methods of mock DS class, so that instead of sending command to real DS instance, they just store the data in 'dit' dictionary (which represents content stored in DS). This way, I can check that when I call e.g. function enable_changelog(..), in the end DS will have correct changelog entry. To put it very bluntly - enable_changelog(..) function just adds correct changelog entry to whatever is passed to it as the first argument. In unit tests, it is mock DS, otherwise it would be real DS class that sends real ldap commands to real DS instance behind. def test_add_repl_manager(fake_ds_inst_with_repl): ds_inst = fake_ds_inst_with_repl ds_inst.repl.add_repl_manager(cn=replication manager, cn=config, Secret123) assert ds_inst.dit[cn=replication manager, cn=config][userPassword] == Secret123 assert ds_inst.dit[cn=replication manager, cn=config][nsIdleTimeout] == 0 assert ds_inst.dit[cn=replication manager, cn=config][cn] == replication manager If you are using a real directory server instance, doing add_repl_manager() is going to make a real LDAP ADD request, right? Will it still update the ds_inst.dit dict? Wouldn't you have to do a real LDAP Search request to get the actual values? Now I can successfully test that enable_changelog really works, without going into trouble defining DSInstance or ldap calls at all. Also, I believe this approach would work for 95% of all functions in lib389. Another benefit is that unit tests are much faster, than on real DS instance. Sidenote: even though everything is defined in separate namespace of 'repl' module as function, in runtime they can be used as normal methods of class DSInstance. That is handled by DSModuleProxy. We already went through this, but not with Roberto. Hopefully, now with some code in our hands, we will be able to understand each other on this 'mocking' issue and come to conclusions more quickly. Let me know what you think. Thank you, Jan -- 389-devel mailing list 389-de...@lists.fedoraproject.org https://admin.fedoraproject.org/mailman/listinfo/389-devel
[389-devel] Proof of concept: mocking DS in lib389
Hello Roberto and Thierry, as I promised, I am sending you a proof-of-concept code that demonstrates, how we can mock DS in unit tests for library function (see attachment). You can run tests just by executing py.test in tests directory. Only 3 files are of interest here: lib389/dsmodules/repl.py - this is a Python module with functions - they expect DS instance as the first argument. Since they are functions, not methods, I can just mock DS and pass that fake one as the first argument to them in unit tests. tests/test_dsmodules/conftest.py - this file contains definition of mock DS class along with py.test fixture, that returns it. tests/test_dsmodules/test_repl.py - this contains unit tests for functions from repl.py. What I do is quite simple - I override ldapadd, ldapdelete .. methods of mock DS class, so that instead of sending command to real DS instance, they just store the data in 'dit' dictionary (which represents content stored in DS). This way, I can check that when I call e.g. function enable_changelog(..), in the end DS will have correct changelog entry. To put it very bluntly - enable_changelog(..) function just adds correct changelog entry to whatever is passed to it as the first argument. In unit tests, it is mock DS, otherwise it would be real DS class that sends real ldap commands to real DS instance behind. Now I can successfully test that enable_changelog really works, without going into trouble defining DSInstance or ldap calls at all. Also, I believe this approach would work for 95% of all functions in lib389. Another benefit is that unit tests are much faster, than on real DS instance. Sidenote: even though everything is defined in separate namespace of 'repl' module as function, in runtime they can be used as normal methods of class DSInstance. That is handled by DSModuleProxy. We already went through this, but not with Roberto. Hopefully, now with some code in our hands, we will be able to understand each other on this 'mocking' issue and come to conclusions more quickly. Let me know what you think. Thank you, Jan proof_of_concept_mocking.tar.gz Description: GNU Zip compressed data -- 389-devel mailing list 389-devel@lists.fedoraproject.org https://admin.fedoraproject.org/mailman/listinfo/389-devel