Google App Engine/Python で単体テスト

Google App Engine SDK for Python には、単体テスト用に各サービスのスタブが提供されています。このスタブと unittest を使えば、ローカル環境で GAE 用アプリの単体テストが可能です。

GAE/Python で unittest を使って、単体テストを行うサンプルは次の通り。

#!/usr/bin/env python
#encoding: utf-8

import os
import sys

# 環境設定
# これをしないと GAE のモジュールをインポートできない。
# テスト対象のクラスも同様。

# ここを自分の環境に合わせて書き変えます
GAE_HOME = 'Google App Engine SDK ディレクトリのパス'
PROJECT_HOME = 'アプリケーションのルートディレクトリのパス'

# テストで使う GAE のモジュールのパスを作成
EXTRA_PATHS = [
    GAE_HOME,
    PROJECT_HOME,
    os.path.join(GAE_HOME, 'google', 'appengine', 'api'),
    os.path.join(GAE_HOME, 'google', 'appengine', 'ext'),
    os.path.join(GAE_HOME, 'lib', 'yaml', 'lib'),
    os.path.join(GAE_HOME, 'lib', 'webob'),
]

# パスを追加する。
sys.path = EXTRA_PATHS + sys.path


import unittest

# スタブをインポート
from google.appengine.api import apiproxy_stub_map
from google.appengine.api import datastore_file_stub
from google.appengine.api import user_service_stub
from google.appengine.api import users
from google.appengine.ext import db

# テスト対象のクラスをインポート
from models import *


APP_ID = u"test_id"
AUTH_DOMAIN = "gmail.com"
LOGGED_IN_USER = "test@example.com"


# 単体テストのベースクラス
class GAETestBase(unittest.TestCase):

    # ベースクラスの setUp 内でスタブを登録しておく
    def setUp(self):
        # API Proxy を登録
        apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap()

        # Datastore のスタブを登録
        stub = datastore_file_stub.DatastoreFileStub(APP_ID,
                '/dev/null',
                '/dev/null')
        apiproxy_stub_map.apiproxy.RegisterStub("datastore_v3", stub)

        # APPLICATION_ID の設定
        # これを忘れると Datastore がエラーを出す
        os.environ["APPLICATION_ID"] = APP_ID

        # UserService のスタブを登録
        apiproxy_stub_map.apiproxy.RegisterStub("user",
                user_service_stub.UserServiceStub())
        os.environ["AUTH_DOMAIN"] = AUTH_DOMAIN
        os.environ["USER_EMAIL"] = LOGGED_IN_USER


# ベースクラスを継承してテストクラスを作成
class FooTest(GAETestBase):

    # あとはテストコードを書く
    def test_create_foo(self):
        foo = Foo(key_name="foo")
        foo.name = "Foo"
        foo.put()
        self.assertEqual(1, Foo.all().count())


if __name__ == '__main__':
    unittest.main()

最初に GAE SDK やテスト対象プロジェクトのパスを設定しているのがポイント。これが無いと、SDK のモジュールをインポートするときに「そんなモジュールはない」って怒られてしまいます。

テストがちゃんと動くようになるまで、かなり苦労しました。GAE/Python単体テストに関する情報が少ない…。GAE/Java なら、 EclipseJUnit で楽にテストが作れるんですけどね。公式のドキュメントにも JUnit を使ったテスト方法が紹介されていますし。