[Twisted-Python] Howto: use Trial to unit test an XML-RPC server

Drew Smathers drew.smathers at gmail.com
Fri Apr 18 14:19:22 MDT 2008


On Fri, Apr 18, 2008 at 3:36 PM, Don Dwiggins <ddwiggins at advpubtech.com> wrote:
> 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
>
>
>  _______________________________________________
>  Twisted-Python mailing list
>  Twisted-Python at twistedmatrix.com
>  http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
>

One question: are you really not returning each Deferred from your
test methods, or was this a typo in the above?

-- 
\\\\\/\"/\\\\\\\\\\\
\\\\/ // //\/\\\\\\\
\\\/ \\// /\ \/\\\\
\\/ /\/ / /\/ /\ \\\
\/ / /\/ /\ /\\\ \\
/ /\\\ /\\\ \\\\\/\
\/\\\\\/\\\\\/\\\\\\
 d.p.s




More information about the Twisted-Python mailing list