node.js + mocha でカバ…ちょ、まてよぉ

きむ…

今んところ、mocha でなんとか「TDD 事始め」に乗りつつあるので、少しずつ「カバレッジテスト」も意識していこうかと。なんだけど、なんかハードル高そうなんだよね。ちぅかさぁ、node.js してんのになんで Visual Studio に頼れって? (Python でメジャーな coverage.py が html レポートの形でサクっとやってたんで、そのノリのを期待してたんだけどなぁ…。)

というわけで「カバレッジテストはちょっと後回し」状態。

所詮個人のお遊びプロジェクトの単体テスト、をしているわけだけれど、その「カバレッジテスト」がさ、色々思い出すことが多くてなぁ。ちょっとそんな話をしておきたくなった、気分的に。


個人プロジェクトはまぁ気楽に好きにやんなはれ、ということになるけれど、これが「チーム」での話となると、これがまぁ、色々あってな。

まず真っ先に思い当たるのが、「カバレッジテスト万能主義」と闘わなければならないこと。一つには「管理する側から見ると非常にわかりやすい指標である」ということがまずあるんで、そういうスタンスは当然ある。闘わなければならないのはソレという話ではない。そうではなくて、エンジニア自身がカバレッジテストを「間違った方向で信頼し過ぎる」傾向が高いことと闘わなければならない、という話。

たまたまわかりやすい例を偶然生み出してしまったので、以下を見てちょーだい:

これ、何かというと、「試行錯誤まっただなかで書いた、今では無駄になったコードを削除」している。これはワタシは目視で発見したけれど、こういうのをまさにカバレッジテストで発見することがある。そしてこれがまぁ、経験上ひっじょーに多いのだが、なぜか「この分岐を是が非でも通す」(つまりテストをパスする)ことばかりに目が行ってしまうエンジニアが、これは滅茶苦茶多い。なんかまさに「技術格差」を感じてしまう最たるものだったりするわけだが、「カバレッジが検出した未到達の「意味解読をする」」ことに考えが至らないんだろうね、そもそもカバレッジテストってまさにそのために使うもんだとワタシは思ってるんだけど。

そして「現実」の場合に本当に深刻なのはこの後の話。この「ワタシは正しい」としたこの考え「こそ」が、感情的な理由だったり、「プロセス管理」の考え方だったりするけれど、「否」とされることが結構多いのである。「開発フェーズ」を厳密に管理するプロセスを採用するような、昔ながらの伝統が色濃い組織ほどそうだが、「テスト中のコード改変は厳禁」という原則が「過度に」適用されてしまう傾向が元々高い上に、「そのことに疑問を抱かない思考停止型エンジニア」が多いとなれば、ワタシのような考え方は、まさしく「主に感情的な理由で」非難されることになる。

めちゃくちゃありがちなんだけど、そういうのってだいたいはその非難する側というのは「仮想敵国」と闘っていて、「お客様に叱られる」(んなわけない)とかね。実際プロジェクトマネージャでさえ許容するような「常識的な判断」を、いち末端エンジニアが「正義感」で非難するようなことが、よく起こるのだ。まぁそんなのに発言力はだいたいないんで、「うるさいなぁ」と思うだけでだいたいは済むんだけれども、こういうのに発言力があると本当に面倒。

ついでにいえばこれは旧式の「ウォーターフォール型開発」に馴染んだエンジニアほど陥りやすい。すなわち、「工程の手戻り」を極端に嫌い、避けるようとするため、「一度通したテスト」を絶対視する傾向が過度に強くなるわけである。なので偶然であれ「通ってしまった誤ったルート」さえも「正義」になり、「直せない/直さない」。馬鹿馬鹿しいって? そんなもんよ、世間の「IT エンジニア」なんて。(実際ウォーターフロー開発でさえ、事情が許せば「ちゃんと勇気を持って手戻るべき」なのであって、その判断を避けようとすることそのものが、質が低いことを証明しているようなもんである。つまり、「古い考え方で切り取ってみた場合でさえも許容出来ない」考え、なのだ、厳しい言い方をすれば。こういうのは、「ウォーターフォールであろうと」、ちゃんと理論的な説明をし、証明をし、修正をしなければならない。そちらの方が本物の正義だ。)

そういう非常に面倒なタイプでないエンジニアであっても、今度は「カバレッジテストは正しいので」という誤った感情が邪魔をして、「人間の頭脳」という素晴らしい道具を(感情的に)否定してしまう、という過ちを犯してしまう。それは「オレが書いたものが間違ってるはずがない」と、誰もがみっともないと思うはずの仮定を暗にしていることになるのだが、「カバレッジテスト」という言葉を被るだけで、なにやら魔法がかったようにこのことを忘れる。後述で話す通り「正しいと思っていることを、正しさはともかくとして全部動かす」ことが何かを担保するわけがないのだが、どうしたものか、「カバレッジテスト」という響きには、何か特別なものがあるようである。

