「それより俺のテストを見てくれ。こいつをどう思う?」「すごく・・・日本語です・・・・」

「それより俺のテストを見てくれ。こいつをどう思う?」

「すごく・・・日本語です・・・・」


オラっち、日本語でテストメソッド名を定義するんダーーイ

PHPって、メソッド名を日本語で定義できます(文字コードUTF-8なら)。PHPでTDDワークショップで初めて知りました。
ということでテストクラスのメソッドを日本語で定義してたんですが、普通はメソッド名が日本語になってたら驚きますよね。

ということで、日本語でメソッドを定義することについて、ちょっとまとめてみます。

日本語で関数定義とかマジ勘弁☆

日本語メソッド名に対する拒否反応はおおよそ以下のような感じ。

とにかくありえない
  • ありえん
  • なにこれキモい
  • (無言で)rm -rf test/unit/*

気持ちはわかります。具体的には次のどちらか、もしくは両方になるんじゃないでしょうか。

ひらがなと漢字がありえない
  • 日本語ネイティブでない人が関わる可能性がある(可能性を無視できない)

確かにこの場合日本語はナシでしょう。(こういうケースが多いと思いますが)
ドキュメントとしてのテストコードの存在意義が潰されてしまいますからね。

自然文がありえない
  • たとえ英語でであっても "testHogeShouldBeConvertedToFugaWheneverFlagIsTrue()" みたいなのはメソッド名として受け入れられない。

あいやしばらく!
こういうケースでは "testHogeParser()"みたいなテストメソッドの中に大量のassersionが詰め込まれて、そのテストメソッドが何をテストしたいのかわかりづらくなっている場合があり、日本語にするかどうかは別としても一考の余地ありだと思っています。

でもちょっと待って、そのテストメソッドはなんのため?

テストメソッドって(別のコードから)呼び出さないよね

このメソッドを呼び出すために日本語をカタカタ打ち込んだりする必要はないです。
後から読むだけであり、あとから読むことが重要です。
通常のメソッドと違ってコードの文脈の中で目にすることはなく、見るのは定義本体のみで、通常のメソッド名に求められるような「簡潔さ」は求められません。

詳細に保証(説明)したいことがある

テストメソッドは1つの事柄をテストすべきで、メソッド名はテストで保証(説明)したい事柄を明確にするものであるべきです。
これは1つのテストメソッドにassertionを1つだけとするのが良いとされる理由の一つです。
「そのメソッドが何をテストしているのか」が重要になります。

仕様書を見るのに近い感覚で後から見ることが多い

テスト対象の仕様やインターフェースを保証するドキュメント的な役割を求めてテストケースを見ることがあります。
アウトラインからテストメソッド一覧を俯瞰する等の場合、一目で内容が頭に入ると嬉しいです。

じゃぁ日本語で書いたらよくね?

上に挙げたような理由で、テストが日本語(英語にしても自然文に近い形)で書いてあると得すると考えています。

表現しやすい&伝わりやすい

ドキュメントやWikiを日本語で書く感覚でテストメソッドを日本語で書いています。
対象処理の仕様や気を付けたい罠動作を他者に伝えたいとき、テストが意図することを表現しやすいです。

一覧時の一目で分かる感

テストメソッド一覧を出したり、テストの失敗レポートが出た時などにその効果を感じます。

*ただし

「環境が許せば」です。日本語ネイティブ以外の人が触れる可能性を無視できないなら日本語にするのは難しいでしょうし、「生理的に受け付けない」というのも無視できない理由になり得ます。
ものにもよります。「Githubで公開」的なものは全部英語でやったほうが得が多いでしょう。

まとめ

つまり日本語がどうどいうよりは

  • テストの粒度を小さく保とう
  • テストが何をテストしているのか明確にしよう

追記 2012/04/26

メソッド名に日本語を使うとエディタによってはシンタックスハイライトが正しく動作しないケースがあることが判明。(なにをいまさら)
英語派に寝返る。

自分用にインストールしたpearを使おうとしてredeclareみたいに文句言われた場合

丁度ファイル入出力が絡むテストを書いて、めんどくさいなー、と思っていたところに
http://blog.yuyat.jp/archives/1280
↑でvfsStreamというものが紹介されていたので試そうとしたらひっかかったのでメモ。

とりあえずCache_Casualのテストを実行してみる

tests/unit/bootstrap.phpに自分用pearのインクルードパスを追加。

<?php
set_include_path('/home/calpo/share/pear/php' . PATH_SEPARATOR . get_include_path());
$ phpunit --bootstrap bootstrap.php Cache/Casual/Container/FileTest.php

PHP Fatal error:  Cannot redeclare class File_Iterator in /home/calpo/share/pear/php/File/Iterator.php on line 196
Fatal error: Cannot redeclare class File_Iterator in /home/calpo/share/pear/php/File/Iterator.php on line 196

自分用のpearディレクトリは後ろに設定する

<?php
// ×
set_include_path('/home/calpo/share/pear/php' . PATH_SEPARATOR . get_include_path());

// ○
set_include_path(get_include_path() . PATH_SEPARATOR . '/home/calpo/share/pear/php');

実行できるようになった。

$ phpunit --bootstrap bootstrap.php Cache/Casual/Container/FileTest.php
PHPUnit 3.5.3 by Sebastian Bergmann.
..
Time: 0 seconds, Memory: 5.25Mb
OK (2 tests, 2 assertions)

PyrusでプロジェクトローカルなPEARライブラリインストール : PHP Advent Calendar jp 2011 Day 10

PHP Avent Calendar jp 2011の10日目、[twitter:@calpo22]です。
→前日「includeとextractの組み合わせでテンプレート処理を作る。PHPのAdvent Calender #9 - それマグで!

※追記 2012/09/16
PHPが古いとかの事情がなければ、今はライブラリの依存管理にはcomposer使うのがいいと思います。
取り急ぎ手元でPHPUnit使いたいとかであれば http://qiita.com/items/81085381c4281e498cde で一発。


みなさんPEAR使ってますか?

PEARのライブラリを普通にインストールすると/usr/binとかに入るんですが、そうじゃなくて自分用に~/binに入れたいとか、フレームワークのvendorディレクトリに入れたいとかありますよね。
これで結構苦労していたところ、今年のPHPカンファレンスで「Pyrus使うと捗るよ」みたいな話を聞きくことができました。
pearの後継となるパッケージマネージャらしく、「なにそれすごい!これでかつる!」と期待に胸をふくらませつつ家に帰って試してみたところ・・・

とにかく日本語の情報が全然無い

という状態でなんか凹みました。

でも、Pyrusだとpearよりもプロジェクトローカルなライブラリのインストールがやりやすいです!
ということでPyrusによるPEARライブラリインストールのご紹介。

まずは俺ローカルなPEARライブラリインストール

~/mypyrusに自分用のPEARライブラリをインストールしてみます。
(必ずしも俺ローカルが必要なわけではないですが、整理しやすかったのでこのようにしています)

pyrus.pharダウンロード

pyrus.phar自体はどこにあってもいいんですが、とりあえず~/mypyrusに入れておくことに。

$ mkdir mypyrus
$ cd mypyrus/
$ wget http://pear2.php.net/pyrus.phar
pyrusの初期設定

最初にpyrusを実行すると初期設定が行われ、パッケージのインストール先のディレクトリ等が設定されます。

$ php pyrus.phar install
Pyrus version 2.0.0a3 SHA-1: BE7EA9D171AE3873F1BBAF692EEE9165BB14BD5D
Pyrus: No user configuration file detected
It appears you have not used Pyrus before, welcome!  Initialize install?
Please choose:
  yes
  no
[yes] :
# ↑yesなのでそのままEnter
Great.  We will store your configuration in:
  /home/calpo/.pear/pearconfig.xml
Where would you like to install packages by default?
[/home/calpo/mypyrus] :
# ↑にインストールなのでそのままEnter

$ ls ~/.pear/pearconfig.xml
/home/calpo/.pear/pearconfig.xml

~/.pear/pearconfig.xmlが作成されます。

config-showコマンドで設定を確認できます。

$ php pyrus.phar config-show
Pyrus version 2.0.0a3 SHA-1: BE7EA9D171AE3873F1BBAF692EEE9165BB14BD5D
Using PEAR installation found at /home/calpo/mypyrus
System paths:
  php_dir => /home/calpo/mypyrus/php
  ext_dir => /usr/lib64/php/modules
  cfg_dir => /home/calpo/mypyrus/cfg
  doc_dir => /home/calpo/mypyrus/docs
  bin_dir => /usr/bin
  data_dir => /home/calpo/mypyrus/data
  ・
  ・

bin_dirが/usr/binになっているので自分用に変更しましょう。
(~/mypyrus/.config が生成されます)

$ php pyrus.phar set bin_dir /home/calpo/mypyrus/bin
$ php pyrus.phar config-show
  ・
  ・
  bin_dir => /home/calpo/mypyrus/bin
  ・
  ・
PHPUnitインストール

準備ができたので俺専用PHPUnitをインストールしてみましょう。

$ php pyrus.phar set auto_discover on
$ php pyrus.phar channel-discover pear.phpunit.de
$ php pyrus.phar install phpunit/PHPUnit
Pyrus version 2.0.0a3 SHA-1: BE7EA9D171AE3873F1BBAF692EEE9165BB14BD5D
Using PEAR installation found at /home/calpo/mypyrus
Sorry, phpunit/PHPUnit references an unknown channel pear.symfony-project.com for pear.symfony-project.com/YAML
Do you want to add this channel and continue?
Please choose:
  yes
  no
[yes] :
# ↑途中チャンネルの追加について問われます
  ・
  ・
pear.phpunit.de/PHP_Invoker depended on by pear.phpunit.de/PHPUnit

どん!
入りました。

$cd ~/mypyrus
$ ls
bin  cache  docs  downloads  php  pyrus.phar

$ ls bin/
phpunit

$ ls php/
File  PHP  PHPUnit  SymfonyComponents  Text
include_pathの設定

早速bin/に入ったphpunitを実行しようとしても、必要なディレクトリがinclude_pathに設定されていないので怒られます。

$ cd ~/mypyrus/bin
$ ./phpunit
Warning: require(PHPUnit/Autoload.php): failed to open stream: No such file or directory in /home/calpo/mypyrus/bin/phpunit on line 42

$ php -i | grep include_path
include_path => .:/usr/share/pear:/usr/share/php => .:/usr/share/pear:/usr/share/php

今回はお手軽に~/mypyrus/bin/phpunit に設定を追加して、~/mypyrus/phpをinclude_pathに入れてしまいます。

<?php
// ↓をrequireの前に追加
set_include_path(
	realpath(dirname(__FILE__).'/../php')
	. PATH_SEPARATOR . dirname(__FILE__)
	. PATH_SEPARATOR . get_include_path());

ただライブラリによってbin/に入るファイルがphpスクリプトだったりシェルスクリプトだったり、PHP_CLASSPATHみたいな独自の環境変数を設定する必要があったりとなんかもう・・・
php.iniが変更できるならそれが一番確実です。

$ cd ~/mypyrus/bin
$ ./phpunit --version
PHPUnit 3.6.4 by Sebastian Bergmann.

いぇーい

プロジェクトローカルなPEARライブラリインストール

続いて適当なプロジェクトのvendorディレクトリにプロジェクト専用PEARライブラリをインストールしてみます。

ディレクトリの設定

setとかのコマンドの前にディレクトリを指定してあげると、そこローカルな作業と設定ができます。
(そのディレクトリに.configが出来上がります)

例えば~/project_hoge/vendorにインストールする場合・・・

$ php pyrus.phar /home/calpo/project_hoge/vendor set bin_dir /home/calpo/project_hoge/vendor/bin
$ php pyrus.phar /home/calpo/project_hoge/vendor config-show
Pyrus version 2.0.0a3 SHA-1: BE7EA9D171AE3873F1BBAF692EEE9165BB14BD5D
Using PEAR installation found at /home/calpo/project_hoge/vendor
System paths:
  php_dir => /home/calpo/project_hoge/vendor/php
  ext_dir => /usr/lib64/php/modules
  cfg_dir => /home/calpo/project_hoge/vendor/cfg
  doc_dir => /home/calpo/project_hoge/vendor/docs
  bin_dir => /home/calpo/project_hoge/vendor/bin
  ・
  ・
インストール

チャンネルの設定などもプロジェクトローカルなのでまた設定します。

$ php pyrus.phar /home/calpo/project_hoge/vendor set auto_discover on
$ php pyrus.phar /home/calpo/project_hoge/vendor channel-discover pear.phpunit.de
$ php pyrus.phar /home/calpo/project_hoge/vendor install phpunit/PHPUnit
  ・
  ・
pear.phpunit.de/PHP_Invoker depended on by pear.phpunit.de/PHPUnit

$ ls ~/project_hoge/vendor/bin
phpunit

こんな感じでプロジェクトローカルなPEARライブラリをほいほいインストールしていけます。

さらに・・・

package.xmlをmakeしてプロジェクトごとに必要なパッケージをまとめて管理したりと夢がひろがるらしいので日本語の情報ください :)

みんなでつかおうPyrus!


明日は[twitter:@kokkekun]さんです。

session_set_save_handler()でカスタムセッションハンドラ設定したらFatal error: Class not found

複数のwebサーバーでセッション情報共有するのにmemcached使うためにカスタムセッションハンドラ設定したら
ばっちり定義してるはずのクラスがなかったことになっててびびった話。

問題のあったスクリプト

  • クラスHogeを定義
  • writeハンドラでnew Hoge()
<?php
class Hoge {}
class MySessionHandler {
    public function open($save_path, $session_name){ /*処理*/ }
    public function close(){ /*処理*/ }
    public function read($id){ /*処理*/ }
    public function write($id, $sess_data){
        $hoge = new Hoge();
    }
    public function destroy($id){ /*処理*/ }
    public function gc($maxlifetime){ /*処理*/ }
}

