単体テストの習慣がない場所で必ず聞く言葉がある。厳密に言えば、答えはNOである。単体テストは変更・追加開発した場合のデグレーションを減らすことはできるが、変更・追加箇所に不具合がないことを保証する方法論ではない。TDDで開発して、単体テストを予めかいておけば、いくらか不具合を減少させることはできるし、検証テストでは作るのが難しいテスト条件でのテストを行うこともできるので、まったく完全なNOではない。検証テストで発見される不具合が少なければ、不具合修正と再テストの時間を減らすことができるので、工数が減る場合もある。単体テストは銀の弾丸ではない。単体テストができるのは、テストケースで定義した状態・状況において不具合が発生すると教えることだけである。いうことを頭においた上で、テストを導入する方法を考えていかなければならない。テストを導入するにあたって考えるべきは下記の5点。単体テスト分の工数増加をどのようにプロジェクトマネージャやプロダクトマネージャに認めさせるか単体テストに関係する記事や本などを読んでいると必ず出てくる問題だが、僕はこれが一番簡単な障害だと思っている。一人ではじめ、その結果が出たらマネージャに見せる。立ち上げたばかりの会社で比較すべきプロジェクトが全くない状態であればこの手法は効かないが、多分そういう会社は少ないだろう。僕の会社はプロジェクト規模大小様々あるので、必ずしも比較できるわけではないが、それでも一人でひっそりと始めることはいつからでもできる。一人で出来る範囲であれば、できることも限られている。大きいプロジェクトであれば、テストコードのない部分とある部分が必ずでき、そしてその部分での検証テスト結果が出るはずだ。テストコードのある部分にだけきわめて検証テストの不具合発生率が低ければ成功である。失敗した場合は?失敗の原因は幾つもあると思う。テストの経験がなかった、自分のスキルが低かった、テスト導入に時間をとられてコードの質が下がった(本当はそれはよくないので、そんなになるくらいなら寧ろテストは書かないほうが良い)などなどあるが、それはいくらでもあとから挽回できる。一回目ダメだったら二回目を狙おう。書いたコードはレポジトリにサブミットしておけばよい。本番では使われないコードなので多少の質は悪くても問題ない。大事なのは続けることである。成功した場合で注意すべきは、現状の工数で問題なかったから、という理由で次回以降でも単体テスト分の工数を上乗せすることを許してもらえない場合だ。これはかなり根深い問題で、もし検証テストが思ったよりも早く終わったなら、その分を実装の工数にいれてくれというしかない場合もある。最悪の場合、検証が早く終わるならその分スケジュールも詰められるだろうと猶予分を削られる場合だ。これはもう本当に根強くこんこんと説得をし続けるほかない。上司が物分かりがよいか、新しいもの好き、もしくは周りに流されるタイプであることを願うほかない。ないのなら、仕方ないのでひっそりと頑張るしかないだろう。いずれにせよ、冒頭で書いたとおり単体テストは工数を短縮するための銀の弾丸ではないので、必要なときに必要なところだけを書くという運用方法に自分で切り替えていくことも必要かもしれない。ビルドサーバJenkinsテストフレームワークテスト対象コード仕様書と設計書たまに仕様書・設計書を見ずに間違ったテスト対象コードからテストを書いて、不具合がつかまえられないのでテストは意味が無いという人がいるが、テストを書くときに仕様書・設計書を確認して意識的にテストNGを出して欲しいと思う。自動化テストの範囲をどこまでと定めるべきかプロジェクトによるので一概には言えないが、結合テスト・システムテストの範囲を定めるように単体テストの範囲も定めるべきである。自動化テストは(あれば)UIテストも含むので、それにはシステムテストの範囲も含まれることがある。どこからは手動のテストでマネジメントをするのかきっちり定めないと、自動化テストの範囲も決まらない。本来は単体テストは結合テストの前までの範囲を受け持つので、結合先はモックなどをつくって擬似データを発生させる必要がある。状態の変化が必要な場合はモックがかなり高度・大規模になる場合もあり、テストが非常に煩雑になるので、そこはシステムテストの責任を委譲するのも手である。個人的にはモックを作成しなければならないテストは、結合テストの範囲とするのが一番シンプルかなと思う(が、だからと言ってしないわけには行かないこともたくさんある)範囲として考えられるのは小さい方から順に下記の通りモックを使用しない範囲で正常系だけを通す(プロジェクトが小さい・不具合発生時のリスクが小さい場合)モックを使用しない範囲でデシジョンカバレッジを100%にするモックを使用しない範囲でコンディションカバレッジを100%にするモックを使用して正常系を通す(結合時の不具合発生が予想され、かつ不具合の特定が難しいと判断出来る場合もしくは結合時の不具合発生をできるだけ抑えたい場合)モックを使用してデシジョンカバレッジを100%にする(状態の変化をデータで持っておく必要があるため、テストデータが爆発的に増える)モックを使用してコンディションカバレッジを100%にするモックを使用したテストが出来るように全体的にコードの設計を見直し、ステートメントカバレッジを100%にする(非現実的)個人的には2までにするか、4までにするか5までにするか、それ以上を頑張るかの判断を最初ですべきだと思う。僕の扱うプロジェクトはソフトウェアだけで完結するものではなく、自分では手を加えることのできない他社製品を制御するのがほぼ必須である。この場合は不具合発生時のリスクを見積もった後、2か4かの選択をする。必要ない場所は3で作る事が多いかな。枯れたシステムを制御する場合は2でもほぼ問題がない。制御するシステムが増えてくると4または5でないと、問題切り分けができなくなる。くわえて上位の自分では手を加えられない大規模なシステムと結合しなければならない場合もある。この場合は結合テスト時の不具合発生の問題切り分けが非常に煩雑でリスクが高いので、4または5を目標にすることが多い。一部システムテストで責務を負うべきところも単体テスト・自動化テストでやってしまうこともある。テストを書きたがらないエンジニアにどうやって書かせるかテストを書くのは意味が無いという人は必ずいるし、その信念はひとつの真理ではある。社会人として尊重した上で、それでも書いて欲しいとお願いするときにどうするかということを考えていかなければならないこともある。テストを導入する手順は次のようにするとわりとスムーズだ。聞いた際にまずテストを書いてそれが本当かどうかエビデンスを出してくれと頼み、テストの書き方を教える。書けるまで徹底的に付き合う。その場合の文句とかはちゃんときき、一緒に解消方法を考える必要ならテストをフレームワーク化する不要な部分までテストが必要!という人がいたら、こういう目的で今回は単体テストを行っているので、あればよいですが無理しなくてよいですと伝えるプロジェクトの変更・追加の前に単体テストの責務の範囲についてチームで話し合う。9を繰り返すスキルのないエンジニア(俺のことだ)の場合要求分析の練習にもなるので、6とかはすごく大事だとおもう。9ができるようになればほぼ定着していると思うので、そのまま続ける。新しいもの好きな人はあきるのも早い場合が多いので、ある程度テストコードが増えてきたら、テストを書きたがらないエンジニアを巻き込むことを考え始めたほうが良い。膨大な既に書かれているテストをどうやってマネジメントするかTDDなどでは殆ど触れられないが、テストコードのマネジメントは非常に難しい。プロジェクトの規模が増えればもちろんテストコードは増えるし、結合先の状態が変わるテストをしようと思ったら、テストデータも増える。テストコードにはコメントを書くそもそもテストコードは読みにくい。僕はコメントはできるだけ書かない派(どうしても必要な箇所にだけ書くべきだが、それ以外は書くべきではない派)だが、テストコードにはコメントを入れておいたほうが良いかもしれない。テストケース名も、どれだけ長くなっても見るだけで何のテストかわかるほうが良い。テスト失敗したっていうメールが飛んできたけど何のテストでコケたのかパッと見分からないというのは結構不幸だ。テストコードはできるだけテンプレート化しておいたほうが良い。これは複数人で同じテストコードを書く場合・スキルの低いエンジニアがテストを書く場合もあるためだ。テストコードはスキルの低いエンジニアこそ書くべきなので(その人が書く場所は不具合が発生しやすくなるため)、テンプレートとガイドラインは必須だと思っておいたほうが良いのかもしれない。テストコードをオブジェクト指向にする状態を持つようにしたり、特定のテスト条件のデータを返す必要がある場合、データはデータ用コンポーネントに全て持っておいたほうが良い(DBのことではない)。テスト条件を作成するコンポーネント、テストを実行するコンポーネント、モック作成のコンポーネントなど、オブジェクト指向にすればするほど、複数人での並行開発が楽になる。単体テストの範囲が狭い場合はそこまで頑張る必要はない。テストは頑張らないくらいの勢いで書いていないと続かないテストコードをレガシー化させないテストが膨大になるに連れ、必ずレガシー化したテストがでてくる。単体テストの場合、そのテストを飛ばして実行するということができてしまう。これではテストの意味がない。レガシー化している・なんのためにあるのかよくわからないテストが出てきたら、そのテストを実行し、なにをテストしているのかを確かめたあと、書き換えよう。時には捨ててしまうこともありかもしれない。テストコードもソースコードの一部なのだ。テストコードだからといってレガシー化させてよいわけではない。死んだコードは不要なコードだ。おなじステートメントを通るテストケースが2つあり、そのふたつが別である意味がないのであれば、片方は削除すべきだ。別である意味があるのなら、それはコメントに書くべきである(大抵の場合は意味ないが)。テストコードはできるかぎり低コストで高パフォーマンスを実現したほうがよい。上司やチーム内に認められるわけではないテストコードだからこそ、それは常に頭の片隅において置かなければならない。開発時のどこからともなく来る不安や、不具合発生時のやけくそなテストコードは別段非難されるべきものではない。あとから適切に処理してやればよいのである。処理をするのは、テストコードを書いた人でなくてもいい。時間がないなら、作る。作れないのなら、他の人に委譲する。一人で開発しているのでなければ、たぶんきっとできるはずだ。Development)は、プログラマが自分の不安を克服し、自分が書くコードに自信を持ちながら一歩一歩進んでいくための手法です。不具合の発生は、端的に言えばこれまでの「自信」を揺らがせる事態です。テスト駆動開発者は不具合にどう立ち向かうのでしょうか?やはりテストを書いて立ち向かってゆくのです。チーム内にテストを書く習慣を持ち込んで三年、最初のうちは工数が増えるだけだ(あるある)、テストを書いても不具合がでるじゃないか(あるある)、システムテストでカバーすればいい(あるある)などという抵抗があり、それでも僕は淡々と雨の日も、晴れの日も、雪の日も、朝も夜も深夜も、終電後のオフィスでも、GW中の人気のないオフィスでも、自動テストをかき、そのプラクティスの勉強会をし、Jenkinsを導入し、定着を図ってきた。三年で、ずいぶんと書いてもらえるようになったと思う。エンジニアは新しいものが好きだ。新しいものがあれば古いものはすぐに捨てて、そちらに飛びつきたがる。自動テストで毎日定期的にビルドを走らせているのに、静的解析ツールが出たと聞けばそれを試したがり、一度使えることがわかればそのまま忘れ去ってしまう。その横で、僕は単体テストを書き、やがてそれだけでは足りないので社内用単体テストのフレームワークを作り、その使い方のドキュメントを書き、GUI自動テストを導入し、ひとまず上司を納得させるためにそれで長期試験と負荷試験を実施し、そしてそれをCIツールに組み込んでいった。今月の半ばにようやくその長い道のりは終わった。それでも、不具合は発生する。そのたびに誰かが口にする。ユニットテストだけではダメなんじゃないか。自動テストではだめなんじゃないか。不具合が発生したのは、テストが書かれていない部分なのだ。テストは常に完全ではなく、また完全になることはない。書き続けていけばやがて不具合が検出される。不具合が検出された場所は、もう二度と不具合の出ない場所だ。不具合が発見されれば、テストコードが書かれるからだ。そのコストとリターンが見合わないということは大いにあるだろう。どこまでを自動化テストの責務とするか、それはその時状況によって決めていくしかない。ステートメントカバレッジ100%にすることが常に正義なわけではない。プロダクトアウトの製品だけを作っているわけではないのだから、ここでは「最低限正常系のテストだけでも」という方針でテストを書くので構わない。1%でも、あるいはたった0.1%の進歩でしかないとしても、ないよりはマシである。それがテストを書くということなのだと思う。笑ったhttp://anond.hatelabo.jp/20130325165709去年一年振り回されたプロジェクト(まだ振り回されている)がこんなかんじだな。議事録を取らない→まじでなかった議事録をとり始めても訂正や意見がない→誰も議事録を見ていない企画書のひな形を作っても曖昧な表現で駄目出しだけする→ひな形すら出て来なかったので、うちで勝手に書いた思いつくままに会議を進める→これだ業務進行の定石を無視して効率の悪い方法を採用する→これだリーダーがウェブに疎い→まさにこれであるメンバーに技術者がいない→技術者は一応いたが展開中だったキラーコンテンツの準備より先に課金方法を取り上げる→これだwwwブレスト中にダメ出しする→企画が上がってこないので勝手に書いたストーリーで(それもどうなんだ)要求分析したが確かに駄目出しが多かった(かと言って代案もない)事あるごとにその分野に疎いことをほのめかす→一部の人々が。言ってる素人だからできると豪語する→それはなかったな素人をターゲットにする→まぁこの分野玄人が少ないので...素人であるの指摘に対して口論で勝利しようとする→そういう人がいましたユーザとしての疑似体験を却下→却下したわけではないが取り入れる時間がなかった問題の切り分けをしない→しないひとがいました。否定するなら新たにきっちり決めるかとおもいきやなし崩しで始まったりとか。それを指摘すると逆ギレするとか。別にウォーターフォール反対派ではないしかと言ってアジャイル慎重派でもないんだが、こう行こうという指針がないまま始めるのはどうなのかと思う。だいたいアンチ・ウォーターフォール派はそれをきかないんだよなぁ。なにか決めたらアジャイルじゃなくなると恐れてるみたいで。一度選定したテーマを途中で覆す→企画が途中でひっくり返ったりとかね高すぎる目標を設定する→無理だと言っているのに即販ツール作ってとかね高すぎる目標に対して全部企画に盛り込もうとする→無理だと言ってるのにね。企画が一番強いので逆らえない→なし崩し→現場が阿鼻叫喚というのが何度も繰り返された小さくリリースするの発想を理解しない→最初に理解してもらえなかったが、最近は理解してくれている一ヶ月以内に市場調査及びユーザの調査をしない→それは一応してたかな一ヶ月経過してもサービスの制作に入る見込みが立たない→要求は決まってなかったけど最低限絶対に必要なところは開始するなどした一ヶ月経過してもチームは素人のまま→一部はちゃんと玄人化したがいつまでも素人、もいる素人の作るサービルは成功の確率が低いことを理解しない→それはなかったなギリギリ許容される範囲内での失敗+すこしずつ啓蒙して良い方向へ向かおうとしている+どんな時でも最低限テストは書き、できるテストは自動化し、ツールを導入して繰り返しの作業は消していくという地道な作業で現在も何とか動いているし、これからは改善するだろうと思っている。ぼくはただ淡々と自動化して悪いところはやめて煩雑にならないようにきをつけて(おれがめんどい)、だれにでも使えるようにしてる(呼び出されるのめんどい)けれども、やはりアンチ・ウォーターフォール派の抵抗が根強くてそれをひっくり返される。アンチ・ウォータフォールだからといってアジャイルなのかというとそういうこともないのが困る。書類を書きたくない、責任を取りたくないという信念のもとに行動する人とどうやってチームメンバとして付き合っていけばよいのか、これからも考えなければならない。今春からようやく会社でレガシーコード改善ガイドを使って勉強会をするようになった(やっと...!)のでちょっと古いエントリを再掲しておく。「テストを書けばよいのでは?」の一言がためらわれる時もある整然とし、秩序だった美しい世界を構築する人がいた。設計もコードも、リファクタリングする指針でさえも一貫したポリシーを持って行われていることが、経験のない僕にもよくわかった。たしかに彼が手を入れた部分は美しかった。その周りには広大な廃墟とスラムが存在していたのだ。古い、改修しながら使っているライブラリはすでに原型をとどめていない。彼はすこしずつ手直しをしてすこしずつ改善していたけれども、ライブラリには手をつける暇もなく去っていった。広大なスラムだけが残った*1。そこを離れていた僕がもどってきたとき、最初の仕事はコードレビューだった。かの整然とした世界を構築できる人の代わりにしては僕はあまりにも物事を知らなさすぎるが、それでも何百分の一かの貢献は期待されている。似たようなコードをそのままコピーして新しいメソッドにしている部分を目にして僕は、同じ処理を抽出してメソッド化したらよいのではないかというレビューをしたが、担当者はスラム化したライブラリがうまく動かなくなるのを怖がってその指摘を受け入れなかった。ライブラリにはもちろんテストがない。クラス関係も複雑で、設計書も仕様書もない。そのコードは関連会社から譲り受けたものだから構築した人もいない。設計ができずスキルの低い僕は自信がなかった。それでもやるべきだとは言えなかった。見る「レガシーコード改善ガイド」を読み始めた。序文からしてすでに涙目である。自分のコードがひどいのは前から分かっているし、なにもひどさに涙しているわけではない。僕はコードが書けない。知識はあっても、簡単なプログラミングができても、僕はコードを書けないと思っている。自分が作るものが本当に意図したとおりに動くのか、わからない。作れば作るほど発散し、自分でも構造がつかめなくなっていくことを知っているから怖いのだ。誰かが作ったコードを正確に読み取り、その動作がなんの機能に当たるのか、わかっているつもりになっている気がする。僕は変更しない方向を選んでしまう。「できるだけ変更せずにとっておいて新たに書き加える」。その繰り返しが膨大な二度と使わないメソッドとクラスのゴミの山を積み上げることになると知っているのに。きれいなコードは有益ですが、それだけでは不十分です。テストなしに大規模な変更をしようとすると、チームは危険な賭けに出ることになります。転落防止の網を張らずに空中ブランコをするようなものです。とてつもないスキルが必要であり、ステップごとに何が起きる可能性があるかをきちんと把握しておかなければなりません。(中略)ここまで、テストについてかなりたくさん書いてきましたが、本書はテストについて解説している本ではありません。どんなコードでも自信を持って変更できるようにするための本です。本分の各章では、コードを理解し、テストで保護し、リファクタリングし、機能を追加するための手法について説明しています。はじめ、単体テストを書くのは嫌いだった。面倒でしょうがなかった。モックオブジェクトに出会って以来、僕は単体テスト職人になりたいとすら思うようになっている。モックオブジェクトで依存関係を切り、仕様書をにらみ、設計書と照らし合わせながら一つずつテストケースを組み立て、そのテストはどうやってすべきか頭をひねる作業は単純に楽しいというのもある。それよりも効率よくそのメソッドが、クラスが、何を意図しているのか、そしてどういう振る舞いをするのか頭の中に自信を持って組み立てていくことができるということの方が大きい。自分の行った変更が問題なかった、ほかの部分を壊していないと自信を持って言えることの方が大きい。向かう対象が大きく複雑で汚いほどその作業は楽しいことを僕は知っている。やって少しずつ設計とは何かを理解できるようになる。仕様として出てくる機能の背景が気になるようになる。これから10年、幸運にも開発者でいられたとしてもきっと僕は整然として矛盾も無駄もない世界を構築することはできないだろう。その世界を理解するための道具はある。始めから終りまで迷うことなく出力することはできなかったとしても、地道にゆっくりと這い寄ることはできる。大きな手術を施すことになったとしても、正してゆくことはできる。スキルが高くなくても、美しい世界を保守することはできるのだ。*1:廃墟は破棄された。続き前回のでもできるはできるのだが、今回の場合セッションが変わるとログインページに戻ってしまうimgのsrcに入っているタグが*.jpgなどの形式ではなくURLfile_get_contentsは新しくセッション作ってページの内容を取得するらしく、やれどもやれどもログイン画面のソースコードしか手に入らなかった。src="URL"のURLで(jpg|png|gif)が含まれないことが判明(先にわかっとけ)。これを取得する方法はなさげなのでとりあえずURL表示させてキャプチャという強引な方法を取ることにしました。※SeleniumではgetEvalまたはrunScriptでjavascriptコードを走らせることができます。16:55Seleniumでテストしたページのキャプチャを撮る+画像を全部保存するTweetもっと読むコメントを書く2012-11-29Seleniumで画像を取得する(キャプチャを使わない)SeleniumcaptureEntirePageScreenshotがfirefox以外だとうまく動かないが、firefoxはnetbeansからだとなぜか実行できないので自前でHTML内にある全ての画像を落とすようにした。エビデンス取らなきゃいけないしページが長い場合はスクロールしなきゃいけないけどようわからんので画像ファイルで落とせればいいや、というかんじ。src="./sample.png"></body></html>ひとまず画像だけが置いてあるHTMLテストファイルなお、phpunit-seleniumを使用しております。本心としてはPHPUnit_Extensions_SeleniumTestCaseクラスを継承したPHPUnit_Extensions_VerifyTestCaseを作成して、そこにsaveImageメソッドを作成してどこでも使えるようにする。テストケースはやっぱりテストケースで分類すべきだよなとちょっと思ったので。ホントはちゃんと名前空間柄使ったほうがいいんだろうけどめんどいので省略。//指定したページの画像ファイルだけを保存する。}}PHPUnit_Extensions_VerifyTestCase.phpついでなのでphpUnit-Seleniumに定義されていないverifyなんとかメソッドとかもここに定義してしまおうと思っている。verifyじゃなくてcustomとかにしたほうがいいのかね。