2008-09-30

Google App Engineのアプリのテストを書く

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
今まで、特定の関数のテストだけはやっていたのですが、
今更ながら、ちゃんとアプリ全体のテストを書こうと思いGoogle App Engineのテストを調べました。
参考にさせていただいたのは、前もお世話になった
http://coreblog.org/ats/3-tips-to-perform-test-driven-development-with-google-app-engine
です。

WebTestというのを利用するのですが、インストールはeasy_installでちょろっと入ります。
ただ実際に使うにはWebObというのも必要みたいなのでeasy_installでちょろっと入れます。
WebTestを使うとどっかのページにブラウザでアクセスしたのと同じようなレスポンスが帰ってきます。
その結果をいい感じに利用するためにBeautifulSoupも入れておくと便利な感じなのでeasy_installでちょろっと入れます。

WebTestに関する詳細は、公式の
http://pythonpaste.org/webtest/index.html
が参考になります。

これで準備ができてテストコードを書きます。
テストには下ではnoseを利用してみました。

# -*- coding: utf_8 -*-

import os
import sys
import re
import nose
from nose.tools import *
from webtest import TestApp
import logging

#webappでアプリを書いているならばimport
from google.appengine.ext import webapp

#アプリで利用しているAPIのスタブをimport
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.ext import db, search
from google.appengine.api import urlfetch_stub
from google.appengine.api.memcache import memcache_stub

#実際のアプリで利用しているハンドラーを定義してテスト環境を準備する
application = webapp.WSGIApplication([
('/', TopPage),
('/login', LoginPage),
('/about', AboutPage),
('/faq', FaqPage),
],debug=True)

app = TestApp(application)

#テストを書く
def test_about():
#必要になる環境変数やヘッダーの値をセット
os.environ['USER_EMAIL'] = ""
os.environ['HTTP_USER_AGENT'] = "aaaa"
os.environ['REMOTE_ADDR'] = "127.0.0.1"
#GET要求の実施。ここで本当は環境変数とかをセットして渡せるみたいなのですが、うまくいかなかったので、直接環境変数をいじりました
res = app.get("/about")
#結果の確認
assert_equal(res.status,"200 OK")
#本文の取得。このやり方ではBeautifulSoupのオブジェクトが帰ってきます。
#res.bodyで普通に文字列が帰ってくる用のです。
soup = res.html
#classがcurrentのdivタグで囲まれた部分の取得
for cnt in soup('div',{'class':'current'}) :
text = cnt.renderContents().strip()
assert text == "About"

if __name__ == '__main__':
#利用するスタブをセットする
stub = datastore_file_stub.DatastoreFileStub(u'xxx','/dev/null',
'/dev/null')
apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', stub)
#なぜかdatastoreのスタブが_appがないとエラーが出るので環境変数を入れてごまかす
os.environ['APPLICATION_ID'] = "xxx"
apiproxy_stub_map.apiproxy.RegisterStub(
'user',user_service_stub.UserServiceStub())
os.environ['AUTH_DOMAIN'] = "gmail.com"
apiproxy_stub_map.apiproxy.RegisterStub(
'memcache',memcache_stub.MemcacheServiceStub())
apiproxy_stub_map.apiproxy.RegisterStub(
'urlfetch',urlfetch_stub.URLFetchServiceStub())


arg = ["dummy","-v"]
nose.main(argv=arg)

2008-09-22

pythonのunitテストをnoseに置き換えてみる

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
pythonではnoseというのをunitテストに使うとステキな気配を感じたので、noseを使ってみました。
しかし、いまいちまだ普通のpythonのunittestに比べてのステキっぷりを把握はできていないのですが、今後はnoseを使ってみることにしてみます。

unittestに比べて
assertEqual(a,b)
みたいに書かずに
assert a == b
と書けるのは直感的にわかりやすいなぁとは思うのですが、エラー発生時に渡されている値を確認するためにはnose.toolsのassert_equalを使った方がわかりやすいらしく、結局
assert_equal(a,b)
と書くとあまり違いがわからなかったりするのです。

あとnoseは基本的には、コマンドラインのツールのようでいろいろなオプションを起動時に渡せるようです。
コマンドラインを指定せずにプログラム内に実行の記述を直接するには

