Skip to main content

NoseGAE: nose plugin for Google App Engine testing

Project description

<div class="document" id="nosegae-test-support-for-google-application-engine">
<h1 class="title">NoseGAE: Test support for Google Application Engine</h1>
<div class="contents topic">
<p class="topic-title first"><a id="contents" name="contents">Contents</a></p>
<ul class="simple">
<li><a class="reference" href="#overview" id="id1" name="id1">Overview</a></li>
<li><a class="reference" href="#what-does-it-do" id="id2" name="id2">What does
it do?</a><ul>
<li><a class="reference" href="#functional-tests" id="id3" name="id3">Functional
tests</a></li>
<li><a class="reference" href="#unit-tests" id="id4" name="id4">Unit tests</a></li>
</ul>
</li>
<li><a class="reference" href="#realism-in-testing" id="id5" name="id5">Realism
in testing</a></li>
</ul>
</div>
<div class="section">
<h1><a class="toc-backref" href="#id1" id="overview"
name="overview">Overview</a></h1>
<p>NoseGAE is a <a class="reference"
href="http://somethingaboutorange.com/mrl/projects/nose/">nose</a> plugin that
makes it easier to write functional and unit
tests for <a class="reference" href="http://code.google.com/appengine/">Google
App Engine</a> applications.</p>
<p>When the plugin is installed, you can activate it by using the
<tt class="docutils literal"><span class="pre">--with-gae</span></tt> command
line option. The plugin also includes an option
for setting the path to the Google App Engine python library, if it is
not in the standard location of <tt class="docutils literal"><span
class="pre">/usr/local/google_appengine</span></tt>.</p>
</div>
<div class="section">
<h1><a class="toc-backref" href="#id2" id="what-does-it-do"
name="what-does-it-do">What does it do?</a></h1>
<div class="section">
<h2><a class="toc-backref" href="#id3" id="functional-tests"
name="functional-tests">Functional tests</a></h2>
<p>The plugin sets up the GAE development environment before your test
run. This means that you can easily write functional tests for your
application without having to actually start the dev server and test
over http. <strong>Note</strong> however that this kind of testing requires that
your application be a <a class="reference"
href="http://www.wsgi.org/wsgi">wsgi</a> application.</p>
<p>Consider a simple hello world wsgi application:</p>
<pre class="literal-block">
import wsgiref.handlers
from google.appengine.ext import webapp