$s = new MySessionHandler();
session_set_save_handler(array($s,'open'), array($s,'close'),
	array($s,'read'), array($s,'write'), array($s,'destroy'), array($s,'gc'));

session_start();
$_SESSION['fuga'] = 'abc';

ごらんの有様だよ

Fatal error: Class 'Hoge' not found in /xxx/xxx/sessiontest.php on line 8

Hogeってすぐそこに定義してるじゃん! Σ(・ω・;)

エラーの原因

どこでエラーになってるか

session_set_save_handler()の引数(callback $write)で指定した処理の実行時にエラーが起きています。

なんでエラーになってるか

http://php.mirror.camelnetwork.com/manual/ja/function.session-set-save-handler.php#refsect1-function.session-set-save-handler-notes

PHP 5.0.5 以降、write ハンドラおよび close ハンドラはオブジェクトが破棄されたあとにコールされます。 そのため、セッション内でデストラクタを使用可能ですが、 ハンドラ内ではオブジェクトを使用できません。

オブジェクトが破棄されて、定義されていたクラスやインスタンスがなかったことになっています。
(requireの状態は残ってるようで、require_onceしても対象のクラスファイルが再度読み込まれたりはしない)

解決方法

デストラクタでsession_write_close()

オブジェクト破棄の前にwriteハンドラコールすればよい。
http://php.mirror.camelnetwork.com/manual/ja/function.session-set-save-handler.php#refsect1-function.session-set-save-handler-notes

