CakePHP

Play, WebSocket and CakePHP mini hack-a-thon 雪山合宿が開催されました

Play, WebSocket and CakePHP mini hack-a-thon 雪山合宿が開催されましたので、レポートを書きます。

場所は長野県須坂市大字仁礼峰の原高原というところにあるペンション マウンテンパパというところです。

写真1

雪が凄かったです。今回はスキーやスノボメイン組みとそうでない人がいます。僕は留守番組(開発組)です。

参加者は、@ecworks_masap@konsan@cakephperさんです。ただ、今回はPython mini hack-a-thon 雪山合宿と合同で行われたため(こちらは毎年行っており、かなりの大規模)人数が15人くらいになりました。

上田駅まで長野新幹線で向かい、駅のそばのそば屋でそばを食べるというのが恒例になっているとのこと。僕も実は2回目。結構ボリューミーです。

写真3

刀屋というお店なので上田駅に行った際にはぜひ。

写真2

こんな感じでペンションを占拠して開発を行っていきます(もちろん了承済み)

今日までの成果は、某執筆(なんか書いてます)、某案件用のCompass+SCSSのスタディ(HTML5+レスポンシブ)、@ecworks_masap さんのPlay2.0インストールのサポート(無事おわり、すばらしい!)とWebSocket関連です。

WebSocketですが、PlayFramework 1.2.4ではChromeのバージョンアップに伴い、RFC6455版が実装されてしまったためまた動かなくなっています。今回はそのリサーチとなります。
再び使えなくなったPlayFramework 1.2.4のWebSocketですが、 チケット#1240にて、対策が議論されております。

今回は、https://github.com/playframework/play/pull/438 にあるパッチをソースに当てて、antコマンドでリビルドし、テストしたところ、再び動くようになりました。詳しい内容は別途ブログ記事で書きます。

また、Flashを使ったWebSocketエミュレーションをPlayFrameworkでも試すというのにもチャレンジしました。jWebSocket Cross-Browser CompatibilityというFlashベースのライブラリがあり、これとPlayFramework 1.2.4パッチリビルド版を使うとFirefox10でもWebSocketチャットを動作可能になっています。

あと、JenkinsのPlayFramework用プラグインにもチャレンジしました。Jenkinsが書き込みをしたりできるようにパーミッションを変更しないといけないところでちょっと詰まりましたが、それ以外はスムーズに行う事が出来、無事テスト&ビルドが実現できました。

なお、このJenkinsのPlayFramework用プラグインは@ikeike443さんが作っています。

夕飯は、ペンションぽい、いい感じのメニューが出て、英気を養いました。

写真4

ポタージュ、シーフードグラタン、サラダ。

写真5

牛肉の赤ワイン煮込み(うまかった)

以上、合宿レポートでした。

Read More

CakePHPのACLを効率的に管理する

CakePHP Advent Calendar 2011の3日目です。

@kara_dが担当させていただくことになりました。

今回はACLについて書こうと思います

ACLは、僕にとって結構CakePHPを選ぶポイントの1つで、気に入っています。
Play Frameworkにもあればいいのにって思ったりします。

ACLってなんだっけ?って方は、
ACL を制御するシンプルなアプリケーション
このチュートリアルで遊んでみてください。

そうそう、今回の記事はバージョン1.3系の話です。2.0系のACLは使った事がないですが、きっとよいでしょう。

ところで、ACLを結構使っていると言うと、俺も使ってるぜってあまりならないことが多いです。
多くの場合、

  • なんか複雑そうだったのでオレオレACLを使っている
  • そもそもプロジェクトでそういう複雑なパーミッション管理が必要でない

という感じではないでしょうか?

しかしCakePHP標準のACLは非常に便利な上に、使うことで生じるメリットがあります。

  • デフォルトで組み込まれているためメンテされており、安心
  • 既に多くの使用実績があり、なかなか厳格
  • それほど多くはないがノウハウがWeb上にある

ということで、積極的な利用をおすすめしたいです。

ACLの管理をコードでする理由

ACLと一口に言っても、その管理の方法は様々なスタイルがあり、よく使われているものにACLプラグインというのがあります。

Alaxos – CakePHP – Plugin ACL

これは、Webの画面からACLやAROのビルドやパーミッション設定が出来るというものです。非常に便利で、特にパーミッション設定などは、一覧で何が出来てなにが出来ないかを把握するのに役立ちます。
ただし、僕はこのプラグインは限定的に利用するにとどめています。

ACLプラグインは、ACL周りの設定には使用せず、現状のACLがきちんとパーミッション設定されているかの確認のみに使用します。

何故でしょうか?

