VOYAGE GROUP エンジニアブログ

voyagegroup_techのブログ
VOYAGE GROUPエンジニアブログです。

programming

Phalcon Punch!!!

こんにちは。VOYAGE MARKETINGで学生インターンをしている西尾@blackstar240と申します。

突然ですが、弊社にはCREEDという経営理念があり、その中の一つに
『圧倒的スピード。』
という言葉があります。
本日は『圧倒的スピード。』を誇るPHPのフレームワーク、Phalconを使い、テストプロダクトを作ったことについて書こうと思います。

はじめに


はじめに、Phalconについて簡単にご紹介します。
Phalconとは、C言語で実装されたPHP Extentionであり、
こちらのベンチマーク結果からもお分かりのように、PHPのフレームワークの中では『圧倒的スピード。』を誇るフレームワークです。
BenchExample
また、devtoolsという、スケルトンコードを生成する便利なスクリプトのコレクションもあり、
これまた『圧倒的スピード。』で実装が出来そうな予感がします。
早速、こちらを参考にフレームワークとdevtoolsのインストールを行い、
さくっとプロダクトを作ってみようと思います。

雛形作り


まず、devtoolsを使ってMVCモデルの雛形を作ります。
Webサーバーのドキュメントルートで
$ phalcon project [project name]
コマンドを叩いて下さい。
そうすると、プロジェクトの雛形が完成します。
ProjectHinhagata
続いて、controllerの雛形を作ります。
[project name]ディレクトリ直下で
$ phalcon controller [controller name]
コマンドを叩いてください。
すると、Phalconの命名規則にのっとった名前に変換され、controllerの雛形も完成されます。
ControllerHinagata
続いては、controllerにecho文を書き、正しく呼び出されているか確認してみようと思います。。。が、

routing


まだ、routingを書いていません!!
これでは、せっかく書いたcontrollerも動くはずがありません。
ここは落ち着いてroutingを記述しましょう。
まず、configディレクトリ配下にroutes.phpファイルを作成します。
config/route.phpの記述例がこちらになります。

add("/phalcon", array(
    'controller' => 'phalcon',
    'action' => 'phalcon',
));

$router->add("/punch", array(
    'controller' => 'phalcon',
    'action' => 'punch',
));

return $router;

続いて、confifg/service.phpに以下の記述を追記します。

/**
 * add routing capabilities
 */
$di->set('router', function(){
    require __DIR__.'/routes.php';
    return $router;
});

$diコンテナにsetすることで依存関係をすっきりさせることが出来ますね!!

これで、routingは完成しましたが、今のままだと、
Built-in serverではhttp://localhost:9000/index.php?_url=$1のリクエストで開発しつづけなければなりません
そこで、先ほど作ったconfig/routes.phpに以下の記述を追記しましょう。

$router->setUriSource($router::URI_SOURCE_GET_URL);
$router->setUriSource($router::URI_SOURCE_SERVER_REQUEST_URI);

これで、http://localhost:9000/phalconのような
綺麗なURIでの実装も可能となりました。

テンプレートの使い分け


ここまで、controllerから直接echo文を実行し、挙動を確認してきました。
続いて、viewのテンプレートを各コンテキストごとに使い分けてみようと思います。
まず、app/views/layoutディレクトリにcontrollerと対応する名前の.volt(.phtml)ファイルを作成します。
ViewsHinagata
controllerからメソッドでviewファイルへのレンダリングを記述します。

view->setTemplateAfter('phalcon');
    }

    public function punchAction()
    {
        $this->view->setTemplateAfter('punch');
    }

}

ここまで記述した所で、実際の挙動を見てみましょう。

Phalcon

Punch

Finish

見事、ファルコンパンチが繰り出されました!!

感想


今回、Phalconを使ってテストプロダクトを作成致しました。日本語でPhalconについて紹介する記事がまだまだ少ない中、開発において必要不可欠な要素についてご紹介することが出来たので、Phalconを使ってみたいと思っている方の一助となれば幸いです。
Webサービス作りにおいて、フレームワークの選択が重要となる昨今、今回のテストプロダクト作りで得た知見も活かし、今後も努力を重ねていく所存であります。
また、公式ドキュメント上にはサンプルコードも掲載されているので、よろしければお試し下さい。
最後に、このエントリーの内容を実装したものがこちらになります。
https://github.com/Nissy0409/phalcon

