Mountain Lionへのupdate

遅ればせながら、OS XをMountain Lionへupdateしました。その中で、開発していたRoR環境が動作しなくなったので備忘録的に。

テストが通らない!

unit testでエラーが出てしまいます。挙動が云々でなく、そもそもDBサーバが動いてねぇよ、的なメッセージ。

rake aborted!
could not connect to server: No such file or directory
        Is the server running locally and accepting
        connections on Unix domain socket "/var/pgsql_socket/.s.PGSQL.5432"?

あれー?Posgres逝ったか??と思って、ターミナルからpsqlを叩くと・・・接続できる・・・。なんじゃい、こりゃ??で、ネット上に情報が落ちてないかウロウロしてた所、ありました。
API Only - Stack Exchange

どうも、database.ymlの中で接続先の指定をする際に、localhost上のPostgresを参照するので設定していなかったのですが、Mountain Lionにすると指定が必要なようです。

test:
  adapter: postgresql
  ・・・
  host: localhost

hostを指定した所、動作しましたとさ。

Titanium.Network.HTTPClientを使ってGAEの認証を通す(その2)

前回、Titaniumを使ってGAE上の認証を通すことを書きましたが、どうも、500のレスポンスが帰ってこなくなるだけで、必要なデータがとれていなかったようなので、再度確認しました。前回の奴は嘘です。忘れて下さい。

GAEアプリでなく、Google側の認証でエラーが発生しているので、ログを確認しようにもできません。ローカル環境で確認したところ、AndroidとiOSでリクエストヘッダは違いこそありますが、根本的に違うわけでは無さそうでした。*1

Client認証を行い、その結果をパラメータとしてURLに直接設定してブラウザを叩くと通ります。当然のことながら認証成功時に含まれる文字列を変更すると、エラーが返って来ます。iOSで帰ってくる500の画面と同じです。

私のコードはsendメソッドにリクエストパラメータを渡していましたが、URLに直接設定してみると・・・通りました。
うーん、httpの時は、GETでsendメソッドの引数にパラメータを設定することで送れていることは確認できたのですが、httpsだとダメ・・・なんでしょうかね?
よくわからないけど試してみたら動いた、という気持ち悪い結果になりましたが、TitaniumでiOS用のソースコードを記述する時は、https通信を行う場合、GETリクエストはURLに書いたほうがよさそうです。


ソースは以下の感じ。

//GAEに対するログイン
//Googleに対してClient認証を行い、その結果をauthTokenに設定
function loginAppEngine(loginParam, authToken) {
	
	var appid = loginParam.appid;
	
	var baseUrl = "https://" + appid + ".appspot.com/";
	var xhr = null;
	if(Ti.Platform.osname === 'android') {
		xhr = Titanium.Network.createHTTPClient();
	} else {
		//iOSの場合、SSLチェックを緩くする
		xhr = Titanium.Network.createHTTPClient({
		    	validatesSecureCertificate: false,
		    	tlsVersion: Titanium.Network.TLS_VERSION_1_0
		});
	}
	
	var query = "?continue=" + baseUrl + "&auth=" + authToken;	
	var url = baseUrl + "_ah/login";

	//POSTだとAndroidでもiOSでも500エラーが返るので、GETメソッドでないといけない
	xhr.open('GET',url + query);
	
	//レスポンスを受け取った際のイベント
	xhr.onload = function(){

		indicator.hide();

		//以降、ごにょごにょする
	};
	
	// エラー発生時のイベント
	xhr.onerror = function(error){
		indicator.hide();
		
		//以降、ごにょごにょする
	};
	
	//リクエスト送信
	var indicator = Ti.UI.createActivityIndicator({
		message : "Loading..."
	});
	indicator.show();

	xhr.send();
}

で、レスポンスヘッダからCookieに設定されているJSESSIONIDをGAEアプリへのリクエストに含めることで、AndroidでもiOSでも認証が必要なGAEアプリからJSONデータを取ってくることができます。

Titanium SDK 2.0.2だからなのか、そもそもだったのかわかりませんが、Androidで動作してiOSで動作しない時はこの辺りを疑ってもいいかもしれません。こんなにハマるとは思わんかった・・・。

*1:ConnectionがcloseだったりKeep-Aliveだったりの差はありましたが、確認した所、このお陰でエラーになっているわけではありませんでした

