検索
連載

PHPテスト失敗の原因を追究する仕事で使える魔法のLAMP(27)

PHPのソースコードをビルドしてテストしてみたら、いくつかのテストが失敗してしまいました。今回は、テストが失敗した理由を探ってみます(編集部)

PC用表示 関連情報
Share
Tweet
LINE
Hatena

テスト失敗の原因を調べる

 前回からPHPの具体的なビルド方法を解説しています。手始めにエクステンションをすべて無効にしてビルドしてみました。configureスクリプトの実行とビルドまで問題なく進めることができましたが、その後、「make test」でテストを実行したところ、いくつかのテストが失敗していることが分かりました。

 「make test」によるテストは、そのソフトウェアの開発者にとっては、新たにバグを作り込んでいないことを確認する手段となっています。一般向けにソフトウェアをリリースするときは、テストの結果はすべて成功となっているはずです。それなのに失敗になるテストがあるということは、環境による問題があるのか、ビルド時に何らかのトラブルが起きていると考えられます。

 テストが失敗したときは、その原因を調査しなければなりません。実際にビルドの方法に問題が見つかることもありますし、環境の違いなどによってたまたま失敗になることもあります。テストが失敗している部分がどれくらい深刻な影響を与えているかということも場合によってまちまちです。深刻でない失敗であれば無視することもできます。

 いずれにしても、詳しく調べてみなければこういった判断はできません。というわけで、前回失敗したテストを1つずつ調べてみることにします。

テストファイルの読み方

 以下は「make test」を実行した結果、出力されたテキストのうち、失敗したテストを示す部分です。

=====================================================================
FAILED TEST SUMMARY
---------------------------------------------------------------------
Bug #55156 (ReflectionClass::getDocComment() returns comment even though the class has none) [Zend/tests/bug55156.phpt]
DateTime::diff() days -- spring type2 type2 [ext/date/tests/DateTime_days-spring-type2-type2.phpt]
DateTime::diff() days -- spring type2 type3 [ext/date/tests/DateTime_days-spring-type2-type3.phpt]
DateTime::diff() days -- spring type3 type2 [ext/date/tests/DateTime_days-spring-type3-type2.phpt]
DateTime::diff() days -- spring type3 type3 [ext/date/tests/DateTime_days-spring-type3-type3.phpt]
DateTime::sub() -- dates [ext/date/tests/DateTime_sub-dates.phpt]
DateTime::sub() -- february [ext/date/tests/DateTime_sub-february.phpt]
preg_replace() with array of failing regular expressions [ext/pcre/tests/006.phpt]
Bug #54971 (Wrong result when using iterator_to_array with use_keys on true) [ext/spl/tests/bug54971.phpt]
=====================================================================

 失敗となったテスト項目が1行ずつ並んでいます。ファイル名はテストを実行するPHPプログラムです。失敗の詳細を調べるときは、このファイル名がヒントになります。

 1つ目の失敗に注目してみると、テストを実行するPHPプログラムのファイル名が「Zend/tests/bug55156.phpt」となっています。これはソースコードを展開したディレクトリからの相対パスによるファイル名です。なお、ZendというのはPHPのスクリプト実行エンジンの名前で、PHPの中核部分ということになります。

 「Zend/tests/bug55156.phpt」の内容は次のようになっています。

--TEST--
Bug #55156 (ReflectionClass::getDocComment() returns comment even though the class has none)
--FILE--
<?php
 
/** test */
namespace foo {
        function test() { }
 
        $x = new \ReflectionFunction('foo\test');
        var_dump($x->getDocComment());
 
        /** test1 */
        class bar { }
 
        /** test2 */
        class foo extends namespace\bar { }
 
        $x = new \ReflectionClass('foo\bar');
        var_dump($x->getDocComment());
 
        $x = new \ReflectionClass('foo\foo');
        var_dump($x->getDocComment());
}
 
?>
--EXPECTF--
bool(false)
string(12) "/** test1 */"
string(12) "/** test2 */"

 いくつかの領域に分かれていて、純粋なPHPプログラムではないことが分かります。このようなテストファイルの1つ1つが1件のテストに該当しています。「make test」を実行すると、テストファイルを順次解析し、テストを実行するようになっているわけです。

 このファイルの形式はごく単純です。最初の領域はテストの項目名で、テスト結果一覧に表示されていたものと同じものです。次の領域がPHPによるテストプログラムです。テストプログラムは、テストの趣旨にそったテストロジックを実行し、その結果を出力するようになっています。その次は、テスト結果として期待される出力です。この通りの内容が出力された場合に限り、テストは成功となります。

 テストが失敗した場合、テストファイルのファイル名から拡張子を変えたいくつかのファイルが残されます。

