GAE+Django+GAEUnit (Part-II)

ということで、GAEUnitを使いながらGAE+DjangoでTDD開発していくメモ二回目。作るものはStatというエンティティにデータを書き込む・読み出すアプリを書いてみる、ということにする。

アプリケーション作成

なぜかDjangoはコントローラとモデルの組み合わせをアプリケーションと呼ぶらしい。アプリケーション名はstatにする。

python manage.py startapp stat

→statというディレクトリが生成される。中身は__init__.py, views.py, models.py

unit_tests.py

まずはStatというクラスの実体を生成し、そいつがNoneではないことを確認。

import unittest
import logging
from google.appengine.ext import db
from gaetest.stat.models import *


class ModelTest(unittest.TestCase):
  def test_new_entity(self):
    stat = Stat()
    self.assertNotEqual(None, stat)

web_tests.py

コントローラのテストはとりあえず空っぽにしておく。

import unittest	
from webtest import TestApp
import django.core.handlers.wsgi
import gaetest.stat.views

class StatTest(unittest.TestCase):
  def setUp(self):
      self.application = django.core.handlers.wsgi.WSGIHandler()

テスト実行

ブラウザでhttp://localhost:8080/test/ を開く(ポート番号は環境に合わせること)

→テスト失敗。

Traceback (most recent call last):
  File "test\unit_tests.py", line 9, in test_new_entity
    stat = Stat()
NameError: global name 'Stat' is not defined

落ちるのは当たり前で、Statというクラスを宣言していないのであった。

stat/models.py

from google.appengine.ext import db

class Stat(db.Model):
	pass

テスト実行

ブラウザでhttp://localhost:8080/test/ を開く(ポート番号は環境に合わせること)

→今度は成功。
こんな感じで、「何をすれば完了か」をテスト側で定義し、そのテストが失敗することを確認してから、本作業を行い、テストが通ることを確認していく。まだStatクラスは空っぽなので、次はプロパティを定義していく。

  • friends_count
  • statuses_count
  • timestamp

を定義してみる。まずはテストから。

unit_tests.py

import unittest
import logging
from google.appengine.ext import db
from gaetest.stat.models import *

class ModelTest(unittest.TestCase):
	def test_new_entity(self):
		stat = Stat(followers_count = 100,
					statuses_count=100000)
		self.assertEqual(100, stat.followers_count)
		self.assertEqual(100000, stat.statuses_count)
		self.assertNotEqual(None, stat.timestamp)

テストすると当然落ちる。まだプロパティ定義してないから。

Traceback (most recent call last):
  File "test\unit_tests.py", line 12, in test_new_entity_with_properties
    self.assertEqual(100, stat.followers_count)
AttributeError: 'Stat' object has no attribute 'followers_count'

stat/models.py

from google.appengine.ext import db

class Stat(db.Model):
	followers_count = db.IntegerProperty(required = True)
	statuses_count = db.IntegerProperty(required = True)
	timestamp = db.DateTimeProperty(auto_now_add = True)

→今度はエラーがでない。

というのをちまちまと繰り返して、テストとコードを交互に書きながらGAEUnitで確認していく、という開発ができるようになる。複数人数でやる場合は、http://localhost:8080/test?format=plain をurllibあたりから呼んで、結果をメールで投げるなりTwitterに書くなりすれば良かろう。

コントローラのテストも同じようにちまちまとやっていく。

web_test.py

import unittest	
from webtest import TestApp
import django.core.handlers.wsgi
import main

class StatTest(unittest.TestCase):
	def setUp(self):
		self.application = django.core.handlers.wsgi.WSGIHandler()

	def test_index(self):
		app = TestApp(self.application)
		response = app.get('/stats')
		self.assertEqual('301 MOVED PERMANENTLY', response.status)
		
		app = TestApp(self.application)
		response = app.get('/stats/')
		self.assertEqual('200 OK', response.status)
		self.assertTrue('test' in response)		

→テストは当然落ちる。

settings.py

INSTALLED_APPSにコントローラを追加

INSTALLED_APPS = (
 #   'django.contrib.auth',
    'django.contrib.contenttypes',
 #   'django.contrib.sessions',
    'django.contrib.sites',
	'gaetest.stat.views',
)

urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
	(r'^stat/$','gaetest.stat.views.index'),
)

views.py

import logging
from django.http import HttpResponse
from models import Stat

def index(request):    
	return HttpResponse("This is a test.")

→今度は通る

こんな感じにコントローラ周りをいじくる時(viewsやurls)も、あらかじめテストを書いておいて「次に何をするのか」を自分に言い聞かせながらぼちぼち書いていくと、考えが発散しなくて楽。