views:

832

answers:

4

I'm about to embark on some large Python-based App Engine projects, and I think I should check with Stack Overflow's "wisdom of crowds" before committing to a unit-testing strategy. I have an existing unit-testing framework (based on unittest with custom runners and extensions) that I want to use, so anything "heavy-weight"/"intrusive" such as nose, webtest, or gaeunit doesn't seem appropriate. The crucial unit tests in my worldview are extremely lightweight and fast ones, ones that run in an extremely short time, so I can keep running them over and over all the time without breaking my development rhythm (e.g., for a different project, I get 97% or so coverage for a 20K-lines project with several dozens of super-fast tests that take 5-7 seconds, elapsed time, for a typical run, overall -- that's what I consider a decent suite of small, fast unit-tests). I'll have richer/heavier tests as well of course, all the way to integration tests with selenium or windmill, that's not what I'm asking about;-) -- my focus in this question (and in most of my development endeavors;-) is on the small, lightweight unit-tests that lightly and super-rapidly cover my code, not on the deeper ones.

So I think what I need is essentially a set of small, very lightweight simulations of the various key App Engine subsystems -- data store, memcache, request/response objects and calls to webapp handlers, user handling, mail, &c, roughly in this order of priority. I haven't found exactly what I'm looking for, so it seems to me that I should either rely on mox, as I've done often in the past, which basically means mocking each subsystem used in a given test and setting up all expectations &c (strong, but lots of work each time, and very sensitive to the tested-code's internals, i.e. very "white-box"y), or rolling my own simulation of each subsystem (and doing asserts on the simulated subsystems' states as part of the unit tests). The latters seems feasible, given GAE's Python-side strong "stubs" architecture... but I can't believe I need to roll my own, i.e., that nobody's already written such simple-minded simulators!-) E.g., for the datastore, it looks like what I need is more or less the "datastore on file" stub that's already part of the SDK, plus a way to mark it readonly and easy-to-use accessors for assertions about the datastore's state; and so forth, subsystem by subsystem -- each seems to need "just a bit more" than what's already in the SDK, "perched on top" of the existing "stubs" architecture.

So, before diving in and spending a day or two of precious development time "rolling my own" simulations of GAE subsystems for unit testing purposes, I thought I'd double check with the SO crowd and see what y'all think of this... or, if there's already some existing open source set of such simulators that I can simply reuse (or minimally tweak!-), and which I've just failed to spot in my searching!-)

Edit: to clarify, if I do roll my own, I do plan to leverage the SDK-supplied stubs where feasible; but for example there's no stub for a datastore that gets initially read in from a file but then not saved at the end, so I need to subclass and tweak the existing one (which also doesn't offer particularly convenient ways to do asserts on its state -- same for the mail service stub, etc). That's what I mean by "rolling my own" -- not "rewriting from scratch"!-)

Edit: "why not GAEUnit" -- GAEUnit is nice for its own use cases, but running dev_appserver and seeing results in my browser (or even via urllib.urlopen) is definitely not what I'm after -- I want to use a fully automated setup, suitable for running within an existing test-running framework which is based on extending unittest, and no HTTP in the way (said framework defines a "fast" test as one that among other thing does no sockets and minimal disk I/O -- we simulate or mock these -- so via gaeunit I could do no better than "medium" tests) + no convenient way to prepopulate datastore for each test (and no OO structure to help customize things).

+4  A: 

I use GAEUnit for my Google App Engine App and I am quite happy with the speed of the tests. The thing that I like about GAEUnit,and I am sure Webtest does it, is that it creates its own version for stubs of everything for testing leaving your "live" versions alone for testing.

So your datastore that you may be using for development will be left as is when you run your GAETests.

AutomatedTester
Running dev_appserver and seeing results in my browser (or even via urllib.urlopen) is definitely not what I'm after -- I want to use a fully automated setup, suitable for running within an existing test-running framework which is based on extending unittest, and no HTTP in the way (said framework defines a "fast" test as one that among other thing does no sockets and minimal disk I/O -- we simulate or mock these -- so via gaeunit I could do no better than "medium" tests) + no convenient way to prepopulate datastore for each test (and no OO structure to help customize things).
Alex Martelli
Still it's a nice framework for other needs (medium-plus semi-integration tests which **do** want HTTP roundtrips, but not the full weight of, say, windmill), so, thanks, and a +1;-)
Alex Martelli
+8  A: 

You don't need to write your own stubs - the SDK includes them, since they're what it uses to emulate the production APIs. Not all of them are suitable for use in unit-tests, but most are. Check out this code for an example of the setup/teardown code you need to make use of the built in stubs.

Nick Johnson
Sure, I do plan to leverage the SDK-supplied stubs where feasible; but for example there's no stub for a datastore that gets initially read in from a file but then not saved at the end, so I need to subclass and tweak the existing one (which also doesn't offer particularly convenient ways to do asserts on its state -- same for the mail service stub, etc). That's what I mean by "rolling my own" (let me edit the question to add this clarification!).
Alex Martelli
Still, the code you point to is a nice example of how to organize the stubbing (and nicely OO for easy further customization), therefore definitely thanks, and +1!-)
Alex Martelli
For the former, you could erase the datastore file after the test, or copy it before the test. Or if you just need it for initial data, populate the datastore in your setup function. Asserting on the datastore's state is simply a case of running queries and checking the results - but yes, other stubs are less friendly, hence my comment about them not all being suitable for unit tests.
Nick Johnson
+3  A: 

NoseGAE is a nose plugin that support unittests by automatically setting up the development environment and a test datastore for you. Very useful when developing on dev_appserver.

Amadeus
A: 

I might also add that Fixture has been very useful in my unit tests. It lets you create models in a declarative syntax, which it converts into stored entities that you can load in your tests. This way you have the same data set at the beginning of every test case!, which saves you from having to create data by hand at the start of every test. Here is an example, from the Fixture documentation: Given this model:

from google.appengine.ext import db

class Entry(db.Model):
    title = db.StringProperty()
    body = db.TextProperty()
    added_on = db.DateTimeProperty(auto_now_add=True)

Your fixture would look like this:

from fixture import DataSet

class EntryData(DataSet):
    class great_monday:
        title = "Monday Was Great"
        body = """\
Monday was the best day ever.
"""

Note however, that I ran into the following issues: 1. This bug, but the included patch does remedy it. 2. The datastore is not -by default- reset between test cases. So I use this to force a reset for each test case:

class TycoonTest(unittest.TestCase):
    def setUp(self):
        # Clear out the datastore before starting the test.
        apiproxy_stub_map.apiproxy._APIProxyStubMap__stub_map['datastore_v3'].Clear()    
        self.data = self.load_data()
        self.data.setup()
        os.environ['SERVER_NAME'] = "dev_appserver"
        self.after_setUp()

    def load_data(self):
        return datafixture.data(*dset.__all__)

    def after_setUp(self):
        """ After setup
        """
        pass

    def tearDown(self):
        # Teardown data.
        try:
            self.data.teardown()
        except:
            pass
mahmoud
Hmm. Not sure why this was downvoted.
mahmoud