[Twisted-Python] the right way of unit testing protocols
What is the right way of unit testing protocols in twisted ? Currently, I'm just subclassing my protocol with unittest.TestCase And directly calling the dataRecieved with a string and am checking If the overridden callbacks (like dataRecieved) are called. The tests pass, but back when I was writing threaded servers, I would actually setup a server on localhost and would connect to The server from another thread, for unit testing. That gives me a lot more confidence. I know that I can setup two reactors, for the client and the server and then try to make them talk, But I'm sure there's a shortcut. I just could'nt figure out the test suites from the Twisted sources. Jeethu Rao
On Thu, 31 Jul 2003 00:26:07 +0530 Jeethu Rao <jeethur@sancharnet.in> wrote:
What is the right way of unit testing protocols in twisted ? Currently, I'm just subclassing my protocol with unittest.TestCase And directly calling the dataRecieved with a string and am checking If the overridden callbacks (like dataRecieved) are called.
That is indeed a good way to test.
The tests pass, but back when I was writing threaded servers, I would actually setup a server on localhost and would connect to The server from another thread, for unit testing. That gives me a lot more confidence.
And doing this sort of testing in addition is also a good idea.
I know that I can setup two reactors, for the client and the server and then try to make them talk, But I'm sure there's a shortcut. I just could'nt figure out the test suites from the Twisted sources.
You only need one reactor. (And twisted doesn't support multiple reactors at the moment anyway.) So don't look for that in the code. Just look at how the tests are done. In general you'd just do some variant on: reactor.listenTCP(1234, myServerFactory) reactor.connectTCP("127.0.0.1", 1234, myClientFactory): while someConditionIsn'tSet: reactor.iterate() # at this point some exchange should have finished successfully -- Itamar Shtull-Trauring http://itamarst.org/ http://www.zoteca.com -- Python & Twisted consulting
On Wed, Jul 30, 2003 at 03:10:39PM -0400, Itamar Shtull-Trauring wrote:
On Thu, 31 Jul 2003 00:26:07 +0530 Jeethu Rao <jeethur@sancharnet.in> wrote:
I know that I can setup two reactors, for the client and the server and then try to make them talk, But I'm sure there's a shortcut. I just could'nt figure out the test suites from the Twisted sources.
You only need one reactor. (And twisted doesn't support multiple reactors at the moment anyway.) So don't look for that in the code. Just look at how the tests are done.
In general you'd just do some variant on:
reactor.listenTCP(1234, myServerFactory) reactor.connectTCP("127.0.0.1", 1234, myClientFactory): while someConditionIsn'tSet: reactor.iterate() # at this point some exchange should have finished successfully
Or you can use the loopback module -- twisted.protocols.loopback. Many of the Twisted tests do this. -Andrew.
On Thu, Jul 31, 2003 at 12:09:00PM +1000, Andrew Bennetts wrote:
In general you'd just do some variant on:
reactor.listenTCP(1234, myServerFactory) reactor.connectTCP("127.0.0.1", 1234, myClientFactory): while someConditionIsn'tSet: reactor.iterate() # at this point some exchange should have finished successfully
Or you can use the loopback module -- twisted.protocols.loopback. Many of the Twisted tests do this.
I'd like to vote _heavily_ on using twisted.protocols.loopback.loopback() Opening TCP sockets in unit tests is just not _unit_ testing in my book. Please don't do it in unit tests. It just makes it harder to run your unit tests in varying environments. (Who says I allow you to bind to port 1234? Who says it's free? Who says I allow you to listen *at all*?) </rant> Also, you should not be testing just interoperability between *your* client and *your* server, but interoperability of your server with a "standard", and interoperability of your client with a "standard". For that, I prefer doing something like this: class LDAPClientTestDriver: """ A test driver that looks somewhat like a real LDAPClient. Pass in a list of lists of LDAPProtocolResponses. For each sent LDAP message, the first item of said list is iterated through, and all the items are sent as responses to the callback. The sent LDAP messages are stored in self.sent, so you can assert that the sent messages are what they are supposed to be. """ def __init__(self, *responses): self.sent=[] self.responses=list(responses) def queue(self, x, callback): self.sent.append(x) assert self.responses, 'Ran out of responses at %r' % x responses = self.responses.pop(0) while responses: r = responses.pop(0) ret = callback(r) if responses: assert ret==0 else: assert ret==1 def assertNothingSent(self): # just a bit more explicit self.assertSent() def assertSent(self, *shouldBeSent): shouldBeSent = list(shouldBeSent) assert self.sent == shouldBeSent, \ '%s expected to send %r but sent %r' % ( self.__class__.__name__, shouldBeSent, self.sent) sentStr = ''.join([str(x) for x in self.sent]) shouldBeSentStr = ''.join([str(x) for x in shouldBeSent]) assert sentStr == shouldBeSentStr, \ '%s expected to send data %r but sent %r' % ( self.__class__.__name__, shouldBeSentStr, sentStr) class LDAPSyntaxAttributesModificationOnWire(unittest.TestCase): def testAdd(self): """Modify & commit should write the right data to the server.""" client = LDAPClientTestDriver( [ pureldap.LDAPModifyResponse(resultCode=0, matchedDN='', errorMessage=''), ]) o=ldapsyntax.LDAPEntry(client=client, dn='cn=foo,dc=example,dc=com', attributes={ 'objectClass': ['a', 'b'], 'aValue': ['a'], }) o['aValue'].add('newValue') o['aValue'].add('anotherNewValue') d=o.commit() val = deferredResult(d) client.assertSent(pureldap.LDAPModifyRequest( object='cn=foo,dc=example,dc=com', modification=[ pureldap.LDAPModification_add(vals=(('aValue', ['newValue']),)), pureldap.LDAPModification_add(vals=(('aValue', ['anotherNewValue']),)), ])) And elsewhere, test the actual serialization of the on-wire protocol message known as pureldap.LDAPModifyRequest: class KnownValues(unittest.TestCase): knownValues=( # class, args, kwargs, expected_result (pureldap.LDAPModifyRequest, [], { "object": 'cn=foo, dc=example, dc=com', "modification": [pureldap.LDAPModification_delete([('bar',)])] }, [0x66, 0x2c] + [0x04, 0x1a] + l("cn=foo, dc=example, dc=com") + [0x30, 0x0e] + [0x30, 0x0c] + [0x0a, 0x01, 0x01] + [0x30, 0x07] + [0x04, 0x03] + l("bar") + [0x31, 0x00]), ... ) def testToLDAP(self): """str(LDAPClass(...)) should give known result with known input""" for klass, args, kwargs, encoded in self.knownValues: result = klass(*args, **kwargs) result = str(result) result = map(ord, result) if result!=encoded: raise AssertionError, \ "Class %s(*%s, **%s) doesn't encode properly: " \ "%s != %s" % (klass.__name__, repr(args), repr(kwargs), repr(result), repr(encoded)) def testFromLDAP(self): """LDAPClass(encoded="...") should give known result with known input""" for klass, args, kwargs, encoded in self.knownValues: m=MutableString(s(*encoded)) m.append('foo') result = klass(encoded=m, berdecoder=pureber.BERDecoderContext()) assert m=='foo' shouldBe = klass(*args, **kwargs) #TODO shouldn't use str below assert str(result)==str(shouldBe), \ "Class %s(*%s, **%s) doesn't decode properly: " \ "%s != %s" % (klass.__name__, repr(args), repr(kwargs), repr(result), repr(shouldBe)) -- :(){ :|:&};:
On Thu, 31 Jul 2003 14:07:10 +0300 Tommi Virtanen <tv@twistedmatrix.com> wrote:
Opening TCP sockets in unit tests is just not _unit_ testing in my book. Please don't do it in unit tests. It just makes it harder to run your unit tests in varying environments. (Who says I allow you to bind to port 1234? Who says it's free? Who says I allow you to listen *at all*?)
Most of Twisted's unittests listen on 0 and then figure out which port was bound to.
Also, you should not be testing just interoperability between *your* client and *your* server, but interoperability of your server with a "standard", and interoperability of your client with a "standard". For that, I prefer doing something like this:
Also a very good idea, but doing both is better than just one. The more tests the better. -- Itamar Shtull-Trauring http://itamarst.org/ http://www.zoteca.com -- Python & Twisted consulting
On Thu, Jul 31, 2003 at 11:03:07AM -0400, Itamar Shtull-Trauring wrote:
Opening TCP sockets in unit tests is just not _unit_ testing in my book. Please don't do it in unit tests. It just makes it harder to run your unit tests in varying environments. (Who says I allow you to bind to port 1234? Who says it's free? Who says I allow you to listen *at all*?)
Most of Twisted's unittests listen on 0 and then figure out which port was bound to.
Which is mostly the reason why I'm bitch about it on _this_ mailing list. I dislike that method. I really do. There have been _months_ during which certain twisted unit tests randomly fail; many of those have been due to interaction with local TCP stack, networking, firewall and miscellanous experimental security features. I really think unit tests should touch the underlying libraries/OS as little as possible. For the cases where you really want to test the OS integration, you want to do things like stress tests, and for e.g. mail tests where you randomly shut down the power to a machine running a stress test, and later check that no messages were lost. -- :(){ :|:&};:
On Thu, Jul 31, 2003 at 10:43:55PM +0300, Tommi Virtanen wrote:
On Thu, Jul 31, 2003 at 11:03:07AM -0400, Itamar Shtull-Trauring wrote:
Opening TCP sockets in unit tests is just not _unit_ testing in my book. Please don't do it in unit tests. It just makes it harder to run your unit tests in varying environments. (Who says I allow you to bind to port 1234? Who says it's free? Who says I allow you to listen *at all*?)
Most of Twisted's unittests listen on 0 and then figure out which port was bound to.
Which is mostly the reason why I'm bitch about it on _this_ mailing list.
I dislike that method. I really do.
There have been _months_ during which certain twisted unit tests randomly fail; many of those have been due to interaction with local TCP stack, networking, firewall and miscellanous experimental security features.
I've *never* seen a failure due to listening on 0, either on my own machines, or on the buildslaves, or reports on this list or IRC. I'd be inclined to agree with you if there was actually some evidence of a problem. I *have* seen failures due to assuming that a *particular* port is available, and that's definitely a no-no, because it could legitimately be in use (especially if someone else is running tests on the same machine! :). That's why none of the Twisted unit tests do this anymore. Listening on 0 (as our TCP tests do, and as twisted.protocols.loopback.loopbackTCP does) asks the OS to use any available port it likes, and I've never seen it fail. -Andrew.
participants (4)
-
Andrew Bennetts
-
Itamar Shtull-Trauring
-
Jeethu Rao
-
Tommi Virtanen