PHP
テスト
ポエム

[PHPerでも]テストコードの意義を確認する[書きましょう]

More than 1 year has passed since last update.

こんにちは皆さん

多くの方にとっては、商用サービスに対しテストコードを書くのは当たり前のことだと思います。
しかし、レガシーシステムや程度の低いところだと、テストコードがないどころか、テストの存在すら知らんというのもよくある話です。
また、私のようにSES出身だと、そもそもろくな研修もせず現場に放り込まれるを繰り返され、何年たってもテストという言葉すら聞かない可能性だってあります。

というわけで、テストコードの意義について、そもそも知らない人や、新人なのに満足な研修を受けられなかった方のために、私なりの解釈をポエムに載せようと思います。
若干旬を過ぎた感じがしますが。。。

テストコードの意義

「テスト」とは

ここで言うテストというのは、あるロジックや処理のテストをコードで記述し、ツールやスクリプトを用いて実施することを言います。

例えば、以下の様なコードがあったとしましょう。

factorial.php
<?php

function factorial($n) {
  if ($n > 0) {
    return $n * factorial($n - 1);
  } else {
    return 1;
  }
}

単純にnの階乗を求めようというコードです。
これに対し、以下の様なコードを書いてみます。

factorial_test.php
<?php
require "factorial.php";

if (factorial(4) === 24 ) {
  echo "OK";
} else {
  echo "NG: " . factorial(4);
}

echo "\n";

このコードを実行してみましょう

$ php factorial_test.php
OK

OK、すなわち、期待通りの動作をしてくれることが確認できました。
これがテストコードを使用したテストです。

ところで、何らかのタイミングで、以下の様なコードになってしまったとしましょう。

factorial.php
<?php

function factorial($n) {
  if ($n > 0) {
    return $n + factorial($n - 1);
  } else {
    return 1;
  }
}

これだと、先ほどのテストでは期待通りの動作をしてくれず、NGを出力してきます。

$ php factorial_test.php
NG: 11

これにより、factorial.phpは期待にそぐわない動きをしている、すなわち「間違っている」ことが検知できます。

テストコードを書く意義

このように、テストコードを使用したテストというのは、ある実コードに対してテストコードを書き、そのテストコードを実行することで、実コードの動作が期待通りかどうかをチェックすることです。
テストコードを書く意義はおおよそ以下のとおりです。

  1. コードが仕様に合致していることを確認できる
  2. 同じテストを繰り返し実施できる
  3. レグレッションを確認できる
  4. 同じ間違いをすることを防げる

コードが仕様に合致していることを確認できる

テストって、もともと仕様通りにできていることを確認するためにあるので、当然といえば当然です。
また、追加で確認する必要がある仕様があれば、テストコードに追加すればよいです。
例えば先程のfactorial.phpですが、0の階乗が1であることを追加で確認したくなりますが、その時は以下のテストを追加すればよいです

factorial_test.php
+ if (factorial(0) === 1) {
+   echo "OK";
+ } else {
+   echo "NG: " . factorial(4);
+ }
+
+ echo "\n";

これに対してテストを実行してみます。

$ php factorial_test.php
OK
OK

二つのテストが実施され、期待した仕様通りであることが確認できました。

同じテストを繰り返し実施できる

これはとても重要な性質です。
例に上げたようなテストだけであれば、いちいち手でテストをしても、そんなに苦ではないでしょう。
しかし、システムが大きくなり、確認すべきテストが何百、何千と増えていった時、いちいち同じテストを手でやっている暇はなくなります。
テストコードを使用したテストでは、通常困難な「全く同じテスト」を「何度でも」実施することができます。

このように、テストコードを使用すると、どんな時でも現在のシステムが、仕様に従ったコードになっているかどうかを確認することができるのです。

レグレッションを確認できる

レグレッションとは、仕様変更なり仕様追加なりが発生した時、変更箇所以外に想定外の変更が発生していないかどうかということです。
このテストはとても重要ですが、一方で通常の手動操作によるテストではほぼ不可能です。

例えば、1000以上項目のあるテストを手動で行っていたとしましょう。
テストは紙に書かれており、仕様に合っていたらマル、間違っていたらバツをつけます。
こんなテストの中で500番目くらいに仕様に合っていない部分が見つかったとしましょう。
その部分を修正した時、「レグレッションテスト」はどのように行うべきなのでしょうか?
そうです、これまでのテストを全部やり直さなければ、レグレッションテストは完遂できません。
そして、そんなことは現実的には不可能でしょう。

しかし、テストコードを使用したテストでは、先に述べた同じテストを繰り返し実施でいるという性質から、レグレッションテストを容易に実行することが可能です。
仕様変更や追加が発生しても、既に作成済みのテストについては、そのテストコードを走らせるだけで、これまでの仕様に関するチェックを行うことができるのです。

同じ間違いをすることを防げる

バグなどの修正の時にも、テストコードにその修正についてのテストを書きます。
どんな人間でも、バグや実装ミスは発生してしまいますが、テストにその修正についてのテストを書くことで、その後同じバグが発生するのを( 少なくともそのコードの中では )防ぐことができます。
バグの再発や同じ間違いをすると、エンジニアとしての評価を下げられる要因になりますので、テストコードを書くことはエンジニアの立場を守るためにも必要だと考えられます。

