Fixture + GoogleAppEngine

fixtureをGoogleAppEngineのDataStoreテストで使う。json編

参考 Using Fixture To Test A Google App Engine Site

DataSetsをjson化するのにfixture.dataset.converterでdataset_to_jsonが提供されていたりもする。ただdate_created(auto_now_add=True)のようなフィールドはDataSetsで前もって値を指定できないので、ここでjson化したデータを、実際(date_createdの付いた)のjsonのレスポンスと比較しても同じにはならない。

.zshrc

gaedir=/usr/local/google_appengine
if [ -d $gaedir ] ; then ;
    export PATH=${PATH}:$gaedir
    export PYTHONPATH=${PYTHONPATH}:${gaedir}:${gaedir}/lib/antlr3:\
${gaedir}/lib/cacerts:${gaedir}/lib/django:${gaedir}/lib/ipaddr:\
${gaedir}/lib/webob:${gaedir}/lib/yaml/lib
fi;
$ source ~/.zshrc
$ pip install WebTest
$ pip install nose
$ pip install NoseGAE

app.yaml

application: sampleblog # _ はapplicationの識別子として使えないので注意
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: blog.py

blog.py

#coding:utf-8
import logging
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
from django.utils import simplejson


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


class Comment(db.Model):
    entry = db.ReferenceProperty(Entry)
    comment = db.TextProperty()
    date_created = db.DateTimeProperty(auto_now_add=True)


