【すぐやろう】ユニットテストで意識するべき、2つの”FIRST”

unittest

ユニットテスト。ソフトウェアテストの中でも、最小単位(関数やクラス)に対して行うテストです。

今では大抵の場合、ユニットテスト=フレームワークを使った自動テスト、を意味します。

そんなユニットテストで、気を付けるべきことは何でしょうか?

そのキーワードは、2つの”FIRST”です。

目次

まずは実際のユニットテスト例

まず、テスティングフレームワークを使った、実装例を見ておきましょう。

ユニットテストの基本構成

言語はJava、テスティングフレームワークにJUnitを使ったサンプルです。

テスト対象のコード。素数を判定するシンプルなクラスです。

package com.example.project;

public class PrimeNumberChecker {

    // 素数を判定するメソッド
    public boolean isPrime(int number) {
        if (number <= 1) {
            return false;
        }
        // 2からnumber-1までの数で割り切れるかどうかを調べる
        for (int i = 2; i < number; i++) {
            if (number % i == 0) {
                return false;
            }
        }
        return true;
    }
}

テストコードは、こんな感じになります。

package com.example.project;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class PrimeNumberCheckerTest {

    // 1は素数でない
    @Test
    public void testOneIsNotPrime() {
        PrimeNumberChecker checker = new PrimeNumberChecker();
        Assertions.assertFalse(checker.isPrime(1));
    }

    // 2は素数
    @Test
    public void testTwoIsPrime() {
        PrimeNumberChecker checker = new PrimeNumberChecker();
        Assertions.assertTrue(checker.isPrime(2));
    }

    // 3は素数
    @Test
    public void testThreeIsPrime() {
        PrimeNumberChecker checker = new PrimeNumberChecker();
        Assertions.assertTrue(checker.isPrime(3));
    }

    // 4は素数でない
    @Test
    public void testFourIsNotPrime() {
        PrimeNumberChecker checker = new PrimeNumberChecker();
        Assertions.assertFalse(checker.isPrime(4));
    }
}

特徴はこんなところ。

  • mainがない テストメソッドを追加するだけで、フレームワークが自動的にテストを実行してくれます。
  • 自動で結果を判定する フレームワークが持つassertなどの判定操作で、OK/NGが判定できます。

テストメソッドを順次呼び出し、判定がすべてOKなら、テストは全パス、ということになります。

PrimeNumberCheckerTest > testOneIsNotPrime() PASSED
PrimeNumberCheckerTest > testTwoIsPrime() PASSED
PrimeNumberCheckerTest > testThreeIsPrime() PASSED
PrimeNumberCheckerTest > testFourIsNotPrime() PASSED

さまざまな言語のユニットテスト

多くの言語で、テスティングフレームワークを利用することができます。

たいていの言語では、ユニットテストのライブラリを導入して利用します。

また、GoやRustといった最近の言語なら、言語仕様自体にテスト機能が組み込まれています。標準装備であるため、余計な手間いらずで使いやすくなっています。

最もよく使われているテスティングフレームワークの例です。

言語フレームワーク補足
C++Google TestGoogle製フレームワーク
JavaJUnitJavaで最も普及
C#xUnit.NET Coreに最適化
PythonpytestPythonで最も人気
JavaScriptJestReact等にも適用できる
PHPPHPUnitPHPで最も広く普及
RubyRSpecRubyで最も普及
KotlinJUnitJavaと同じフレームワーク
SwiftXCTestApple公式フレームワーク
GotestingGoの標準ライブラリ
RustBuilt-in Test FrameworkRust標準のテスト機能

書き方の作法はそれぞれのフレームワークで異なりますが、mainがない、判定操作がある、といった特徴はどれも同じです。

ユニットテストで意識する、”FIRST”原則

ユニットテストの具体的な組み方は、それぞれのフレームワークの解説記事を見るとして、

ここからは、ユニットテストで意識しておく、大事な2つの“FIRST”の話です。

そのひとつめ、FIRST原則。ユニットテストを組む時に守っておくべき、5つの原則です。

FIRST原則

ユニットテストで守るべき、5つの原則

  • Fast(高速)
  • Isolate(独立)
  • Repeatable(繰り返し可能)
  • Self-Validate(自己検証)
  • Timely(適切なタイミング)

それぞれ見ていきましょう。

Fast:高速でテストができること

ユニットテストは、一度組んだら、プログラム修正のたび何度も何度も実行します。

最初はテスト数も少ないから、すぐ終わります。

だけど、だんだんテスト数が増えてくると、一回流し終えるだけでも時間がかかるようになってきます。

高速で終わるようにテストを作りましょう

  • 複雑なアルゴリズム計算など、どうしても時間がかかるなら、他のテストと分離できるようにしておきます。
  • サブモジュールで時間がかかるなら、スタブやモックで差し替えるといった工夫もしておきましょう。

Isolate:他のテストと独立であること

テストは、継続的に追加されたり見直されたりします。なので、テストが将来どのような順番で動くのか、保証がありません。