$ ls -p Zend/tests/bug55156.*
Zend/tests/bug55156.diff  Zend/tests/bug55156.out   Zend/tests/bug55156.sh
Zend/tests/bug55156.exp   Zend/tests/bug55156.php
Zend/tests/bug55156.log   Zend/tests/bug55156.phpt

 拡張子.phpと拡張子.expのファイルには、それぞれ先ほどのテストファイル中のプログラムの領域と、期待される出力の領域の内容がそのまま格納されています。拡張子.shのファイルはこのテストを実行するためのシェルスクリプトです。

 拡張子.outのファイルは、テストプログラムを実行したときの出力結果です。拡張子.diffは.outと.expとの差分、つまりテスト結果の差異となっています。拡張子.logには、期待する結果と出力結果がまとめられていますので、通常はこのファイルを見ればよいでしょう。「Zend/tests/bug55156.log」は次のような内容になっています。1行目が示す結果が、期待する結果と異なっていることが分かります。

---- EXPECTED OUTPUT
bool(false)
string(12) "/** test1 */"
string(12) "/** test2 */"
---- ACTUAL OUTPUT
string(11) "/** test */"
string(12) "/** test1 */"
string(12) "/** test2 */"
---- FAILED

 これで、どのような内容のテストだったのか、具体的な結果がどうだったのかということを調査できるようになりました。次はこの結果で問題ないかということを評価する必要があります。

テスト失敗という結果をどう評価する?

 テスト失敗という結果の評価は経験や知識が必要となるため、なかなか難しいものがあります。上辺だけの一般論になってしまいますが、3つの評価ポイントを紹介します。その後、今回の失敗をどのように評価できるかを解説します。

 まず、1つ目のポイントです。ビルドをする立場としては、ビルド手順や環境によって起きた失敗かどうかを確認したいところです。依存する外部のソフトウェアやライブラリを正しく使えていないことによる失敗かどうかなどに着目します。また、ビルド時に有効にした機能をきちんと使えていることも重要です。

 2つ目は、テストの失敗がインストールしたソフトウェアの利用に悪影響があるのかどうかという点です。ささいな問題であればそのままにしておいてもよいですし、まったく使わない機能であれば、問題自体が大きなものでも無視するという判断ができます。実際の利用者とともに検討するのがよいでしょう。

 3つ目は、テストそのものが失敗しているかどうかという点です。テストのための環境初期化に失敗していたり、何らかの制限でテストが実行できないケースが考えられます。原因としては、ビルド時に正しくライブラリをリンクできていない場合や、メモリやネットワークの制限など、さまざまなケースが考えられます。エラーメッセージなどから原因を読み取って、取り除き、再度テストを実行します。

 さて、先ほどまで調べてきたテストの失敗例に戻ります。上から見ていくと、「Zend/tests/bug55156.phpt」の内容はソースコードからドキュメントコメントを得るテストです。ドキュメントコメントとは、一定の決まった形式でコメントを置くと、自動でドキュメントに変換できるようなコメントのことです。これはほとんど影響ないので、問題はないと考えられます。

 次に6つ並んでいる失敗は、ファイル名から同じエクステンションで発生した失敗であることが分かります。すべて「ext/date/tests/」にあるものだからです。テスト内容を見ると、日付の足し引きといった演算結果が期待と異なっているようです。これをどう評価するかは難しいのですが、ここでは問題なし、としたいと思います。

 こう判断する最大の根拠は、この失敗がほかでもたくさん起きているからです。PHPのWebサイトには、どのようなテストでよく失敗が発生しているのかを列記したWebページがあります。このデータはPHPをビルドしている世界中のユーザーが報告してきたものです。アクセスすると、バージョン5.3.8をビルドするときによく失敗しているテストを確認できます(図1)。

図1 PHPでテスト失敗の報告データを集計しているサイト。今回の例で失敗しているテストが上位を占めている。
図1 PHPでテスト失敗の報告データを集計しているサイト。今回の例で失敗しているテストが上位を占めている。クリックすると拡大

 リストの上位に、今回の例で問題になっている失敗が並んでいます。普遍的な失敗であるということから、エクステンションかテストそのものに問題がある可能性が高いと考えることができます。そして、1つ目の失敗は第1位にありますので、同じように推測できます。

 次の「ext/pcre/tests/006.phpt」も図1のサイトを見ると上位に位置していています。典型的な失敗例ということです。さらにこのテスト名で検索すると、PHP-DEVメーリングリストのやりとりからテストのミスであることが分かります。

 最後のテスト「ext/spl/tests/bug54971.phpt」の出力は、次のようになっています。

Fatal error: Class 'DOMDocument' not found in /home/haruhiro/27/php-5.3.8/ext/spl/tests/bug54971.php on line 11

 DOMDocumentが存在しないということですが、これは「--disable-all」でエクステンションを無効にしているためです。無効になっているエクステンションに関係するテストは、本来であればスキップするようになっているはずですが、そうなっていないというテストの問題です。よく使われるエクステンションであるがゆえに見つからなかったのだと思われます。これも問題なしと判断できます。

 結局、すべてのテスト失敗を見ても、大きな影響はないだろうと判断できます。「PHPはテストが100%成功するわけではないのが普通だから」と結果を無視するというのもいいのですが、詳しく調査することでスキルアップにもつながるはずです。

 次回はよく使うエクステンションを有効にしてビルドしてみます。

著者紹介

株式会社イメージズ・アンド・ワーズ
代表取締役
山口晴広(やまぐち はるひろ)



「仕事で使える魔法のLAMP」バックナンバー

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る