<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Jason R Briggs &#187; TDD</title>
	<atom:link href="http://www.briggs.net.nz/log/tag/tdd/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.briggs.net.nz/log</link>
	<description>Techie stuff from the perspective of a Kiwi abroad</description>
	<lastBuildDate>Mon, 28 Jun 2010 06:45:23 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>TDD, Jython &amp; REST (Part 3)</title>
		<link>http://www.briggs.net.nz/log/2005/10/02/tdd-jython-rest-part-3/</link>
		<comments>http://www.briggs.net.nz/log/2005/10/02/tdd-jython-rest-part-3/#comments</comments>
		<pubDate>Sun, 02 Oct 2005 15:27:00 +0000</pubDate>
		<dc:creator>jrbriggs</dc:creator>
				<category><![CDATA[technical]]></category>
		<category><![CDATA[jython]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[TDD]]></category>

		<guid isPermaLink="false">http://localhost/log/2005/10/02/tdd-jython-rest-part-3/</guid>
		<description><![CDATA[With the addition of xml validation and value checking, the testing scripts actually become somewhat useful: > PUT http://localhost:8080/ws/user '' 'username=testuser1&#38;email_address=testuser1@test.com' > GET http://localhost:8080/ws/user/testuser1 < assert status 200 < assert header Content-Type text/xml < assert xmlschema ../web/user.xsd < assert xmlbody /user/username=testuser1 < assert xmlbody /user/email_address=testuser1@test.com > DELETE http://localhost:8080/ws/user/testuser1 Depak Vohra&#8217;s article at ONJava, shows how [...]]]></description>
			<content:encoded><![CDATA[<p>With the addition of xml validation and value checking, the testing scripts actually become somewhat useful:</p>
<pre>
<code>
> PUT http://localhost:8080/ws/user ''
    'username=testuser1&amp;email_address=testuser1@test.com'
> GET http://localhost:8080/ws/user/testuser1
< assert status 200
< assert header Content-Type text/xml
< assert xmlschema ../web/user.xsd
< assert xmlbody /user/username=testuser1
< assert xmlbody /user/email_address=testuser1@test.com
> DELETE http://localhost:8080/ws/user/testuser1
</code>
</pre>
<p>Depak Vohra&#8217;s <a href="http://www.onjava.com/pub/a/onjava/2004/09/15/schema-validation.html">article</a> at ONJava, shows how to perform schema validation using xerces/jaxp.  Adapting his code to jython is straightforward, by adding the following imports to the test script from <a href="http://jasonrbriggs.blogspot.com/2005/09/tdd-jython-rest-part-2.html">part 2</a>:</p>
<pre>
<code>
from java.<span class="pyname">io</span> <span class="pykeyword">import</span> StringReader
from java.<span class="pyname">lang</span> <span class="pykeyword">import</span> System
from javax.<span class="pyname">xml</span>.<span class="pyname">parsers</span> <span class="pykeyword">import</span> DocumentBuilderFactory
from javax.<span class="pyname">xml</span>.<span class="pyname">parsers</span> <span class="pykeyword">import</span> DocumentBuilder
from org.<span class="pyname">xml</span>.<span class="pyname">sax</span>.<span class="pyname">helpers</span> <span class="pykeyword">import</span> DefaultHandler
from org.<span class="pyname">xml</span>.<span class="pyname">sax</span> <span class="pykeyword">import</span> InputSource

System.<span class="pyname">setProperty</span>(<span class="pystring">'javax.xml.parsers.DocumentBuilderFactory'</span>,
        <span class="pystring">'org.apache.xerces.jaxp.DocumentBuilderFactoryImpl'</span>)
</code>
</pre>
<p>Depak&#8217;s Validator becomes:</p>
<pre>
<code>
<span class="pykeyword">class</span> <span class="pyname">Validator</span>(DefaultHandler):
    <span class="pykeyword">def</span> <span class="pyname">__init__</span>(self):
        self.<span class="pyname">validationError</span> = <span class="pynumber">0</span>
        self.<span class="pyname">saxParseException</span> = None

    <span class="pykeyword">def</span> <span class="pyname">error</span>(self, exception):
        self.<span class="pyname">validationError</span> = <span class="pynumber">1</span>
        self.<span class="pyname">saxParseException</span> = exception

    <span class="pykeyword">def</span> <span class="pyname">fatalError</span>(self, exception):
        self.<span class="pyname">validationError</span> = <span class="pynumber">1</span>
        self.<span class="pyname">saxParseException</span> = exception      

    <span class="pykeyword">def</span> <span class="pyname">warning</span>(self, exception):
        pass
</code>
</pre>
<p>Finally, a new assertion method is added to the Test class:</p>
<pre>
<code>
    <span class="pycomment">#</span>
    <span class="pycomment"># assert that an xml document validates against an xml schema</span>
    <span class="pycomment">#</span>
    <span class="pykeyword">def</span> <span class="pyname">assert_xmlschema</span>(self, linearr, req, res):
        xml = res.<span class="pyname">getContent</span>()

        factory = DocumentBuilderFactory.<span class="pyname">newInstance</span>()
        factory.<span class="pyname">setNamespaceAware</span>(<span class="pynumber">1</span>)
        factory.<span class="pyname">setValidating</span>(<span class="pynumber">1</span>)
        factory.<span class="pyname">setAttribute</span>(<span class="pystring">'http://java.sun.com/xml/jaxp/properties/schemaLanguage'</span>,
                <span class="pystring">'http://www.w3.org/2001/XMLSchema'</span>)
        errhandler = Validator()
        factory.<span class="pyname">setAttribute</span>(<span class="pystring">'http://java.sun.com/xml/jaxp/properties/schemaSource'</span>,
            linearr[<span class="pynumber">0</span>])
        builder = factory.<span class="pyname">newDocumentBuilder</span>()
        builder.<span class="pyname">setErrorHandler</span>(errhandler)

        dom = builder.<span class="pyname">parse</span>(InputSource(StringReader(xml)))

        <span class="pykeyword">if</span> errhandler.<span class="pyname">validationError</span> == <span class="pynumber">1</span>:
            errmsg = <span class="pystring">'%s, %s'</span> % (<span class="pyfunction">str</span>(errhandler.<span class="pyname">validationError</span>),
                    errhandler.<span class="pyname">saxParseException</span>.<span class="pyname">getMessage</span>())
        <span class="pykeyword">else</span>:
            errmsg = <span class="pystring">''</span>

        assert errhandler.<span class="pyname">validationError</span> != <span class="pynumber">1</span>, errmsg
</code>
</pre>
<p>Value checking is accomplished with the following method:</p>
<pre>
<code>
    <span class="pycomment">#</span>
    <span class="pycomment"># assert that a particular xml element contains a particular value</span>
    <span class="pycomment">#</span>
    <span class="pykeyword">def</span> <span class="pyname">assert_xmlbody</span>(self, linearr, req, res):
        xml = res.<span class="pyname">getContent</span>()
        <span class="pykeyword">if</span> not self.<span class="pyname">xml</span> <span class="pykeyword">or</span> self.<span class="pyname">xml</span> != xml:
            factory = DocumentBuilderFactory.<span class="pyname">newInstance</span>()
            factory.<span class="pyname">setNamespaceAware</span>(<span class="pynumber">1</span>)
            builder = factory.<span class="pyname">newDocumentBuilder</span>()
            self.<span class="pyname">dom</span> = builder.<span class="pyname">parse</span>(InputSource(StringReader(xml)))
            self.<span class="pyname">xml</span> = xml

        <span class="pycomment"># get rid of the starting '/'</span>
        <span class="pycomment"># (could leave this out in the test, but it looks better in to me)</span>
        <span class="pykeyword">if</span> linearr[<span class="pynumber">0</span>].<span class="pyname">startswith</span>(<span class="pystring">'/'</span>):
            linearr[<span class="pynumber">0</span>] = linearr[<span class="pynumber">0</span>][<span class="pynumber">1</span>:]

        <span class="pycomment"># split into xml element list and a value based on the first '=' position</span>
        equalspos = linearr[<span class="pynumber">0</span>].<span class="pyname">find</span>(<span class="pystring">'='</span>)
        tmp = linearr[<span class="pynumber">0</span>][<span class="pynumber">0</span>:equalspos]
        value = linearr[<span class="pynumber">0</span>][equalspos+1:]
        elements = tmp.<span class="pyname">split</span>(<span class="pystring">'/'</span>)

        <span class="pycomment"># loop through the elements in the list and retrieve each named node</span>
        node = self.<span class="pyname">dom</span>
        <span class="pykeyword">for</span> x <span class="pykeyword">in</span> xrange(<span class="pynumber">0</span>, <span class="pyfunction">len</span>(elements)):
            nl = node.<span class="pyname">getElementsByTagName</span>(elements[x])
            <span class="pykeyword">if</span> not nl:
                raise AssertionError, <span class="pystring">'sub element "%s" is missing'</span> % elements[x]
            <span class="pykeyword">else</span>:
                node = nl.<span class="pyname">item</span>(<span class="pynumber">0</span>)
            <span class="pykeyword">if</span> x == <span class="pyfunction">len</span>(elements)-1:
                eval = node.<span class="pyname">getFirstChild</span>().<span class="pyname">getNodeValue</span>()
                assert eval == value, <span class="pystring">'element %s (value "%s") does not match expected %s'</span>
                            % (elements[x], eval, value)
                return
        raise AssertionError, <span class="pystring">'missing element %s'</span> % tmp
</code>
</pre>
<p>Which takes an assertion line such as &#8220;assert xmlbody /user/username=testuser1&#8243; and looks up elements in the dom, where &#8220;user&#8221; is the root node, and &#8220;username&#8221; is a child node of user.  At the moment this is very basic value checking which won&#8217;t work well for more complicated documents &#8212; but I&#8217;m trying to avoid complication as much as possible anyway, so it&#8217;s difficult to justify (to myself at least) going to greater lengths to make something more flexible.  The main enhancement I&#8217;m looking to make initially is the ability to specify the nth child of a node.  For example, something like the string &#8220;/list/user[2]/username&#8221; (in case it&#8217;s not painfully obvious, the second &#8220;user&#8221; element in &#8220;list&#8221;).</p>
<p>But for the moment, this will hopefully get me going.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.briggs.net.nz/log/2005/10/02/tdd-jython-rest-part-3/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>TDD, Jython &amp; REST (Part 2)</title>
		<link>http://www.briggs.net.nz/log/2005/09/28/tdd-jython-rest-part-2/</link>
		<comments>http://www.briggs.net.nz/log/2005/09/28/tdd-jython-rest-part-2/#comments</comments>
		<pubDate>Wed, 28 Sep 2005 17:28:00 +0000</pubDate>
		<dc:creator>jrbriggs</dc:creator>
				<category><![CDATA[technical]]></category>
		<category><![CDATA[jython]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[TDD]]></category>

		<guid isPermaLink="false">http://localhost/log/2005/09/28/tdd-jython-rest-part-2/</guid>
		<description><![CDATA[In part one of this rumination, I discussed test-driven-development of a REST-style application written using Jython. An example of a test method would be something like: def test_getvaliduser(): req = MockHttpServletRequest('GET', 'user', 'http://localhost:8080/ws/user/testuser1', '', '') req.setContentType('text/html') res = MockHttpServletResponse() user.doGet(req, res) assert res.getStatus() == 200, 'incorrect status %s (%s)' % (res.getStatus(), res.getReason()) assert res.getContentType().startswith('text/html'), 'expected [...]]]></description>
			<content:encoded><![CDATA[<p>In part <a href="http://jasonrbriggs.blogspot.com/2005/09/tdd-jython-rest.html">one</a> of this rumination, I discussed test-driven-development of a REST-style application written using Jython. An example of a test method would be something like:</p>
<pre>
<code>
<span class="pykeyword">def</span> <span class="pyname">test_getvaliduser</span>():
    req = MockHttpServletRequest(<span class="pystring">'GET'</span>, <span class="pystring">'user'</span>, <span class="pystring">'http://localhost:8080/ws/user/testuser1'</span>, <span class="pystring">''</span>, <span class="pystring">''</span>)
    req.<span class="pyname">setContentType</span>(<span class="pystring">'text/html'</span>)

    res = MockHttpServletResponse()

    user.<span class="pyname">doGet</span>(req, res)

    assert res.<span class="pyname">getStatus</span>() == <span class="pynumber">200</span>, <span class="pystring">'incorrect status %s (%s)' % (res.getStatus(), res.getReason())</span>
    assert res.<span class="pyname">getContentType</span>().<span class="pyname">startswith</span>(<span class="pystring">'text/html'</span>), <span class="pystring">'expected text/html, got %s'</span> % res.<span class="pyname">getContentType</span>()
</code>
</pre>
<p>The main problem I have with this idea, is the amount of coding (read: effort) required for each test method, and therefore the amount of duplicated effort testing each resource (servlet). Testing other components in this manner, or testing non-standard functionality, is another matter, of course.</p>
<p>I&#8217;ve come up with what I believe is a more workable solution.  Reducing the previous example to something like the following:</p>
<pre>
<code>
== Get Valid User
> GET http://localhost:8080/ws/user/testuser1
< assert status 200
</code>
</pre>
<p>Which is a serious reduction in the amount of typing required for a test, particularly when you add the setup functions (missing from the 'old' test example):</p>
<pre>
<code>
== Get Valid User
> PUT http://localhost:8080/ws/user '' &#92;
    'username=testuser1&amp;email_address=testuser1@test.com&amp;level=1&amp;password=password'
> GET http://localhost:8080/ws/user/testuser1
< assert status 200
< assert header Content-Type text/xml
> DELETE http://localhost:8080/ws/user/testuser1
</code>
</pre>
<p>Hopefully the script is relatively straightforward to understand: '==' signifies a new test, '&gt;' is an http call, and 'here</a>.</p>
<p>The next step is to add handlers for checking the content in the response (particularly xml), both in form and content.</p>
<p>(If anyone is interested in a working example, let me know and I'll endeavour to post something useable)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.briggs.net.nz/log/2005/09/28/tdd-jython-rest-part-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>TDD, Jython &amp; REST (Part 1)</title>
		<link>http://www.briggs.net.nz/log/2005/09/16/tdd-jython-rest-part-1/</link>
		<comments>http://www.briggs.net.nz/log/2005/09/16/tdd-jython-rest-part-1/#comments</comments>
		<pubDate>Fri, 16 Sep 2005 18:34:00 +0000</pubDate>
		<dc:creator>jrbriggs</dc:creator>
				<category><![CDATA[technical]]></category>
		<category><![CDATA[jython]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[TDD]]></category>

		<guid isPermaLink="false">http://localhost/log/2005/09/16/tdd-jython-rest-part-1/</guid>
		<description><![CDATA[I&#8217;ve been working on a personal project for a few months now (begun while living in Thailand for a few months), combining a number of different technologies I currently find interesting. I&#8217;ve chosen Jython as the programming language in recognition of how powerful I&#8217;ve found developing in Python &#8212; the reasons for not going completely [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been working on a personal project for a few months now (begun while living in Thailand for a few months), combining a number of different technologies I currently find interesting. I&#8217;ve chosen Jython as the programming language in recognition of how powerful I&#8217;ve found developing in Python &#8212; the reasons for not going completely down the Python route (much as I would like to be using Py 2.4 modules) are mainly because of library issues. In particular I haven&#8217;t found a templating library as good as <a href="http://jakarta.apache.org/velocity">Velocity</a> in the Pythonic world.  Of course there is  <a href="http://www.cheetahtemplate.org/">Cheetah</a>, but while it is as close to Velocity as it&#8217;s possible to be without &#8216;sleeping in the same bed&#8217;, there is still something about Cheetah that rubs me the wrong way. I suspect it&#8217;s simply because, as a long time Velocity user, I have become so accustomed to the techniques we use with that library, that slight changes/quirks in the other become larger issues, completely out of proportion to their actual magnitude.</p>
<p><a href="http://en.wikipedia.org/wiki/Representational_State_Transfer">REST</a> is less a technology than a way of thinking which I&#8217;m endeavouring to apply to both personal and professional projects. How successful that application is remains to be seen, but thinking about web services in terms of a resource with a simple set of verbs, has certainly resulted in a re-think about how one approaches component development. It&#8217;s difficult to justify a full J2EE deployment (EJBs and so on) when your operations are reduced to a basic four (GET, POST, PUT, DELETE) and the business logic fits comfortably within the framework provided by a servlet (in my case a Jython-Servlet).</p>
<p>Finally <a href="http://en.wikipedia.org/wiki/Test_driven_development">TDD</a> (which technically should&#8217;ve been mentioned first, considering where it falls in the development cycle), comes from the understanding that developing a project, which I hope to eventually turn into something commercially viable, requires more than just hacking together a bunch of parts in the hope that my frankenstein will live when electricity is applied to its extremities. I have to also consider the future of this system &#8212; have confidence in both my design and in any changes/refactoring I might make to it after it has a community of users.</p>
<p>TDD is at the crux of the code sample below.  This is just the beginnings of a testing system for my rest+jython servlets.</p>
<p>It may make more sense, to some, to reuse what&#8217;s already available (JUnit for example, or the unit-testing modules already included in Python/Jython), but this is as much a learning exercise, as serious development, since I haven&#8217;t yet had to opportunity to apply TDD in any professional projects.</p>
<p><span id="more-12"></span></p>
<pre>
<code>
<span class="pycomment">#! /usr/bin/env ../bin/jython</span>

<span class="pykeyword">import</span> base64
<span class="pykeyword">import</span> sys

<span class="pykeyword">def</span> <span class="pyname">authbasic</span>(username, password):
    return <span class="pystring">'Basic %s'</span> % base64.<span class="pyname">encodestring</span>(<span class="pystring">'%s:%s'</span> % (username, password))

global successtests
successtests = <span class="pynumber">0</span>
global failedtests
failedtests = <span class="pynumber">0</span>
global errortests
errortests = <span class="pynumber">0</span>

<span class="pykeyword">def</span> <span class="pyname">contains</span>(srclist, matchlist):
   <span class="pykeyword">for</span> i <span class="pykeyword">in</span> matchlist:
       <span class="pykeyword">if</span> i not <span class="pykeyword">in</span> srclist:
           return false
   return true

<span class="pycomment">#</span>
<span class="pycomment"># given a module name and a list of tests, run each test in the module</span>
<span class="pycomment">#</span>
<span class="pykeyword">def</span> <span class="pyname">runTests</span>(mod, testnames=None):
   global successtests
   global failedtests
   global errortests

   exec <span class="pystring">'import %s as testmod'</span> % mod
   <span class="pykeyword">if</span> hasattr(testmod, <span class="pystring">'setup'</span>):
       getattr(testmod, <span class="pystring">'setup'</span>)()

   <span class="pykeyword">for</span> name <span class="pykeyword">in</span> dir(testmod):
       <span class="pykeyword">if</span> name.<span class="pyname">startswith</span>(<span class="pystring">'test_'</span>):

           <span class="pykeyword">if</span> testnames:
               chkname = name[<span class="pynumber">5</span>:]
               <span class="pykeyword">if</span> chkname not <span class="pykeyword">in</span> testnames:
                   <span class="pykeyword">continue</span>

           try:
               try:
                   <span class="pycomment"># get the method from the module object</span>
                   testmethod = getattr(testmod, name)

                   <span class="pycomment"># run the test, and if successful increment our counter</span>
                   testmethod()
                   successtests = successtests + <span class="pynumber">1</span>
               except AssertionError, ae:
                   <span class="pycomment"># handle assertion failures</span>
                   failedtests = failedtests + <span class="pynumber">1</span>
                   print '[%s] failed because of "%s"' % (name, <span class="pyfunction">str</span>(ae))
           except Exception, e:
               <span class="pycomment"># handle errors that fall outside of the test boundaries</span>
               errortests = errortests + <span class="pynumber">1</span>
               print '[%s] failed because of "%s"' % (name, <span class="pyfunction">str</span>(e))
               (type, value, traceback) = sys.<span class="pyname">exc_info</span>()
               print traceback.<span class="pyname">dumpStack</span>()

   print <span class="pystring">'''%s tests succeeded</span>
%s tests failed assertions
%s tests failed because of errors''' % (successtests, failedtests, errortests)

<span class="pykeyword">if</span> __name__ == <span class="pystring">'__main__'</span>:
   <span class="pykeyword">if</span> <span class="pyfunction">len</span>(sys.<span class="pyname">argv</span>) == <span class="pynumber">2</span>:
       runTests(sys.<span class="pyname">argv</span>[<span class="pynumber">1</span>])
   <span class="pykeyword">elif</span> <span class="pyfunction">len</span>(sys.<span class="pyname">argv</span>) == <span class="pynumber">3</span>:
       runTests(sys.<span class="pyname">argv</span>[<span class="pynumber">1</span>], sys.<span class="pyname">argv</span>[<span class="pynumber">2</span>].<span class="pyname">split</span>(','))
</code>
</pre>
<p>NB:  I&#8217;ve hacked out some of the functionality required to set up that particular test (such as creating and deleting the user record), but you get the idea.</p>
<p>A sample scripting testing a valid user of a user management web service would look like this:</p>
<pre>
<code>
from wstesting <span class="pykeyword">import</span> MockServletContext, MockServletConfig, MockHttpServletRequest, MockHttpServletResponse

<span class="pykeyword">import</span> miscutils
<span class="pykeyword">import</span> test

<span class="pykeyword">import</span> props

global user
user = None

<span class="pykeyword">def</span> <span class="pyname">setup</span>():
   <span class="pycomment"># change the database connect string to use the test database</span>
   props.<span class="pyname">db_connect_string</span> = <span class="pystring">'jdbc:postgresql:testdb'</span></span>

   <span class="pycomment"># dynamically load the module</span>
   usermod = miscutils.<span class="pyname">loadmod</span>('.<span class="pyname">.</span>/web',<span class="pystring">'user.py'</span>)

   <span class="pycomment"># create a servlet context for testing with</span>
   ctx = MockServletContext.<span class="pyname">getInstance</span>().<span class="pyname">setRootPath</span>('.<span class="pyname">.</span>/web')

   <span class="pycomment"># load the servlet and then initialise with the config object</span>
   global user
   user = usermod.<span class="pyname">user</span>()
   user.<span class="pyname">init</span>(MockServletConfig(<span class="pystring">'user'</span>))

<span class="pykeyword">def</span> <span class="pyname">test_getvaliduser</span>():
   req = MockHttpServletRequest(<span class="pystring">'GET'</span>, <span class="pystring">'user'</span>, <span class="pystring">'http://localhost:8080/ws/user/testuser1'</span>, <span class="pystring">''</span>, <span class="pystring">''</span>)
   req.<span class="pyname">setContentType</span>(<span class="pystring">'text/html'</span>)

   res = MockHttpServletResponse()

   user.<span class="pyname">doGet</span>(req, res)

   assert res.<span class="pyname">getStatus</span>() == <span class="pynumber">200</span>, <span class="pystring">'incorrect status %s (%s)' % (res.getStatus(), res.getReason())</span>
   assert res.<span class="pyname">getContentType</span>().<span class="pyname">startswith</span>(<span class="pystring">'text/html'</span>), <span class="pystring">'expected text/html, got %s'</span> % res.<span class="pyname">getContentType</span>()
</code>
</pre>
<p>So that&#8217;s the basic, lots of typing approach to TDD for my REST app. Next step is to completely scrap that structure, and do it the right way (i.e. a method that doesn&#8217;t involve writing the similar code over and over again for each service). More on that later&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.briggs.net.nz/log/2005/09/16/tdd-jython-rest-part-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
