🐘

【ユニットテスト入門】自動テストの意義とPHPでの具体的な使い方【基礎編】

2020/02/09に公開約7,200字

こんにちは、たつきちです。

エンジニア歴12年ぐらいで今はベンチャー企業のCTOをしています。

この記事では、プログラムの「ユニットテスト」について解説していきます。

  • ユニットテストって何?
  • ユニットテストが大事って聞くけど、プログラムなんて動けばいいんじゃないの?
  • ユニットテストって具体的にどうやって使うの?

こういった疑問にお答えできるかなと思います。

ぜひ最後までお付き合いください。

1. 手動テストと自動テスト

具体的に「ユニットテスト」というものについて話す前に、まず前提からしっかりおさらいしておきましょう。

そもそもプログラムのテストには 「手動テスト」「自動テスト」 があります。それぞれについて改めて説明します。

手動テストとは

手動テストは、その名のとおり 作成したプログラムを手動で動かしてみて、期待どおりの動作をするかどうかを目視で確認する というテスト方法です。

プログラムの途中で一時的に変数の中身を出力してみて期待どおりの値になっているかを目視確認するようなテスト方法も、手動テストの一種と言えるでしょう。

PHPなら var_dump($var); exit; みたいなコードを一時的に挿入して目視確認しながらプログラムを書くというのはあるあるですよね。

自動テストとは

自動テストは、 「従来は手動テストでやっていたような確認作業をプログラムによって自動化したもの」 のことです。

経験のない方にはなかなかイメージしづらいかもしれませんね。

具体的には、以下のような方法でテストを自動化します。

  1. 本体のプログラムコード(=テスト対象のプログラムコード)を作る
    • これを 「プロダクションコード」 と呼びます
  2. 「プロダクションコードを実行して動作結果をチェックするプログラムコード」を作る
    • これを 「テストコード」 と呼びます
  3. 作ったテストコードを実行すると、プロダクションコードが期待どおりに動作しているかどうかを自動で確認することができる

これが自動テストです👍

手動テスト vs 自動テスト

今まで自動テストを書く習慣がなかった方からすると、

  • 「テストのためだけに余分にプログラムを書くなんて面倒くさい」
  • 「手動テストでちゃんと確認するから自動テストなんて別に書かなくていい」

と思うかもしれません。

はい、おっしゃるとおりです😅

実際、自動テストを書くのはそれなりに面倒くさいですし、 本当に手動テストでしっかり確認ができるのなら、別に自動テストを書く必要はありません 👌

例えば、「ちょっとした作業を自動化するために使い捨てで作ったプログラム」なんかに、わざわざ自動テストを書く人はいないと思います。

つまり、別に自動テストがあらゆる状況において絶対正義というわけではなく、あくまでケースバイケースなのです。

ただ、ある程度規模が大きなプログラムや、長期的にメンテナンスしていく必要のあるプログラムなどでは、 ほとんどの場合、自動テストを書いておいたほうが、結局自分があとあと楽できる というのが現実です。

そう、自動テストには わざわざ余分な工数をかけてでも書くだけのメリットがある のです。

具体的にどんなメリットがあるのかについては次の章で詳しく説明します👍

なお、手動テストと自動テストの比較については、下記ページのスライドが分かりやすくて参考になりますので、深く知りたい方はあとで読んでみるといいかもしれません。

【資料公開】自動テスト vs 手動テスト – Ryuzee.com
https://www.ryuzee.com/contents/blog/3931

2. 自動テストのメリット

さて、「自動テストにはわざわざ余分な工数をかけてでも書くだけのメリットがある」と言いました。

具体的にはどんなメリットがあるのでしょうか。

これは、ある程度大きな規模のプログラムを書いたことがある方なら共感してもらえると思うのですが、 あるバグを直したら、全然関係ないはずの別の箇所がバグった みたいなことってよくありますよね。

しかもこの手のトラブルは往々にして、別の箇所がバグったことにすぐに気づくのが難しいので、結局リリース後に気づいて対応に追われたりします😓

さらには、この対応をしたことによってまた別の箇所がバグって…と負の無限ループに突入することもままあります😓

想像したくもないですね…

自動テストは、まさにこれを防いでくれるんです!

  • 常にプロダクションコードとセットでテストコードを書くようにしておく
  • その上で、プロダクションコードを一箇所でも修正したら、その都度全部のテストコードを実行するように習慣づける(あるいは自動化する)
  • すると、 もし関係ない別の箇所がバグったら、その部分のテストコードがエラーになるので、その場ですぐに気づくことができる