動的にheightが変わる場合のScrollView

1画面に収まらないデータを表示する画面を実装するのに、ScrollViewを使用していましたが、Androidでは思い通りだったのですが、iOSではスクロールしませんでした。

var self = Ti.UI.createWindow();
var scrollView = Ti.UI.createScrollView({
  contentWidth:'auto',
  contentHeight:'auto',
  layout : 'vertical',
  showVerticalScrollIndicator:true,
  showHorizontalScrollIndicator:true
});
self.add(scrollView);

var view = Ti.UI.createView({
  backgroundColor:'#336699',
  width:'100%',
  top:'10dp',
  layout : 'vertical',
  height : 'auto'
});
scrollView.add(view);

//・・・以降、viewに対してLabelをaddする
//Labelのheightは一意に決まらない

思い切って、
Titanium BBS(JP unofficial)
で聞いてみたところ、viewのheightを'auto'からTi.UI.SIZEとすれば良いという回答をもらいました。

var view = Ti.UI.createView({
  backgroundColor:'#336699',
  width:'100%',
  top:'10dp',
  layout : 'vertical',
  height : Ti.UI.SIZE
});

実際に試した所、想定通りに表示されました。あー、そういうことだったんですね。返信いただいた方、ありがとうございました!

いやー、JavaScriptだけでAndroid/iOSアプリができるって、楽ですねぇ。

Titanium.Network.HTTPClientを使ってGAEの認証を通す

※2012/06/24追記 この方法では正常動作しません。修正版はこちら

TitaniumでGAEアプリからJSONでデータを取るような実装をしていて、認証部分の処理を書いていました。

//GAEに対するログイン
//Googleに対してClient認証を行い、その結果をauthTokenに設定
function loginAppEngine(loginParam, authToken) {
	
	var appid = loginParam.appid;
	
	var baseUrl = "https://" + appid + ".appspot.com/";
	var xhr = null;
	if(Ti.Platform.osname === 'android') {
		xhr = Titanium.Network.createHTTPClient();
	} else {
		//iOSの場合、SSLチェックを緩くする
		xhr = Titanium.Network.createHTTPClient({
		    	validatesSecureCertificate: false,
		    	tlsVersion: Titanium.Network.TLS_VERSION_1_0
		});
	}
	
	var url = baseUrl + "_ah/login";

	xhr.open('GET',url);
	
	//レスポンスを受け取った際のイベント
	xhr.onload = function(){

		indicator.hide();

		//以降、ごにょごにょする
	};
	
	// エラー発生時のイベント
	xhr.onerror = function(error){
		indicator.hide();
		
		//以降、ごにょごにょする
	};
	
	//パラメータ作成
	var param = {};
	param["continue"] = encodeURI(baseUrl);
	param["auth"] = encodeURI(authToken);

	//リクエスト送信
	var indicator = Ti.UI.createActivityIndicator({
		message : "Loading..."
	});
	indicator.show();

	xhr.send(param);
}

ところが、Androidでは問題なく動作するのに、iOSでは500エラーが発生します。


数日間悩んでいたのですが、理由は簡単

	var url = baseUrl + "_ah/login";



	var url = baseUrl + "_ah/login/";


とすれば良かったのです。
えー、Java(jmeter)だと大丈夫だったのになー。Objective-Cの仕様なんでしょうか?Androidで動いてiOSでエラーになるhttpリクエストがある場合、URLを見なおしてみると良いかもしれません。


あと、iOSの場合の

		xhr = Titanium.Network.createHTTPClient({
		    	validatesSecureCertificate: false,
		    	tlsVersion: Titanium.Network.TLS_VERSION_1_0
		});

が異様にキモい。

GAE上で動作するアプリを作ってみた

ふと、仕事以外のコードを書きたくなって。
時間を見つけて書きためてました。


IT以外の人でも、redmine的なもの(ITS)って必要だよね*1

でも、サーバ管理とか面倒だろうし

クローズドな人たちで使えて、かつデータはインターネット上にあった方がいいよね


ということで、個別のGoogle App Engineにデプロイしてもらう形のアプリを作りました。


Mishimaと言います。


無料クォータでどこまでいけるかは使い方次第ですが、数人のプロジェクトならなんとか大丈夫かな?と思います。

マニュアルめいたものも書いてみました*2