テストコードを書くべき理由

これまでのことを総合すると、テストコードを書くということは開発コードの検証とメンテナンスを機械化することです。

テストコードを書かないということは、この仕組を使用できないということです。
テストコードを書かないということは、手でテストを実施するということです。
テストコードを書かないということは、仕様の追加・変更、バグの修正に際し、手動でレグレッションテストをしなければならないということです。( そして、多くの場合これは実行されないでしょう。 )
テストコードを書かないということは、同じミスやバグが再発する可能性を排除できないということです。
つまり、テストコードを書かないアプリは、ちょっと修正を入れただけでどこでバグが発生するかわからない危険物に成り下がります。

特に商用アプリを顧客に納品するのであれば、テストコードを書くべきでしょう。

テストにまつわる周辺事項

テスト駆動開発

テストコードが仕様に沿って作られるのであれば、テストコードを先に作ってしまい、そのテストが通るように実コードを書くというやり方が考えられます( テストファースト )。
このような開発手法をテスト駆動開発( TDD: Test Driven Development )と呼びます。
律儀にテスト駆動開発をするのなら、以下の手順で開発をすることになります。

  1. 仕様に沿ってテストコードを書く。このとき実コードはないので、当然テストは失敗する( RED )
  2. テストコードが通るように実コードを書く。テストは成功する( GREEN )
  3. 実コードが最も読みやすくなるようにテストが通ることを確認しつつ、コードを修正する( REFACTORING )

Gitを使用しているならば、1, 2, 3の各フェーズで一度ずつコミットをすると完璧でしょう。

とはいえ、これはある意味理想論のようなところもあります。
テストを書くよりも実コードを書いたほうが仕様のイメージがしやすいということもありますので、テストが後でも先でも別に構わないと思います。

ユニットテスト

ユニットテストとは一つの処理単位に対してその仕様を確認するための仕組みを提供するテストツールです。
ユニットテストはいろんな言語で用意されており、総称してxUnitと呼ばれます。JavaだったらJUnit、PHPにもPHPUnitがあります。
TDDを採用する場合、テストツールとしては通常使用言語のxUnitを使う事になります。

ユニットテストには実施したテストの総数や成功数、失敗数を集計する仕組みや、デバッガを使用してテスト実施時に実行されたコードを調べる仕組み( カバレッジ解析 )などが含まれており、テスト状況を可視化するための機能が備わっている場合が多いです。
実際、PHPUnitにもそのような機能があり、CI( 継続的インテグレーション )などで状況を監視し続けることができます。
PHPUnit

テストコードに関する問題

テストコードを書くことで、いろいろ有利になりますが、扱いをミスると凄まじく面倒くさいことになります。そのような事態を引き起こす事項について軽く見てみます。

テストコードの価値を理解できない上層部

もはやテストコードを書く以前の問題ですが、SESが受託とかやり始めると、上層部の連中がやたらとテストコードの価値を否定してくることがあります。実際私が昔いたSESの会社の老害社長もそんな感じで、技術かけらもわからん営業上がりのボンクラのくせに、やたらと口出すアホだったので、その会社を辞めました。
そういう感じの会社はどうせまともに開発回せないので、素直に辞めて別のまともな会社に移ったほうがいいと思います。

テストコードを書いても絶対安心ではない

テストコードを書くことで、絶対に仕様通りのコードが書けるとは限りません。
テストコード自体も人間が書くため、間違えることがありえるからです。
間違えたテストコードを書いてしまいそれに従った実コードが実装された場合、それは確定したバグとなります。

ただし、バグが発見され、テストコードが修正されれば、以降そのテストは正常なものとなります。
テストコードは確かに絶対安心できるものではありませんが間違っても一回までです。テストコードを書かなくてもいい、というわけではなく、テストコードを書いても油断はしないようにしようということです

カバレッジ信仰という迷信

ユニットテストを使用してテストコードを書いていると、カバレッジレポートをよく見るようになると思います。カバレッジレポートにより、テストで実コードをどれだけ実行できたかを確かめることができます。

スクリーンショット 2016-07-30 22.24.16.png

実際、カバレッジレポートはどれだけテストが書けているかの指標にはなります。
しかし、テストの目的はあくまで仕様を満たしているかどうかの確認ですので、カバレッジレポートにおけるカバレッジ率は副産物に過ぎません。
実際に見たことがあるのですが、カバレッジ率を上げることだけを目的とし、意味のないテストを書いている人( true == true みたいなやつ )がいたりします。
あくまでカバレッジのチェックはテストの抜け漏れを探すためであり、カバレッジ自体を上げることが目的ではないことを認識しておくと良いと思います。

まとめ

テストコードを書きましょうという話でした。
本来、新人研修とかでもやるべきではないかなぁと思うものなのですが、経験5〜6年超えていても、テストコードを知らない人と書いたりします。
特にPHPエンジニアにはそのような人が多かったと思うので、もっとみんなでテストコード書きましょう!

そんなポエムでした。

参考

ユニットテスト(wiki)
テスト駆動開発
テストコードの期待値はDRYを捨ててベタ書きする