[Twisted-Python] Howto: use Trial to unit test an XML-RPC server
Don Dwiggins
ddwiggins at advpubtech.com
Fri Apr 18 13:36:26 MDT 2008
I'm offering the following as an experience report and a draft of a
howto article:
-----------------------------
I've been developing an XML-RPC server using Twisted, and unit testing
it with the distribution unittest, with each test connecting as a client
and exercising a particular method. I ran into a problem, however:
My server is essentially a special-purpose front end to a database,
providing limited access to it. Some of the server methods modify the
database (I'm using adbapi for DB access). For unit testing purposes, I
want each test to leave the test database unchanged when it's done. In
unit testing stored procedures, for example, I create a connection in
SetUp, use it in the test function to send a SQL "exec", and do a
rollback in TearDown. Nice and easy, since I'm using the same
connection throughout. In testing my server, however, I don't have
access to the connection it used to access the database (and in fact
shouldn't have, since my unit test functions are just clients to the
server, and know nothing of the database itself).
This led me to the following approach, using Twisted's Trial extension
of unittest. I converted my unittest module to be run under Trial, as
follows: rather than running the server, the test module imports the
server module, giving it access to the XMLRPC class itself, and the
ability to directly call its methods. Since the server's methods return
Deferreds, it's easy enough to call a method, then attach a callback to
it that does the checking of its results. Here's an example:
-------------
import MyServer
class MyServerTests(unittest.TestCase)
def setUp(self):
# Instantiate object to be tested here
self.srvr = MyServer.XMLRPCServer()
def testFrobulate(self):
d = self.srvr.xmlrpc_frobulate(theFrobulatee)
def checkResult(info):
# Test that the frobulation occurred correctly
pass
d.addCallback(checkFrobulation)
def checkFrobulation(self, resultOfFrobulation):
# Test whether it turned out OK
--------------
This works beautifully when frobulate doesn't modify the database; when
it does, however, I have the same problem as before: the actual
connection used is hidden in the guts of adbapi.
For this case, I changed the coding a bit to allow testing. Rather than
ConnectionPool.runQuery(), I use .runInteraction(), passing a function
that expects a DBAPI cursor as its first argument (when running in the
server, the function will be called in the context of a subthread). The
code in the server module then looks like this (I'm running on Py 2.4):
-------------
@defer.deferredGenerator
def xmlrpc_frobulate(theVictim):
frobInProgress = defer.waitForDeferred(
self._dbpool.runInteraction(
self.frobulateInteraction, theVictim) )
yield frobInProgress
didItWork = frobInProgress.getResult()
yield didItWork
return
def frobulateInteraction(self, cursor, theVictim):
cursor.execute("exec FrobulateOn " + theVictim)
# Check the results, return True or False
--------------
Now, in the unit test, I can call self.srvr.frobulateInteraction like this:
-------------
def testFrobulate(self):
self.cursor = self.connection.cursor()
d = self.srvr.frobulateInteraction(self.cursor,
self.theFrobulatee)
def checkResult(info):
# Test that the frobulation occurred correctly
pass
d.addCallback(checkFrobulation)
-------------
Now, since frobulateInteraction is using the connection from SetUp, the
rollback in TearDown will restore the state of the DB.
In effect, by going "under the covers" of the server, I'm bypassing the
parts of the server that are supplied by Twisted, and focusing on the
code that I've written, which is exactly what I wanted to test.
Feedback solicited...
--
Don Dwiggins
Advanced Publishing Technology
More information about the Twisted-Python
mailing list