最後までお付き合いいただき、ありがとうございました。

プライベートメソッドのテストは必要ない!!

こんにちは、RPAの関口です。

最近週に一度、来年の新卒達と一緒にTDDをやりながらワイワイガヤガヤしております。そのなかで「プライベートメソッドのテストはどうすれば良いのか?」 という話題がありました。プライベートメソッドのテストについては
プライベートメソッドのユニットテストは書かないもの?
がよくまとまっていると思います。プライベートメソッドのテスト方法について考える中で「TDDの手順に従えばプライベートメソッドのテストがしたくなることは無い」のではないか?と思うようになりました。

プライベートメソッドはリファクタリングの結果現れる!

数値の配列を渡すと平均を計算して返してくれる機能を持ったクラス、AverageCalculatorを作りたいとします。平均計算の手順をまとめると以下のようになります。
  1. 配列の合計値を計算する
  2. 合計値を配列の要素数で割って返す
ここで「よし!まずは配列の合計値を計算する機能をつけよう!」と思って次のようなテストを書くと

calcSumメソッドの可視性について
  • 途中計算でしか使わない合計値の計算は、プライベートメソッドにしたい
  • でもプライベートメソッドだとテストのためにReflectionを使う必要があって面倒
という悩みが発生してしまいます。最初「平均値を返すメソッドを追加」しようと思っていたのに「合計値を返すメソッドを追加」しようとしたためです。平均値計算の手順はあくまで手順、初心に帰って平均値を返すテストを書きましょう。
そして合計値計算のメソッドは分離したいなぁと思ってリファクタリングする際に、calcSumという名のプライベートメソッドが自然と現れます。 このプライベートなcalcSumメソッドのためにテストを追加する必要はありません。中身がどのような実装になっていようと外部から呼び出されるcalcAverageメソッドが期待通りの結果を返してくれれば良いのです。またcalcSumメソッドはパブリックなcalcAverageメソッドを通じてテストされている、とも言えます。
 
パブリックメソッド経由でテストする
多くの場合、そのクラスのパブリックメソッド経由でプライベートメソッドのテストも同時に行えます。テストできているか不安があるならカバレッジを測定しましょう。

もしcalcSumの代わりに大変複雑な処理だった時はどうするの?

そのような場合、いきなりcalcAverage相当のメソッドを作るの事は難しいでしょう。一旦calcSumのメソッドの追加を行う必要が出てくるかと思います。もちろんテスト付きで。 でもそのcalcSumはAverageCalculatorクラスがやるべき事なのでしょうか?実はSumCalculatorという名の新しいクラスを作って、その中にパブリックメソッドとして書くのが正しいのではないでしょうか?
別クラスのパブリックメソッドとする
プライベートなメソッドのテストを書きたいということは、実はテスト対象の責務が多すぎることを示唆している場合があります。テストがどうしても書きたい場合は、その責務はテスト対象のプライベートな振る舞いでは無く、誰かのパブリックな振る舞いなのでしょう。テスト対象のプライベートメソッドを「クラスの抽出」や「メソッドの移動」を使って、テスト対象のコラボレータのパブリックメソッドとして抽出し、普通にパブリックメソッドとしてテストしましょう。

他の方法

ここまで書いておいてなんですが
  1. calcSumのテストと実装をパブリックで書く
  2. calcAverageのテストと実装をパブリックで書く
  3. リファクタリングでcalcSumメソッドの可視性をプライベートにして、calcSumのテストは削除
という手もあるかと思います。というかこちらのほうが自然ですね。せっかく書いたテストを捨ててしまうのは勿体無い気がしますが、実装中に書いたテストと最後に残すテストを同じにしなければならないというルールはありません。calcSumはcalcAverageを通してテストされているので良しとしましょう。

そんなこんなでプライベートメソッドのテストは必要ない!!・・・のではないかと最近思い始めました。

PerlのO/Rマッパ、 Teng の便利な使い方のご紹介

こんにちは、Research Panel Asiaの大山です。 今回は、Perlで最近ホットなO/Rマッパ、 Teng の便利な使い方を共有いたします。

みなさんは、