バグの早期発見。これこそが自動テストの意義そのものです 👍

3. テスト範囲による分類

ところで、この記事のテーマは「ユニットテスト」ですが、ここまでは「自動テスト」というものについて長々と説明してきました。

ちょっと混乱させてしまったかもしれませんが、ここからやっと「ユニットテスト」という言葉が出てきます。

実は、プログラムのテストには「手動/自動」という分類の他にも、 「プログラム全体のうちのどれぐらいの範囲をテストするか」 という基準による分類もあり、「ユニットテスト」というのはその分類における1つなのです。

この「テスト範囲による分類」は、実はやや定義が曖昧で、人によって多少認識の違いがあるように思います。が、一般的には以下のように分類することが多いのかなと思います。

  • ユニットテスト
  • インテグレーションテスト
  • システムテスト

それぞれ詳しく説明します。

ユニットテスト

ユニットテスト(略して「UT」、別名「単体テスト」)は、プログラム全体を小さな部品の集まりとみなした場合に、一つひとつの部品(ユニット)単位でテストする手法のことです。具体的には、「メソッド」や「関数」といった単位で期待どおりの動作をするかどうかをテストするイメージです。

インテグレーションテスト

インテグレーションテスト(略して「IT」、別名「結合テスト」)は、複数のユニットを組み合わせて動作させたときに、期待したとおりに連携して動作しているかどうかを確認するテストです。

分かりやすい例としては、例えばMVCフレームワークを使ったアプリケーションなら、コントローラーのテストがインテグレーションテストに当たると考えてよいでしょう。この場合、「(色々なユニットを呼び出した結果出力される)画面の中にちゃんと○○という文言が表示されているかどうか」といった観点でテストしたりします。

システムテスト

システムテスト(略して「ST」、別名「総合テスト」)は、インテグレーションテストよりもさらに広範囲に、プログラム全体を通して期待どおりに動作しているかどうかを確認するテストです。

4. テストの分類方法まとめ

色々な種類のテストが出てきたので、一度整理しておきましょう。

  • プログラムのテストには、「手動テスト」と「自動テスト」がある
  • プログラムのテストには、そのテスト範囲によって「ユニットテスト」「インテグレーションテスト」「システムテスト」という分類がある

細かい話ですが、後者の分類はあくまで「テスト範囲」による分類であって、 そのテストが自動で行われるか手動で行われるかは本質的に関係がない というのは留意しておくべきポイントです。

つまり、プログラムのテスト手法は、「手動/自動」と「テスト範囲」という2軸で、下表のようなイメージで分類することができるということになります。

手動 自動
ユニットテスト
インテグレーションテスト
システムテスト

ただ、特にユニットテストについては、 単に「ユニットテスト」と呼んで「自動化されたユニットテスト」のことを指すケースがほとんどです。

Wikipediaにもそのような言及がありますね。

単体テスト - Wikipedia
今日では、単体テストはxUnitといったテスト自動化ツールを用いて行われるのが主流となっており、単体テストを自動化されたテストとして言及するケースもある(本項目も、自動化されたテストとしての記述を含む)。しかし、単体テストはあくまでテストの粒度に対する分類であり、必ずしもテスト自動化を意味しないため、注意が必要である。
https://ja.wikipedia.org/wiki/単体テスト

この記事でも、ここから先は「自動化されたユニットテスト」を指して単に「ユニットテスト」と呼ぶことにしますので、ご留意ください✋

5. PHPでのユニットテストはPHPUnitを使う

さて、ここからはPHPを使っているケースを例に、具体的なユニットテストのやり方を解説していきます。

PHPでユニットテストを書く場合、PHPUnit というテスティングフレームワーク(自動テストのプログラムを実装するために必要な機能が色々と組み込まれている便利な基盤)を使うのがデファクトスタンダードとなっています。

一つ簡単な具体例を見てみましょう。

例えば、以下のようなプロダクションコードがあるとします。

<?php
// Greeter.php

class Greeter
{
    public function sayHello(string $name): string
    {
        return 'Hello, ' . $name . '!';
    }
}

このプロダクションコードに対してユニットテストを行うなら、

  • 適当な引数を渡してみて、期待どおりの文字列が返ってくるか

