🧪

ユニットテスト考察

2022/12/17に公開

はじめに

この記事はUnipos Advent Calendar 2022の記事です

こんにちは、とりかつ(@torikatsu923)です。

「なぜユニットテスト(UT)を書くのか」

ユニットテストを書こうとすると必ずと言っていいほど上の質問が飛んできます。
開発者の肌感で、「テストは書いたほうがいい」は当たり前に近い感覚だと思います。
ですが、その目的や効果を問われると言葉が詰まってしまいます。

今回の記事では、ユニットテストの目的や効果について整理し、**「なぜUTを書くのか」**という問いに対し説明責任を果たせられる状態を目指します。

なぜ目的・効果の説明が難しいのか

UTの目的や効果を問われた時の回答はとても難しいですよね。
私は、この問題は3つの問題に分解できると考えています。

  • テストに絶対はない
  • カバレッジが全てではない
  • 関係者がさまざまである

テストに絶対はない

テストに絶対はありません。
テストが示せるのは欠陥が存在しないことではなく、欠陥が存在することです。
そもそも何かが存在しないことの証明は困難で、悪魔の証明なんて言われたりもします。
また、人間はバグを作り込む生き物です。そんな人間がテストを書くからにはテストに抜け漏れが発生するのはごく自然なことです。

ですが、UTを書き始めたいときなぜか「UTはバグをなくす最強のツールなんだ!」と思い込みバグの撲滅を目標に掲げがちです。

テストを書く目的を「バグの撲滅」としてしまうと、こんなコミュニケーションに陥ります。
(こんな意地悪してくるマネージャはいないと思いますが...)

エンジニア「バグを撲滅するため、UT書きたいです!」

マネージャ「完全にバグなくせるの?」

エンジニア「いや...それは...」

マネージャ「完全にバグなくせないなら、UTに工数割く意味あるの?」

エンジニア「あっ、はい...」

こういった理由から、UTを始める際は 「バグの撲滅」を目標に据えない ようにしましょう。

カバレッジが全てではない

UTを書き始める時テストカバレッジが気になると思います。

ある程度のカバレッジを叩き出すことには効果があります。
しかし、カバレッジが担保されていないとUTの恩恵を受けられないわけではありません。
また、カバレッジが高いからと言ってUTが十分とは限りません。

リファクタリングでも次のように述べられていました。

「どれだけテストをすれば十分なのか」(中略) カバレージによる分析が有効なのは、コード中のまだテストされていない箇所を突き止める場合のみであり、テストスイート自体の品質を保証するものではない (中略) 十分なテストスイートが揃っているかどうかは、主観で決めるのが最も良い

今までUTが存在しなかったプロジェクトだと、次のような意見が出ることがあります。

「全部に対してUT書いてカバレッジ高めないと、UTの効果ないんでしょ? 全部に対してUT書くとか現実的じゃない」

実際、どんなプロジェクトにおいてもUTの効果は書き始めてすぐから現れます。
カバレッジの高さとUTの効果には相関がある程度にとどめ、カバレッジが絶対ではないという価値観を作りましょう。

関係者によって興味・関心が異なる

UTを書く目的や効果に対する問いは経営者やマネージャー、チームの開発者など社内のさまざまな人から飛んできます。
同じ目標を追っているとはいえ、経営者などの上位レイヤと現場のエンジニアでは興味・関心に差があります。

ある会社では次のようになっているかもしれません。

関係者 興味・関心
上位レイヤ 費用対効果、プロダクトの品質
エンジニア コードの品質、開発者体験

上位レイヤに対して現場のエンジニアがコードの品質や開発者体験について熱弁したところで、お互いの期待値がズレたままなので議論は並行したままです。
こういった理由から、テストの効果・目的を説明する際はその人が何に興味・関心があるのかを踏まえた上で、その側面からUTの説明することが求められます。

UTの目的・効果

ここまで、UTの目的・効果を説明する難しさと、それぞれの対処法について述べてきました。
ではUTの目的とは一体なんでしょうか?

私は、 「作成したクラスや関数のインターフェースに対し洞察を得られる」 ことだと考えています。

達人プログラマーでも次のように述べられていました。

テストとはバグを見つけることではない

さらに次のような記述もありました。

テストの主な利点は、テストについて考え、テストを記述している時にあり、テストを実行している時ではないと我々は確信しています

UTを書く際、必ず書いたクラスや関数の呼び出しを行います。
つまりコードを書いた本人が、作成したクラスや関数の最初の使い手です。

使い手の立場になることで、不便な点が明るみになったり、不吉な臭いを嗅ぎつけられます。
そのFBをもとにインターフェースを改善し、より良いコードづくりの助けになります。

これらの活動の結果、次のような効果が発生します。

  • バグの減少
  • 変更が容易な品質の高いコードづくり
  • プロダクトの品質向上
  • 自動回帰テストによるテスト工数削減
  • 開発体験の向上
    ... etc

効果的なUTの書き方

UTを効果的なものにするため、「何に」対してUTを書くのかというUTの戦略は非常に重要です。

では、「何に」対してUTを書いたら良いのでしょうか?

クラスでしょうか、関数でしょうか、それともモジュールでしょうか?

私は、 「振る舞い」 だと考えています。

前項でUTの目的は「作成したクラスや関数のインターフェースに対し洞察を得られる」ことだと述べました。
UTを書くことで洞察を得てインターフェースを洗練させることが、非常に重要な活動です。

振る舞いに対しUTを記述することの意義はさまざまな場所で触れられています。
また、テスト駆動開発(TDD)が「振る舞い駆動開発(BDD)」と呼ばれることも、振る舞いに対しUTを書くことに意義があることの表れだと考えられます。

また、インターフェースは変化しやすく振る舞いは変化しづらいため、そのテストは変更に強く壊れづらいものとなります。

【余談】UTはTDDに乗っ取らなければならないのか

よく「コードを書くより先にUTを書かなければならない」という意見を聞きます。

私はUTを書くのはコードの後先どちらでも良いと考えています。
UTを書く目的が「作成したクラスや関数のインターフェースに対し洞察を得られる」ことである以上、どちらを先に書いてもその目的を達成することができるからです。

ですが、テストを先に書くことで効果的に洞察を得ることができるかもしれません。
どちらを先に書くか決まりはないため、個人の好みで良いと思います。

おわりに

今回の記事では、ユニットテストの目的や効果について整理し、「なぜUTを書くのか」という問いに対し説明責任を果たすために必要な情報をまとめました。
この記事がUT書きたいけどうまく目的・効果を説明できないという方の助けになれれば幸いです。

参考文献

Discussion