C# テスト駆動開発の実践方法:単体テスト(ユニットテスト)の基本を解説 #4

当連載ではテスト駆動開発について解説しています。これまでにもテスト駆動開発における基本として、テスト駆動開発のメリットやデメリットを紹介してきました。過去の記事一覧は「C# テスト駆動開発の実践方法」から確認することができるのでチェックしてみてください。

今回この記事ではテスト駆動開発で実施するべきテストについて解説してきたいと思います。テスト駆動開発では主に「ユニットテスト」を中心に、小さな粒度でのテストを行っていくことがメインになります。そのために必要知識を整理していきます。

アプリケーション開発には大きく分けると「結合テスト」と「単体テスト」の2種類があり、結合テストとはよりシナリオに近いテストを実施し、単体テストは機能内でのより小さな単位、場合によってはメソッド単位でのテストになります。テスト駆動開発では「単体テスト」を実施していきます。

単体テストとは

「単体テスト」とはプログラムを構成する比較的小さな単位が個々の機能を正しく果たしているかどうかを検証するテストのことを指しています。小さな単位のことを「ユニット」とも呼ぶため、単体テストは「ユニットテスト」と呼ばれることもあります。

アプリケーション開発においては関数やメソッドが単体テストの単位となることが多くなります。単体テストは実施対象となる単位が数行~数十行程度のコードになることが多いので、開発の早い段階で実施されることが多いのが特徴です。

テスト駆動開発では機能をユースケースといった大きな単位でテストするのではなく、このユニットに着目してテストをすることがメインとなります。構成する部品が正しいことを確認しながら、より大きな機能を組み立てる土台を作っていきます。

単体テストの仕組み

単体テストは小さなユニットでテストを行っていきます。テスト対象は関数・メソッドとなりますので、それらを起動して結果を確認する機能が必要となります。テスト駆動開発では「ドライバ」「スタブ」を上手く活用しながらテストを実施していきます。

テスト項目を実行させるモジュールを「ドライバ」と呼び、テスト対象の下位モジュールが完成していない場合のための代用品として記述するモジュールを「スタブ」と呼んでいます。

テスト対象となるメソッドは様々な処理から呼ばれ、様々な処理を呼ぶ可能性もあります。中間層であるメソッドの場合は何かに依存され、またモジュール自身も何かに依存していることが大半です。それらを疑似的に用意する必要があるということです。

テスト駆動開発では、この「ドライバ」役をテストクラスが担い、「スタブ」役もテストクラスで作成してモジュールに引き渡すような場合もあります。スタブが代替となるモジュールは、例えばデータを返却するAPIなどが主になるかと思います。そのようなモジュールが必要な場合は、とりあえず仮のものを使用して、メソッド自体が正しい振る舞いをするかどうかを判断するようにします。

単体テストの着目点

単体テストではいったい何に着目してテストを実施するべきなのか迷う人も多いかと思います。実際のところ、私も新人の頃はアプリケーションのテストは何に着目すればいいか分かっていませんでした。それくらいふんわりしている項目だと思います。

単体テストで着目するのは「分岐網羅テスト」と「境界値テスト」になります。分岐網羅テストとはテスト対象となるコードの分岐をすべてちゃんと通るかをチェックし、境界値テストは条件となる境界値が正しい値であるかを確認してい来ます。

分岐網羅テスト

分岐網羅で重要になるのは条件をすべて網羅して、条件分岐が正しくされているかを判定することです。例えば会員区分を渡して料金が返ってくるメソッドをテストする場合、すべての会員区分で正しい料金が返却されるかを確認します。

private int GetPriceByMember(int memberKbn)
{
    if (memberKbn.Equals(1))
    {
        return 1000;
    }
    if (memberKbn.Equals(2))
    {
        return 1300;
    }
    if (memberKbn.Equals(3))
    {
        return 1500;
    }
    return 0;
}

上記のような分岐があった場合にテストでは引数で渡す値のmemberKbnを1、2、3とそれ以外の4パターンテストを実施します。分岐にすべて入るかが観点となるため、分岐網羅では少なくとも4パターンは必要になるということです。

境界値テスト

その次に確認するべきは「境界値」になります。「境界値テスト」とも呼んだりしています。これはある基準によってOK・NGが分かれる場合に、OK側の境界値とNG側の境界値をチェックして正しい値で判定が分かれているかを確認していくテストのことを指します。

public bool IsMatchCriteria(int input)
{
    return input <= 100 ? true : false;
}

例えば上記のような引数と特定の値を比べて真偽を決めるようなメソッドでは、真偽の基準である値に対して境界値が正しいかというテストを実施します。この場合は100で判定されているため、テスト観点となるのは100と101は最低限必要です。

このようなメソッドで70と120みたいな境界値から乖離しているテストを行っても意味がありません。こういうメソッドで重要となるのは「厳密に判定されるか」ですので、境界値周辺は最低限の確認対象となるのです。

曖昧な部分を排除するために、境界値や特定の条件による分岐が存在する場合は、その境となる値をOK側とNG側でチェックします。これを「境界値テスト」と呼び、アプリケーション開発においてテストに含めるべき項目になります。

テスト項目は「分岐」に着目する

テスト項目を洗い出すのは慣れが必要になると思います。まだエンジニアになりたての場合などは、「どのような値がテスト対象となるのか?」を考えるのに苦労するかと思います。自分自身もそうでした。

そういう方のためにテスト項目を洗い出すための着眼点をお伝えしました。重要なのは「分岐」です。コードで表現するメソッドは様々な判定が入りこみますので、それらが「ちゃんと正しく分岐されるか」が重要になるのです。

「正しく分岐されるか」に着目すると行わなければならないテスト項目が浮かび上がってきます。そこで見出したテスト項目に対してドライバとスタブを活用して、単体テストを行いながら開発を進めるのがテスト駆動開発になるのです。