[Twisted-Python] Avoiding network when testing Perspective Broker

exarkun at twistedmatrix.com exarkun at twistedmatrix.com
Thu Oct 25 17:22:42 EDT 2012


On 09:18 am, lacrima.maxim at gmail.com wrote:
>Hi!
>
>I am learning to develop TDD way. I want to create a server that

Hooray!
>understands PB protocol. Initially I thought it would be a good idea to
>avoid real network connections in my tests, so I tried to use

Yes, that's definitely what you want to do.
>`proto_helpers.StringTransport`:

StringTransport is frequently what you want in order to test a protocol 
implementation, so you're on the right track.

Except... actually you're not implementing a protocol.  You're *using* a 
protocol implementation that exists already and has its own unit tests.

It would be better if you found a way to test your code without 
involving the PB protocol implementation or StringTransport.  However, I 
admit that the tools for doing this with PB are almost non-existent.  On 
the other hand, application code written for use with AMP is much more 
amenable to testing.

Just something to think about.
>----------
>import cStringIO
>from twisted.spread import pb
>from twisted.trial import unittest
>from twisted.test import proto_helpers
>
>class Document(pb.Root):
>
>    def remote_convert(self, props):
>        self.props = props
>
>
>class DocTestCase(unittest.TestCase):
>
>    def setUp(self):
>
>        # set up server
>        self.doc = Document()
>        factory = pb.PBServerFactory(self.doc)
>        self.broker = factory.buildProtocol(('127.0.0.1', 0))
>        tr = proto_helpers.StringTransport()
>        self.broker.makeConnection(tr)

So far so good.  You made a factory, got it to make you a protocol, and 
hooked the protocol up to a StringTransport you made.  That's all good 
stuff.
>
>        # this is what a client sends
>        self.props = {'name': 'MyDoc',
>                      'path': '/path/'}
>
>        # prepare data
>        serialized_props = self.broker.serialize(self.props)
>        msg = ('message', 1, 'root', 'convert', 1,
>               ['tuple', serialized_props], ['dictionary'])
>        io = cStringIO.StringIO()
>        self.broker._encode(msg, io.write)
>        self.chunk = io.getvalue()

This part isn't quite as good.  `broker.serialize` is basically an 
implementation detail.  `broker._encode` is *definitely* an 
implementation detail.  The exact structure of the message isn't quite 
an implementation detail, but it's such a low-level detail that you 
really don't want to be thinking about it while writing tests for 
something like Document.remote_convert.

Instead, you should probably use the PB client API (ie, PBClientFactory 
and what comes out of it) to interact with this server.  Let the PB 
client implementation figure out what bytes to "send" to your server.
>    def test_convert(self):
>        # data arrived
>        self.broker.dataReceived(self.chunk)

You'll definitely need to call dataReceived at some point.  Perhaps more 
than once.  So this is pretty good.
>        self.assertEqual(self.props, self.doc.props)
>----------
>
>However, `Document.remote_convert` is never executed so the test above
>fails. After debugging I discovered that `Broker._encode` produces
>different results depending on whether `self.broker.makeConnection(tr)` 
>is
>called or not.

I haven't bothered to look into what might be going on here, because I 
think you should forget about the `._encode` code and start using a 
client instead.  The client is much more likely to produce the correct 
network traffic to exercise the code you want to exercise.  The same 
goes for the two further tests below - which seem to differ only by a 
call to setPrefixLength, which should make absolutely no difference to 
this code when it is being used in practice, so the difference it makes 
in the tests is probably due to driving the code wrong, which problem 
will go away if you start using PBClientFactory etc.

Hope this helps,
Jean-Paul
>And I created a test case that shows a difference. Here `test_convert1`
>succeeds while `test_convert2` fails:
>----------
>class DocTestCase(unittest.TestCase):
>
>    def setUp(self):
>        self.doc = Document()
>        factory = pb.PBServerFactory(self.doc)
>        self.broker = factory.buildProtocol(('127.0.0.1', 0))
>
>        self.props = {'name': 'MyDoc',
>                      'path': '/path/'}
>
>        serialized_props = self.broker.serialize(self.props)
>        self.msg = ('message', 1, 'root', 'convert', 1,
>                    ['tuple', serialized_props], ['dictionary'])
>
>    def test_convert1(self):
>        self.broker.currentDialect = 'pb'
>        self.broker.setPrefixLimit(64)
>        self.broker.transport = proto_helpers.StringTransport()
>
>        io = cStringIO.StringIO()
>        self.broker._encode(self.msg, io.write)
>        self.broker.dataReceived(io.getvalue())
>
>        self.assertEqual(self.props, self.doc.props)
>
>    def test_convert2(self):
>        self.tr = proto_helpers.StringTransport()
>        self.broker.makeConnection(self.tr)
>
>        io = cStringIO.StringIO()
>        self.broker._encode(self.msg, io.write)
>        self.broker.dataReceived(io.getvalue())
>
>        self.assertEqual(self.props, self.doc.props)
>----------
>
>I wonder what causes this behavior and, in general, if 
>`StringTransport` is
>suitable for testing PB protocol.
>
>Thanks in advance. For your convenience I attached files with these 
>test
>cases.
>
>--
>with regards,
>Maxim



More information about the Twisted-Python mailing list