ホーム > タグ > テスト

テスト

CakePHP開発者が知るべき10のこと

10 things you must know about cakephp

先日、こんな記事が上がっていました。

この記事でまとまっているのは、Android開発において必要な10の項目です。
インターフェースの設計から、データの取り扱いまで。

AndroidはモバイルデバイスのOSで、CakePHPは単なるWebフレームワーク。
しかし、予め用意されたルールやAPIを活用する点は同じです。
つまり、フレームワーク全般において、開発者が知るべきことをまとめることが出来るはずです。

ここでは、私が良く利用するCakePHPフレームワークについて、開発者が知るべき10のことをまとめます。


フォーラムにこの記事を書き込んだところ、shunさんがtipsを追記してくれています!
モデルに、配列を取り入れた経緯などを紹介してくれています。
こちらも併せてどうぞ。

1. CakePHPで良いのか

Cakephp logo

CakePHPを使う際に、知るべきことその1。
それは、あなたは本当にCakePHPを使うべきなのかということです。

現在、あらゆるフレームワークが溢れ返っています。

  • Ruby
    • Ruby On Rails
    • Sinatra
  • Python
    • Django
    • Pylons
    • Juno
  • Perl
    • Catalyst
    • CGI::Application
  • PHP
    • CakePHP
    • Zend Framework
    • Symforny
    • Lemonade
  • Java
    • Stuts
    • Slim3

これだけあるフレームワークの中で、なぜ、私はCakePHPを選ぶのか

そもそも、その開発に使う言語は、本当にPHPで良いのか
なぜ、PHPを使うのか。

例えば、Quoraを立ち上げた、Facebookの元CTOのAdam D’Angeloさんはこんなことを言っています。

PHP was out of the question. Facebook is stuck on that for legacy reasons, not because it’s the best choice right now. Our main takeaway from that experience is that programming language choice is very important and is extremely costly to change.

Why did Quora choose Python for its development?より

少し、古い記事ですが、現在のウェブではこれが現実でしょう。

Google App EngineAmazon AWS、あるいはその他のPaaS
環境を用意するのは、昔ほど難しくありません。
そして、コストもさほど高くありません。

私は、”PHP/CakePHPを使うな“と言っているのではありません。
盲目的にならず、その開発がどんなものかを考えて、フレームワーク選ぶべきだと言っているのです。

これは、CakePHPに限ったことではありませんが

2. CakePHPの癖を知る

Cakephps model

CakePHPを使うとして、次に知るべきことはCakePHPの癖です。
特にデータの取り扱い、つまりモデル周りには、癖があります

>>> from mysite.polls.models import Poll, Choice # モデルクラスを import します。 

# まだ Poll は一つもできていません。
>>> Poll.objects.all()
[]

# 新たな Poll を作成しましょう。
>>> import datetime
>>> p = Poll(question="What's up?", pub_date=datetime.datetime.now())

# 出来たオブジェクトをデータベースに保存します。 save() は明示的に呼ば
# ねばなりません。
>>> p.save() 

# これでオブジェクトに ID が割り当てられました。お使いのデータベースに
# よっては、この値は "1" ではなく "1L" のときもあります。心配することは
# ありません。単にデータベースバックエンドが Python 長整数型で値を返す
# ようになっているだけのことです。
>>> p.id
1

# データベースの各カラムに Python の属性としてアクセスします。
>>> p.question
"What's up?"
>>> p.pub_date
datetime.datetime(2007, 7, 15, 12, 00, 53)

# 属性を変更して save() を呼び出すとカラムの値を変更します。
>>> p.pub_date = datetime.datetime(2007, 4, 1, 0, 0)
>>> p.save() 

# objects.all() はデータベース上の全ての Poll を返します。
>>> Poll.objects.all()
[<Poll: Poll object>]

はじめての Django アプリ作成、その1より

>> p = Post.new(:content => "A new post")
=> #<Post id: nil, name: nil, title: nil,
     content: "A new post", created_at: nil,
     updated_at: nil>
>> p.save
=> false
>> p.errors
=> #<OrderedHash { :title=>["can't be blank",
                           "is too short (minimum is 5 characters)"],
                   :name=>["can't be blank"] }>

Ruby on Rails Guides: Getting Started with Rails: 6.6 Using the Consoleより

PythonやRubyは、あらゆるものをオブジェクトとして扱います
そのため、データをインサートする際にも、まず、モデルのインスタンスを生成します。
その後、インサート用のメソッドを呼び出し、インサートを完了させます。

しかし、CakePHPではデータを配列で扱います。
データをインサートする際も、新しいインスタンスを作ったりしません。

function edit($id) {
    //POST された form データがあるか?
    if(!empty($this->data)) {
        //form データが有効で保存できた場合...
        if($this->Recipe->save($this->data)) {
            //セッションにフラッシュメッセージをセットしリダイレクトする
            $this->Session->setFlash("Recipe Saved!");
            $this->redirect('/recipes');
        }
    }

    //form データがない場合、編集する recipe を検索し
    //ビューに渡す
    $this->set('recipe', $this->Recipe->findById($id));
}

3 CakePHPによる開発: 3.7.4 データを保存するより

このことを知らなければ、あなたはforeachと配列の地獄に落ちるでしょう
CakePHPでは、これをすっきりと記述するためにSetクラスが存在します。

Setクラスを活用し、配列地獄に落ちないように、気をつけましょう。

3. 高速開発手法を知る

Cakephps develop

せっかく、フレームワークを使うのだから、その資産を利用しましょう。
フレームワークの資産、それはプラグインです。

例えば、あなたがtwitter連携機能を開発しているとします。
必要なのは、あなたの書いたコードでなく、これらのプラグインです。

Facebook連携機能をエンハンスするとします。

さらに、携帯サイトに対応するとします。

これらのプラグインを作り、公開してくれる人はだいたい決まっています。
その人たちを把握しておきましょう。

順不同、敬称略です。
複数のCakePHPプラグインを公開している方をまとめました。
全てgithubへのリンクとなっているので、フォローしておくと良いでしょう。

4. 学習方法を知る

Cakephps study

CakePHPを学ぶ方法を知ることは、あなたのためにも、そしてあなたの周りに居る人のためにもなります。

あなたの作ったウェブサイトを、後輩に引き継ぐかもしれない
あるいは、新たなCakePHP開発者を追加するかもしれない

こうした場合に、CakePHPの良い学習方法を知っていると役に立ちます。

対象者が、フレームワーク、それどころかPHPの初心者である場合、CakePHPの公式マニュアルが役に立ちます。

これは簡単なブログ作成のチュートリアルで、初心者でも手軽に始めることが出来ます。

しかし、このチュートリアルには弱点があります。
それは、MVCモデルを活用しきれていないこと
もし、このチュートリアルだけでCakePHPを学習すると、ひどい目に遭います。

次に、学習すべきはこれです。

モデル側にロジックをまとめることを覚えましょう。
あるいは、hiromi2424さんのエントリも大変参考になります。

CakePHPを利用したウェブサイト開発を一通り学習したいのであれば、この本がお勧めです。

CakePHPによる実践Webアプリケーション開発 CakePHPによる実践Webアプリケーション開発

  • 著者: 安藤 祐介, 岸田 健一郎, 新原 雅司
  • 出版社: 毎日コミュニケーションズ
  • 発売日: 2009/04/08

CakePHP v1.2を対象に書かれているため、少し情報が古い可能性があります。
しかし、その点を除いても、大変参考になります。
業務アプリケーションを対象とした開発手順は、とても実践的です

最後に、話題のTDDをCakePHPでも取り入れましょう

Webアプリケーションテスト手法 Webアプリケーションテスト手法

  • 著者: 水野 貴明, 石井 勇一, 新藤 愛大, 岸田 健一郎, 荻野 淳也, 安井 力, 田中 慎司
  • 出版社: 毎日コミュニケーションズ
  • 発売日: 2008/07/25

CakePHP 1.3で標準テスティングフレームワークSimpleTestの使い方が、わかりやすくまとまっています。

5. サイトを高速化する

Cakephps speedup

サイトのアクセス負荷が増えてきたときのために、その負荷対策もCakePHP開発者は知るべきでしょう
まず、簡単なところから。

CakePHP 2.0でも標準で取り入れられたプラグインです。
モデルの読み込みをマジックメソッドに寄せることで、プログラムのロードを高速化します。

一般的なウェブサイトであれば、全てのページが動的である必要はありません
まずは、デフォルトのキャッシュシステムを利用しましょう。

しかし、このキャッシュシステムは完璧ではありません。
プログラムのロードが少なくなるだけで、少なからずCakePHPが呼ばれます。

そこで、静的ページには次のプラグインを利用します

このプラグインを利用すると、HTMLの静的ファイルをwebroot以下に吐き出してくれます
CakePHPにアクセスしないのだから、爆速ですよね。

モデル側のキャッシュは、デフォルトで用意されているキャッシュ機構を利用します。

これを有効化すると、クエリ毎に結果をキャッシュしてくれます
ただし、1リクエスト毎のメモリキャッシュである点と、データ更新後の挙動には注意が必要です。

最後に、独自のキャッシュ機構を用意する場合
Cacheクラスを利用します。

自動でシリアライズをしてくれたり、キャッシュの有効期限を決めれたりと、便利なクラスです。

6. デプロイを自動化する

Cakephps deploy

CakePHPはデプロイが簡単です。
作成したアプリケーションを、ウェブサーバが参照出来る場所に置くだけです。