if __name__ == '__main__':
arg = ["dummy","-v"]
nose.main(argv=arg)

とかやると起動オプションを渡して実行することもできるようです。

参考にさせていただいたサイトは、
http://makunouchi.jp/zope3/7396323127
です。

2008-09-18

iアプリ端末バグ?

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
作成したアプリが起動しない端末がありました。
F902i
がそうです。
きちんと調べられていないのですが、
http://www.moreread.net/mario/Any/appli/memo.php?id=14
を見ますと、
画像読み込みに問題がありそうです。
ちょっと対処方法がわからないので、まだ対応できていません。
F902iをお持ちの方ご迷惑をおかけして申し訳ありません。

2008-09-17

GQLのWHERE条件にINを使うときの注意点

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
GQLのWHERE条件にINを使うときに配列を渡すわけですが、その配列に要素が一つもないとどうもその条件がまったく聞いていない気配。

search_list = []
aaa = db.GqlQuery("SELECT * FROM AAA WHERE colcolIN :list",list = search_list)

aaa = db.GqlQuery("SELECT * FROM AAA")
と同じことになっている気配。
個人的には、ひっかかるものが何もないという感じで返してくれる方がうれしかったのですが。

なのでこの動作だとちょっとイヤだなと思う人は、
事前に渡す配列の要素数が0であることをチェックする
か、
絶対にひっかからないダミーの要素を一個入れておく
などする必要がありそうです。

2008-09-16

SOBARCOを作る時に参考にしたサイト

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
参考にしたサイトをメモ

Google App Engineのドキュメント
http://code.google.com/appengine/docs/
http://groups.google.co.jp/group/google-app-engine-japan

Pythonのドキュメント
http://www.python.jp/doc/release/index.html

iアプリのドキュメント
http://www.ep.u-tokai.ac.jp/~nakazato/API/504i/base/jguide504_apiref020517/javadoc/overview-summary.html

jQueryのドキュメント
http://www.openspc2.org/JavaScript/Ajax/jQuery_study/ver1.2.6/index.html

エンジニアのためのWebデザイン教室
http://itpro.nikkeibp.co.jp/article/COLUMN/20080214/293856/

WebプログラマのためのHTMLデザイン(その2)
http://fromnorth.blogspot.com/2008/08/webhtml2.html

HTMLタグ・CSS・JavaScript - Web制作のインデックスサイト
http://www.tagindex.com/index.html

Dynamic Drive CSS Library
http://www.dynamicdrive.com/style/

アイコン集
http://speckyboy.com/2008/07/28/96-best-ever-free-icon-sets-for-web-designers-developers-and-bloggers/

モバイルサイトをPCで見るためのツールやFirefoxアドオン
http://blog.livedoor.jp/ld_directors/archives/51079649.html

Firefoxでモバイル端末をシミュレートするアドオン「FireMobileSimulator」
http://ke-tai.org/blog/2008/09/04/firemobilesimlator/

iアプリでバーコードを読む

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
iアプリでバーコードやQRコードを読むのは結構簡単なのです。

前にどっかで検索して調べたはずなのですが見つからなかったので書いておきます。
以下のような感じです。
と思ったら、過去に見たものよりも別のもっとよいページがありました。
http://www.geocities.jp/inu_poti/makeiapp/tuika/codeReader.htm


CodeReader cr;
cr = CodeReader.getCodeReader(0);
boolean use_auto = false;
int can_use_codes[];
can_use_codes=cr.getAvailableCodes();
int i=0;
while(true){
try{
int use_code=can_use_codes[i];
if(use_code==CodeReader.CODE_AUTO) use_auto=true;
i++;
}catch(Exception e){
break;
}
}
if(use_auto) {
cr.setCode(CodeReader.CODE_AUTO);
} else {
cr.setCode(CodeReader.CODE_QR);
}
try {
cr.read();
}catch(InterruptedOperationException e){
}
//読み取った値を取得
String read_code = cr.getString();
//その他付加情報(あんまり使い道なし)
int code = cr.getResultCode();
int type = cr.getResultType();

