[Twisted-Python] Testing AMP-based code

exarkun at twistedmatrix.com exarkun at twistedmatrix.com
Wed Jun 12 17:16:12 MDT 2013


On 5 Jun, 07:13 pm, free at 64studio.com wrote:
>Hi,
>
>following up from ticket #6502, I'm looking for recommendations/best
>practices for writing unit-tests for AMP-based code.
>
>As described in the ticket, the issue I'm currently facing is that the
>AMP implementation is subtly not re-entrant safe and doesn't work with 
>a
>synchronous transport, for example this code raises an exception:
>
>http://twistedmatrix.com/trac/attachment/ticket/6502/example.py
>
>I'm starting to think that the most appropriate testing strategy would
>be to mock/stub AMP.callRemote (or the protocol class altogether)
>instead of trying to use a fake transport.
>
>Thoughts?

I think this is thinking in the right direction.  Twisted generally 
tries to be responsible for testing its own code, and the serialization 
from commands to bytes (and the reverse) that AMP does is part of 
Twisted, so you should really be free from the burden of testing that 
that stuff works.

One thing it's worth noticing is that the AMP class itself contains very 
little code.  Instead, it inherits most functionality from a few base 
classes.  It's worth learning about the division of responsibility 
between these classes because they can be helpful in writing cleaner AMP 
code and - relevant to this topic - writing AMP unit tests.

One of the base classes, BinaryBoxProtocol, is almost entirely concerned 
with serialization logic.  You can probably ignore this one entirely to 
begin with (although consider the consequences of 
serialization/deserialization living in this one class, independent of 
the rest of the protocol logic: perhaps you want to exchange AMP 
commands with a web browser and would find JSON an easier format to work 
with than AMP's int16 string scheme... etc).

Next, BoxDispatcher.  This one is what actually implements `callRemote` 
and the reverse - ampBoxReceived, turning an AMP box (already parsed), 
into an incoming method call or the result of a previous outgoing method 
call.  It operates on locator (to look up how to handle incoming method 
calls) and a box sender (to send out boxes representing method calls or 
responses).  It doesn't know about the network, so your box sender can 
be a purely in-memory thing, implementing some box handling logic purely 
as Python code and no I/O.

As far as the locator goes, if you want the standard 
`@SomeCommand.responder` functionality, then you can easily get this by 
re-using the next base class of AMP, CommandLocator.  This one's pretty 
straight-forward: subclass it and those decorators will work for you.

Ignore the last one, SimpleStringLocator, it's for extremely old-style 
AMP code that no one should be writing anymore.

So this all means that your application logic can all live on a 
CommandLocator subclass.  When you really want to put this on an AMP 
server, you can hook an AMP instance up to your CommandLocator subclass 
(AMP takes a locator as an __init__ argument).  When you want to test 
your command implementations, you can hook the CommandLocator up to a 
BoxDispatcher and a box sender and throw boxes straight at it with no 
network interation.

Some pieces are probably still missing from the public API - for 
example, you do want to test that your objects all get properly 
serialized and deserialized through AMP, particularly if you're 
implementing custom Argument types.  There are some private APIs, 
_objectsToStrings and _stringsToObjects mostly, that really help with 
testing this, and we should think about how to expose this functionality 
publically.  Also, we should document this whole pile of stuff.  Maybe 
you'd be interested in writing something up after you've had a chance to 
play with these ideas?

Jean-Paul




More information about the Twisted-Python mailing list