この「ニワトリが先かタマゴが先か」の問題を解決するために、 デストラクタから session_write_close() を コールすることが可能です。

こんな感じでOK
<?php
class MySessionHandler {
	// 省略 //

	// ↓追加
	public function __destruct(){
		session_write_close();
	}
}

SSLでエラー ssl_error_rx_record_too_long (-12263)

ブラウザにssl_error_rx_record_too_longと出力されて接続できない。

エラーログには↓のように出る。

[Mon Sep 19 22:46:23 2011] [error] [client xxx.xxx.xxx.xxx] Invalid method in request \x16\x03\x01

一般的な原因

以下のような理由でhttpd.confのSSL設定部分を通ってないことが原因

  • バーチャルホストの設定で
    • ポート番号の設定間違い
    • IPアドレスの設定間違い
    • 別ファイルにしたssl.confのinclude忘れ
    • 意図とは違うバーチャルホストの設定を通っている(後述)
    • 設定は正しいがhttpdの起動に問題 (← 今回はコレ)

設定は正しいのにSSL関連の設定部分を通らないケース

httpd.confに

<IfDefine SSL>
 ・
 ・
</IfDefine>

と書いてあるのに"SSL"がDefineされていなかったというオチ。

-DSSLオプションつきでhttpdを起動しないといけない

