Pythonでテスト 連載(1) なぜユニットテストを書くのか?

こんにちは。技術開発部データサイエンティストチームの西岡と申します。

データサイエンスチームでは、CTR・CVR予測や、ユーザターゲティングの精度向上に日々取り組んでいます。

前回の伊良子の連載でも述べてある通り広告配信をおこなうアドサーバにはLuaやGoなどが使用されていますが、
データサイエンスチームでは分析がメインとなるためPythonを使用し始めました。 Pythonにはscikit-learnpandasなどの分析用ライブラリが充実しており、モデリング等を迅速におこなえるためです。

もちろん、分析のみならず製品への機能追加もPythonでおこなっています。 そこで、この連載ではPythonのユニットテストの書き方・テストフレームワークを紹介します。
まず第1回となる今回は、そもそもなぜユニットテストを書くのかについて説明します。

そもそも「テスト」とは?

「テスト」が何なのかということについて、wikipediaには

ソフトウェアテスト(software test)は、コンピュータのプログラムを実行し、 正しく動作するか、目標とした品質に到達しているか、 意図しない動作をしないかどうかを確認する作業のことである。

とあります。

要はデバッグのための工程です。 そもそも、テストという大仰な名前をつけずとも

プログラムを書く -> 動作確認する -> 正しく動かなければ修正 -> 再度動作確認する -> ...

という流れはエンジニアであれば意識せずとも普段からやっていることです。

「ユニットテスト」とは?

ユニットテストが何なのかについてはこちらのサイトが参考になります。

単体テスト(ユニットテストと呼ばれることもあります)は、プログラムを構成する比較的小さな単位(ユニット)が個々の機能を正しく果たしているかどうかを検証するテストです。通常、関数やメソッドが単体テストの単位(ユニット)となります。

例をあげると、

という関数があったとして、

というように、ユニットテストでは外部のプログラムからテスト対象の関数やメソッドを呼び出し、期待した結果が得られるかを検証します。

なぜユニットテストを書くのか?

ユニットテストを書く利点について、いろいろあるかと思いますがここでは以下の3点をあげておきます。

  • プログラムの理解が容易になる

    ユニットテストが存在することで、テスト対象のコードの振る舞いが理解しやすくなります。

    get_nested_arrayは名前からは配列を返すのか?と予想してしまいますが、このテストがあることで辞書型を返すということがわかります。

    多人数が関わる開発では、自分が書いたプログラムを他の人が触る、あるいは他の人が書いたプログラムを自分が触ることは日常茶飯事のため、ユニットテストはプログラムの理解を助けることにつながります。

  • プログラムのリファクタも兼ねる

    複雑な関数やメソッドではテストすべき項目が多く、テストもどんどん複雑になっていきます。

    関数・メソッドに多くのことをさせすぎず役割を1つに絞っておくことでテストも簡潔で分かりやすいものになります。 はじめからユニットテストを書くことを念頭においておけば、設計を意識しながらコードを書くことにつながり、プログラムもすっきりしたものになります(参考: テスト駆動開発)。

  • プログラムの変更が容易になる

    プログラムに変更を加えると予期せぬ箇所にも影響が出て動作がおかしくなる、ということはままあります。 プログラムにユニットテストが付属していれば他の部分に影響が出てもテストが失敗するため、バグの早期発見につながります。

ユニットテストは「銀の弾丸」ではない

前項ではユニットテストはいいことずくめであるかのような書き方をしましたが、ユニットテストにも「限界」があります。

  • 工数がかかる

    製品のプログラムとは別にテスト用のコードを書かなければならないため、当然ながら工数がかかります。 プロジェクトの規模や人数を考えてどの程度まで網羅するべきかをその都度決めておく必要があるでしょう。

  • 全てをユニットテストでテストできるわけではない

    ユニットテストでのテストが向いているのは、関数やメソッドなどのプログラムを構成する最小単位です。

    例えばWebアプリケーションを開発していると、画面に期待した通りHTML要素が配置されるかもテストしなければなりません。 しかしユニットテストでは画面のテストをおこなうのは難しく、しかも画面はデザインの変更などにより頻繁に変わる部分であるため、テストコードを書くには向いていません。

ユニットテストを書くのは「手段」に過ぎない

前述した通りユニットテストは「銀の弾丸」ではありませんが、それでもメンテナンスコストの低下、開発速度の向上など、ユニットテストを書くことで多大な恩恵が受けられると思います。

しかし、ユニットテストを書く際には気をつけておくべきことがあります。
あくまでユニットテストを書くのは「手段」にすぎず「目的」ではないということです。

最大の目的はバグをなくすことです。

そのために必要なのは適切なテストケースを考えることです。たとえどれだけ変更がそのプログラムに加えられても、このプログラムはこう動いてほしいと明示化することです。

終わりに

本連載の第一回では、なぜユニットテストを書くべきかを中心に書きました。

次回の連載では、実際にPythonのユニットテストフレームワークを使ってどうテストを記述するのかを解説します。