しかし、長期で運用する場合、色々と問題が生じます。
例えば、一時ファイルの問題です。

CakePHPはデフォルトでも、アプリケーションに支障が出ない範囲で様々なファイルをキャッシュし、一時ファイルに格納します。
そのため、新たなコードやファイルを追加した場合に、この一時ファイルが悪さをすることがあります
予期せぬエラーが発生する場合、この一時ファイルが悪さをしていることが多いのです。

デプロイの度、一時ファイルを手動で消しても良いのですが、それは面倒です。
そして、ミスも増えます。

デプロイの簡単なCakePHPですが、その処理も自動化しておくと、多くのメリットが得られます

CakePHPもCapistranoを利用し、デプロイを自動化できます。
もし、CLIに慣れないエンジニア、あるいはデザイナーが周りに居る場合は、Webistranoも良いでしょう

Capistranoを使えば、簡単にデプロイを自動化できます。
しかし、データベースのマイグレーションが課題として残ります
CakePHPのスキーマシェルは、とても貧弱です
デフォルトのアップデート機能では、自動デプロイに耐えることが出来ません。
そこで、CakeDCが公開しているMigrationsPluginを利用します。

このプラグインを利用することで、Ruby On Railsのようなマイグレーションシェルを得ることが出来ます。
マイグレーションアップ、マイグレーションダウンも自由自在です。

7. 継続的インテグレーションを取り入れる

Cakephps ci

4. 学習方法を知る“の中で、テストの話に簡単に触れました。
もし、あなたがテストケースを運用し始めたなら、継続的インテグレーションを取り入れましょう。

CakePHPでも、Hudsonを利用することで継続的インテグレーションすることが出来ます
Ryuzeeさんの記事を見れば、数時間で環境を整えられるでしょう。

8. バリデーションを活用する

Cakephps validate

CakePHPを利用するのに欠かせない機能。
それはバリデーションです。

マニュアルの例は、とてもシンプルです。
バリデーションには、この他にも様々な使い道があります

4. 学習方法を知る“でも紹介しましたが、hiromi2424さんの記事を参考にしてください。

さらに、バリデーションを使うにあたって以下のことを踏まえておきましょう。

  • バリデーションは必ずしも、テーブルのフィールドと1対1でなくても良い。つまり、テーブルに存在しないフィールドに対しても、バリデーションを定義出来る。
  • 独自バリデーションメソッドの中から、保存予定の全てのデータにアクセス出来る。つまり、Model::$dataプロパティを利用し、複雑なバリデーションも書ける。
  • Model::beforeValidate()コールバックを利用することで、オプションによってバリデーションを切り替えることが出来る

バリデーションメソッドは、様々な可能性を秘めています。

9. エディタを活用する

Cakephps editor

CakePHPを利用出来るエディタを把握しましょう。
PHPを書くことが出来るエディタであれば、開発に支障はありません。
しかし、専用のプラグインがあるエディタを利用すれば、開発速度が上がります。

個人的に残念なのは、有用なvimプラグインがないことです。
そのうち、個人的に開発したいと思っているので、vimmerの方はしばしお待ちを。

10. 最新の情報を得る

Cakephps info

最後に、CakePHPのトレンドを把握するために、必要な情報源を押さえましょう。

全てRSSリーダーに登録しておけば、ばっちりですね。
もし、CakePHPネタをたくさん書いてるのに、載っていない!っていう人が居たら教えてください。
掲載させて頂きます。