「道具」は「道具でしかない」のだ。だから「カバレッジレポートで未踏となる」ことが「間違いであると断言する」には根拠が必要であり、それを考えるのは、紛れもなく「あなただ」。機械ではない。カバレッジレポートは「通らなかった」としか言わない。それしか言わない。「間違ってる」なんて言ってない。これを「通ってはいけない」であるとか、「通らなくて当然」と判断できるのは人間しかいないのである。(要するに「なんで機械ばっかりを一方的に信用出来るわけ?」てハナシだぞ。)

そして言うまでもなく「カバレッジ100%神話」である。たまーにというレベルでなく本当に、C++ や java の、「プライベートコンストラクタが未踏なことでさえも許さない」という硬直したルールが適用され(かけ)ることがあり、まぁそういうときは「特例をルール化する」ということを常識的に考えるわけなんだけれど、ワタシのようにそういったルール作りに関与する側やマネージャ陣が考えもしないような反応をする末端エンジニアが、…、やっぱいるんだよ。どんだけ「そこはやらんでいいんだってば」といっても、「100% にすることに夢中になる」。そもそも xUnit 系譜のテストフレームワークって、ゲーム性が高いというかね、「楽しい」側面もあるので、わからんでもないんだけれど、どんだけ見てきたかわからんよ、「プライベートコンストラクタを通そうと頑張るエンジニア」。こんなんかなり特殊なコードを書かなきゃテスト出来ないんだけどね、それでもやっちゃうんだわ、彼らは。(というかねぇ、ワタシのケースでは、後輩があまりにも気に病むんで、面倒になってワタシがその「特殊で馬鹿げたテストを書いた」。)

最後の話が実は一番深刻、なんだけれど、一番見えにくく、わかりにくい。

言うまでもなくカバレッジテストは、「フローの分岐」の到達、未踏をチェックするものである。すなわち:

Python を例にする
1 x = some_feature();
2 if x == 1:
3     do_1();
4 elif x == 2:
5     do_2();
6 else:
7     do_3();

要するにこの全ての分岐を通すべし、というのがカバレッジテストの基本的な考え方である。

この非常に当たり前の「基本思想」について、正しい意味で理解出来ていないと、とんでもない誤解がまかり通ることになるわけである。すなわち、「カバレッジ 100% なので万事おけ」。誤った方向での信頼、というのはこのことを言っている。すなわち:

  1. カバレッジテストは、「書いたコードの分岐を通す」というだけのことである。
  2. すなわち、「分岐するロジックの誤り」を検出「出来るはずがない」
  3. 隠された分岐の件

1.、2. はもっと違った言葉で言えば「プログラマ自身が考えた全てを動かす」ということに過ぎない。もっと言う? 「プログラマ自身が考えた全てしか動かせない」。現実には多くの「標準的レベルのエンジニア」であれば、ワタシ同様に、カバレッジレポートを「解読」して「あれ、このロジック、そもそも間違ってら」と気付けるのだが、先の「ゲーム性」の話からもわかる通り、「とにかく全部通す」ことだけに夢中になり、それで本質を忘れたままで「満足してしまう」エンジニアがかなり多いのだ。

「正しい意味で理解」なんて言ってるけどさぁ、こんなん「あったりまえだ」と思わないかい? 例えば道路建設するとしてな、「作った道路を全部通ってみました、だから安全です」。わかるだろ?

実際「xUnit + カバレッジテスト」を常識的な意味で適用しようとするプロジェクトなら(というか他のテストもちゃんとするプロジェクトつーことよ)このことは大した問題にはならないのだが、たまに「カバレッジ100%」だけを要求されることがあり、そしてこれも「常識的なエンジニア」なら「それでも xUnit する」のだが、そうした常識のないエンジニアがこれを真に受ければ、まさに「ワタシが正しいことをしたつもりになったことを全て動かすことが出来ました」で本当に「テスト完了」だと思ってしまうという悲劇的なことが起こる。(実際かなり近い状況に「なりそうになった」経験がある。頑なに拒否して xUnit させたけど、そこでも問題になったのは「管理したい側(顧客)」ではなく、内部のバカエンジニアであった。簡単に言っちゃえば、「お客さんがそう言ってる!」で思考停止してた、つー話。これがまぁ「チームマネージャ」だったんだわ、まぁ疲れた。ちなみにワタシはそこでは「チームリーダ」的な立場。)

3. はこれは本当に注意して欲しい。こういうことである:

これはほんとに今ワタシが作ってるヤツの断片
1     var m = (new RegExp('<meta property="og:image" content="([^"]+)">')).exec(html);
2     if (m) {
3         var pic = _this._omit_imageurl_base(m[1]);
4         if (!pic.startsWith("/img/sp/")) {
5             result["p"] = pic;
6         }
7     }