class Hello(webapp.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write('Hello world!')

def application():
return webapp.WSGIApplication([('/', Hello)], debug=True)

def main():
wsgiref.handlers.CGIHandler().run(application())

if __name__ == '__main__':
main()

</pre>
<p>And a simple functional test suite for the application:</p>
<pre class="literal-block">
from webtest import TestApp
from helloworld import application

app = TestApp(application())

def test_index():
response = app.get('/')
assert 'Hello world!' in str(response)

</pre>
<p>The important part to note is the <cite>application()</cite> function that
returns the application. That function provides a way get the
application under test and call it directly, without having to pass
through the dev app server. And that's all you need to do for basic
functional testing.</p>
<blockquote>
<pre class="doctest-block">
&gt;&gt;&gt; import os
&gt;&gt;&gt; support = os.path.join(os.path.dirname(os.path.realpath(__file__)),
... 'support')
&gt;&gt;&gt; hello_app = os.path.join(support, 'helloworld')
&gt;&gt;&gt; from nose.plugins.plugintest import run
&gt;&gt;&gt; from nosegae import NoseGAE
&gt;&gt;&gt; run(argv=['nosegae', '--with-gae', '--gae-application', hello_app,
... '-v', hello_app], plugins=[NoseGAE()])
test.test_index ... ok
&lt;BLANKLINE&gt;
----------------------------------------------------------------------
Ran 1 test in ...s
&lt;BLANKLINE&gt;
OK
</pre>
</blockquote>
</div>
<div class="section">
<h2><a class="toc-backref" href="#id4" id="unit-tests" name="unit-tests">Unit
tests</a></h2>
<p>Functional tests are only one kind of test, of course. What if you
want to write unit tests for your data models? Normally, you can't use
your models at all outside of the dev environment, because the Google
App Engine datastore isn't available. However, since the NoseGAE
plugin sets up the development environment around your test run, you
can use models directly in your tests.</p>
<p>Consider a simple models file that includes some doctests:</p>
<pre class="literal-block">
from google.appengine.ext import db

class Pet(db.Model):
&quot;&quot;&quot;
The Pet class provides storage for pets. You can create a pet:

&gt;&gt;&gt; muffy = Pet(name=u'muffy', type=u'dog', breed=u&quot;Shi'Tzu&quot;)
&gt;&gt;&gt; muffy # doctest: +ELLIPSIS
Pet(name=u'muffy', type=u'dog', breed=u&quot;Shi'Tzu&quot;, ...)
&gt;&gt;&gt; muffy_key = muffy.put()

Once created, you can load a pet by its key:

&gt;&gt;&gt; Pet.get(muffy_key) # doctest: +ELLIPSIS
Pet(name=u'muffy', type=u'dog', breed=u&quot;Shi'Tzu&quot;, ...)

Or by a query that selects the pet:

&gt;&gt;&gt; list(Pet.all().filter('type = ', 'dog')) # doctest: +ELLIPSIS
[Pet(name=u'muffy', ...)]

To modify a pet, change one of its properties and ``put()`` it again.

&gt;&gt;&gt; muffy_2 = _[0]
&gt;&gt;&gt; muffy_2.age = 10
&gt;&gt;&gt; muffy_key_2 = muffy_2.put()

The pet's key doesn't change when it is updated.

&gt;&gt;&gt; bool(muffy_key == muffy_key_2)
True
&quot;&quot;&quot;
name = db.StringProperty(required=True)
type = db.StringProperty(required=True,
choices=set([&quot;cat&quot;, &quot;dog&quot;,
&quot;bird&quot;,
&quot;fish&quot;, &quot;monkey&quot;]))
breed = db.StringProperty()
age = db.IntegerProperty()
comments = db.TextProperty()
created = db.DateTimeProperty(auto_now_add=True, required=True)

def __repr__(self):
return (&quot;Pet(name=%r, type=%r, breed=%r, age=%r, &quot;
&quot;comments=%r, created=%r)&quot; %
(self.name, self.type, self.breed, self.age,
self.comments, self.created))

</pre>
<p>Without NoseGAE, the doctests fail. (To see this we have to run nose
in a new process, since we've already loaded the GAE environment in
this one).</p>
<blockquote>
<pre class="doctest-block">
&gt;&gt;&gt; pets_app = os.path.join(support, 'pets')
&gt;&gt;&gt; from subprocess import Popen, PIPE
&gt;&gt;&gt; print Popen(
... [&quot;nosetests&quot;, &quot;-v&quot;, &quot;--with-doctest&quot;,
pets_app],
... stderr=PIPE).stderr.read() # doctest: +IGNORE_EXCEPTION_DETAIL +ELLIPSIS
+REPORT_NDIFF
Failure: ImportError (No module named google.appengine.ext) ... ERROR
&lt;BLANKLINE&gt;
======================================================================
ERROR: Failure: ImportError (No module named google.appengine.ext)
----------------------------------------------------------------------
Traceback (most recent call last):
...
ImportError: No module named google.appengine.ext
&lt;BLANKLINE&gt;
----------------------------------------------------------------------
Ran 1 test in 0...s
&lt;BLANKLINE&gt;
FAILED (errors=1)
&lt;BLANKLINE&gt;
</pre>
</blockquote>
<p>With NoseGAE, they pass.</p>
<blockquote>
<pre class="doctest-block">
&gt;&gt;&gt; from nose.plugins.doctests import Doctest
&gt;&gt;&gt; run(argv=['nosetests', '-v', '--with-doctest',
... '--with-gae', '--gae-application', pets_app, pets_app],
... plugins=[Doctest(), NoseGAE()])
Doctest: models.Pet ... ok
&lt;BLANKLINE&gt;
----------------------------------------------------------------------
Ran 1 test in ...s
&lt;BLANKLINE&gt;
OK
</pre>
</blockquote>
</div>
</div>
<div class="section">
<h1><a class="toc-backref" href="#id5" id="realism-in-testing"
name="realism-in-testing">Realism in testing</a></h1>
<p>Besides the dev appserver and the datastore, the main sticking point
for testing Google App Engine applications is the highly restrictive
runtime environment. When you test without NoseGAE, tests that should
fail (because the tested code <strong>will fail</strong> when run inside the Google
App Engine) may pass.</p>
<p>For instance, consider an app that uses the <cite>socket</cite> module, like
this one:</p>
<pre class="literal-block">
import socket
import wsgiref.handlers

class App:
def __call__(self, environ, start_response):
# This won't work under GAE, since this is app code
here = socket.gethostbyname('localhost')
start_response('200 OK', [('Content-type', 'text/plain')])
return ['Hello %s' % here]

def application():
return App()

def main():
wsgiref.handlers.CGIHandler().run(application())

if __name__ == '__main__':
main()

</pre>
<p>With a simple functional test:</p>
<pre class="literal-block">
from webtest import TestApp
from bad_app import application
import socket

app = TestApp(application())

def test_index_calls_gethostbyname():
# this works, because in test code GAE sandbox is not active
host = socket.gethostbyname('localhost')
response = app.get('/')
assert 'Hello' in str(response)
assert host in str(response)

</pre>
<p>This test will pass when run outside of the Google App Engine
environment. (Again, we have to run this in a new process, since this process
had already been set up for GAE).</p>
<blockquote>
<pre class="doctest-block">
&gt;&gt;&gt; bad_app = os.path.join(support, 'bad_app')
&gt;&gt;&gt; print Popen(
... [&quot;nosetests&quot;, &quot;-v&quot;, bad_app],
... stderr=PIPE).stderr.read() # doctest: +ELLIPSIS +REPORT_NDIFF
test.test_index_calls_gethostbyname ... ok
&lt;BLANKLINE&gt;
----------------------------------------------------------------------
Ran 1 test in ...s
&lt;BLANKLINE&gt;
OK
&lt;BLANKLINE&gt;
</pre>
</blockquote>
<p>When run with NoseGAE, it will fail, as it should.</p>
<blockquote>
<pre class="doctest-block">
&gt;&gt;&gt; run(argv=['nosetests', '-v', '--with-gae',
... '--gae-application', bad_app, bad_app],
... plugins=[NoseGAE()])
test.test_index_calls_gethostbyname ... ERROR
&lt;BLANKLINE&gt;
======================================================================
ERROR: test.test_index_calls_gethostbyname
----------------------------------------------------------------------
Traceback (most recent call last):
...
AttributeError: 'module' object has no attribute 'gethostbyname'
&lt;BLANKLINE&gt;
----------------------------------------------------------------------
Ran 1 test in ...s
&lt;BLANKLINE&gt;
FAILED (errors=1)
</pre>
</blockquote>
<p>It is important to note that only <strong>application</strong> code is
sandboxed by
NoseGAE. Test code imports outside of the sandbox, so your test code has full
access to the system and available python libraries, including the Google App
Engine datastore and other Google App Engine libraries.</p>
<p>For this reason, <strong>file access is not restricted</strong> in the same
way as it
is under GAE, because it is impossible to differentiate application code file
access from test code file access.</p>
</div>
</div>

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

NoseGAE-0.1.tar.gz (10.5 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page