[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