うさぎ組

kyon_mm with software

このエントリーをはてなブックマークに追加

RE: テスト考2014 #SWTestAdvent

TLでたまたま見かけたブログが発狂しそうなくらいの感情の起伏を感じたので、なにを思ったのかTwitterに書いていたのですが、長くなってきたので、ブログに書くことにしました。なんかツイートがものすごい勢いでRTとかfavされていたので、きっとみなさんテストに興味があると思うんだ!(前半くらいはTwitterに書いた内容と同じです。)

件のブログ -> テスト考2014 : http://kenn.hatenablog.com/entry/2014/01/03/095026

はじめに

このエントリはソフトウェアテストAdventCalendar2014の8日目の記事です。

http://connpass.com/event/4544/

要約

僕はそれなりに「テストが嫌いで仕方ない側の人間」であるという自覚があります。テストを撲滅するためにソフトウェア開発を勉強しています。その上で言いたいことがあてはまるシチュエーションがあるのは理解できるのですが、それを一般化するかのように書かれていて、一部はテストエンジニアを侮辱しているともとれる可能性があった(僕は性格が悪いのでそう受けとった)ので、気になるところに意見します。 リファクタリングやポールグレアムをもちだして(しかもたいして主張と関係がない)、品質を混同するように書いているのは本当によろしくない。最低限満すべき品質について考えずに、そして前提となるような状況を説明できない人が増えると正直困ります。 結論として、私はもっと品質やテストや開発について勉強して啓蒙したいと思った次第。

件のブログで気になるところ

まだ件のブログを読んでいない方は読んでから下を続けるといいと思います。(別に読まなくてもいいけど。) いろいろ言いたいところはありますが、テストを中心とした開発をしている身として気になったのは次の2つです。

  1. リファクタリングに関するテスト
  2. AirBreakによる本番バグ検知によるテストケース追加

リファクタリングに関するテスト