見かけ上「ワタシが書いたコード」としての分岐は if 文の2つ分しかないように見える。そう思って、その分岐分だけテストすればいい、と考えてしまうのが「誤った信頼」である。C0 だ C1 だと専門的な理解を持っていようが、そこに理解が至らないようなら、そもそも「テスト」の考え方そのものが理解出来ていない。この例の場合、正規表現そのものがパターンを持っており、実際は「if (m) が真になる」のパターンそのものが一つでない。

ついでに言っとくと、「言語の性質そのものが、カバレッジテストと馴染まない」こともあるので注意。例えば SQL が(ほぼ)そうである。いわゆる宣言型と呼ばれるタイプの言語は、カバレッジテストそのものが、適用出来ないもしくは適用しにくい。


無論ここまでしてきた話の「闘う相手」というのは、たとえば後輩や同僚である。こういったことを、一つ一つ丁寧に説明していかなければならないということなのだが、「理想的には」こういったことは「開発中に教える」んではなく、ちゃんとした「教育で」まかなえたらいいのに、と、毎度思うのだが、まぁ、だいたいはそういうちゃんとした時間を作れないのが普通よね。

で、現実問題、この「過度の信頼」がどの層に顕著だと危険なのか、ということに関しては…。「顧客」がわかりやすい指標を求めるのは当たり前。だからここを「非難」してはいけない。ただ、機会というかそういう場を持てるなら、事情を説明してあげてもいいかもしれない。なので最も危険なのは、「チームの管理的な立場となるエンジニア」がこうだと危険だ、ということになるのだろうね。この層が教育出来る素養があるならば、「誤った方向に導かない」ことが出来るということになるのだから。

なんでもそうだけど、だいたいの組織の「問題」というのは、「中堅層」で露呈してることが多いんだよね。若者にはだいたいは罪はない。真ん中がダメなチームは全体もダメ。だいたい何かしら破滅要素持ってる。たとえ具体的な問題が露見していないとしても、いつか何かの拍子に破綻する。

まぁそういうことなんであれば「その真ん中を育てた上がもっと悪だ」ということになるんだけれど、ワタシが見てきた組織だとねぇ、なんというかな、「上がちゃんとし過ぎてる」ことが問題だったりもしたんだよねぇ。どうもそういう組織って、「上がちゃんとしている通りに自分もやってるつもり」に真ん中がなっちゃってることが多い気がして。ワタシは良く感じるんだけど、「スキルはエレベータ式じゃないねんぞ」てことよ。なんか「自然と上と同じになる」と思ったまま安穏と過ごしてしまうような感じなんだよねぇ。(つまり10年上の先輩をみて、「10年後には何もしなくてもああなる」と暗黙で思い込んでいる、というハナシ。)


もう一つ、「致命傷ではないが」という些細な問題が、カバレッジテストでは起こることがある。

テストフレームワークそのものの質にもよるのだが、例えば以下である:

1 MalUrlBuilder.prototype.get_charas = function(malid, name, cname, no_proxy) {
2     var _this = this;
3     var url = (no_proxy ? "" : _this._PROXY) + _this._BASE_URL + "/anime/";
4     url += _this.id2malid(malid);
5     var cn = cname || _this.calc_cname(name, cname, "anime");
6     url += "/" + cn;
7     url += "/characters";
8     return {"url": url, "key": malid};
9 }

これが「宗教論争」の種をまくことが、たまに起こる。

3行目は「三項演算子」。5行目はいわゆる評価のショートカット記法だが、ともに「スタイル標準」で禁止されることがあるので、禁止されていれば幸いで、それは従えば良いだけである。そもそもカバレッジテストと相性が良くない、ということでもある。だから禁止されると却ってスッキリして良かったりもする。

問題は、「規約が禁止していない」場合の方である、むしろ。

ワタシは (Pythonista の癖に) C/C++ 脳も発達しているので、こうした短絡表現は結構好んで使うんだけれど、カバレッジテストで不便だと思えば、あえて書き換えることもある。特にこれらを「未踏」とみなしてくれないカバレッジツールを使う場合は、エッジケースで危ういと感じれば、わざわざそうする。

つまり「スタイルに対しての考え方はかなりフレキシブルに」考える方なんだけれど、…まぁ言いたいことはわかるでしょ? 前なんかの話でも書いた気がするんだけれど、どうしたわけか、こうした「わかりやすい話」って、皆熱中しがちなんだよ。言い方悪いけど、「バカでも参加出来る」からね。なので、この「カバレッジツールでの「検出」(出来ない分岐)」の発覚をきっかけにして、このスタイル論議に夢中になり始めてしまうエンジニアが結構いる、てこと。