をチェックすればよさそうですね。

これをPHPUnitで実装すると、以下のような内容になります。

<?php
// GreeterTest.php

require __DIR__ . '/Greeter.php';

use PHPUnit\Framework\TestCase;

class GreeterTest extends TestCase // --- (1)
{
    public function testSayHello()
    {
        $greeter = new Greeter(); // --- (2)
        $result = $greeter->sayHello('Takashi'); // --- (3)
        $this->assertEquals('Hello, Takashi!', $result); // --- (4)
    }
}
  • (1) PHPUnitが提供している TestCase クラスを継承する(これにより、テストに便利な機能が色々使える)
  • (2) テスト対象のプロダクションコードを実行する準備をする(ここでは Greeter クラスのインスタンスを用意する)
  • (3) テスト対象のユニット(ここでは Greeter クラスの sayHello() メソッド)を実際に呼び出して結果を格納
  • (4) 期待する結果(ここでは Hello, Takashi! という結果)どおりになっているかどうかをチェック

ということをやっています。

(4) の $this->assertEquals() というメソッドは、PHPUnitが提供してくれている機能で、 引数に渡した2つの値がもし異なっていたらエラーになる というものです。

つまり、 「プロダクションコードが意図したとおりの内容になっていれば、この2つの値は等しくなるはず」 ということを確認しているわけです。

これがPHPUnitを使ったユニットテストの基本的な実装方法になります。

簡単ですね!

6. PHPUnitのインストール方法と実行方法

テストコードの書き方は基本的には前章のとおりです。最後に、PHPUnitのインストール方法と実行方法を説明しておきます。ぜひご自身のプログラムで実際にユニットテストを書いてみてください👍

PHPUnitは、PHPのパッケージマネージャー「Composer」を使って簡単にインストールすることができます。

Composerのインストール方法や概念、使い方についてはこの記事では説明を割愛させていただきます。ご要望があればまた別の記事で詳しく説明したいと思いますので、ご期待ください😇

$ composer require --dev phpunit/phpunit

これだけでインストール完了です👍

以下のように、インストールされた phpunit コマンドを、テストコードを実装したクラスファイルのパスを渡しつつ実行すればOKです。(前章で例示したテストを実行しているイメージです)

$ vendor/bin/phpunit GreeterTest.php

ただ、この方法だと1クラスずつしかテストを実行できないので、普通は phpunit.xml というPHPUnit用の設定ファイルを用意しておいて、引数なしで phpunit コマンドを実行すればテストコードがすべて一気に実行されるようにしておくのが一般的です。

PHPUnitの設定ファイルについての詳細は以下の公式ドキュメントをご参照ください。

この辺りの具体的な設定方法なども今回の記事では一旦割愛させていただきます🙏別途記事にしてほしい等ご要望があればぜひ Twitter にメンションかDMをいただければと思います!

まとめ

  • プログラムのテストには、「手動テスト」と「自動テスト」がある
  • プログラムのテストには、そのテスト範囲によって「ユニットテスト」「インテグレーションテスト」「システムテスト」という分類がある
  • 現代においては、単に「ユニットテスト」と呼んだ場合には「自動化されたユニットテスト」を指すことがほとんど
  • PHPでユニットテストを実施する場合、PHPUnitというテスティングフレームワークを使う
  • PHPUnitはComposerで簡単にインストールして使える

駆け足でご説明してきましたが、ひとまずユニットテストの基礎の基礎については一通りご理解いただけたのではないかと思います。

正直なところ、「より実践的なユニットテストの書き方を学ぶにあたっては必須知識だけど、この記事では説明しきれなかった」という内容も多々あります。(「DI(Dependency Injection)」や「モック」の概念など)

今回はあくまで基礎編ということで、まずは

  • 「ユニットテスト」という用語の意味を正しく理解していただく
  • ユニットテストの意義について納得していただく
  • 実際にユニットテストを実践する際の具体的なイメージを持っていただく

といったところをゴールに一旦記事化させていただきました。

本文中にも注釈しましたが、より実践的な内容については、別途記事にして丁寧に説明していきたいと考えていますので、ぜひ続編にご期待いただければと思います!

ご要望やコメントは Twitter にて随時受け付けておりますので、お気軽にコメントください!

GitHubで編集を提案

Discussion

ログインするとコメントできます