sqlite> .tables
user_log_2013_03  user_log_2013_04
user_log_2013_05  user_log_2013_06
user_log_2013_07  user_log_2013_08
こんな要領で、定期的にDBにテーブルが作られていくような仕組みを見たことはあるのでしょうか。 最近のMySQLでは著しくパーティショニング機能が改善されていますが、少し前のバージョンを使用していると 制約と制限 の都合で選びにくかったり、テーブルが巨大になるとスキーマの変更が容易ではなくなるなどの事情で、このような構造を選択する開発現場もあるのではないかと思います。

こういった、決まったスキーマの別名テーブルを扱う状況では、O/Rマッパの実装次第ではなかなか面倒なことになります。素SQL+DBIで実装するのも当然有効な選択肢と思いますが、Tengを使うととても簡単に解決することが可能です。

[MyModel.pm]


package MyModel;
use parent qw(Teng);
1;

[MyModel/Schema.pm]


package MyModel::Schema;
use strict;
use warnings;
use Teng::Schema::Declare;

sub get_table {
    my ($self, $table_name) = @_;
    $table_name =~ s/\d{4}\_\d{2}$/xxxx_xx/;
    $self->SUPER::get_table($table_name);
}

sub get_row_class {
    my ($self, $table_name) = @_;
    $table_name =~ s/\d{4}\_\d{2}$/xxxx_xx/;
    $self->SUPER::get_row_class($table_name);
}

table {
    name 'user_log_xxxx_xx';
    pk 'id';
    columns qw( id hoge fuga );
    row_class 'MyModel::Row::UserLog';
};

1;

Teng::Schema の中で、テーブルと行オブジェクトを扱うところの実装がとても単純になっているので、 このように get_table と get_row_class の2つのメソッドをオーバーライドしてあげるだけで、 扱うテーブル名によってちょっとした挙動の変更ができます。

こうすると、


my $handle = MyModel->new({ connect_info => ['dbi:SQLite:']});

my $item1 = $handle->insert(
    user_log_2013_07 => { hoge => 'hoge' }
);
# => MyModel::Row::UserLog インスタンス

my $item2 = $handle->insert(
    user_log_2013_08 => { hoge => 'hoge' }
);
# => MyModel::Row::UserLog インスタンス

$item1->update({ fuga => 'fuga' });
$item2->update({ fuga => 'fuga' });

$item1->delete;
$item2->delete;
といった具合に、いつものような行オブジェクトの生成ができるので、あとは MyModel::Row::UserLog のようなところに実装を追加していけばよいでしょう。 簡単ですね。

それでは、またお会いしましょう。ごきげんよう。

jQueryを用いた美しいajax処理

こんにちは、Research Panel Asia の関口(@takkyuuplayer)です。

jQueryを用いた非同期処理の待ち合わせについて書きたいと思います。 昨今WebにリッチなUIが求められていますが、その際によく遭遇するのが次のような処理です。

「処理A(非同期)が終わってからその結果を元に処理Bを行いたい。」

jQuery.getなんかでデータを取ってくる際には必須ですね。このような非同期処理の待ち合わせはjQuery.Deferredを使うと簡単に実現可能です。

jQuery.Deferred

ex1.html
 
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
</head>
<body>
<ul id="async_test">
<li>0</li>
</ul>
<script language="javascript">
var time_ms = 1000;
var ajax = function(arg) {
var deferred = $.Deferred();
setTimeout(function() {
deferred.resolve(arg+1);
}, time_ms);

return deferred.promise();
}

var tmpl = _.template('<li><%= count %> : <%= wait %>ms later</li>');
ajax(0).done(function(ret) {
$('ul#async_test').append(tmpl({count:ret, wait:time_ms}));
});

</script>
</body>
</html>

また、jQuery.whenを使って「処理Aと処理A’が両方終わってから、処理Bを行う」事もできます。

jQuery.when

ex2.html : ※下記にはex1.htmlとの変更部分のみ示してあります。
      var tmpl = _.template('<li><%= count1 %> / <%= count2 %> </li>');
$.when(ajax(0), ajax(100)).done(function(ret1, ret2) {
$('ul#async_test').append(tmpl({count1:ret1, count2:ret2}));
});