例えば、あるテストでファイルに書き込み、次のテストでそのファイルを読み込む、なんてしても、その順番に動くとは限らない。

テストは互いに独立して動作する必要があります。そのテストを1つだけ実行しても、全部実行しても、順番を変えても、同じ結果を出すようにしておきます。

リソース/モック/スタブなどの準備は、テストケースごとに準備するのがよいですね。

Repeatable:常に同じ結果となること

テストは動作結果を保証するものです。なので、何回実行しても、常に同じ結果を返すようにします。

この妨げとなるのが、タイミングにより変動する情報を扱うもの。

  • 現在時刻
  • データベースのデータ
  • ランダムデータ

こういったデータに直絶依存する処理は、テストのたびに結果が変わってしまいます。

依存性注入といったテクニックで変動情報を分離しておき、その部分をモック化するなどが必要です。

Self-Validate:自動で判定できること

テストは、自動的に成功か失敗かを判断するようにしておきます

プリント結果を目視で判断する、なんてテストを組み込んではダメです。自動でやる意味ない。

大量のテストを流したときに、すべて正常か、もしくはどこが異常か。フレームワークの判定機構で、自動的に判定できるようにしておきましょう。

Timely:いつでもテストできること

いま書いているコードを、直したら、すぐにテストです。こういう環境を整えておきます。

最近では、CI/CD(Continuous Integration(継続的インテグレーション)Continuous Delivery(継続的デリバリー)の考え方も普及しています。

ソフトウェアの変更を常にテストし、自動で本番環境にリリース可能な状態にする。こうして常に品質水準を保つのです。

FIRST原則が示すもの

FIRST原則に則る、ということは、率直に言えばこういうこと。

ユニットテストは

  • いつでも
  • すぐに
  • 何回でも

できるようにしておく

いつでも、すぐに、何回でもテストすることで、プログラムの品質を高水準で保つのです。

テストを、”FIRST” (最初)からやろう

ユニットテストで大事な2つの”FIRST”。ふたつめはこれです。

テストを最初からする

コーディングを開始すると同時にテストもコーディングする。(Test First

最初からテストを組み込む

一般的には、コーディング→テストの順に、開発工程が進みます。

でも、コーディングが完了してから、ユニットテストを実装するのは、遅すぎです

後付けでユニットテストを実装しようとすると、ものすごく手間がかかります。

ひとつめのクラスや関数のインターフェースを作ったら、すぐさまテストもコーディング。これくらい早めにテストを組みましょう。

テストファーストによる隠れたメリット

まずテストを組む。このテストファーストを意識すると、隠れたメリットも実感します。

1.インターフェースが洗練される

モジュールのテストを実装するとき、モジュールを使う側の気持ち、に立つことができます。すると、

  • このメソッド名だと分かりにくいかな?
  • こういう引数は余計かな?

みたいなことが、よく見えるようになります。

使う人が、分かりやすく使いやすいように、インターフェースが洗練されていくわけです。

2.モジュール構成が洗練される

ユニットテスト環境は、本番環境と別に作ります。

たくさんのテストをするので、できるだけテスト環境を簡単に作りたい

そんなときに、複雑な依存関係があったりすると、ビルド手順が面倒。余計な依存関係を外したりといった工夫をしだします。

その過程で、モジュールの疎結合・高凝集が進むというわけです。

3.自信を持って修正できる

プログラムが大規模になってくると、ある修正が、ほかに影響を与える可能性が出てきます。

プログラムの修正は、不安との戦いです。

そんなときに、すかさずユニットテスト。

その場でテストが通ることを確認できれば、自信をもって修正することができるようになるのです。

2つのFIRSTで、テスト駆動開発へ

ここまで挙げた、2つめのFIRSTが合わさると、こうなります。

ユニットテストは

  • 最初から
  • いつでも
  • すぐに
  • 何回でも

できるようにしておく

ここまでできるようになってくると、テストを主体に考える開発スタイルに、チェンジしていきます。

そう、それが、テスト駆動開発(TDD:Test Driven Development)と呼ばれるスタイルです。

テスティングフレームワークを使い、テストを自動化し、TDDへシフトチェンジしていきましょう。

まとめ

ユニットテストは、プログラムの品質を高水準で保つ、改修によるデグレードを防ぐ、など、様々なメリットがあります。

なんですが、

プログラマーが、ユニットテストで得られる究極の恩恵、って結局これでしょう。

ユニットテストの仕組みを作ると、テストが楽しくなる!

ユニットテストが充実してくると、こんな感じになってきます。

  • やっとバグの原因を突き止め、プログラムを改修。
  • おもむろにテストを開始。次々と実行されるテスト群。流れるように表示されるメッセージ。
  • そして最後に表示される、No Error。システムオールグリーン、Yeah!

そんな爽快感を味わうために、プログラマーはテストをする。そういっても過言ではないでしょう^^

ユニットテストを楽しみつつ、高品質なソフトウェアを開発しましょう!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次