テストコードの書き方はわかったけどテストケースってどうやって挙げたらいいの?


はじめに

炭山水です。こんばんわ。

テストって必ずする or 必ずテストコードは書くと思いますが、身近な例で「JUnitの使い方とかPHPUnitの使い方とかはわかってもいざテストケースってどんなのやったらいいかよくわからない」という声をききまして筆を執りました。

ガチなテスト論は専門書に譲るとして、本記事ではその触りというかイントロダクション的なことをお話ししようと思います。

ちなみにQiita初投稿です。本日参加したTech::Survivorのもくもく会で「Qiitaを何かしら1記事投稿すること」が課題だったのでそれ用に書いたものですw

TECH::Survivor
エンジニアに成長機会を提供することを目的に作られたオンラインコミュニティの勉強会アカウントです。
https://techsurvivor.connpass.com/participation/

対象読者とお話しする範囲

最近プログラマとしてお仕事始めたり、勉強し始めたりした人を想定しています。
※ テストってそういえばちゃんと考えたことなかったなって人も。。。読んでね?

だいたいこういう状況かなと思います。

  • JUnitとかのテストコードの書き方自体はわかった
  • で、どんなテストケース(パターン)挙げればいいの?

お話しする範囲ですが、テストの種類とか大きいところから話すと長くなるので、↑の状況を想定して「とあるメソッドにどういうテストを書いたら良いか」というユニットテストを例にして、具体的なやり方をお話しします。

おさらい:そもそもシステムとは

すっごいざっくりいうと、システムって、システムに何かを入力すると何かが出てくるものです。極論すべてがこれです。

なので、これもだいぶ極論ですが、テストって何をするものかというと「入力に対して、出力が意図したものとなっているかを確認する」となります。

図でいえば、炭と山水を入力したら「私は炭山水です」と出力されればOKですし、エラーが出たり「私は山水炭です」って出てきたらNGです。

これを想定されるすべてのパターンで行います。

仕様の確認

では実際にやっていきましょう。まずはテストするメソッドの仕様から。

例としてこんな仕様のメソッドを作れと言われているとします。
なんとなくですが、とある管理画面の閲覧権限チェックとかそんなのを想定です。

  • アクセスしたユーザーのユーザーIDとページに許可されたユーザーIDを比較
  • 許可されたユーザーIDに、アクセスしたユーザーIDが含まれるときはtrue、含まれないときはfalseを返す
  • アクセスユーザーIDがEmptyの時はfalse

仕様を入力と出力の形に整理する

仕様を把握したら、仕様を入力と出力に分け、それぞれ個別の要素のパターンを洗い出します。とにかく全部書いてください。

値の種類についてはほんとは「同値試験」とか「境界値試験」とかあるのですがここはいったん組み合わせについてのみ話すことにします。値の種類の分け方についてはとりあえずフィーリングで読んでもらって、同値とか境界値については調べてみてください。

単体テストなので、運用上ありうるデータかどうかは、愚直に全部書くのがコツです。増やしたところで大してめんどくさくないですし、そもそも運用上ありうるデータかどうかはこのメソッドの関知するところはないためです。

ここでサボって後からテスト不足が発覚する方がめんどくさいです。

入力 : メソッドの引数

項目 パターン
アクセスユーザーID null,空文字,あり
アクセス許可ユーザーIDリスト null,空リスト,アクセスユーザー含む/含まない,リストにnullを含む/含まない, リストに空文字を含む/含まない

出力 : メソッドの返り値

項目 パターン
結果 true,false

入出力パターンをテスト項目として整理

さて、では入力と出力(期待値ともいう)を組み合わせて実際のテストケースに起こしていきましょう。

コツとしては、これも愚直にやることです。まず入力の組み合わせパターンを列挙していって全部書きます。

どうせユニットテストのパラメータなんてコピペで増やせるのでガンガンやってしまいましょう。

図のようにディシジョンテーブルってやつで整理すると楽です。

入力のところに組み合わせを〇で書いていって、出力のところは入力値を見てあるべき結果に〇を打つという形で書いていきます。

なお、図のテーブルは書きかけです。。。

テストコード実装

結構ノリで書いてるので動かなかったらゴメンナサイ。
テストフレームワークは各自ご利用のもので脳内変換してください。

static Stream<Arguments> checkPermissionProvider(){
    return Stream.of(
        Arguments.of(null,null,false),
        Arguments.of(null,CollectionUtils.emptyList(),false),
        //
        // ディシジョンテーブルのパターンを書いていくだけ
        //
    );
}

@ParameterisedTest
@MethodSource("checkPermissionProvider")
void checkPermissionTest(String userId, List<String> allowIds, boolean result){

    //
    // テスト対象メソッド呼んだりアサーション書いたり
    //
}

おわりに

どうでしょうか?いきなりIDEを開いてテストどうしようかなとうんうんうなるより、機械的にガシガシテストがかけるの実感できますでしょうか?

特にテストコードはもうディシジョンテーブルの内容を、1ケース目のコピペで増やすだけです。

なお、余談ですが、ここでテストパターンが複雑で、そんな機械的に書けないぞ??ってってなったら、たぶんそのメソッドが責任を持ちすぎています。設計を見直しましょうね。