[Twisted-Python] Unit testing, trail, inlineCallbacks, deferreds and mocking
Hi! I've just started a new project using Twisted and I want to write unit tests since the beginning. Unfortunately I've got some trouble understanding how should I do it. I read 'Test-driven development with Twisted', read some articles on the web and searched on the mailing list but I couldn't find anything which make it clear for me. I've got a class: class SessionCleaner(object): def __init__(self, session_db, interval=10): self.session_db = session_db self.lc = task.LoopingCall(self.check_old_sessions) self.lc.start(interval) @defer.inlineCallbacks def check_old_sessions(self): log.msg('check_old_sessions()', logLevel=logging.DEBUG) try: old_sessions = yield self.session_db.get_old_sessions() for s in old_sessions: yield self.session_db.process_stopped(s) except txredisapi.ConnectionError as e: log.msg('check_old_sessions - connection error {}' .format(e), logLevel=logging.WARNING) session_db is a object with methods which makes some calls to Redis. Testing if __init__ works correctly is easy - I can mock task.LoopingCall and check if it was called with correct attributes. I've got trouble testing check_old_sessions. Since I'm writing unit tests I don't want to call real session_db methods and make real Redis queries. I'd like to mock them and test just few things: - is the method get_old_sessions called? - is the method process_stopped called N times with the arguments returned by mocked get_old_sessions? - is txredisapi.ConnectionError handled correctly? So is there any "right" way of mocking functions which returns deferreds? Or maybe I should test this method differently? Best regards, Patryk
Hey I raised a similar question a while ago: http://twistedmatrix.com/pipermail/twisted-python/2013-July/027241.html Since then, our approach has evolved into the below, which may be useful. https://github.com/jamesbroadhead/bttrtwisted/blob/master/bttrtwisted/testin... Usage: expected = Foo() remote_result = Result() thing = Thing() thing.call_external_service = dmockfunc(remote_result) d = thing.function_under_test(..) d.addCallback(self.assertEqual, expected) return d You can use the func_dict param to gen_nondeferred_mock to have it stand in-place-of an object As always with mocks, a little can be helpful, but if you find you're instantiating a lot of them, you may want to reconsider your approach. Feedback welcome! [ the repo is for experiments, so use with care ] James On 27 January 2015 at 13:00, Patryk Ściborek <patryk@sciborek.com> wrote:
Hi!
I've just started a new project using Twisted and I want to write unit tests since the beginning. Unfortunately I've got some trouble understanding how should I do it. I read 'Test-driven development with Twisted', read some articles on the web and searched on the mailing list but I couldn't find anything which make it clear for me.
I've got a class:
class SessionCleaner(object): def __init__(self, session_db, interval=10): self.session_db = session_db self.lc = task.LoopingCall(self.check_old_sessions) self.lc.start(interval)
@defer.inlineCallbacks def check_old_sessions(self): log.msg('check_old_sessions()', logLevel=logging.DEBUG) try: old_sessions = yield self.session_db.get_old_sessions() for s in old_sessions: yield self.session_db.process_stopped(s)
except txredisapi.ConnectionError as e: log.msg('check_old_sessions - connection error {}' .format(e), logLevel=logging.WARNING)
session_db is a object with methods which makes some calls to Redis.
Testing if __init__ works correctly is easy - I can mock task.LoopingCall and check if it was called with correct attributes.
I've got trouble testing check_old_sessions. Since I'm writing unit tests I don't want to call real session_db methods and make real Redis queries. I'd like to mock them and test just few things: - is the method get_old_sessions called? - is the method process_stopped called N times with the arguments returned by mocked get_old_sessions? - is txredisapi.ConnectionError handled correctly?
So is there any "right" way of mocking functions which returns deferreds? Or maybe I should test this method differently?
Best regards, Patryk
_______________________________________________ Twisted-Python mailing list Twisted-Python@twistedmatrix.com http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
On 27 January 2015 at 13:00, Patryk Ściborek <patryk@sciborek.com> wrote:
Hi!
I've just started a new project using Twisted and I want to write unit tests since the beginning. Unfortunately I've got some trouble understanding how should I do it. I read 'Test-driven development with Twisted', read some articles on the web and searched on the mailing list but I couldn't find anything which make it clear for me.
I've got a class:
class SessionCleaner(object): def __init__(self, session_db, interval=10): self.session_db = session_db self.lc = task.LoopingCall(self.check_old_sessions)
def __init__(self, session_db, interval=10, reactor=reactor) self.lc = task.LoopingCall(self.check_old_sessions) self.lc.clock = reactor Then in tests you can replace the default reactor with twisted.internet.task.Clock In this way you have control over the looping call.
self.lc.start(interval)
@defer.inlineCallbacks def check_old_sessions(self): log.msg('check_old_sessions()', logLevel=logging.DEBUG) try: old_sessions = yield self.session_db.get_old_sessions() for s in old_sessions: yield self.session_db.process_stopped(s)
except txredisapi.ConnectionError as e: log.msg('check_old_sessions - connection error {}' .format(e), logLevel=logging.WARNING)
session_db is a object with methods which makes some calls to Redis.
In tests you can replace session_db with an InMemorySessionDB which is rigged to return on demand a failure or success. class InMemorySessionDB(object): def __init__(self): self._session = [] self._stop_session = {} def addRiggedSession(self, session, stop_result): self._session.append(session) self._stop_session[session] = stop_result def get_old_sessions(self) return self._session def process_stopped(self, session): return self._stop_session[session] InMemorySessionDB can also inherit form the real deal class and just overwrite exit points. This is more like a mock with a spec.. as it will fail if arbitrary methods are called with arbitrary arguments. I don't think there is a right way of doing it, but I try to avoid using generic Mock or MagicMock objects. Good luck! -- Adi Roiban
Hi James, Thank you for Your response, it was very helpful :) I hope you don't mind if I use your testing.py in my project. I wrote tests for the SessionCleaner class and they seems to be OK ( https://github.com/scibi/pyradacctsrv/blob/master/tests/test_cleaner.py). I saw your post from July 2013 but that thread was more about docstrings and checking return values of test methods than mocking and deferreds ;) Kind regards, Patryk On Tue, Jan 27, 2015 at 3:48 PM, James Broadhead <jamesbroadhead@gmail.com> wrote:
Hey
I raised a similar question a while ago: http://twistedmatrix.com/pipermail/twisted-python/2013-July/027241.html
Since then, our approach has evolved into the below, which may be useful.
https://github.com/jamesbroadhead/bttrtwisted/blob/master/bttrtwisted/testin...
Usage: expected = Foo() remote_result = Result() thing = Thing() thing.call_external_service = dmockfunc(remote_result)
d = thing.function_under_test(..) d.addCallback(self.assertEqual, expected) return d
You can use the func_dict param to gen_nondeferred_mock to have it stand in-place-of an object
As always with mocks, a little can be helpful, but if you find you're instantiating a lot of them, you may want to reconsider your approach.
Feedback welcome! [ the repo is for experiments, so use with care ]
James
On 27 January 2015 at 13:00, Patryk Ściborek <patryk@sciborek.com> wrote:
Hi!
I've just started a new project using Twisted and I want to write unit tests since the beginning. Unfortunately I've got some trouble understanding how should I do it. I read 'Test-driven development with Twisted', read some articles on the web and searched on the mailing list but I couldn't find anything which make it clear for me.
I've got a class:
class SessionCleaner(object): def __init__(self, session_db, interval=10): self.session_db = session_db self.lc = task.LoopingCall(self.check_old_sessions) self.lc.start(interval)
@defer.inlineCallbacks def check_old_sessions(self): log.msg('check_old_sessions()', logLevel=logging.DEBUG) try: old_sessions = yield self.session_db.get_old_sessions() for s in old_sessions: yield self.session_db.process_stopped(s)
except txredisapi.ConnectionError as e: log.msg('check_old_sessions - connection error {}' .format(e), logLevel=logging.WARNING)
session_db is a object with methods which makes some calls to Redis.
Testing if __init__ works correctly is easy - I can mock task.LoopingCall and check if it was called with correct attributes.
I've got trouble testing check_old_sessions. Since I'm writing unit tests I don't want to call real session_db methods and make real Redis queries. I'd like to mock them and test just few things: - is the method get_old_sessions called? - is the method process_stopped called N times with the arguments returned by mocked get_old_sessions? - is txredisapi.ConnectionError handled correctly?
So is there any "right" way of mocking functions which returns deferreds? Or maybe I should test this method differently?
Best regards, Patryk
_______________________________________________ Twisted-Python mailing list Twisted-Python@twistedmatrix.com http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
_______________________________________________ Twisted-Python mailing list Twisted-Python@twistedmatrix.com http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
participants (3)
-
Adi Roiban
-
James Broadhead
-
Patryk Ściborek