実は最初の頃ACLの管理にはこのプラグインを使っていました。
しかし、CakePHPを使ったプロジェクトが大きくなるにつれて、複数人数で開発を行うようになり、ACL設定の漏れやマージミスが発生したのです。

そこで、至った結論が、「ACLはコードで管理する」というものでした。

ACLのパーミッションの管理方法について

CakePHPには、ACLの設定をコードで行う事が出来るAPIが用意されています。

例えば、Pagesコントローラーのindexについてアクセス許可をしたい場合、

$this->Acl->allow($group, "controllers/Pages/index");

という書き方で対象のグループに許可を与える事が出来ます。だいたい、僕が使うグループは、管理者グループ、一般ユーザーグループ、特殊ユーザーグループ(プレミア会員など)みたいな感じでユーザーを設定しておいて、それぞれに許可を出します。

アクセスを禁止する場合は、

$this->Acl->deny($group, "controllers/Pages/index");

denyメソッドを使います。

もちろん、これはコントローラー側でACLコンポーネントを使用するという前提の話です。
こういった感じで、各アクションのパーミッション設定をしていきます。
CakePHPでは、プラグインのアクションにもパーミッションが設定できるようになっており、

$this->Acl->allow($group, "controllers/MyCategory/Categories/index");

みたいな書き方でMyCategoryプラグインのCategoriesコントローラーのindexアクションの設定が行えます。

また、

$this->Acl->allow($group, "controllers/Pages");

という書き方をすると、該当コントローラーのパーミッションを一度に設定することも出来ます。

これらを使って、コードとしてコントローラーに書いておくことで、

  • コードとして書かれているため、Gitなどで共有できる
  • 間違いがあった場合、お互いに修正できる
  • 新しい環境での構築が楽

上記のメリットがあります。

効率的な管理

ただし、プロジェクトが進むと、アクションとグループの数が増えて、ACLのコードが膨大になったりします。
1000行超えとかあります。

さて、こういうアクション毎に設定していった大量のACLを整理するにはどうしたらよいでしょうか?
考え方のベースとしては、ACLの一括設定を有効に使っていくというのが考えられます。

スタイルとしては2つで、許可を行うものの一覧を表すホワイトリスト形式と、許可しないものの一覧を列挙するブラックリスト方式です。

ホワイトリスト形式は、こんな感じで、

$this->Acl->deny($group, "controllers/Pages");
$this->Acl->allow($group, "controllers/Pages/about");
$this->Acl->allow($group, "controllers/Pages/test");
$this->Acl->allow($group, "controllers/Pages/contact");

一旦全てを許可しない状態に設定したあと、許可するものを指定していく方式です。
許可するものを視覚化できるので、一般ユーザーなどの管理に使えます。書かなかったアクションは許可されないからです。

ブラックリスト形式は、逆に、

$this->Acl->allow($group, "controllers/Pages");
$this->Acl->deny($group, "controllers/Pages/about");
$this->Acl->deny($group, "controllers/Pages/test");
$this->Acl->deny($group, "controllers/Pages/contact");

こういう風に、全てを許可した上で、禁止するアクションのみを指定していきます。
この方式は管理者ユーザーのような、大多数の部分にはアクセスできるけれど、一般ユーザーの登録みたいなアクセスされるとなんらかの不整合が起きたりするところへ禁止する形に向いています。
ブラックリスト方式の注意点は、新しくアクションが追加されたことを知らずに、ACLを再構築してしまうと、暗黙のうちに許可されてしまう点です。

これらを駆使して、ACLのパーミッションコードを書いていくと、結構縮まります。

ということで、良いACLライフを!!

明日は@shin1x1さんです。よろしくお願いします。

Read More

WebページのビジュアルデザインをSeleniumで自動テストするには?

Seleniumの起動画面

Webアプリケーションのデザインの確認って大変面倒だよね。

ブラウザ経由での自動テストを行うSelenium周りをいろいろとちゃんとやっていこうと整備し始めたのは、先日のBlog記事通り。

それで、Seleniumの基本的な使い方はあれこれ理解したが、これを、ブラウザ上のデザインのテストにも使えないかと模索を始めた。今回はJava/ScalaのWebフレームワークであるPlayFrameworkをベースに解説するが、Selenium周りはあまり変わらないのではと思う。

Webページ(ビュー)のテストには何があるのか

基本的にビューのテストには3種類あると思っている。

  • きちんと値が出力されているか
  • 正しい遷移が行えるか
  • きちんと表示がされているか

「きちんと値が出力されているか」「正しい遷移が行えるか」については、まあ普通にSeleniumを使えば出来るので割愛。

Seleniumの基本的な使い方