/etc/sysconfig/httpd を作成

OPTIONS="-DSSL"

おまけ)意図とは違うバーチャルホストの設定を見てしまう件

ネームベースのバーチャルホストを設定している場合に問題になります

SSL接続時は、ホスト名も暗号化されています。
なので、SSLの設定を読み込んでからでないとホスト名を特定できません。
でもネームベースのバーチャルホストの場合、ホスト名が分からないのでバーチャルホストの設定を読むことができません。

先頭のバーチャルホストの設定が使われます

このとき、とりあえずhttpd.confで最初に定義してあるバーチャルホストの設定が使われます。
(ホスト名が決まったらネームベースバーチャルホストの設定も有効になります)

つまり

目的のバーチャルホストの設定だけがんばってもだめで、先頭のバーチャルホストのSSL設定もちゃんとされている必要があります。

quickrun: Specified outputter is not registered: buffer

久しぶりに:BundleInstall!して、quickrun.vimがVersion0.5.0になったら動かなくなっちゃったけど、
vim scriptもgitもよく分かってなかったので前のバージョンを使うようにするのに苦労したからメモ。

[追記 20110906]
ちゃんと新しいvimを使ってればこんなことにはならないと考えます。
(コメント参照)

quickrunできなくなっちゃった

Vundleでquickrun.vim導入していました。