class EntriesHandler(webapp.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'application/json;charset=utf-8'
        entries = []
        for entry in Entry.all():
            comments = []
            for comment in Comment.all().filter("entry =", entry):
                comment_dict = \
                    dict(comment=comment.comment,
                         date_created=comment.date_created.isoformat())
                comments.append(comment_dict)
            entry_dict = dict(title=entry.title,
                              body=entry.body,
                              date_created=entry.date_created.isoformat(),
                              comments=comments)
            entries.append(entry_dict)
        json = simplejson.dumps(entries, indent=True)
        #logging.info(json)
        self.response.out.write(json)


routing = [('/entries', EntriesHandler)]

application = webapp.WSGIApplication(
        routing,
        debug=True)


def main():
    run_wsgi_app(application)

if __name__ == '__main__':
    main()

tests/datasets.py (http://fixture.googlecode.com/hg/fixture/examples/google_appengine_example/tests/datasets.py)

from fixture import DataSet


class EntryData(DataSet):
    class great_monday:
        title = "Monday Was Great"
        body = """\
Monday was the best day ever.  I got up (a little late, but that's OK) then I ground some coffee.
Mmmm ... coffee!  I love coffee.  Do you know about
<a href="http://www.metropoliscoffee.com/">Metropolis</a> coffee?  It's amazing.  Delicious.
I drank a beautiful cup of french pressed
<a href="http://www.metropoliscoffee.com/shop/coffee/blends.php">Spice Island</a>, had a shave
and went to work.  What a day!
"""


class CommentData(DataSet):

    class monday_liked_it:
        entry = EntryData.great_monday
        comment = """\
I'm so glad you have a blog because I want to know what you are doing everyday.  Heh, that sounds
creepy.  What I mean is it's so COOL that you had a great Monday.  I like Mondays too.
"""

    class monday_sucked:
        entry = EntryData.great_monday
        comment = """\
Are you serious?  Mannnnnn, Monday really sucked.
"""

load_data_locally.py
(http://fixture.googlecode.com/hg/fixture/examples/google_appengine_example/load_data_locally.py)一部修正

import sys
import os
import optparse
from fixture import GoogleDatastoreFixture
from fixture.style import NamedDataStyle


def main():
    p = optparse.OptionParser(usage="%prog [options]")
    default = "/tmp/dev_appserver.datastore"
    p.add_option("--datastore_path", default=default, help=(
            "Path to datastore file.  This must match the value used for "
            "the same option when running dev_appserver.py if you want to view the data.  "
            "Default: %s" % default))
    default = "/tmp/dev_appserver.datastore.history"
    p.add_option("--history_path", default=default, help=(
            "Path to datastore history file.  This doesn't need to match the one you use for "
            "dev_appserver.py.  Default: %s" % default))
    default = "/usr/local/google_appengine"
    p.add_option("--google_path", default=default, help=(
            "Path to google module directory.  Default: %s" % default))
    (options, args) = p.parse_args()

    if not os.path.exists(options.google_path):
        p.error("Could not find google module path at %s.  You'll need to specify the path" % options.google_path)

    groot = options.google_path
    sys.path.append(groot)
    sys.path.append(os.path.join(groot, "lib/django"))
    sys.path.append(os.path.join(groot, "lib/webob"))
    sys.path.append(os.path.join(groot, "lib/yaml/lib"))

    from google.appengine.tools import dev_appserver
    import blog
    from tests import datasets

    config, explicit_matcher = dev_appserver.\
        LoadAppConfig(os.path.dirname(__file__), {})
    dev_appserver.SetupStubs(
        config.application,
        clear_datastore=False,  # just removes the files when True
        datastore_path=options.datastore_path,
        history_path=options.history_path,
        blobstore_path=None,  # 追加 KeyError: 'blobstore_path' を避ける
        login_url=None)

    datafixture = GoogleDatastoreFixture(env={'EntryData': blog.Entry,
                                              'CommentData': blog.Comment})

    data = datafixture.data(datasets.CommentData, datasets.EntryData)
    data.setup()
    print "Data loaded into datastore %s" % \
        (options.datastore_path or "[default]")

if __name__ == '__main__':
    main()

tests/test_entries.py

#coding:utf-8
import unittest
from fixture import GoogleDatastoreFixture
from webtest import TestApp
import blog
from datasets import CommentData, EntryData
from django.utils import simplejson

datafixture = GoogleDatastoreFixture(env={'EntryData': blog.Entry,
                                          'CommentData': blog.Comment})


class TestListEntries(unittest.TestCase):
    def setUp(self):
        self.app = TestApp(blog.application)
        self.data = datafixture.data(CommentData, EntryData)
        self.data.setup()

    def tearDown(self):
        self.data.teardown()

    def test_entries(self):
        response = self.app.get("/entries")
        assert simplejson.dumps(EntryData.great_monday.title) in response
        assert simplejson.dumps(EntryData.great_monday.body) in response
        assert simplejson.dumps(CommentData.monday_liked_it.comment) \
            in response
        assert simplejson.dumps(CommentData.monday_sucked.comment) \
            in response

Create custom datasets.

$ ./load_data_locally.py --datastore_path=./my.datastore

Run server with custom data.

$ dev_appserver.py . --datastore_path=./my.datastore

Run tests.

$ nosetests -v --with-gae

test_entries (tests.test_entries.TestListEntries) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.296s
OK
Posted: May 26th, 2010 | Author: | Filed under: 技術 | Tags: , , , , , | No Comments »

Fixture + SQLAlchemy

Using LoadableFixtureを読む

A DataSet class is loaded via some storage medium, say, an object that implements a Data Mapper or Active Record pattern. A Fixture is an environment that knows how to load data using the right objects. Behind the scenes the rows and columns of the DataSet are simply passed to the storage medium so that it can save the data.

DataSetクラスはなんらかの格納媒体(以下ORMapper)例えばDataMapperやActiveRecordパターンで実装されたオブジェクトによってロードされる。Fixtureは正しいオブジェクトを使って、どのようにデータをロードするかを知っている環境だ。内部的にはDataSetの行と列は単純にORMapperに渡されて、ORMapperがデータを保存する。

The Fixture class is designed to support many different types of databases and other storage media by hooking into 3rd party libraries that know how to work with that media. There is also a section later about creating your own Fixture.

Fixtureクラスは多くの異なるタイプのデータベースやストレージメディアに対し、どのように接続するかを知っているサードパーティーのライブラリにフックすることで、それらをサポートするようデザインされている。後のほうに独自のFixtureを作るセクションがある。

Fixture is designed for applications that already have a way to store data; the LoadableFixture just hooks in to that interface.

Fixture はデータの格納方法を既にそなえたアプリケーションのためにデザインされている。LoadableFixture はただそのインターフェースにフックしているに過ぎない。
—————-
以下An Example of Loading Data Using SQLAlchemy を参考にデータセットとテストコードを書いた。

Read the rest of this entry »

Posted: May 13th, 2010 | Author: | Filed under: 技術 | Tags: , , , , | No Comments »