ここで、Seleniumのテストコードの書き方についておさらいしてみようと思う。Seleniumにはいろいろなテストコードの書き方が出来て、すごくオーソドックスなのは、HTMLを使って書く方法。これは、よく知られているからググればわかると思う。要するにテーブルを使ってテストケースのメソッドと値とかを書いていく。仕組みさえわかってしまえば、HTMLのテーブルを書くだけなので、デザイナーにも書けるところが魅力である。

個人的な考えでは、Seleniumのことをよく理解し、テストコードを思いつくくらいのプログラミングリテラシーがあるなら、コードで書くのも簡単なのではと思うが、実際はHTMLで書いた場合、人が見たときの確認のしやすさはあるんだろうと思う。どれくらいこの書き方が使われているかは知らない。

もう1つの方法としては、各言語別のラッパー的なライブラリを使う方法がある。PHPでは、PHPUnitにSeleniumTestCaseというのがあって、これを使うとコードを使って書く事が出来る。

public function testTitle() {
    $this->open('http://localhost:8808/');
    $this->assertTitle('Hello Selenium!');
    $this->assertElementContainsText('id=content', 'Hello');
}

こんな感じだ。この手法は少し間接的で、Selenium RCを一旦立ち上げておいた上で、コマンドラインなどからPHPを実行する。成功すると、コマンドラインで結果が表示される。さすがに、この方法はデザイナー向けではないかもしれない。プログラマーが最も使うのはこういうスタイルだと思う。

さて、今回のデザインのテストつまりは「きちんと表示がされているか」については、上記の方法ではなくPlayFrameworkのSeleniumテストを使って行う。お断りを入れておくと、別にPlayFrameworkでなければ出来ないとかそういうことでは全然ないので、一般的なSeleniumのデザインテストで使用可能な方法だと思う。

PlayFrameworkでのSeleniumを使うメリットはというと、

  • フレームワークに組み込まれていて環境構築が不要
  • フレームワークのテンプレートの書式がそのまま使える
  • テスト画面がグラフィカル

というのがある。

Seleniumのビューテストでは、Seleniumに用意されている変数をいろいろと使ったほうがいいように思う。

というのもデザインはいろいろな要素がめまぐるしく変化していくことが多く、テスト用の情報は一旦別で定義したほうがいいように思うからだ。

Seleniumの変数はこんな感じで定義できる

store(値, 変数名)

その他にも、属性値を変数に入れる命令storeAttributeなどいろいろある。これはSeleniumのドキュメントに詳しく載っている

[参照]Selenium Reference

レイアウトのテストを行う

さて、まずは要素の表示位置のテストを行ってみよう。

こんなデザインがあるとして、

テストするデザイン

これが段落ちしているかどうかのテストを行ってみる。ちなみに、左のボックスは「section-title」というID、右のボックスは「section-description」というIDがついている。

<div id="hello-section">
    <h2 id="section-title">Hello Selenium!</h2>
    <p id="section-description">Hello description!!</p>
</div>

Seleniumは、storeElementPositionTopというメソッドが存在していて、要素のtop位置を取得することが出来る。つまり、

#{selenium}
    open('/sites')
    // Test for layout variable
    storeElementPositionTop('id=section-title', GGG)
    assertAttribute('section-description@data', javascript{storedVars["GGG"]})
#{/selenium}

を行うと、テストが行える。ここで行っているのは#section-titleのtop位置と#section-descriptionのtop位置を取得して同じならテストをパスするというものだ。

これらは、Seleniumの用意されているメソッドを使う事でテストが可能だが、もっと細かいデザインのテストを行うには不足だ。
ということで、僕はJavaScript(jQuery)とdata属性(べつにdata属性でなくてもいいけど)、storeAttributeメソッドを使ったテストを推奨してみる。storeAttributeというのは、要素の属性の値を取得して変数に入れる事ができるメソッドだ。

まずは、先ほどのテストを書き直してみよう。
テスト用のJavaScriptを作成する。これは、jQueryを使って、対象のタグのdata属性に調べたいプロパティの値を挿入している。

// テスト用JavaScript
$(function() {
    $('#section-title').attr('data', $('#section-title').offset().top);
    $('#section-description').attr('data', $('#section-description').offset().top);
});

そしてこのJavaScriptはファイルで保存し、テストモードで起動しているときだけ読み込むようにする。こうすることで、既存の環境やHTMLには手を加えないでテスト対応を行う検査が可能になる。PlayFrameworkでならこんな具合だ

#{if play.Play.id == 'test' }
<script src="@{'/public/javascripts/vaqum_test.js'}" type="text/javascript" charset="${_response_encoding}"></script>
#{/if}