Amazonとかでサービス公開したらもっと実装が楽なんでしょうけど、いかんせん、そんな環境を維持する余裕がないものでGAEアプリになります(最近は価格改定のせいで下火なんですかね??)

GAE + slim3 + jQueryという形で動作するWebアプリで、GitHubにもソース公開しています。
ライセンスの相談には id:daisuke-m に乗ってもらいました。ありがとうございます。

次はTitanium Mobileでスマフォアプリ対応してみようかな。

*1:多分

*2:これが一番面倒でした

IPv6セミナー + Hackathon in 新潟に参加してきたze!

ネットワークもよくわかりませんが、技術者たるもの「IPv6」くらい試しておかないとね、くらいのノリで参加してきました。まぁまぁ準備はしてたんですけど、思うようにはいきませんでした。でも、Javaは結構優秀で、IPv4IPv6意識しなくてもそこそこいける気がします。さすがー

作ったGAEアプリとか、GAEの認証周りにおけるJMeterのシナリオの作り方のコツとかはおいおいまとめます。
関係者の皆様、お疲れ様でした。やるじゃん、長岡!

Ipv6+JMeter+GAE


※2012/05/29追記
GoogleIPv6サービスは6月6日まではwhitelistモードで、Google over IPv6に登録されたDNSを使ってないと、まだIPv6では接続できないはずという情報を頂きました。なるほど。
[twitter:@maz_zzz]さん、ありがとうございます!

テストの男坂

ござ先輩の記事に触発されたので書いてみる。

自分が書いたプログラムに対してテストをする必要があることについてはご認識の通りでしょう。自分の趣味に留まらず、そのプログラム(で作られたシステム)がビジネスを回す上で必要だったり、「お金」という対価を得る場合は特に。
「ちゃんとテストしました」と自信満々で言われ、いざ使ってみてつまんない実行時エラーが出てきた時、受け取った側はどんな顔をすればいいかわかりません。ましてやそんなのが何回も続くとその人の信用はガタ落ちです。時間をかけにかけまっくてシステムを作って、満を持してユーザに見せた時にそんな状態だと人格そのものを否定される可能性だってあります。
言い訳がましく「入ってるべきデータが入っていなかったからです」と報告しても「なんで落ちたかの説明よりもとっとと直せ。お前が作ったプログラムは信用ならん」と言われるの、切ないですよね。でも、ユーザがあなたに依頼してるのは「ちゃんと動くもの」を作って頂戴、ってことなんです。

テスト、テスト、テスト

開発者にとって一番身近なのは単体テストでしょう。最近では単体テストと言えばテストコードによる自動化が当たり前になってきています。何回も同じテストを手動で繰り返すのかったるいですもんね。デグレが発生しても検知してくれるし。
ただ、テストコードを書いているから品質はバッチリです、というのはいささか危険。システムの全ての振る舞いをテストコードに落としこむのは時間がいくらあっても足りません。いくらテストコードを書いても、「バグが存在しないこと」は証明できません。
言い方はアレですが、単体テストは所詮、単体テストであり、システム全体の品質を担保するものとは考えない方が良いでしょう。品質については、「次のテスト工程で観点の異なるテストケースを確認する為に簡単には落ちない」くらいにしておいた方が気が楽になるとおもいます。単体テストで全部のテストパターンを考えるとなると、ビックバンテストを前倒しにやってるだけになっちゃいますからね。

テストコードを書いてるんだからバグは存在しないでしょ?の誤解

テストコードの中で、「Aであること」は定義できますが、「Aでないこと」の定義には膨大なテストパターンが必要になります。
例えば、「Aテーブルにレコードを登録する」処理を呼び出した時に「Aテーブルにレコードが登録されること」は容易に確認できますが、その処理の中で「Bテーブルにレコードが『登録されない』こと」を証明するには考えられる前提条件全てに対してテストしなければなりません。前提条件には入力パラメータだけでなく、システムで扱う全ての要素(テーブル、CPU、メモリ等々)を考慮しなければならず、証明するにはテストパターンがいくらあっても足りないことは容易に想像がつくでしょう。Bテーブルの登録処理を呼び出していないから大丈夫、というのはあくまでもソースコード上の話であり、テストコードを実行した結果での証明にはなりません。テストコードを書いてもバグが存在しないことを証明できないのはこの為です。