論議、ちぅかねぇ、どっちかというと「そうなのかっ」と「やらんでもいいのに人のコードにまでご親切ご丁寧に」ありとあらゆる箇所を直して回りだすエンジニアが、現れる、ちぅことよ。ちょっとした警察官みたいになっちゃうんだよね。人が書いたコードまでご丁寧に修正してまわりだす」。もっと大事なことあるはずなんだけどなぁ、とハタからみてて思うんだけど、やってることそのものが実害あるわけじゃないから、叱責は出来ない、というたちの悪いヤツ。要するに java なんかだと checkstyle の「わかりやすくて優先度の低いもの」を集中的に頑張るエンジニアが、ある時期になると現れる、つーことよ。いや…そこじゃなくて、て思う、て話。


結局ワタシがいつも思うのは、今回話題にしたカバレッジテストに限らないけれど、何か特定の技術がある場合に、「全体の中の位置付け」を考える癖をちゃんと身に付けて欲しいなぁ、ということなんだよね。そうすりゃ「カバレッジ命!」みたいな意味わからんこだわりなんか出てこない。

そして何かにつけてワタシは色んなとこで言ってる気がするけれど、「なぜそれをするのか」の理解を、ちゃんとすべきなんだ。ワタシはよく「自分がやってることを理解しろ」という言い方をするんだけど、とにかく「意味はわかってないけどやる」というのは、理想的にはまったくなくなるように努力しなさいな。一番たち悪いのよ、「自分でなんでそれをしてるかわかってない」のって。「やらされ思考になるな」とはよく言われるでしょ、ワタシが言ってるのも結局ソレだよ。何やるにも動機付けが一番大事。

もうひとつはやっぱり「魔法的な響きに騙されるな」ということ。これも良く書いてる気がするが、「専門用語」は何か特別なもの、と捉えられる傾向が高く、「なんかすごそう」と思ってしまいがちらしい。実際は「常識的な考え方を見に付ける」という、「当たり前だが難しいこと」を地道にやっていくしかないはずなのだが、こうした「響きの強いもの」というのは、何か万能なものに見えてしまうことが多いらしい。実際テストの考え方なんか、「ちゃんとした書籍」がかなり厳しい言い方で色々諭してくれたりするんで、ちゃんと読んだほうがいいぞ。かなり数学だったりもして、難しさもあったりはするけれど、だけどね、「組み合わせ爆発」であるとかそういった「テストそのものが持つ難しさ」てのは、いくら「魔法のようにみえるものたち」をかき集めたって、なかなか出てこないよ。(つまり考え方がちゃんとしてなかったら、xUnit でさえ本当の意味で価値を持たない、ということ。)

実際これまで知らなかった人がワタシの「カバレッジテスト」の説明を読んでさ、「なんかすげー」もんに思うかい? 思うはずがないでしょう。(ただし、実践してみればわかることだけれど、「効果は絶大」。これは実際手を動かしてやってみないとなかなか実感出来ない。ワタシが「そのために使うもんだと思ってる」と言ったのは、これは「コードを強制的に読み直しさせられるから」である。これが「効く」。滅茶苦茶。そのため、とはまさしくこのため。このコード検討を強制されることを望んでカバレッジツールを使う。)


ついでなので。

そもそも「XP プログラミング」がとても強力なのは、もはや常識ではあるんだけれど、ただ、「テストの観点表を作る」という、昔から、そう、「大昔から」使われてる技術を軽視したらダメなのですよ。プロジェクト管理に携わる人なら、基本これは必ずさせるべき。実際観点表をちゃんと作ってから xUnit 書いた方が、綺麗で保守性が高いテストになるんだからさ。

前にも例にした「保守出来ないテストを書いたバカ先輩」(殺したくなった)みたいなのが現れちゃうのも、こういった基礎的なことをチームで共有してないから。なんか前にも言ったけどさ、こういうのね、「当人は最後まで気付かない」まま周りに迷惑かけ続けるんだよ。(そして書いたかもしらんが、その「被害を被った」のはワタシである。そのテストを「全部隅々まで書き直し」て初めて本体の「バカさ」が見えてきて、そして…、大改造した。)

「保守出来ないテスト」というのは「保守出来ないアプリケーション」よりも被害が大きい。実際プロジェクトが保守しずらくなってきたら、テストから疑ったほうがいい。たぶんテストが保守出来ないものになってる。テストが保守性高ければ、本体の保守も簡単になっていく傾向が高いよ、これは経験上間違いない。


なんてことを、書いてみたのはいいが…、さて、こいつはどうしてくれようか…。わっしゃ「カバレッジテスト」したいのぢゃ。(今すぐ、ではないけど。それ以前にやることまだまだいっぱいあるので。具体的には「テスト全体の整備」そのもの。入力データ、期待値データの整理とかもろもろね。)