便利ですね。任意個数の待ち合わせを行うにはどうすればよいでしょうか?引数を配列にすることで、「処理A, A’, A’’, ....が終わってから処理Bを行う」事ができます。

複数処理の同時待ち合わせ

ex3.html : ※下記にはex1.htmlとの変更部分のみ示してあります。
      var deferreds = [];
for (var i=0; i<20; i++) {
deferreds.push(ajax(i*10));
}

var tmpl = _.template('<li><%= text %></li>');
$.when.apply(null, deferreds).done(function() {
var rets = Array.prototype.slice.call(arguments);
$('ul#async_test').append(tmpl({text:rets.join(' / ')}));
});

ただし、実際にex3.htmlのようなコードを書くことは稀でしょう。非同期処理はjQuery.getやjQuery.postといった、HTTPリクエストであることが多いと思います。ex3.htmlでは20ものHTTPリクエストを同時に発生させてしまいます。そこで求められるのは「処理Aが終わったら処理A’を行い、それが終わったら処理A’’を行い・・・最後に処理Bを行う」事です。私は悩んだ末以下のように再帰で解決しました。

順番に待ち合わせる

ex4.html : ※下記にはex1.htmlとの変更部分のみ示してあります。
      var deferreds = [];
var max = 20;
for (var i=0; i<max; i++) {
deferreds.push(function(arg) { return ajax(arg*10); });
}

var rets = [];
var do_next = function() {
if(deferreds.length == 0 ) {
var tmpl = _.template('<li>Finished</li>');
$('ul#async_test').append(tmpl({text:rets.join(' / ')}));

return;
}

var arg = max - deferreds.length;
var deferred = deferreds.shift();
deferred(arg).done(function(ret) {
rets.push(ret);
var tmpl = _.template('<li>Process : <%= text %></li>');
$('ul#async_test').append(tmpl({text:rets.join(' / ')}));
do_next();
});
};

do_next();

ajaxに渡す引数の処理がいけていませんが、deferredsの中に入れるのをnewしたオブジェクトにしておけば解決できるでしょう。

まとめ

jQueryで可能なのはDOM操作だけではありません。Deferredを始めいろいろ便利機能が揃っています。時間がある時に一度jQuery API ドキュメントを眺めてみてはいかがしょうか?

内定者研修は朝から晩までペアでTDD漬け

これまで各自で課題を進めてきましたが、今日は終日集合研修ということで朝から晩までペアでTDD漬けです。

▼お題

▼環境設定

▼講師
@brtRiverがメインだったのですが、@bash0C7@ajiyoshiが途中からレビューに乱入してきましたw
13内定者研修_tdd_20130111


▼ショッピングカートのお題を改変
ショッピングカートのお題は在庫の概念が入ってると複雑になるよねーということで下記のように改変しました。
# ECサイト
1. 商品を1つ指定し、購入することができる。
* 商品
    * 商品名、価格
2. 商品を複数指定し、購入することができる。
* 消費税は商品の合計金額の5%
3. 商品を購入するときに配送方法、支払い方法を選択できる。
* 配送方法
    * 通常配達: 配送料 0円
    * お急ぎ便: 配送料 200円
* 支払い方法
    * クレジットカード
    * 代金引換
    * コンビニ・ATM払い
* 消費税は商品の合計金額に配送料を加えた金額の5%
4. オプションは組み合わせで制限がある。
* お急ぎ便を選択した場合、コンビニ・ATM払いは選択できない
5. 商品の合計金額によって配送料金が変わる。
* 商品の合計金額が10,000円以上の場合はお急ぎ便の配送料が0円になる。
6. 数量によって割引になる商品がある。
* RedBullを24個以上購入する場合、単価が20%割引になる。
7. 購入日付によって割引になる商品がある。
* 2月1日から2月末までの期間にダウンジャケットを購入する場合、単価が15%割引になる。
* 3月1日から3月末までの期間にダウンジャケットを購入する場合、単価が30%割引になる。


19:30時点でピザを食べながらまだまだ続くww
13内定者研修_tdd_20130111_2


追記
21時過ぎから@ajiyoshiのライブコーディングが始まり、結局23時過ぎまで盛り上がりました。
計13時間の耐久TDDペアプロ研修お疲れ様でした。
記事検索
QRコード
QRコード