テストモードで起動中のサイトを確認すると、目的のh2タグやpタグにdata属性にtopのプロパティ値が入っているのがわかるはずだ。
これを、Selenium上で使う。

書き方としてはこんな感じ

#{selenium}
    open('/sites')
    // Test for layout variable
    storeAttribute('section-title@data', VAQUM)
    assertAttribute('section-title@data', javascript{storedVars["VAQUM"]})
    assertAttribute('section-description@data', javascript{storedVars["VAQUM"]})
#{/selenium}

Seleniumでは、変数を${VAQUM}のように書く事もできるが、PlayFramework上ではテンプレート言語とバッティングしてしまうため、上記のように記述。
storeElementPositionTopみたいな組み込みメソッドを使わないことで、CSSのプロパティが入ったdata属性をstoreAttributeで取得することが出来、それを使ってテストを実行することが出来る。

テスト成功

試しに、失敗するようにpaddingの値とかを変えてみよう。
すると、このように段落ちしてしまう。

段落ちしたデザイン

その後、テストケースを実行すると、赤い表示が出てエラーであることが確認された。

どうもテストが失敗したっぽい

どういう感じでプロパティの値がずれているかも出るので、非常にわかりやすい。

やはり、テストが失敗した

topの位置が下になっていることがわかる

上記の方法を使う事で、例えばテーブルの行の色を1行1行入れ違いにするデザインのテストなんかも行える。
ちょっとやってみよう。

テーブルのデザインテスト

一列ごとに色が変わるテーブル

上記表示になる、こんなHTMLがあったとする

<table id="sample-table">
    <tr>
        <td>JavaScript</td>
        <td>Dart</td>
    </tr>
    <tr>
        <td>Java</td>
        <td>Scala</td>
    </tr>
    <tr>
        <td>XHTML</td>
        <td>HTML5</td>
    </tr>
</table>

これは、

$(function() {
    $('#sample-table').each(function(){
        $(this).find('tr:odd').addClass('odd');
        $(this).find('tr:even').addClass('even');
    });
});

こんなJavaScript(jQuery)によって交互に色分けされる。ここで、class属性を見てきちんとclassが出力されているかのテストを行ってもいいけど、CSSの指定がおかしいせいで、クラスは正しいのに表示がされないという可能性が考えられる。

そこで、

// テスト用JavaScript
$(function() {
    $('#sample-table').each(function(){
        $(this).find('tr:odd>td').attr('data', $.fmtColor($(this).find('tr:odd>td').css('background-color')));
        $(this).find('tr:even>td').attr('data', $.fmtColor($(this).find('tr:even>td').css('background-color')));
    });
});

こんなテスト用のJavaScriptを用意して、data要素にbackground-colorを書き込んでみる。
色の値をテストするので、jquery.color.utilsのような色変換のjQueryもテストモードのみで読み込んでおこう。
Seleniumのテストコードは、下記の通り。

#{selenium}
    open('/sites')
    // Test for table sell color
    store('#333333', TABLE_COLOR_ODD)
    store('#000000', TABLE_COLOR_EVEN)
    assertAttribute('xpath=//table[@id='sample-table']//tr[1]/td[1]/@data', javascript{storedVars["TABLE_COLOR_EVEN"]})
    assertAttribute('xpath=//table[@id='sample-table']//tr[2]/td[1]/@data', javascript{storedVars["TABLE_COLOR_ODD"]})
#{/selenium}

これがパスすると、まあ色はちゃんと交互に出ているよねみたいな確認が行える。

これらビジュアルに関わるテストというのは、大半が目視でやっているのが現状だ。今後ページ数がどんどん増えるサイトや、いろいろな画面遷移のパターンがある複雑なWebアプリケーションは、目視テストだけでも相当大変だと思う。それに、1日に1回テストを行う事で、デザインの崩れを早期に発見できるだろう。目視テストは完全になくすことは出来ないが、かなりの部分のテストをこういった方法のデザインテストでまかなえるのではないだろうか。

今後の課題

最後に今後の課題を挙げておく。

  • テスト用の一時保持属性はdata属性でいいのかどうか? 他の要素に別でマークアップして書き出すという手法も考えられなくないが、状態の変化を確実につかむには、対象の要素それ自体に記述するのがいいだろうと思う。style属性でもいいと思う。
  • Selenium側からjQueryを直接使って対象のスタイルにアクセス可能かどうか? これもまだ未調査。できるなら、それのほうがスマートかも。
  • 多分テストしやすいHTMLコードとしにくいHTMLコードがあるはず。どういう部分がポイントだろう?
Read More