Bundle 'quickrun.vim'

ふと思い立って:BundleInstall!で更新したところquickrunでエラーが出るようになってしまいました。

/home/calpo/.vim/bundle/quickrun.vim/autoload/quickrun/outputter/browser.vim の処理中にエラーが検出されました:27:
E475: 無効な引数です: 77
quickrun: Specified outputter is not registered: buffer
続けるにはENTERを押すかコマンドを入力してください

古いバージョンにする

プラグインディレクトリに移動して前のバージョンのコミットID確認
$ cd ~/.vim/bundle/quickrun.vim/
$ git log

commit 7e440caa04fef77ab29c7bb9c954866c60399020
Author: thinca <thinca+vim@gmail.com>
Date:   Tue Jul 26 00:00:00 2011 +0000

    Version 0.5.0

    - Implemented the module system.
      - Some options are not compatible.
      - See :help quickrun-module more details.
    - Moved QuickRun() into quickrun#operator().
    - Other many changes.

commit 162ff2788fea8e2da9a3feea8db9ed6a89c63c45
Author: thinca <thinca+vim@gmail.com>
Date:   Mon May 23 00:00:00 2011 +0000

    Version 0.4.7: - Added and improved the default values.
Version 0.4.7をチェックアウト
$ git checkout 162ff2788fea8e2da9a3feea8db9ed6a89c63c45

とりあえずこれで再びquickrunが使えるようになりました。

$ git branch -a
* (no branch)
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

なんか間違ってる気もするけどとりあえずはこれでしのぐの・・・

root権のない一般ユーザーでも開発環境でCI(Jenkins/PHPUnit/Phing)してみたい (中編)

前回の続き、pearPHPUnitインストールなど。

自分用pearの準備

