テストコードについてなんとなく説明
テストコードの話で盛り上がっていたので久しぶりに記事を書いてみます。
動画を見た感じだと「電脳カプセル」のメンバーはスキルに多少差があるようなので
この記事は超初心者向きに書いてみます。
サンプルコードはPHPのコードになります。
テストコードって何?
その名の通りテストするためのコードです。
$this->assertTrue($result); // isOne()の結果がTrueかテスト
じゃあ、テストコードはなんのために書くの?
テストコードとはリリースしようとしているプログラム(システム)を自動でテストするためのコードです。
1だったらTrue、それ以外はFalseを返す関数isOne(int Num)を作ったとします。
isOne(int Num)をテストするプログラムは以下のようになります。(抜粋)
$result = isOne(1); // 渡した引数が1か判定する関数を呼び出し
$this->assertTrue($result); // isOne()の結果がTrueかテスト
$result = isOne(2); // 渡した引数が1か判定する関数を呼び出し
$this->assertFalse($result); // isOne()の結果がFalseかテスト
テストコードを書く目的は自信を持って修正できるようにするためです。
開発をすすめるとこの変数名おかしいから変更しよう、ここの処理は共通化できるから
別の場所に移動しよう、ということがあると思います。
修正を行うと修正前と修正後で同じを動きをするか不安になります。
不安だからテストするのですが毎回値を変えながら数パターンのテストするのはめんどくさいです。
修正の規模が大きいとテストに漏れが出るかもしれません。
テストコードがあれば、すべてのテストコードが通れば修正に問題ないし、
テストコードが通らなかったら一旦修正前に戻すことも出来ます。
「”多分”動いている”はず”」をなくせます。
テストプログラムがあれば品質は担保されるか?
そもそもテストプログラムを書くのは品質を担保するためではありません(結果、品質が上がることはあると思います)。
システムを作るときは設計フェーズ、開発フェーズ、テストフェーズ、運用フェーズがあると思いますが
「テスト」コードだからといってテストフェーズでたくさんのテスト項目を自動的にこなすためのものではありません。(そういう使い方をしているチームもあるかもしれませんが)
どういうことか説明したかったのですが簡潔に説明するのが難しいので機会があればTDD(テスト駆動開発)を勉強してください。
テストコードが間違ってた場合はどうするの?
間違ってていいんです。
先程言ったようにテストコードは品質を担保するものではないので品質を保証するテストは別にきちんと行ってください。
その際にバグが発生したならテストコードのテストケースが間違っている(漏れている)のでテストコードを修正してから製品コードを修正してバグが治ったことと他に影響がでてないことを確認します。
元々がリーダブルコードの輪読会だったのでその辺の話も
多分短いコードがいいのか理解しやすいコードがいいのかの話でテストコードが出てきたと思うのですが、一般的に同じ処理を繰り返すならfor文で繰り返します。
先程のisOne(int Num)のテスト
for($i; $i < 3; $i++){
$result = isOne($i);
$this->assertTrue($result);
if($i > 1){
$this->assertFalse($result);
}
}
ですが、テストコードはforで書かないほうがいい場合があります。
$result = isOne(1); // 渡した引数が1か判定する関数を呼び出し
$this->assertTrue($result); // isOne()の結果がTrueかテスト
$result = isOne(2); // 渡した引数が1か判定する関数を呼び出し
$this->assertFalse($result); // isOne()の結果がFalseかテスト
$result = isOne(3); // 渡した引数が1か判定する関数を呼び出し
$this->assertFalse($result); // isOne()の結果がFalseかテスト
後者の書き方で書いておくと「どのようなテストを行っているか明白」「どの値でエラーがあったか明白」というメリットがあります。
以上、文才が無いのでうまく伝わったかわからないですが「電脳カプセル」のみなさんがテストコードで疑問に思ってた部分に回答してみました。
LAMP環境構築手順メモ
# passwd ユーザ名
# yum -y update
# yum -y install zlib-devel
# yum -y install openssl-devel
# yum -y install curl-devel
# yum -y install libxml2-devel
# yum -y install gcc
# yum -y install ncurses-devel
# yum -y install gcc-c++
# yum -y install autoconf
作業用ディレクトリ作成
# mkdir src
# cd mkdir
Apacheインストール
# wget http://ftp.jaist.ac.jp/pub/apache//httpd/httpd-2.2.22.tar.gz
# tar xvof httpd-2.2.22.tar.gz
# cd httpd-2.2.22
# ./configure --prefix=/usr/local/apache2.2.22 --enable-modules='so ssl rewrite expires vhost-alias deflate cache disk-cache mem-cache'
# make
# make install
# ln -s /usr/local/apache2.2.22/ /usr/local/apache2
Apache起動設定
# cp build/rpm/httpd.init /etc/rc.d/init.d/httpd
# vi /etc/rc.d/init.d/httpd
httpd=${HTTPD-/usr/sbin/httpd}
httpd=${HTTPD-/usr/local/apache2/bin/httpd}
pidfile=${PIDFILE-/var/log/httpd/${prog}.pid}
pidfile=${PIDFILE-/usr/local/apache2/logs/httpd.pid}
CONFFILE=/etc/httpd/conf/httpd.conf
CONFFILE=/usr/local/apache2/conf/httpd.conf
# chkconfig httpd on
MySQLインストール
# cd /root/src
# wget http://dev.mysql.com/get/Downloads/MySQL-5.1/mysql-5.1.63.tar.gz/from/http://ftp.jaist.ac.jp/pub/mysql/
# tar xvof mysql-5.1.63.tar.gz
# cd mysql-5.1.63
# ./configure --with-charset=utf8 --with-extra-charsets=all --with-mysqld-user=mysql --with-innodb --prefix=/usr/local/mysql
# make
# make install
# groupadd mysql
# useradd mysql -g mysql
# /usr/local/mysql/bin/mysql_install_db --user=mysql
# cp /usr/local/mysql/share/mysql/my-medium.cnf /etc/my.cnf
# cp /root/src/mysql-5.1.63/support-files/mysql.server /etc/rc.d/init.d/mysqld
# chmod 755 /etc/rc.d/init.d/mysqld
# chkconfig --add mysqld
PHPインストール
# cd /root/src
# wget http://dev.mysql.com/get/Downloads/MySQL-5.1/MySQL-devel-5.1.63-1.glibc23.i386.rpm/from/http://ftp.iij.ad.jp/pub/db/mysql/
# rpm -ihv MySQL-devel-5.1.63-1.glibc23.i386.rpm
# wget http://dev.mysql.com/get/Downloads/MySQL-5.1/MySQL-shared-compat-5.1.63-1.glibc23.i386.rpm/from/http://ftp.jaist.ac.jp/pub/mysql/
# rpm -ihv MySQL-shared-compat-5.1.63-1.glibc23.i386.rpm
# vi /etc/ld.so.conf.d/mysql-i386.conf
/usr/local/mysql/lib/mysql #ldconfig
#ldconfig
----
make時に以下のエラーが出るので、libmysqlclient.soをコピー
gcc: /usr/lib/mysql/libmysqlclient.so: No such file or directory
make: *** [libphp5.la] エラー 1
# cp /root/src/mysql-5.1.63/libmysql/.libs/libmysqlclient.so /usr/lib/mysql/libmysqlclient.so
----
# wget http://download.icu-project.org/files/icu4c/4.8.1.1/icu4c-4_8_1_1-src.tgz
# tar xvof icu4c-4_8_1_1-src.tgz
# cd icu/source/
# ./configure
# make
# make install
# cd ../../
# wget http://jp2.php.net/get/php-5.3.13.tar.gz/from/jp.php.net/mirror
# tar xvof php-5.3.13.tar.gz
# cd php-5.3.13
# ./configure --with-apxs2=/usr/local/apache2.2.22/bin/apxs --enable-mbstring --enable-mbregex --with-openssl=shared --with-pdo-mysql --with-mysql --with-mysqli --enable-intl --with-curl=/usr/include/curl/
# make
# make install
# vi /usr/local/apache2/conf/httpd.conf に追加
AddType application/x-httpd-php .php
# cp /root/src/php-5.3.13/php.ini-development /usr/local/lib/php.ini
# pear upgrade-all
# pecl install APC
# vi /usr/local/lib/php.ini
extension=apc.so
# pear channel-discover pear.phpunit.de
# pear channel-discover pear.symfony-project.com
# pear install phpunit/PHPUnit
TDD Boot Camp in Tokyo懇親会に参加してきました。
ただ飲みに行っただけではなくブログで報告できるような成果を得られて実りある懇親会でした。
成果1:私の現場の話を聞いてもらったことがある@t_wadaさんに会社としてはまだまだレガシーな環境だけどチームとしてはローカル環境で開発してGitでバージョン管理が出来て、メンバーからペアプロ、TDDをやってみたいと意見があがってきたと少しずつだけど開発の現場が前進してきてることを報告できた。
成果2:以前、@ShiroKappaさんに「今の開発チームが自分以外は外注さんだからアジャイルの考え方だったり、Git使い方だったりを教えてもいずれいなくなるから会社としてのメリットがない」と話をした時に「外注の人が自社に帰った時に前の現場でyanchiって人がこういう事やってて、こういう成果があったよって考え方を広めてくれればそれはすごいことじゃない?」って話をしてくれて先日振り返りミーティングをやったときに外注のメンバーがKeepで「Gitが便利。自社でも使ってみたい」と言ってくれて@ShiroKappaさんが言ってくれたことを実感できたことを報告できた。
成果3:以前、Twitterで「そろそろ、cherry-pick使えるようにならないと辛い気がしてきた。近々@HIROCASTさんか@shishi4twさん捕まえてレクチャーしてもらおう。」って発言したら@HIROCASTさんに「このコマンドつかうってことはブランチ開発にそもそも問題がある気がする。あんまり使わないコマンドだと思う。」とリプライもらって「????(どういう事?)。」ってなってたので、その理由が聞けてすっきりした。
要は俺のブランチでの作業の粒度が大きいの問題でもっと細かい粒度でブランチ分ければそもそもcherry-pick使わなくっていいよねって事だった。
ついでに自動デプロイの「Capistrano」の話を聞けたので近々触ってみる。
社内技術発表会でORMについて話しました
以下、社内ブログに上げた記事
手抜きでゴメンなさい。
以下のcodezineの記事を見てください。
http://codezine.jp/article/detail/5858
記事の方ではPrimaryKeyからのデータ取得の例しか記載されてないのでPrimaryKey以外のSELECTの仕方を記載します。
(私がDoctrineとPropleしか経験が無いのでその2つのみですが)
・Dctrine
$q = Doctrine_Query::create()
->select('a.name')
->from('Account a')
->where('a.amount > 2000');
$accounts = $q->execute();
Prople
$c = new Criteria();
$criterion = $c->getNewCriterion(UserPeer::ID, 1);
$criterion->addOr($c->getNewCriterion(UserPeer::ACCOUNT, 'hoge'));
$c->add($criterion);
$c->add(UserPeer::ID, 10, Criteria::LESS_THAN);
$user = UserPeer::doSelect($c);
DoctrineとPropelを使ってみての感想ですが、Doctrineの方はORMを使いながらもメソッド名がSQLライクなので生成されるSQLがイメージしやすい、Propelの方は生成されるSQLはイメージしにくいがオブジェクト指向は理解しているがSQLは苦手という人には使いやすいと思います。
発表後に出た質問
・設定ファイル(xmlなど)を用意するのは手間じゃないか?
たしかに設定ファイルが無いよりは一手間かかりますが、フレームワークによっては1つの設定ファイルからテーブル作成用のSQLとORMのコードを生成してくれる物もあるので、他のライブラリやフレームワークと一緒に利用することで手間を軽減できる場合もある
・グループなどのリレーショナルなどは出来るのか?
設定ファイルにリレーショナルの情報をきちんと記述してあげればリレーショナル関係もクラスの情報として保持できます。
・パフォーマンス等の理由により使用できない場合が考えられるが、その場合ORMを使ったプロジェクトと使わないプロジェクトが存在してダブルスタンダードとなり両方勉強する必要がないか?
個人的な見解としてORM等のライブラリを使わないで開発できる能力は最低限の必要なものだと思っています。
その上で0からも作れるけどもっと楽したいよねって時にORMなりフレームワークなり使えばいいかなと思います。
以下、技術発表に参加できなかった技術リーダーとのやり取り。
・技術リーダー
http://www.symfony-project.org/doctrine/1_2/ja/06-Working-With-Data
ここみると複雑なWHEREの追加がどうなってるのか気になるかな。
例えば (A or B) and (C or D)を実装すると
$q->where('A=?', 1)
->orWhere('B=?', 1)
->andWhere('C=?', 1)
->orWhere('D=?', 1)
でいいのかな・・・?
A or (B and C) or D
と解釈されそう?
このあたり知ってたら教えて欲しいです。
UNIONもないしある程度VIEWで実装済みであることを前提としてるのかな。
後は「データを削除する」のサンプルなんぞを見ても分かるけど
$user = Doctrine::getTable('User')->find(1);
$user->delete();
$deleted = Doctrine_Query::create()
->delete()
->from('User u')
->where('u.id = ?', 1)
->execute();
の2パターンあって、上はオブジェクト的だが無駄な処理有り、下はSQL的だが無駄な処理無し。
でもORM使うなら上がデフォなんだろうか?それとも下でガリガリ書くの?という疑問が浮かぶ。
・yanchi
・(A or B) and (C or D)の検索
Doctrineは実務では使ってないのでPropelほど詳しくないのですが質問の検索条件を投げるとしたら多分以下のようになります。
$table = Doctrine::getTable('User')->createQuery('User u');
$or1 = Doctrine_Query::create()
->Where(A)
->orWhere(B);
$or2 = Doctrine_Query::create()
->Where(C)
->orWhere(D);
$table->where($or1)->andWhere($or2 );
()でくくった優先度のQueryを個別に作り、それをand(又はor)で繋ぐ感じになります。
以下の方法でもいけるみたいですが
$q = Doctrine::getTable('User')->createQuery('User u')
->where(A or B)
->andWhere(C or D);
・削除処理
削除の処理については上のオブジェクトを取得してから削除が一般的です。
下の場合は複数(数千件とか)のデータを纏めて消すときに使用します。
こんな感じでいいですか?
・技術リーダー
回答ありがとです。
ちょっとさらっと雑談したけど毎回DELETEのたびに必ずSELECTを発生させるのはどうなのか、というのはありますかね。
コストは安くても明らかに無駄な処理なので。
Doctrineはがっつり触ってないので(A or B) and (C or D)の検索 の仕方間違ってたらご指摘ください。
今見なおしたら「UNIONもないしある程度VIEWで実装済みであることを前提としてるのかな」に答えてないなぁ。
まぁ、DoctrineでもPropelでも生SQL投げる方法あるし、そもそも個人的な見解だとそんな複雑なSQL投げないといけないならテーブル定義見直そうよって思うんだけど。。。
公開鍵によるSSH接続
$ cd /root/test/.ssh
$ ssh-keygen -t dsa(パスフレ-ズ入力なしで)
$ scp -p id_dsa.pub root@[サーバのアドレス]:.ssh/
ログインパスワ-ドを入力して公開鍵をサ-バ側へコピ-
<サ-バ-側の設定>
$mkdir /root/.ssh
$ cd /root/.ssh
$ touch authorized_keys2
$ chmod 600 authorized_keys2
$ cat id_dsa.pub >>authorized_keys2
server$ rm id_dsa.pub