かなりの長文になってしまいました。
(そして、最後はヘタってしまいました:(

恐らく、生粋のCakePHP Bakerの皆さんは全て押さえているでしょう。
初級、あるいは中級者の方の参考になれば、幸いです。

また、他のフレームワークでも、”知るべき10のこと“シリーズを書いてみませんか?

少人数開発に役立つ5つのまとめ

team_development.jpg

ここ2ヶ月間で気になる記事がたくさん上がっていました。
特に少人数チームにおける開発に関する記事です。

昨日、書き上げた”1年間の技術的負債を返すために読んだ3冊の本“にある通り、お知らせメールでは1年間の技術的負債を返そうとしています。
そのためには今まで曖昧だった箇所を浮き彫りにし、改善する必要があります。
また、せっかくなので新しいモノも取り入れたい。

こうしたことを考えながらの2ヶ月だったので、自然と目に止まった記事が3つありました

どれもこれも、チーム開発で役立ちそうな記事です。
今日はこの3つの記事を中心に、少人数開発に役立つ様々な情報をまとめてみます。

体制を整えるための2記事

まず、少人数での開発がどんなモノか、そして何が必要かを知ります。

スタートアップ企業で8年間Webの開発をしてみての反省点いろいろでは、少人数開発で気をつけなければならないことがまとめられています。
とても当たり前なことがたくさん書いてあります
しかし、当たり前だからこそ、忘れがちなことです。

1. 丁寧に正しく作ろう

一つだけ選ぶとしたらこれ。これがすべてにつながってくる。スタートアップ企業にとって、スピードが大切というのは間違いないのだけど、目先のスピードのために、色々なことを犠牲にしてしまっていたことがあった。正しく丁寧に作ることが中長期で考えるとスピードにもつながる、ということを頭で理解しつつも、つい手を抜いてしまっていた。

まず、「今必要でないことはやらない – YAGNI 」ということを常に考えよう。そして、その上で「今必要」となったものは全力で丁寧に正しく作ろう。

2. 丁寧に正しく作れないなら既存のプロダクトを使う

丁寧に正しく作るのにはしっかりとしたリソースが必要。ただし、人やお金というリソースは常に限られているので自分たちが集中しないといけないところをしっかり決めよう。

独自フレームワークはやめよう

独自のフレームワークは正しく作れないならやめよう。「正しく作る」というのはメンテナンスとか含めて。もし作るなら専用のリソースを確保するぐらいできないと無理。片手間でフレームワーク作りとか無理。外部に向けて公開しても恥ずかしくないレベルのものを作れるようでない限りはやめたほうがいい。

スタートアップ企業で8年間Webの開発をしてみての反省点いろいろより

この2つは、本当に気をつけながらやっています。
今、何を作っているのか“、”本当に、自分がこれを作るべきなのか“を常に気をつけながら開発しましょう。

スタートアップ企業で8年間Webの開発をしてみての反省点いろいろがマインド的な部分であったのに対して、複数人(2-3人)でウェブサービスを開発するコツではツール的な部分がまとまっています。
開発環境には仮想化を使う“、”開発はチケットファーストで行う“など。
簡潔ですが、要点を押さえたわかりやすい記事です。

この2記事を読むことで、少人数開発に必要なマインドとツールがわかると思います。
私自身、この2記事を参考に環境を構築しています。

バージョン管理システムを一新するための2記事と2アプリケーション

バージョン管理システムにはgitを使います。

gitをチームで使う場合、問題となるのがブランチの運用です。
一から自分で考えても良いのですが、そのルールが熟れるにも時間が掛かる。
そこで、誰かが作ったルールに乗っかってしまいましょう。
そこでこの記事が役に立ちます。

この記事にはgitにおけるブランチの運用が、わかりやすくまとまっています。
チームのマニュアルとしても、十分に使えるレベルでしょう

こういったルールを守るためには、ちょっとした仕組みも欲しいところです。
例えば、”masterブランチには直接コミット出来ないようにする“といった仕組みです。

この記事では、Redmineのチケットとコミットを連携させるためにmasterブランチへの直接のコミットを禁止するフックを紹介しています。
この記事で言っているトピックブランチは、A successful Git branching model を翻訳しましたのフィーチャーブランチのことです。
色々試してからですが、私もこのフックを導入しようと企んでいます。

gitに関連するアプリケーションも2つ取り上げます。

Gitoriousはgithubのコピープロジェクトです。
Webブラウザ上でGitリポジトリを管理できます。

開いた人は”githubを使えばいいんじゃない?“と思うでしょう。
ところが、このGitorious、オープンソースプロジェクトなんです。
つまり、自分のサーバにインストール出来る。
リポジトリの管理は結構面倒なので、GUIで直感的に操作できるGitoriousを入れると良いと思います。

TowerはMac用のGitのGUIクライアントです。
Gitを使っていてよく聞くのが、決定版のGUIクライアントがないことです。
その決定版GUIクライアントとして期待されているのが、このTowerです。

“期待されている”と書いたのは、残念ながらまだリリースされていないからです。
11月リリース予定なので、この記事と一緒にリリースされれば良いのですが…。

[追記]
記事を書いていたら、リリース告知がありました。
この記事が公開されるころには、リリースされているはず!

Thanks for your patience! Get your repos ready. From Monday on Tower will be available for everyone! #git #client #mac

テストを自動化するための3記事

少人数開発では、人手が足りません。
テストチームなんていませんよね。
そのため、テストは自動化が基本になると思います。

Sereniumは、Webアプリケーションのテストを自動化してくれます。
これはすごい! Web案件必須 Seleniumを読めば、Sereniumの概要がわかります。
PHPUnit と Selenium RCで機能テストをやってみるを読めば、簡単にSereniumを導入できそうです。

さらに、毎日のテストを自動化するためにHudsonを入れます
Hudsonはビルドやテストを定期的に実行し、その結果を管理するためのツールです。
日本Hudsonユーザー会も発足した「Hudson勉強会」活動報告にはUstreamの動画と共に、豊富な事例がまとまっています。
初心者のための簡単な紹介もありますよ。

また、RedmineとHudsonを連携するこんな事例もありました。

Hudson x REDMINE

HudsonとSereniumを入れて、効率的にテストを実行しましょう。

チケット管理システムを正しく運用するための1記事と1冊

チケット管理システムといえば、RedmineTracです。
ここではRedmineを取り上げます。

Redmine導入時にやってしまいそうなことが、よくまとまっています。
導入は簡単です。
しかし、適切に運用するためにはそのツールで使っている用語の意味を知ることが大事です。
これを読んでから、Redmineの運用を始めると、よりRedmineを活用できるでしょう。

さらに、この記事でも紹介されていますが、こんな本も出ています。

Redmineによるタスクマネジメント実践技法 Redmineによるタスクマネジメント実践技法

  • 著者: 小川 明彦
  • 出版社: 翔泳社
  • 発売日: 2010/10/13

今までRedmineに関する本はその導入の仕方や使い方がメインだったので、嬉しい一冊です。

アジャイル開発をマネタイズするための1記事

最後にちょっと毛色の違う記事を取り上げます。

私は基本的に受託開発をしていないのですが、とても参考になる記事です。
受託開発では”システム当たりいくら“が基本だと思います。
しかし、この記事を読んで”こういったビジネスモデルもあるのか“と目から鱗が落ちました。

もし、受託開発をする場合は、こういった形で仕事がしてみたいですね。

いかがでしょうか。
この記事は正直、素晴らしい記事に乗っただけの記事です。
ですが、”ここまで色々と情報が揃ってるのにまとめないのはもったいない!“と思い、この記事を書いてみました。
参考になれば幸いです。

1年間の技術的負債を返すために読んだ3冊の本

technical_debt.jpg

[この記事を読む前に]

タイトルに騙されて来た方はごめんなさい。
恐らく、知っていることばかりが書いてあると思います。
“3冊の本”もベストセレクションではありません。
“たまたま”選んだ3冊の本です。
それでも読んでくれる心優しい方はどうぞ、先にお進みください。

技術的負債は日々、返済していますか。
技術的負債って何?という方はこちらへ。

えー、正直、私は技術的負債が溜まっています。
お知らせメールを本格的に初めて1年が経とうとしています。
何も無い状態から、手探りで始めて今の状態までなんとか持って行きました。
この計画が立ち上がった当時(2年ぐらい前かな)、自分ができたのは、

  • PHPが書ける(書けるだけ)
  • サーバが少しわかる(cdとlsが打てるだけ)

これぐらいです。
ちょっと大げさですが、あながち嘘じゃない

そんなこんなで、試行錯誤の毎日。
運営を始めるとさらに大変で、技術的負債がどんどん溜まっています

やっと、機能追加も落ち着いてきたので、技術的負債を返そうという話が相方との間で出ています。
コードのリファクタリングを含め、開発環境を全面的に見直したいところです。

そこで、技術的負債を返すべく、勉強のために3冊の本を読みました。
今日はその3冊の内容にプラスアルファして、1年間で学んだことをまとめていきます。

そもそもなぜ、技術的負債が溜まったのか

私は新しいモノ好きです。
そして、気に食わないことはとことん突き詰める質です。

そんな性格から、モノを作っていると色々なことが気になってくる。

これはこれで、本当にいいのか。

これが頭の中に浮かぶともう止まらない。
これに追い打ちをかけるように、新しい技術や手法が次々と飛び込んでくる

これを使えば、さっきの問題は解決するんじゃないか。

こんな毎日で、日々やり方を変えていきます。
そうすると残るのは、過去のやり方でやったコードや環境です。

確かに新しいやり方はしっくり来るし、とても心地良い。
しかし、”過去のやり方でやったところを直すには時間が掛かる“。

そもそも、現状のやり方で問題なく動いているところです。
これを直すには、気力と時間が必要です。

そして、”それじゃ後でまとめてやろう“という一言に甘える。
簡単に言ってしまえば、これが技術的負債の正体なんでしょう。

技術的負債を返して、負債を溜めないために

技術的負債を貯めない方法、その一つがアジャイル開発なんだと私は考えました。
TDDを取り入れた、スピード感のあるインテグレーション開発

私はTDDを去年のCakeMatsuriから、試行錯誤で実践しています。

今まで個人的にTDDをやってきて気がついたのは、”TDDは技術的負債を返すための道具ではない“ということです。
確かに、TDDを実践すると気持良く開発を進めることが出来る。
しかし、TDDだけでは技術的負債は溜まっていきます。
TDDを実践する前よりはマシですが、それでも溜まっていく

TDDはアジャイル開発の一部だと言うことです。
恐らく、TDDはアジャイル開発の手法と併せて、初めてその威力を発揮する
そのリズミカルな開発サイクルに乗せることで、日々技術的負債を返していける。
そう考えたんです。

前置きが長くなりました。
これが、今回読んだ3冊の本です。

アジャイルプラクティス 達人プログラマに学ぶ現場開発者の習慣 アジャイル・プラクティス

  • 著者: Venkat Subramaniam
  • 出版社: オーム社
  • 発売日: 2007/12/22
アート・オブ・アジャイル デベロップメント ―組織を成功に導くエクストリームプログラミング アート・オブ・アジャイル デベロップメント

  • 著者: James Shore
  • 出版社: オライリー・ジャパン
  • 発売日: 2009/02/18
バグがないプログラムのつくり方 JavaとEclipseで学ぶTDDテスト駆動開発 (Be agile!) バグのないプログラムのつくり方

  • 著者: 川端 光義
  • 出版社: 翔泳社
  • 発売日: 2004/09/22

この3冊の本から、私が重要だと思う点、そして今後実行すべきタスクをまとめてみます。

アジャイル・プラクティス

アジャイルプラクティス 達人プログラマに学ぶ現場開発者の習慣

アジャイル・プラクティスはとても読みやすい本です。
アジャイル開発の入門には最適でしょう。

  • 200ページの読みやすい厚さ
  • 2500円という(技術書にしては)買いやすい値段

アジャイル・プラクティスはアジャイル開発の”スピード“とその”リズム感“を主眼において書かれています。
例えば、アジャイル開発の設計段階の解説では、こんな記述があります。

設計の出発点では、個々のメソッドやデータ型に注力する戦術的設計よりも、責務の観点から考えるクラス設計のほうがふさわしい。というのも、責務中心のクラス設計であれば、抽象レベルを高くしたまま、ゴール思考で考えることができるからだ。実のところ、CRCカードによる設計は、まさしくそのための手法だ。CRCカードによる設計では、クラスを次の視点から表現する。

ホワイトボード、スケッチブック、付箋紙は、素晴らしい設計ツールだ。複雑なモデリングツールは、気づきを与えてくれることよりも、集中力を削いでしまうことのほうが多い。

アジャイル・プラクティス 戦略的設計と戦術的設計(p52)より

付箋とホワイトボードが好きな開発者は数多い
その理由がこれなんでしょう。
私自身、なんとなく理解していましたが、さらにその利用用途が明確になりました。
どうせなら、どでかいホワイトボードが欲しいな、とぼんやりと考えてみたり。

ホワイトボードはさておき、私たちの開発環境で必要なもの、それはこれです。

製品が使い物になるために必要な本質的な機能はどれなのかをユーザに聞いてみよう。その際には、話が脇道にそれないように気をつけよう。素敵だけども実装できるかどうかわからない機能の話に夢中になったりしないこと。また、想像しうる限り最高に魅力的なインターフェースを目指したりしないこと。
アプリケーションを早めにユーザの元に届けると、うれしいことがある。

アジャイル・プラクティス 短いイテレーションでインクリメンタルにリリースする(p72)より

アジャイル開発では、作成したソフトウェアのデモやデプロイまでが小さなサイクルの中にあります。
そもそも、この”ソフトウェアのデモ“が重要なファクターだと思います。
つまり、このデモの準備も自動化し、高速に行う必要がある。

お知らせメールは受託開発ではありません。
完全に自社開発・自社運用でやっています。
顧客に見せるデモという段階はありませんが、開発した機能をトータルテストする段階はある。
これをアジャイル開発におけるデモと置き換えることが出来ると思います。

現状、これが自動化されていません。
これは、やろうやろうと思ってやってこなかった技術的負債です。
必ず自動化して、開発サイクルを高速化しようと思います。

その他にTDDの進め方から、コードの書き方までアジャイル開発に必要な事項を幅広く取り上げてあります。
アジャイルについてまったく知らないなら、この本から読むことをオススメします。
事実、アジャイルを知らない私が、とてもスラスラと読むことが出来ました。

アート・オブ・アジャイル デベロップメント

アート・オブ・アジャイル デベロップメント ―組織を成功に導くエクストリームプログラミング

アジャイル・プラクティスの次に読んだのはアート・オブ・アジャイル デベロップメントです。
これはアジャイル・プラクティスの内容を詳細にしたような本です。

チームにアジャイルを適用するために、必要な事項がまとめられています。
例えば、アジャイルを始めるにあたって、チームの規模は初めに気にするところだと思います。
アート・オブ・アジャイル デベロップメントには以下のような記述があります。

もちチームのプログラマが4名未満だったら…
それでもXPプラクティスの多くはまだ適用できるが、ペアプログラミングは難しくなるだろう。この場合、そのプログラマが高品質なコードを書くのに情熱を持ち、誠実であるなら大丈夫だ。こうした情熱があれば、XPの技術的なプラクティスを規律を持って適用できるだろう。
常に同席してくれるオンサイト顧客を持つのにも苦労するかもしれない。代わりに彼らの近くに席を並べよう。そうすれば必要なときに彼らの関心を引くことができる。

アート・オブ・アジャイル デベロップメント XPを導入する(p50)より

プログラマが少ないチームだと、各人のモチベーションが重要だと言うのは納得出来ます。
二人で開発していると、チームとしてのモチベーションがほとんど存在しません

個々のモチベーションが重要です。
二人三脚で一人が走らないとまったく動きませんよね
そんな感じです。

やはり、小さなチームでやる以上、お互いの理解と同意が必要ですね。

その他に、アート・オブ・アジャイル デベロップメントで参考になるのはコーディング規約に関する話です。

私はかつてプログラマ4名からなるチームを率いていた。書式に関して、彼らは大きく異なるアプローチをとっていた。コーディング標準について議論するときに、私はカッコとタブに関して3つの異なるアプローチのカタログを作った。それぞれのアプローチには熱心な支持者がいた。私はそんな議論にはまり込みたくなかったので、好きなスタイルを使ってもいいよと言った。
結果は予想通りだった。コードの書式には、3つの異なるアプローチが使われていた。1つの短いメソッドの中に2つの異なるインデントが使われているのを見たことがある。
何が私を驚かせたか分かるかい?そんなにひどくなかったんだ。確かにレイアウトは見苦しいし、私は一貫性がある方が好きだ。それでもコードを読むことができた。結局、書式以外のコーディング標準のほうがもっと重要だったんだ。

アート・オブ・アジャイル デベロップメント コーディング標準(p138)より

これも長くコードを書いていると分かってきます。

コードは美しく書くのではなく、明瞭に書く。

明瞭というのはコードの意味が明瞭ということ。
例えばこんなコードです。

$artiles = array(
	'title1',
	'title2',
	'title3');
foreach($articles as $no => $article) {
	printf('No.%s : %s', $no, $article);
}
$vars = array(
	'title1',
	'title2',
	'title3');
foreach($vars as $key => $value) {
	printf('No.%s : %s', $key, $value);
}

明らかに、前者よりも後者が分かりやすいですよね
明らかに、後者よりも前者が分かりやすいですよね
レベルの低い例で申し訳ないのですが、これは本当に大事です。
この話がメソッド名やクラス名に繋がってきます。

PHPやPython、PerlやCというのは、全てプログラミング言語です。
言語なんです。
つまり、わかりやすい文章を明瞭に書く必要がある
それを先程の引用から考えさせられました。

アジャイル・プラクティスを読んで生まれる、”じゃあ、実際にはどうするの?“という疑問に答えてくれる一冊です。

バグがないプログラムのつくり方

バグがないプログラムのつくり方 JavaとEclipseで学ぶTDDテスト駆動開発 (Be agile!)

これはEclipseを使ったJava開発に向けて書かれた本です。
ですが、テストを書く上で必要なtipsが書かれた本でもあります。
個人的には”6章 テスト駆動開発のエトセトラ“だけ読めば十分だと思います。

6章には、TDDにおける犯しがちなミスその解決策がまとめて載せてあります。

賢人さんたちは、その仕様変更や機能追加の際には、必ず先に従来のコードをリファクタリングしてから始めるようにしていました。そうすることで、機能追加する前に従来のコードに対する理解も深まり、追加後のリファクタリングできそうなポイントも見つけることができます。

愚痴さんの参加しているプロジェクトもTDDを採用しており、たくさんのテストコードがあって、すべて成功しています。ここまでの状況は、賢人さんのプロジェクトと同じです。しかし愚痴さんは、テストコードのメリットは何度も実行できることだけだと思っていたので、テストコードの見直しは行いませんでした。

賢人さんのところは、テストコード自体のわかりやすさを保ったまま、ソフトウェアを大きくすることができました。仕様変更や機能追加の際に、テストコードから見直す場合にも、わかりやすいのは非常に助かりました。
愚痴さんのところも、機能追加の際にテストコードは非常に役立ちました。しかし、仕様変更の際に従来のテストコードを見直す必要がある場合には、時間がかかるようになってしまいました。

バグのないプログラムのつくり方 テスト駆動のエトセトラ(p144)より

私も面倒臭いがために、この中の愚痴くんと化しているところがいくつかありました。
わかってはいたけど、修正できない
まさに技術的負債ですね。

技術的負債を返すために、まずはテストコードのリファクタリングから始めましょうか。

買うのはちょっとという人は、立ち読みでも良いと思います。
6章を少し読んでみてください。
テストを見直すきっかけになります。

まだまだ得たものはたくさんあるのですが、記事に書けるのはこんなところです。
最後に個人的タスクをまとめておきます。
必ず実行して、技術的負債を完済します。

  • 必ず実行するタスク一覧
    • テストコードのリファクタリング
    • コーディング規約の見直し
    • コーディング規約チェック自動化
    • テスト自動化
    • デプロイ自動化
    • デモ環境へのデプロイ自動化

最後に

最近、技術書に関する記事が盛り上がっていましたね。

人それぞれ、各書籍に対して色んな意見があると思います。
とりあえず、立ち読みで、あるいは図書館で借りて読んでみてはどうでしょうか。
自分にとって役に立たないのであれば、読まなければ良いんですから。
(少しの時間が取られるかもしれませんが、良書にあったときに得られるものと比べたら大したことはないでしょう。)

私が今回読んだ3冊も、通読なんてしていません。
何を学びたいか、何を得たいかだけ意識して、自分の欲しいところだけ読みました。

自身のレベルによって学べるところは様々なんだから、読む本は自分で決めましょう!

これが言いたかっただけで、”最後に”を書いてみました(^^;

Google日本語入力のイベントに行ってきた – コンセプト編

google_ime_logo.png

先週の土曜日、Google 日本語入力 TechTalk 2010に参加してきました。

Google日本語入力を使ったこともインストールしたこともないのに、イベント情報を見た瞬間に参加することを決定。
面白半分で参加することに決めました。

しかし、このイベントは予想外に面白いものでした。
Google日本語入力プロジェクトの立ち上げや、そのコンセプト、さらにはコードリーディングまで。

残念ながら私は技術力も頭も足りないので、詳しく理解できなかった点がたくさんあります。
ですが、有益な情報は共有したいので、記事にまとめてみます。

長くなってしまうので、2部に分けます。
今回はコンセプト編!

Google日本語入力のイベント

このイベントの参加案内があったのは2010年9月17日。
場所は六本木のGoogle東京オフィス14:00-19:00のなかなか長丁場。
懇親会という名の”タダ飯付き“というイベントです。

実際に当日行ってみると、綺麗なホールに約100名以上のエンジニアが集まるという規模の大きさ。
そして、入り口にはなんとお酒があるというおもてなし。
さすがGoogle様、分かってらっしゃる。

オープニングトークで、以下の内容でセッションが行われることが発表されました。

  • Google 日本語入力ができるまで
    • Google日本語入力のコンセプトやプロジェクトの立ち上げについて。配られた冊子を見れば、ほとんどわかる。
  • Google 日本語入力の設計概要
    • Google日本語入力の設計について。既存のIMEと異なる点を取り上げる。
  • Mozc ソースコードレビュー
    • Mozcのソースコードを読む上ためのポイントを説明。ソースを晒しながら、改造するのに必要な点なども。
  • ライトニングトーク
    • IMEに限らず、”入力”というテーマでレベルの高いLTを参加者とGoogle社員が。

順を追って”Google日本語入力“が理解できるようです。
ワクワクしてきました。
(決して、お酒が入ったからではないですよ:P)

Google日本語入力ができるまで

Google日本語入力が出来るまで“は会場で配られたこの冊子を読むとわかります。

google_ime_book.JPG

中は漫画になっています。

google_ime_comic.JPG

わかりやすいので、皆さんに見てもらいたい。
どこかに公開していないのかな?

と思ったらありました。

これを読んでしまえば、この記事もいらn(ry

20%ルール

Google日本語入力は、皆さんお馴染みの”20%ルール“を使って立ち上げられたプロジェクトです。
20%ルールの簡単な説明と、それを使って立ち上げられたプロジェクトが紹介されます。
あの”Google App Engine“も20%ルールで作られたプロジェクトだと、このセッションで知りました。

続いて、このプロジェクトを立ち上げた開発者が紹介されます。

  • 工藤 拓さん
    • 言わずと知れたmecabの開発者。Googleではもしかして検索を担当しているそうです。
  • 小松 弘幸さん
    • 予測変換システム「PRIME」の開発者。工藤さんから声が掛かる前から、Google日本語入力のアイディアを温めていたそうです。

MeCabの開発者”工藤 拓“さんが開発に携わっていたんですね。
そして、Googleに居るというのもびっくり。
まったく予習していないのがバレバレですが…。

リリースまでのプロセス

リリースまでは以下のプロセスで開発を進めたそうです。

  1. ディスカッション
  2. 開発・テスト
  3. ドックフード
  4. 開発版リリース
  5. ベータ版リリース

どれもこれも興味深い話ばかりでした。
特に印象に残ったのは”ディスカッション“と”テスト“、それに”ドックフード“です。

ディスカッション

Google日本語入力をどんなものにするか“を20%ルールを使い、話し合ったそうです。
初めの段階でこういった話をするのは珍しい話じゃない、というか当たり前です。
走る前に考えましょう。

普通じゃないのがこの期間。
約半年“の間、ディスカッションだけに20%ルールの時間を使ったそうで。

その中身は、以下の内容。

  • 既存のIMEの成功/失敗事例
    • 既存のIMEの悪いところや良いところに関して話し合っていく。Google日本語入力のイメージを固める作業でもあったようです。
  • Google日本語入力のコンセプト
    • “どんなIMEを作りたいか”を事例を踏まえ、コンセプトとして固めます。Google日本語入力のコンセプトは”As you think of・空気のように”です。

こういったコンセプトを詰めるのに半年も使うんですね。

6ヶ月 x 4週 x 8時間 = 192時間

約200時間を費やして決めたコンセプト。
優秀なプログラマはコーディングする時間よりも、考える時間の方が長い。
その最たる例な気もします。

テスト

テストで興味深かったのは、以下の3点です。

  • ユニットテスト
  • 変換率と当たり前の変換テスト
  • UIのテスト

Google日本語入力ではしっかりユニットテストが行われています。
これは当たり前なので、特筆すべき点はありません。

面白かったのは”変換率と当たり前の変換テスト“です。
日本語変換という性質上、変換のために”学習型のアルゴリズム“を使います。
この変換はこのアルゴリズムを使って機械的に変換するため、その部分のテストが難しいです。
そこでGoogle日本語入力では、”変換率“のテストの他に、”当たり前の変換テスト“を行っているそうです。

これは何か。
ユーザビリティのテストに当たるもので、”ユーザが欲しい変換“が出来るかどうかをテストするものです。
例えば、”あいます”と入れれば”アイマス”ではなく”会います”が出てくる。
“どこいく”といれれば”ドコイク”ではなく”どこ行く”が出てくる。
こういった具合です。

このような辞書をテスト用に作成し、このテストが全て通らないと出荷しないそうです
テストとその自動化に徹底していますね。

このセッションの最後に、

インターフェースなどのテストの難しい部分はどうしているか?

という質問がありました。
これに対する答えは、

我々もそのベストプラクティスを探している。しかし、できるだけユニットテストに落としこむことが重要。

というものでした。
Googleでもやはり、テストの王道に従い、ユニットテストを積み重ねることで品質を保っているんですね。
どれだけパーツを独立した設計にできるか。
それが重要ですね。

ドックフード

Google日本語入力のイベントに参加するまで、この言葉も知りませんでした。

自分で自分の作ったドックフードを食ってから出せ。
最低限、食べられるモノが出来てから出せ“といった意味が込められています。

Googleでもそれに乗っ取って開発とテストを進めています。
毎日ビルドし、それを社内版として公開しているそうです。
そうして、社内の人間に使ってもらい、フィードバックを得る。

これって基本だと思いますが、とても大事なことですよね。
これを怠った会社はもはやソフトウェア会社じゃないような。
自分で作ったモノが使いにくいのに、誰が使ってくれるんでしょうか。

自分への戒めとしたいですね。

今回の記事はこんなところで。
次回は”Google日本語入力の設計“に関してまとめます。
細かいところまでは理解できなかったので、表面だけさらっと…(苦笑)

他の参加者の方のまとめもどうぞ!

CakePHPでフィクスチャに惑わされずにテストを書く方法 – モック編

cakephp_testing.png

前回挙げたチュートリアルはやってみましたか?
快適なテストライフを送ってますか?

テストケースをたくさん書いていると気づくのは、フィクスチャがメンテナンスの邪魔をするということ。
フィクスチャに初期データを定義すると、それを気にしながらテストケースを作ることになります。
これがとても面倒くさいんです。

これを解消すべく、今日はモックを使ったテストケースの書き方を紹介します。

モックとは

SimpleTestのモックで参考になるのは、以下の書籍です。

Webアプリケーションテスト手法 Webアプリケーションテスト手法

  • 著者: 水野 貴明 (著), 石井 勇一 (著), 新藤 愛大 (著), 岸田 健一郎 (著), 荻野 淳也 (著), 安井 力 (著), 田中 慎司 (著)
  • 出版社: 毎日コミュニケーションズ
  • 発売日: 2008/7/25

この書籍のp154にモックについて以下のように書いてあります。

モックを使うとデータファイルからではなく擬似的に値を返せるので、OrderReaderがどのような動作をするのか可視的に図ることができます。もしCSVから違う入力形式をサポートするように仕様が変わった場合にも、このテストコードを見れば、修正が用意となります。

さてモックというと何だか難しく聞こえますが、テストコードの記述手順を箇条書きにしてみれば、それほどでもないと思うでしょう。モックを使わない場合のテストは以下の手順です。

1. テストするメソッドを呼び出す
2. テスト結果を評価する

一方でモックを使う場合は、以下の手順です。

1. モックを生成する
2. モックで戻り値を設定する
3. テストするメソッドを呼び出す
4. テスト結果を評価する
5. モックが使われたか確認する

“Webアプリケーションテスト手法”より

またモックを英英辞書で引くと以下のようにあります。

You use mock to describe something which is not real or genuine, but which is intended to be very similar to the real thing.

“Collins Cobuild English Dictionary”より

“very similar to real thing”がポイントですね。
つまり、一言でモックを言うならば”見せかけのクラス”です。

ランキングモデルのテストケースの修正

今回の記事は前回の”cakephpを使ったテスト駆動開発“の続きです。
使用したコードやチュートリアルの流れなどは全て前回の記事を参考にして下さい。

以下が前回、作成したランキングモデルのテストケースです。

/* Ranking Test cases generated on: 2010-07-13 18:07:16 : 1279014616*/
App::import('Model', 'Ranking');

// テストケース用のクラスはTestCaseで終わる名前にし、CakeTestCaseを継承する。TestCaseより前はファイル名と一致させる。必ずしもテストするクラス名と一致させる必要はない。
class RankingTestCase extends CakeTestCase {
  var $fixtures = array('app.ranking');

  function startTest() {
    $this->Ranking =& ClassRegistry::init('Ranking');
  }

  function endTest() {
    unset($this->Ranking);
    ClassRegistry::flush();
  }

  /**
   * addGoodメソッドのテスト
   */
  // testで始まるメソッドがテストとして実行される。テストメソッドは必ずtestでメソッド名を始めること。
  function testAddGood() {
    debug('addGoodメソッドのテスト');

    // 正常: id=1のデータに対してaddGoodメソッドを実行する。
    // 確認: 返り値がtrueであること、goodがプラス1されていること。
    $ret = $this->Ranking->addGood(1);
    $this->assertTrue($ret);
    $params = array(
      'conditions' => array('id = ' => 1),
      'fields' => array('good'),
      'recursive' => -1
    );
    $data = $this->Ranking->find('list', $params);
    $expected = array(1 => 2);
    // この部分がテスト: 期待する結果とメソッドの戻り値を比較して等しいならテストが通る。等しくないならテストが失敗する。
    $this->assertEqual($expected, $data);

    // 異常: id=2のデータに対してaddGoodメソッドを実行する。
    // 確認: 返り値がfalseであること。
    $ret = $this->Ranking->addGood(1);
    // この部分がテスト: メソッドの戻り値がfalseならテストが通る。false以外ならテストが失敗する。
    $this->assertFalse($ret);

  }

  /**
   * getGoodTitlesメソッドのテスト
   */
  // testで始まるメソッドがテストとして実行される。テストメソッドは必ずtestでメソッド名を始めること。
  function testGetGoodTitles() {

    // 正常: 取得件数を5件に設定し、getGoogTitlesメソッドを実行する。
    // 確認: 上位から5件のデータが取得出来ること。
    $ret = $this->Ranking->getGoogTitles(5);
    $expected = array(
      array(
        'Ranking' => array(
          'id' => 1,
          'title' => 'title1',
          'good' => 10,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 2,
          'title' => 'title1',
          'good' => 9,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 3,
          'title' => 'title1',
          'good' => 8,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 4,
          'title' => 'title1',
          'good' => 7,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 5,
          'title' => 'title1',
          'good' => 6,
        ),
      ),
    );
    // この部分がテスト: 期待する結果とメソッドの戻り値を比較して等しいならテストが通る。等しくないならテストが失敗する。
    $this->assertEqual($expected, $ret);

  }

}
?>

このうち、テスト対象のRankingモデルをモック化してしまいます。
こうすることで、特定のメソッドを実行したときに呼び出されるfindメソッドやdeleteメソッドのコール回数や引数を確認出来るのです。

テスト用のクラスRankingTestCaseを書く前に以下の記述を追加してください。

/**
 * テーブルを使用させないためにテスト対象のRankingモデルをオーバライド
 */
class TestRanking extends Ranking {
  var $useTable = false;
}

Mock::generatePartial(
  'TestRanking', 'MockRanking',
  array('exists', 'updateAll', 'find')
);

これでRankingモデルがモック化されたMockRankingモデルが生成されます。
モックの考え方は初め、意味がわからないと思います。
そのため、今回は説明の前にテストケースを書いてしまいます。

<?php
/* Ranking Test cases generated on: 2010-07-13 18:07:16 : 1279014616*/
App::import('Model', 'Ranking');

/**
 * テーブルを使用させないためにテスト対象のRankingモデルをオーバライド
 */
class TestRanking extends Ranking {
  var $useTable = false;
}

Mock::generatePartial(
  'TestRanking', 'MockRanking',
  array('exists', 'updateAll', 'find')
);

class RankingTestCase extends CakeTestCase {
    // TestRankingクラスを作ったこと、モッククラスを作ったことでテーブルが不要に。フィクスチャをコメントアウトする。
	// var $fixtures = array('app.ranking');

	function startTest() {
		$this->Ranking =& ClassRegistry::init('MockRanking');
	}

	function endTest() {
		unset($this->Ranking);
		ClassRegistry::flush();
	}

  /**
   * addGoodメソッドのテスト
   */
  function testAddGood() {
    debug('addGoodメソッドのテスト');

    // 正常: id=1のデータに対してaddGoodメソッドを実行する。
    // 確認: exists, updateAllメソッドが呼ばれていること。返り値がtrueであること。

    // existsメソッドが引数なしで呼ばれていること。
    $this->Ranking->expectOnce('exists', array());
    // existsメソッドの返り値をセット
    $this->Ranking->setReturnValue('exists', true);

    // updateAllメソッドがgoodをインクリメントする内容の引数で呼ばれていること。
    $fields = array('good' => 'good + 1');
    $conditions = array('id = ' => 1);
    $this->Ranking->expectOnce('updateAll', array($fields, $conditions));
    // updateAllメソッドの返り値をセット
    $this->Ranking->setReturnValue('updateAll', true);

    // addGoodメソッドの呼び出しと結果の確認
    $ret = $this->Ranking->addGood(1);
    $this->assertTrue($ret);

  }

  function testAddGoodNothing() {

    // 異常: 存在しないデータ(existsメソッドがfalseを返すデータ)に対してaddGoodメソッドを実行する。
    // 確認: existsメソッドが呼ばれていること。updateAllメソッドが呼ばれていないこと。 

    // existsメソッドが引数なしで呼ばれていること。
    $this->Ranking->expectOnce('exists', array());
    // existsメソッドの返り値をセット
    $this->Ranking->setReturnValue('exists', false);

    // updateAllメソッドが呼ばれていないこと。
    $this->Ranking->expectNever('updateAll');

    $ret = $this->Ranking->addGood(10);
    $this->assertFalse($ret);

  }

  /**
   * getGoodTitlesメソッドのテスト
   */
  function testGetGoodTitles() {
    debug('getGoodTitlesメソッドのテスト');

    // 正常: 取得件数を5件に設定し、getGoodTitlesメソッドを実行する。
    // 確認: findメソッドが呼ばれていること。

    // findメソッドが上位5件を取得する条件で呼ばれていること。
    $params = array(
      'limit' => 5,
      'page' => 1,
      'order' => 'good DESC',
      'recursive' => -1,
    );
    $this->Ranking->expectOnce('find', array('all', $params));
    $return = array(
      array(
        'Ranking' => array(
          'id' => 6,
          'title' => 'title6',
          'good' => 10,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 5,
          'title' => 'title5',
          'good' => 9,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 4,
          'title' => 'title4',
          'good' => 8,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 3,
          'title' => 'title3',
          'good' => 7,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 2,
          'title' => 'title2',
          'good' => 6,
        ),
      ),
    );
    $this->Ranking->setReturnValue('find', $return);

    $ret = $this->Ranking->getGoodTitles(5);
    $expected = array(
      array(
        'Ranking' => array(
          'id' => 6,
          'title' => 'title6',
          'good' => 10,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 5,
          'title' => 'title5',
          'good' => 9,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 4,
          'title' => 'title4',
          'good' => 8,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 3,
          'title' => 'title3',
          'good' => 7,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 2,
          'title' => 'title2',
          'good' => 6,
        ),
      ),
    );
    $this->assertEqual($expected, $ret); 

  }

}
?>

コメントに入れた観点でテストを実施しています。
前回のテストと今回のテストの観点の比較を以下に書いておきます。

前回のテスト 今回のテスト
1. テスト対象のメソッドを呼び、テーブルに実際にデータを挿入する。 1. テスト対象のメソッドを呼び、モック化されたクラスのメソッドを呼ぶ。
2. このデータをテストケースで読み込み、想定したデータと一致するか、確認する。 2. こうして呼ばれたメソッドの呼び出し回数や引数が一致するか、確認する。

まずはモックを使うと、”このようなテストが出来る“ということが理解出来ましたか?
これがわかれば後は書き方を理解するだけです。

モックの作り方

先ほどのコーディングで何をしているのか、モックはどう書くのかを順を追って説明して行きます。

まずはモックのイメージです。
始めに書いた以下のコードは、次のようなクラスを作成することを意味しています。

/**
 * テーブルを使用させないためにテスト対象のRankingモデルをオーバライド
 */
class TestRanking extends Ranking {
  var $useTable = false;
}

Mock::generatePartial(
  'TestRanking', 'MockRanking',
  array('exists', 'updateAll', 'find')
);

mock_object.png
モックのイメージ

このことは、Mock::generatePartialをprintすると簡単にわかります。

<?php
class MockRanking extends TestRanking {
  var $_mock;
  var $_mocked_methods = array('exists', 'updateall', 'find');

  function MockRanking() { $this->_mock = &new SimpleMock();
    $this->_mock->disableExpectationNameChecks();
  }

  function setReturnValue($method, $value, $args = false) {
    if (! in_array(strtolower($method), $this->_mocked_methods)) {
      trigger_error("Method [$method] is not mocked");
      $null = null;
      return $null;
    }
    $this->_mock->setReturnValue($method, $value, $args);
  }

  /** ----- 省略 ----- **/

  function exists() {
    $args = func_get_args();
    $result = &$this->_mock->_invoke("exists", $args);
    return $result;
  }  

  function updateAll() {
    $args = func_get_args();
    $result = &$this->_mock->_invoke("updateAll", $args);
    return $result;
  }  

  function find() {
    $args = func_get_args();
    $result = &$this->_mock->_invoke("find", $args);
    return $result;
  }

}
?>

わかりますか?つまり、Mockクラスがgenerateメソッドの呼び出しを受けて、自動的にコードを吐き出しているんです。
PHPはスクリプト言語なので、こうして吐き出されたコードも簡単に自身のコードの一部として扱うことが出来ます。
こういった面白いハックを見ると、自分でも作ってみたくなりますね。

話が少し逸れました。モックのクラスの生成について理解出来ましたか?
これが理解出来れば後は簡単、テストに使用する基本的なメソッドの動きがわかってくると思います。

後はもう自由にテストを書けますよね?

モックでよく使用するメソッド

最後にモックでよく使用するメソッドをまとめておきます。
もちろん、モック化したクラスに対してメソッドを実行してください。

このメソッド一覧があれば、どんなテストも簡単に書けるようになるでしょう。

メソッド名 動作
expectAt
($n, $method, $arguments)
n回目の特定のメソッドの呼び出しが指定した引数であることを確認する。
expectCallCount
($method, $n)
特定のメソッドがn回呼び出されることを確認する。
expectNever
($method)
特定のメソッドが呼び出されないことを確認する。
expectOnce
($method, $arguments)
呼び出し回数が1回で、かつ指定した引数であることを確認する。引数指定を省略した場合は、呼び出し回数のみ確認する。
setReturnValue
($method, $value)
特定のメソッドの返り値をセットする。
setReturnValueAt
($n, $method, $value)
特定のメソッドのn回目の返り値をセットする。

モック、なかなか便利でしょう。
私はよく、フィクスチャを書くのが面倒なときやデータベースの仕様が固まっていないときに、モデルのモックを作りテストします。

特にデータベースの仕様が固まっていないときに便利です。
テストケースを書きながら、テーブルのフィールドや型を考えることが出来ます。

これは以下の4ステップが、

  1. データベース仕様を決める。
  2. メソッドを作成する。
  3. データベース仕様を変更する。
  4. メソッドを変更する。

次の2ステップに変わることを意味します。

  1. メソッドを作成する。
  2. データベース仕様を決める。

なかなか、便利なので皆さんもやってみてください。

モックを使ったテストケースの書き方がわかりましたか?
もし、わからない点や間違った点があれば気軽にコメントをください。

[2010/09/08 追記]

Pythonでのテストの記事ですが、テストやメソッドを設計する上で参考になる点が多いと思います。
ご一緒にどうぞ。

CakePHPを使ったテスト駆動開発

ブログ初ポストはCakePHPを使ったテスト駆動開発です。

CakePHPはユニットテストとしてSimpleTestに対応しています。
SimpleTestをインストールするだけで、モデルやコントローラ、シェル、ルーティングクラスなどのユニットテストが出来るようになります。

今日はこのCakePHPとSimpleTestを使ってテスト駆動開発の流れを説明します。
ただ、僕自身テスト駆動開発を学んだのは去年のCake祭りなので、至らない点が多々あります。
もし何かあれば、コメントでご指摘ください。

今更感もありますが、この場を借りてCake祭りでテスト駆動の指導をしてくださった、@sizuhikoさんに感謝します。

開発手順

まずは開発手順を示します。少し細かいですが、テスト駆動では以下のような順で開発していきます。

  1. 設計する。
  2. テストケースを書く。
  3. テストケースをデバッグする。
  4. コードを書く。
  5. テストケースを実行する。
  6. コードをデバッグする。
  7. テストケースを全て通す
  8. コードが完成する。

コードとテストケースが分かれていること、それに始めにある程度設計してしまうのがポイントです。
設計といっても、私がやるのは白紙のA4用紙にクラスとメソッド名を書き出すぐらいです。
こうして置くと、テストケースもコードも書きやすくなります。

これだけ見ても、実感がないと思うので実例で説明していきます。

ランキングモデルの開発

良くある例としてCakePHPを使ったランキングを作ります。
といっても全てを作る時間はないので、ランキングデータを取り扱うランキングモデルをテスト駆動開発で作ります。

設計

モデル名やスキーマを次のようにします。

  • モデル名 : Ranking
    • addGood : goodを追加するメソッド
    • getGoodTitles : goodの順にデータを取得するメソッド
  • テーブル名 : rankings
    • id : 主キー
    • good : 良いと思った人の数
    • title : ブログのタイトル

ブログにgoodボタンをつけて、それを押すとスキーマのgoodが1増えるイメージです。
例としてはありがちですが、facebookの「良いね!」機能から考えました。

今回は書きませんが、コントローラからaddGoodメソッドやgetGoodTitlesメソッドを呼びます。

テストケース・コーディング

設計が決まったらテストケースを書いていきます。さっそく書き始めたいですが、その前に色々とやることがあります。
空の状態(CakePHPのappディレクトリ)からの開発を想定してますので、モデルやテーブルを作る必要があります。

まず、データベース設定をして、この画面が出るようにしましょう(設定の仕方は省略します)。

startup.jpg

次はschema.phpを作りましょう。schema.phpはCakePHPでデータベースのスキーマを管理出来るファイルです。
ここにスキーマ情報を書き込んでおけば、データベースのマイグレーションが簡単になります。

cake schema generate

Welcome to CakePHP v1.3.2 Console
---------------------------------------------------------------
App : ranking
Path: /home/tfmagician/ranking
---------------------------------------------------------------
Cake Schema Shell
---------------------------------------------------------------
Generating Schema...
Schema file: schema.php generated

schema.php

<?php
/* SVN FILE: $Id$ */
/* Ranking schema generated on: 2010-07-13 17:07:41 : 1279010981*/
class RankingSchema extends CakeSchema {
  var $name = 'Ranking';

  function before($event = array()) {
    return true;
  }

  function after($event = array()) {
  }

}
?>

まだ、データベーステーブルを作っていないので空の状態でテンプレートが出来上がります。
ここに設計したスキーマ情報をプロパティとして加えます。

  var $rankings = array(
    'id'    => array('type' => 'integer',  'null' => false, 'default' => NULL, 'key' => 'primary'),
    'title' => array('type' => 'string',   'null' => false, 'default' => NULL, 'key' => 'index'),
    'good'  => array('type' => 'integer',  'null' => false, 'default' => 0),
    'indexes' => array(
      'PRIMARY' => array('column' => 'id',    'unique' => 1),
      'title'   => array('column' => 'title', 'unique' => 0),
    ),
    'tableParameters' => array(
      'charset' => 'utf8',
      'collate' => 'utf8_general_ci',
      'engine'  => 'MyISAM',
    ),
  );

これを元にCakePHPにテーブルを作らせます。

cake schema create

Welcome to CakePHP v1.3.2 Console
---------------------------------------------------------------
App : ranking
Path: /home/tfmagician/ranking
---------------------------------------------------------------
Cake Schema Shell
---------------------------------------------------------------

The following table(s) will be dropped.
rankings
Are you sure you want to drop the table(s)? (y/n)
[n] > y
Dropping table(s).
rankings updated.

The following table(s) will be created.
rankings
Are you sure you want to create the table(s)? (y/n)
[y] >
Creating table(s).
rankings updated.
End create.

これで指定したスキーマのテーブルが出来ているはずです。phpmyadminなどで確認しておきましょう。

次にテストで使うコードとモデルのコードをbakeで作成します。

cake bake model

Welcome to CakePHP v1.3.2 Console
---------------------------------------------------------------
App : ranking
Path: /home/tfmagician/ranking
---------------------------------------------------------------
---------------------------------------------------------------
Bake Model
Path: /home/tfmagician/ranking/models/
---------------------------------------------------------------
Possible Models based on your current database:
1. Ranking
Enter a number from the list above,
type in the name of another model, or 'q' to exit
[q] > 1
Would you like to supply validation criteria
for the fields in your model? (y/n)
[y] > y

Field: id
Type: integer
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphanumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalto
12 - extension
13 - inlist
14 - ip
15 - maxlength
16 - minlength
17 - money
18 - multiple
19 - notempty
20 - numeric
21 - phone
22 - postal
23 - range
24 - ssn
25 - time
26 - url
27 - userdefined
28 - Do not do any validation on this field.
... or enter in a valid regex validation string.

[28] >

Field: title
Type: string
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphanumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalto
12 - extension
13 - inlist
14 - ip
15 - maxlength
16 - minlength
17 - money
18 - multiple
19 - notempty
20 - numeric
21 - phone
22 - postal
23 - range
24 - ssn
25 - time
26 - url
27 - userdefined
28 - Do not do any validation on this field.
... or enter in a valid regex validation string.

[19] >
Would you like to add another validation rule? (y/n)
[n] >

Field: good
Type: integer
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphanumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalto
12 - extension
13 - inlist
14 - ip
15 - maxlength
16 - minlength
17 - money
18 - multiple
19 - notempty
20 - numeric
21 - phone
22 - postal
23 - range
24 - ssn
25 - time
26 - url
27 - userdefined
28 - Do not do any validation on this field.
... or enter in a valid regex validation string.

[20] >
Would you like to add another validation rule? (y/n)
[n] >
Would you like to define model associations
(hasMany, hasOne, belongsTo, etc.)? (y/n)
[y] >
One moment while the associations are detected.
---------------------------------------------------------------
Please confirm the following associations:
---------------------------------------------------------------
Would you like to define some additional model associations? (y/n)
[n] >

---------------------------------------------------------------
The following Model will be created:
---------------------------------------------------------------
Name:       Ranking
DB Table:   `rankings`
Validation: Array
(
    [title] => Array
        (
            [notempty] => notempty
        )

    [good] => Array
        (
            [numeric] => numeric
        )

)

Associations:
---------------------------------------------------------------
Look okay? (y/n)
[y] >

Baking model class for Ranking...

Creating file /home/tfmagician/ranking/models/ranking.php
Wrote `/home/tfmagician/ranking/models/ranking.php`
SimpleTest is not installed. Do you want to bake unit test files anyway? (y/n)
[y] > y

You can download SimpleTest from http://simpletest.org

Baking test fixture for Ranking...

Creating file /home/tfmagician/ranking/tests/fixtures/ranking_fixture.php
Wrote `/home/tfmagician/ranking/tests/fixtures/ranking_fixture.php`
Bake is detecting possible fixtures..

Creating file /home/tfmagician/ranking/tests/cases/models/ranking.test.php
Wrote `/home/tfmagician/ranking/tests/cases/models/ranking.test.php`

(あ、忘れていましたが、cakeコマンドはcakeコンソール(cake/console/cake)のことです。適宜置き換えてくださいね。)

これでテストケースとフィクスチャ(後で説明します)、モデルのテンプレートが完成しました。
あとはコーディングしていくだけです。

と、一つ忘れていたことがありました。SimpleTestのインストールです。
SimpleTestはアプリケーションのvendorsか、CakePHP本体と同じ階層のvendorsに配置します。

cd vendors/
wget http://downloads.sourceforge.net/simpletest/simpletest_1.0.1.tar.gz
tar zxvf simpletest_1.0.1.tar.gz
rm simpletest_1.0.1.tar.gz

配置したら、ブラウザからtest.phpにアクセスします。

test.php
test_php.jpg

左メニューからApp > Test Casesをクリック
test_cases.jpg

コンテンツからmodels / Rankingをクリック
ranking_test.jpg

bakeで作ったテンプレートのテストが実行されているのがわかります。
まだ何も書いていないですが、テンプレートのテストが実行され、1件のテストケースが通っています。

これでテストが実行出来るようになりました。テストケースをコーディングします。
テストケースはさきほどのbakeの最後に書かれている通り、tests/cases/models.ranking.test.phpにあります。
これを元に、オリジナルのテストケースをコーディングします。

tests/cases/ranking.test.php

/* Ranking Test cases generated on: 2010-07-13 18:07:16 : 1279014616*/
App::import('Model', 'Ranking');

// テストケース用のクラスはTestCaseで終わる名前にし、CakeTestCaseを継承する。TestCaseより前はファイル名と一致させる。必ずしもテストするクラス名と一致させる必要はない。
class RankingTestCase extends CakeTestCase {
  var $fixtures = array('app.ranking');

  function startTest() {
    $this->Ranking =& ClassRegistry::init('Ranking');
  }

  function endTest() {
    unset($this->Ranking);
    ClassRegistry::flush();
  }

  /**
   * addGoodメソッドのテスト
   */
  // testで始まるメソッドがテストとして実行される。テストメソッドは必ずtestでメソッド名を始めること。
  function testAddGood() {
    debug('addGoodメソッドのテスト');

    // 正常: id=1のデータに対してaddGoodメソッドを実行する。
    // 確認: 返り値がtrueであること、goodがプラス1されていること。
    $ret = $this->Ranking->addGood(1);
    $this->assertTrue($ret);
    $params = array(
      'conditions' => array('id = ' => 1),
      'fields' => array('good'),
      'recursive' => -1
    );
    $data = $this->Ranking->find('list', $params);
    $expected = array(1 => 2);
    // この部分がテスト: 期待する結果とメソッドの戻り値を比較して等しいならテストが通る。等しくないならテストが失敗する。
    $this->assertEqual($expected, $data);

    // 異常: id=2のデータに対してaddGoodメソッドを実行する。
    // 確認: 返り値がfalseであること。
    $ret = $this->Ranking->addGood(1);
    // この部分がテスト: メソッドの戻り値がfalseならテストが通る。false以外ならテストが失敗する。
    $this->assertFalse($ret);

  }

  /**
   * getGoodTitlesメソッドのテスト
   */
  // testで始まるメソッドがテストとして実行される。テストメソッドは必ずtestでメソッド名を始めること。
  function testGetGoodTitles() {

    // 正常: 取得件数を5件に設定し、getGoogTitlesメソッドを実行する。
    // 確認: 上位から5件のデータが取得出来ること。
    $ret = $this->Ranking->getGoogTitles(5);
    $expected = array(
      array(
        'Ranking' => array(
          'id' => 1,
          'title' => 'title1',
          'good' => 10,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 2,
          'title' => 'title1',
          'good' => 9,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 3,
          'title' => 'title1',
          'good' => 8,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 4,
          'title' => 'title1',
          'good' => 7,
        ),
      ),
      array(
        'Ranking' => array(
          'id' => 5,
          'title' => 'title1',
          'good' => 6,
        ),
      ),
    );
    // この部分がテスト: 期待する結果とメソッドの戻り値を比較して等しいならテストが通る。等しくないならテストが失敗する。
    $this->assertEqual($expected, $ret);

  }

}
?>

コメント中にも書いてありますが、assertメソッドがテストです。
例えばassertEqualメソッドは、「2つの変数が等しいことを期待する」という意味になります。
この期待に応えない(変数が等しくない)場合、テストは失敗します。後でスクリーンショットで示します。

またテストメソッドが実行される順は、次のようになります。

  1. startTest
  2. テストメソッド1(testで始まるメソッド)
  3. endTest
  4. startTest
  5. テストメソッド2(testで始まるメソッド)
  6. endTest

なのでこのテストケースでは、以下の順でテストが実行されます。

  1. startTest
  2. testAddGood
  3. endTest
  4. startTest
  5. testGetGoodTitles
  6. endTest

必ずstartTestとendTestメソッドが呼ばれる点がポイントです。これらのメソッドでクラスの初期化などの処理を実施し、テストを書きやすくします。

このテストではデータの挿入、更新を行っています。しかし、さきほどテーブルを作ったばかりなのでデータはまだ入っていないはずです。
逐一、テストの度に手作業でデータを入れてもいいのですが、それはとても面倒です。そこでフィクスチャという機能を使います。

CakePHPのフィクスチャ機能はテーブルのデータをPHPのコードとして書いておくと、勝手にテスト前にデータを挿入してくれます。
挿入のタイミングはテストメソッド(テストケースの中のtestで始まるメソッド)毎です。つまり、特定のテストメソッドでデータを更新したり削除しても、次のテストメソッドでは新しいデータが入っています。
これを知っておくと、テストケースを書くのが楽になります。

tests/fixtures/ranking_fixture.php

/* Ranking Fixture generated on: 2010-07-13 18:07:16 : 1279014616 */
class RankingFixture extends CakeTestFixture {
  var $name = 'Ranking';

  // スキーマ情報をfieldsプロパティとして書いておきます。indixesはインデックスの指定、tableParametersはテーブルの設定値です。schema.phpを同じ形式で指定します。
  var $fields = array(
    'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
    'title' => array('type' => 'string', 'null' => false, 'default' => NULL, 'key' => 'index'),
    'good' => array('type' => 'integer', 'null' => false, 'default' => '0'),
    'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'title' => array('column' => 'title', 'unique' => 0)),
    'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
  );

  // データをrecordsプロパティとして書いておきます。
  var $records = array(
    array(
      'id' => 1,
      'title' => 'title1',
      'good' => 5,
    ),
    array(
      'id' => 2,
      'title' => 'title2',
      'good' => 6,
    ),
    array(
      'id' => 3,
      'title' => 'title3',
      'good' => 7,
    ),
    array(
      'id' => 4,
      'title' => 'title4',
      'good' => 8,
    ),
    array(
      'id' => 5,
      'title' => 'title5',
      'good' => 9,
    ),
    array(
      'id' => 6,
      'title' => 'title6',
      'good' => 10,
    ),
  );
}
?>

フィクスチャの書き方は省略します。ただ、連想配列で指定するので少し考えれば意味はわかるかと思います。
書き方がわからない場合はschema.phpを参考にしてください。

テストケースのデバッグ

作成したテストケースをデバッグします。まだモデル側のコードは書いていませんが、まずはtest.phpを開いてみましょう。

test.php
ranking.jpg

メソッドをまだ用意していないため、モデルにクエリを発行されてしまっています(CakePHPでは存在しないメソッドは全てクエリとして扱われます)。
これではデバッグにならないので、モデルにメソッドだけ用意しましょう。

models/ranking.php

<?php
class Ranking extends AppModel {
  var $name = 'Ranking';
  var $displayField = 'title';
  var $validate = array(
    'title' => array(
      'notempty' => array(
        'rule' => array('notempty'),
        //'message' => 'Your custom message here',
        //'allowEmpty' => false,
        //'required' => false,
        //'last' => false, // Stop validation after this rule
        //'on' => 'create', // Limit validation to 'create' or 'update' operations
      ),
    ),
    'good' => array(
      'numeric' => array(
        'rule' => array('numeric'),
        //'message' => 'Your custom message here',
        //'allowEmpty' => false,
        //'required' => false,
        //'last' => false, // Stop validation after this rule
        //'on' => 'create', // Limit validation to 'create' or 'update' operations
      ),
    ),
  );

  function addGood($id) {
  }

  function getGoodTitles($n) {
  }
}
?>

再度、test.phpを開きます。

test.php
test_debug.jpg

そうすると、一つだけクエリが発行されているのがわかります。
すでにお気づきの方がいるかもしれません。そうです、テストケースで実行しているRankigクラスのメソッド名がgoogTitlesとなっているのです。
よくあるtypoですね。修正し、再度テストケースを実行します。

test.php
test_debug_passed.jpg

これでテストケースのコードは大丈夫のようです。この段階でテストは通っていなくとも大丈夫です。
なんせ、まだ本体のコードを書いていないのですから。

コーディング

それでは本体のコードを書いていきます。先ほど追加した空のメソッドに以下のようにコーディングします。

models/ranking.php

  function addGood($id) {
    $this->id = $id;
    if(!$this->exists()) {
      return false;
    }
    return $this->updateAll(array('good' => 'good + 1'), array('id = ' => $id));
  }

  function getGoodTitles($n) {
    $params = array(
      'limit' => $n,
      'page' => 1,
      'order' => 'good DESC',
      'recursive' => -1,
    );
    return $this->find('all', $params);
  }

コーディングが完了したらtest.phpのページを再度開きます。

test.php
test_passed.jpg

テストが通ると、テストした件数と緑色のバーが表示されます。
この緑色のバーと作成したテスト件数があっていれば、テストは完全に通っています。

ここではあっさりテストを通してしまいましたが、実は1度のコーディングでテストが完了した訳ではありません。
この程度のコードでも2〜3度、テストを失敗し、コードを書き直しています。

やってみるとわかるのですが、テストケースがあることで簡単に、かつ手軽にコードを書き換えることができます。

テストケースを使ったテスト駆動開発を知るまでは、var_dumpを活用し、逐一途中のデータを見ながらコードを書いていました。
しかし、この方法だと仕様が自分の頭の中にしかないため、メソッドの機能が多様化してしまったり、想定しないバグに出くわしたりします。

テストケースを書くことで、頭の中にあるメソッドの仕様をコードとして書き出すことになります。
そうすることで、曖昧だった点や考慮しなければいけないケースも浮き彫りとなります。
さらにコードとして書き出すことで、メソッドを作る上での目標が定まり、コーディングしやすくなります。

テストケースというコードが増えるため、これを書くのを面倒に思ってしまいますが、この面倒さ以上のメリットがテスト駆動開発にはあると私は思います。

テスト駆動開発をする上でのポイント

最後に、テスト駆動開発でのポイントを書いておきます。参考にしてください。

  • テストケースを必ず始めに書く。
    • 後から書こうと思うと面倒くさくなる。また、後から書くと先ほど書いたメリットを受諾出来ない。
  • メソッド、クラスの機能追加時も必ずテストケースを書く。
    • テストケースも更新していかないと、せっかく書いたコードが無駄になる。テストケースはコードがしっかり動いていることの証拠である。
  • バグを見つけたときこそ、必ずテストケースを書く。
    • テストケースは上記と同様にコードがしっかり動いていることの保証である。
  • バグを見つけたときは、テストケースでしっかり対策する。
    • 統合テストでバグを見つけた場合はデバッグし、原因を見つける。その原因をメソッドレベルにまで落とし、そのメソッドのテストケースを書き、解決する。従ってテストケースも独立していることが重要である。
  • テストケースを出来るだけ独立させる。
    • 対象のメソッド、クラス以外の部分で仕様変更があった場合に、テストが通らなくなるのはテストケースのメンテナンスが面倒になる。出来るだけテストケースの対象を1つのメソッド、あるはクラスにしぼる。

これまでテスト駆動開発をやってきて学んだことを上げてみました。
テスト駆動開発で重要なことは一言で言うとメンテナンスしやすいテストケースを書くことだと思います。
テスト駆動開発をこれから始める方は、この点に注意すると、私の二の舞*1にならないかと思います。

ご参考になれば幸いです。

*1 テスト駆動開発を始めた頃は良かったのだが、その後メンテナンスが大変になった。結局、テストケースを全て捨てることに…。

ホーム > タグ > テスト

スポンサードリンク
書いている人
つぶやき
RSS 気になるニュース
過去の記事

ページの上部に戻る