pear自体はPHPについてくるので誰でもつかえると思うんですが、インストール先が/usr/local/phpとかだったり、そもそもpearが古かったりするので自分用のを入れましょう。

.pearrcの生成

/home/calpo/pear に自分用のpearを置くとすると、以下のようにします。

$ cd ~/
$ pear config-create /home/calpo/share .pearrc
Configuration (channel pear.php.net):
=====================================
Auto-discover new Channels     auto_discover    <not set>
Default Channel                default_channel  pear.php.net
HTTP Proxy Server Address      http_proxy       <not set>
PEAR server [DEPRECATED]       master_server    <not set>
Default Channel Mirror         preferred_mirror <not set>
Remote Configuration File      remote_config    <not set>
PEAR executables directory     bin_dir          /home/calpo/share/pear
PEAR documentation directory   doc_dir          /home/calpo/share/pear/docs
PHP extension directory        ext_dir          /home/calpo/share/pear/ext
PEAR directory                 php_dir          /home/calpo/share/pear/php
 ・
 ・

.pearrcには自分用pearの設定が保存され、↑で出力された内容は"pear config-show"でいつでも確認できます。

自分用pear本体のインストール
$ pear install -o PEAR
WARNING: channel "pear.php.net" has updated its protocols, use "pear channel-update pear.php.net" to update
downloading PEAR-1.9.4.tgz ...
 ・
 ・
PEAR: To install optional features use "pear install pear/PEAR#featurename"

自分用pearが/home/calpo/share/pear/pearにできました。

pearコマンドが自分用pearを指すようにする

ただそのままだと結局共有pearを見に行っているので・・・

$ which pear
/usr/bin/pear

~/.bashrcあたりにパスを追加

export PATH=/home/calpo/share/pear:${PATH}
# aliasでもいいかも?
# alias pear ~/share/pear/pear
$ which pear
~/share/pear/pear

PHPUnitインストール

チャンネル追加してインストール
$ pear channel-discover pear.phpunit.de
$ pear channel-discover components.ez.no
$ pear channel-discover pear.symfony-project.com
$ pear install phpunit/PHPUnit

Did not download optional dependencies: ezc/ConsoleTools, use --alldeps to download automatically
Failed to download pear/HTTP_Request2 within preferred state "stable", latest release is version 2.0.0RC1, stability "beta", use "channel://pear.php.net/HTTP_Request2-2.0.0RC1" to install
phpunit/PHPUnit can optionally use PHP extension "dbus"
pear/XML_RPC2 requires package "pear/HTTP_Request2" (version >= 0.6.0)
phpunit/PHPUnit requires package "pear/XML_RPC2"

文句言われた

一個ずつ足りないものを入れようとするとさらに文句言われたりしたので最終的に↓のようにやった

$ pear install channel://pear.php.net/Net_URL2-0.3.1
$ pear install channel://pear.php.net/HTTP_Request2-2.0.0RC1
$ pear install pear/XML_RPC2
$ pear install phpunit/PHPUnit
$ phpunit -v
PHPUnit 3.5.3 by Sebastian Bergmann.

これでやっと入る

curl関連でけちが付いた場合は → http://lazesoftware.com/blog/11/0213/

Phingインストール

pearでさっくり
$ pear channel-discover pear.phing.info
$ pear install phing/phing
$ phing -v
PHP Warning:  require_once(phing/Project.php): failed to open stream: No such file or directory in /home/calpo/share/pear/php/phing/Phing.php on line 22

インストールはWarningでつつも無事終わるが動かない。

PHPのinclude_pathが通ってないので通す

通常のpearインストールディレクトリは自動的にinclude_pathに含まれるようになってるんですが、今回は自分用なので自分でinclude_pathを設定する必要があります。
share/pear/php/phing.php の最初の方に↓を追加

<?php
ini_set('include_path', ini_get('include_path').':'.dirname(__FILE__));
 ・
 ・

(本当はphp.iniに書いたりphpコマンド実行時に-dオプション指定したりしたいけど)

./phing -v
Phing 2.4.6

完了。