じゃーどうすればいいのよ

だからといって、テストコードを書くことが意味のないことではありません。「Aであること」方式の定義はしやすいので、そのパターンを増やせば間違いなくバグは出にくくなります。テストしてない所にはバグが潜んでいる可能性があります。どこまで想像力豊かにテストケースを洗い出せるかがバグが少ないシステムへの分かれ道です。

  • 複雑なSQL
    • InsertやUpdateよりも、Select文。それも複数のテーブルを結合したものや、検索条件が気持ち悪いくらい入り組んでいるもの。
    • ストアド使ってるならそれも入れよう。
    • システムはデータを出し入れするものなのでここが間違っているとシステムの存在価値ゼロ。
  • そのシステムのキモとなるビジネスロジック
    • この処理ちょっと面倒だな、と思った箇所、そこがテストコード書くべき箇所です。
    • 共通的に使われる箇所もキモになるビジネスロジックでしょう
    • 表示するデータや登録するデータをメモリ上でこねくり回す処理なんかは絶好のテストコード記述ポイント
  • 手動でテストしづらい箇所
    • 絶妙なタイミングで確認するテストとか
  • 繰り返し実施するのがかったるい箇所

メソッドの作りも呼び出したらどうなるか、が定義しやすいように心がけることでテストしやすいものに大変身。assertを記述しやすいメソッドの構成を心がけましょう。
また、テストコードがあれば100%デグレに気づくわけではなく、テストコードに定義されていないものはデグレとして検知されません。例えばあるメソッドに、「Cテーブルにレコードを追加する処理」を追加機能として実装した時、元のテストコードを動かしてもテスト失敗することは無いでしょう。テストコードに「Cテーブルのレコードが登録されないこと」の定義がされていない限りは。そのような時はMockクラスを使うのですが、そこまでやるかは工数とスケジュール次第かと思います。変わるかどうかもわからない箇所にMockを仕込むよりも、処理を追加した時にテストクラスを見なおした方がかかる工数が低いこともあるでしょう。ユーザが欲しいのはテストコードでなく、「動くシステム」の筈なのでその辺を天秤にかけた方がいいと思います。

いきなり手を動かすのも大事だけど

考えるより手を動かしてテストコード書け、という話は必ずしも正解ではないと思います。なんたって私たちは知的労働者なんです。何をどう作るか考えた上で手を動かしたほうが効率がいいこともあるでしょう。処理を実装する際には、机上でも良いので実装設計することをオススメします。その際に同値分割とかディシジョンテーブルを用いて効率的なテストケースを作れると、ワンランクアップ。作りながらでも良いですが、ソースコードを書くテンションが高すぎて見逃すこともあるでしょう。はやる気持ちを抑えて深呼吸です。
設計者と実装者が分かれている場合、設計の段階でテストケース込みの設計書があれば、意思疎通のズレを早期に打ち取ることができます。
後は、扱うデータにnullが渡される可能性があるのか無いのか、nullの場合どうなるべきかを意識するだけでつまらないバグを減らすことができる筈です。データの出処を抑えることが良い設計の第一歩。
更に言うと、設計した時にデータの流れを見なおして考慮漏れがないかを確認すべきです。実装してから間違いに気づいてしまうと、なかなかソースコードを捨てられないことから対応がやっつけになることが多く、そんなコードを後世に残してしまったことを悔やむことになるからです。ちゃんと設計して実装するのとリファクタ込みで実装するのとトータルの工数がどれだけ差があるのかわかりませんが。

テストコードだけでは幸せになれない

テストコード書いて楽できるところは楽しようぜ、という世の中の流れは大いに賛成です。ですが、それだけでは幸せになれません。そもそも間違った仕様でテストコードを記述してしまえば、Allグリーンでも価値をもたらさないクソコードです。
設計と実装の距離を短くして、さらにユーザにも近づこうよ、というアジャイル的な流れが大きくなりつつありますが、「アジャイルであれば今までの失敗がなかったんだ」的なことになりそうなのがちょっと怖いです。ウォーターフォールでもアジャイルでも失敗したんじゃね?(もしくはその逆も)という観点が抜けてやいないか大いに気になるところではあります。価値をもたらさないクソシステムを量産する側にならないように一層気を引き締めなければならんなー、と感じる今日この頃でした。