CODE_AUTOは機種によっては問題があるようです。
なので利用できるかどうかをきちんとチェックした方がよさげです。
あと、ドコモのマニュアルを見るとCODE_OCR(テキスト認識)というコード種別もセットできるようなのでひょっとしたら機種によってはOCR機能を利用できる可能性もあります。

2008-09-12

SOBARCOというのを作ってみました

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
SOBARCOというものを作ってみました。
携帯でバーコードを読み取って公開するものです。

このブログで書いていたのは、このSOBARCOを作るために調べたもののメモです。

蔵書管理に使うこともできると思いますし、バーコードを利用した一言ブログとしても利用できると思っています。

もしよろしければ使ってみてください。
今後いろいろ改良を加えてみたいと思っています。
よろしくお願いします。

Google app engineのmemcacheの期限の上限

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google app engineでmemcacheを利用しているのですが、キャッシュ期限を
expire = 60*60*24*30
と30日のつもりで設定していてローカル環境ではうまく動いていたのですが、本番にアップしたらこの期限は駄目でした。
expire = 60*60*24*29
にしたら問題なく動きました。
マニュアルには、
up to 1 month
と書いてあったので、1ヵ月間ということで30日いけるだろうと思ったのです・・・
なので上限は、上記二つの間にあるはずです。

2008-09-05

モーダルダイアログなフォームを作る

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
正式にはなんていうのかわからないのですが、更新作業などをする時にページ遷移をせずに現在のページが暗くなって、その上にモーダルダイアログな感じのフォームが出てくる感じのことをやりたいわけです。
こういうものは、どうもLightbox風と呼ばれているような気配もします。

実際にやるには、jQueryと組み合わせるならば
ThickboxというjQueryのプラグインを使うといいようです。

実際にフォームを表示させるには、
<a class="thickbox" title="Update" href="http://www.blogger.com/update?placeValuesBeforeTB_=savedValues&TB_iframe=true&height=400&width=600&modal=true"> Update </a>
みたいな感じで普通にリンクに書いてclassにthickboxと書けばよいようです。

こうすると開きたいたいフォーム(この場合はupdate)をiframeで開いてくれます。
?placeValuesBeforeTB_=savedValues&TB_iframe=true&height=400&width=600&modal=true
の部分を実際に表示させたいurlにつけるのがポイントです。

そして開いたiframe内でiframeを閉じたいときには
<input onclick="self.parent.tb_remove();" type="button" value="閉じる">
って感じに普通にフォーム内に書いておけば閉じてくれます。
&modal=true
をつけておかないとこの機能は使えないようです。
&modal=true
がなければ、Thinkboxの方で閉じるボタンをつけてくれます。

更新作業などが終わって、iframeでのフォームを閉じる時に元の画面の情報を更新したい時には、以下のような感じのボタンを用意することで対応できました。
<input onclick="self.parent.location.reload();" type="button" value="閉じる">

同じようなものをやるものとして、
jqModalというのもあるらしいです。こちらの方がサイズが小さいけど高機能らしいです。

2008-09-04

IEでjQueryを利用してxmlをパース

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
サーバからxmlを受け取るajaxアプリを最近firefoxで確認しながら作っていたのですが、ふとIEで見てみたらxmlのパースがうまく出来ていないことが判明しました。

どうもIEでは、明示的にヘッダーに
Content-Typeにtext/xml; charset=utf-8としてあげないとだめみたいです。

なので、google app engineでxmlを返す際には
きちんと以下のような感じでヘッダーを指定しないといけないようです。
self.response.headers["Content-Type"] = "text/xml; charset=utf-8"
self.response.out.write(template.render(path, template_values))

ちなみにクライアント側のコードは以下のような感じです。

function hogehoge(val) {
if (!confirm('よろしいですか?')) {
return;
}
$.ajax({
url: '/hogehoge/hoge?key=' + val,
type: 'GET',
error: cant_connect,
success: get_xml_message
});
}

function cant_connect() {
alert('サーバーと接続できませんでした');
}

function get_xml_message(xml) {
var message;
$(xml).find('message').each(function(){
message = $(this).text();
});
alert(message);
}



ちなみにIEでは、きちんとheaderでContent-Typeを指定してないと
$(xml).find('message').each(function(){
の部分がうまく動いてくれないのでした。