次の引用は控えめに言っても僕が「この文章は頭がおかしい」と思った部分です。多分弟子がこんなことを言ったら(ry

テストの存在は、外部インターフェースを壊すことなく内部をリファクタリングすることを可能にしてくれるが、一番よく変化し、壊れやすいのは内部構造のほうであったりする。

もし、あなたのアプリが公開されたAPIをもち、それを維持していく必要があるなら、もっとも特徴的なAPI(たとえば認証まわり)いくつかに対してテストを書くべきだ。しかし、すべてのAPIに対してテストを書いてカバレッジ100%を目指すのはやめたほうがよい。おそらく、追加で書かれるテストの存在価値は限りなくゼロに近いし、そうでなければ、そのことのほうが問題だ。

その追加で書かれるテストは、おそらくほとんどが他のAPIからのコピペになるだろう。コピペでコードを書くことは害悪とされているのに、テストならばコピペは許されるのだろうか。

上から順に指摘します。

「一番よく変化し、壊れやすいのは内部構造のほうであったりする。」というのは「そういう場合もある」というだけだし、内部構造が壊れているという表現がよくわからないです。だいたい、外部からのテストがあるなら、つまり、外部から見て壊れていなければそれは壊れていないはずですよね。これは言っても「リファクタリングする必要がある」くらいの表現だと思う。「リファクタリングする必要がある」を「壊れている」って表現するのはいささか大きい気がするし、むしろそれはやたらと内部品質を気にする人の言葉なのではないでしょうか。エントリ全体からの印象とは逆です。

「しかし、すべてのAPIに対してテストを書いてカバレッジ100%を目指すのはやめたほうがよい。おそらく、追加で書かれるテストの存在価値は限りなくゼロに近いし、そうでなければ、そのことのほうが問題だ。」ってあるんだけど、僕としては「いや、この文章のほうがよっぽど問題だよ!!!」って感じです。このエントリーで言っている「カバレッジ100%」がそもそも、それ何カバレッジよ?っていう話はあるのですけど(現実には、状態遷移、組み合わせ、ユースケース、仕様、シナリオなどがあり、それぞれに測定方法が存在する。書いてあるのは多分ソードコードカバレッジでC0かC1の100%だと思う)、そういう指摘はいいとして「おそらく、追加で書かれるテストの存在価値は限りなくゼロに近いし、そうでなければ、そのことのほうが問題だ。」というのが根拠が全くないです。

それが後述の「その追加で書かれるテストは、おそらくほとんどが他のAPIからのコピペになるだろう。コピペでコードを書くことは害悪とされているのに、テストならばコピペは許されるのだろうか。」だとしても、それも明らかな間違いです。そもそもなんでテストコードのコピペをするんだよ!!本当にリファクタリングやCleanCodeを読んだのですかね。自分で間違いと思ったことをなんでするんですかね。

テストの存在価値が薄いときはとりあえず3つはあります。

  1. 「テストしなくても保証されている」
  2. 「テストするコストが保証したい範囲を超えている」
  3. 「テスト対象の価値が薄い」

いずれでもなければ、テストすべきものであるはずです。テストの優先順位の話とやらなくていいというのは全く違います。

一番よいコードは、何も書かないことである。というと禅問答のようだが、自分で車輪の再発明をするのではなく枯れたライブラリを使うというのも、この範疇である。これは、同じことを実現するコードなら短いほうがよい、と一般化できる。そうした場合に、テストコードというのは実現したいこと(=アプリケーションコードの価値)には影響しないが、書いた行数は増える、すなわちメンテナンスという意味ではアプリケーションコードと同等のコストを払うことになるので、同じことを実現するためのコード量が増え、冗長になったことと等価である。

だから、テストは必ず書くという考え方はばかげている。アプリケーションコードと全く同じで、コストとリターンを見極めながら必要に応じて書く、が正しい。

一番よいコードはプロダクションコードのみであるというのは同意ですし、コストとリターンを見極めながら必要に応じて書くが正しいのもわかるのですが、エントリ全体で対象とされているテストというのは贔屓目に見ても限定された範囲のテストです。具体的に言うと機能性テストのうち仕様書ベースに正常系のみでおこなっていて、テストレベルはコンポーネントレベルに思えます。これらのテストはコンパイラやツールがもっと洗練されれば確かに開発者の負担は減るのですが、この方が主張しているRubyというプログラミング言語がそういったものかどうかはわかりません(僕自身はRubyは文法を軽く知っていてRSpecとRailsをたまに触る程度なので詳しくないのです)。簡単に書けることと、それが正しいかどうかを保証できるかどうかは相関はありそうですが、別なことだと思います。

AirBreakによる本番バグ検知によるテストケース追加

AirBreakを使うことはとてもいいことだと思うのですが、次の部分が気になりました。

おかげで、たいていのバグは、デプロイ直後に見つかり、すぐ修正できる。テストなんかより、よっぽど日々の役に立っているという実感がある。

いまAirbrakeの履歴をざっとみて、「これ、テストがあれば防げたなぁ」と感じるバグの割合を探ってみると、5-6%程度もない。つまり、たとえテストのカバレッジ100%であっても防げなかったバグがほとんどだったということになる。ほとんどの場合は、そもそも想定外の手順でリクエストがきたり、壊れたデータがきたりという例外なので、想定した範囲のケースしか扱えないテストで検出できないのは当たり前である。

逆に、こうしてプロダクションにリリースしてから発覚した「想定外の手順」や「想定外のデータ」をレグレッション対策としてテストケースに追加していくのは、ものすごく有効だ。自分の頭で想定できないパターンなので、将来の役に立つ。つまり、予測にもとづいてテストケースを増やすのではなく、実際に起きた問題にもとづいてテストケースを増やしていく。これはYAGNI原則という意味で重要なアプローチの違いである。

これらを読んだときには正直、テストエンジニアを馬鹿にしているのかなって思うほどでした。身の回りで言われたら、僕のこと嫌いなんだろうなって思ってソフトウェア開発の話をしなくなるかもしれないレベルです。まぁそれは大袈裟にしても多分いろんなことを考えます。

最も何を言っているのかわからなかったのは”ほとんどの場合は、そもそも想定外の手順でリクエストがきたり、壊れたデータがきたりという例外なので、想定した範囲のケースしか扱えないテストで検出できないのは当たり前である。”という部分です。わからないのですが、この程度テストして当然の範囲ですし、そんなに難しくないと思います。

仮に難しかったとして、それをテストしなくていい理由がどこにあるのかわかりません。「技術的に無理」でもないですし「ものすごい高コスト」というわけでもないと思います。後者、つまり高コストな場合にはそれを低コストにする仕組みを自分でつくればいいだけだと思います。操作手順の全組合せをしたテストケースを自動生成でもして一日に一回は3万件でも10万件でもテスト実行すればいいと思います。それをするだけで、「ユーザーは操作手順によるバグに一切遭遇しない」という状況を手にいれられます。

想定外のデータもある程度は自動生成できます。XMLであればXSLTから正常なXML、不正なXMLは生成できますし、JSON版をつくるのもそんなに難しくないです。仮にバイナリだとしても「正常なルール」があるはずで、それがどんなに複雑だとしてもある程度はつくれるはずです。(ツールがなかったとしても簡単な不正データ、よく生成されてしまう不正データ、大胆に異なる不正データくらいは簡単につくれる。)

そしてこれらを主だって仕事にしているのがテストエンジニアです。テストエンジニアはソフトウェアの状態を観察することにおいて全力をそそぎます。そしてそのためにはあらゆる手段を用います。テストエンジニア全員が優れているわけでもないですし、万能なわけでもありません。ですが僕があのエントリから受ける印象では、そのほとんどのバグはテストエンジニアによってリリース前に発覚したのではないかと思います。(それをそのままリリースするかどうかはまた別の話です)。他も含めての全てのバグを見つけられるとはあまり思いませんし、なんにせよテストエンジニアの知識やスキル、そしてチームとしての働き方に依存する話ではあります。でも、それにしたってこの内容であれば。。。って思ってしまう感じでした。

テストをYAGNIとして捉えるのは結構ですが、「どの品質が最低限満たすものか」を定めずにリリースしていいのは、学習とか研修とかそういう感じではないですかね。仮に「機能性テストのうち仕様書ベースに正常系のみでおこなっていて、テストレベルはコンポーネントレベル」が最低限だと言うのであればそれでいいのかもしれませんが、現実にリリースした後にバグが出ているのであれば、ある程度それを先に簡単にテストできるように努力するのがエンジニアなのではないかなぁと思います。

この範囲のテストでリリースしているのであれば「テストはお客さんが本番のデータをつくってやってくれる」って言っているようなものですからね。なので、対象のソフトウェアに何が求められているかは重要だよなぁと思います。ちょっと趣味でやってみるっていう程度ならもしかしたらいいのかもしれませんが、その感覚を「ある程度はどこでも通用するようなテストに対する考え方」としてもってこられるとそれは飛躍しすぎた話です。

まとめ

テストをしたくないという感情を素直に持ち続けて行動するのは素晴しい(盲目的にテストをしているわけではない)のですが、盲目的にテストをしなくていい理由にはならないというのが残念だったし、もっと状況を説明するように書いてもらえれば誤解があったというのがわかるのかもしれませんが、あのエントリは正直に言えばイライラするし粉々にしたいレベルでした。

ただ、素晴しいと思ったのは「バグから学習する」ということです。これは賞賛すべきことですし、非常に賛同します。バグは抽象化していけば「ソフトウェア開発のアンチパターン」になります。そしてプラクティスはアンチパターンからこそ生まれるからです。そのアンチパターンを発生させにくい仕組みこそがプラクティスですから。

ということで、「あぁ、テストを勉強しなきゃな」って思った方は次のエントリが参考になります!(おい

ソフトウェアテストの書籍まとめ 2013 #SWTestAdvent

テスト戦略、設計、技法、ツール、品質、ブログなどのリンクをまとめてみた 2013 Summer