テスト入門の本選び。9,999・10,000・10,001の境目をレンズで確かめるイラスト。

Java 技術書・書評

Javaのテスト入門におすすめの本|テストケースを選べる最初の1冊

Javaでテストを初めて学ぶなら、最初の1冊は『ソフトウェアテストの教科書[増補改訂 第2版]』です。「何を入力して、何が返れば正しいのか」を決めるために選びます。

Javaの文法を使い、処理をクラスに分けられるようになったら、次はその処理を自分で確かめる段階です。「JUnitのテストが緑になった」から一歩進んで、なぜそのケースを選んだのかまで説明できるようになりましょう。

2冊目の『単体テストの考え方/使い方』は、テストを書いた経験ができてからの候補です。最初に2冊とも買う必要はありません。以下の短いコードを見て、ケースを選べないのか、書いたテストの守り方に迷うのかを分けます。

この記事でわかること

  • テスト入門で最初に選ぶ1冊と、その本が必要な理由
  • 送料のコードから、ケースの不足と期待値の決め方を考える方法
  • 本で学んだ判断をJUnitに移し、Springへ進む目安

目指すのは、仕様からテストを選び、変更後も確かめられる人

学習の目標は、本の用語やJUnitの機能を丸ごと暗記することではありません。小さなJavaの処理に対して、入力・期待する結果・そのケースを選んだ理由をそろえて、繰り返し実行できることです。

私は、コードの理解を確かめるときに「今回の要件」「取れる選択肢」「その手段を採用した理由」「他の手段より適する理由」を重視しています。テストにも同じ問いを当てはめられます。思いついた数値を渡しただけでは、何を守るテストなのかが引き継げません。

たとえば、送料を決める処理の確認なら、手で画面を操作する方法と、Javaのメソッドを直接呼ぶ方法があります。今回は入力と戻り値だけで料金の約束を確かめられるため、JUnitで直接呼びます。画面の見た目や操作は、このテストでは確認できません。何を確認するために選んだ方法かを言えることが大切です。

この送料のコードなら、何円を入力して確かめますか?

商品小計から送料を求める、学習用の例です。仕様は次の3つとします。小計0円を受け付けることも、この例で決めた約束です。

  • 小計は整数の円で、0円から100万円までを受け付ける。
  • 小計が10,000円以上なら送料無料。それ以外は500円。
  • 範囲外の小計を渡したら、IllegalArgumentExceptionにする。
    public int calculate(int subtotal) {
        if (subtotal < 0 || subtotal > 1_000_000) {
            throw new IllegalArgumentException("小計は0円以上100万円以下です");
        }
        return subtotal >= 10_000 ? 0 : 500;
    }

calculateが返すのは、商品小計を含めない送料だけです。上のコードはこの仕様どおりに動きます。ここでは、書き間違いを探すのではなく、仕様を確かめるケースを選んでみてください。

最初に次のテストを1件書いたとします。6,000円を渡し、送料500円が返ることを自動で確かめています。

@Test
void 小計6000円の送料は500円() {
    ShippingFee shippingFee = new ShippingFee();
    assertEquals(500, shippingFee.calculate(6_000));
}

@TestはJUnitに実行対象だと伝える印です。assertEquals(期待値, 実際の値)は2つを比較し、違えば失敗にします。500を先に置いているのは、仕様から決めた送料が500円だからです。

ここで迷ったら、最初の1冊へ

6,000円の1件に、何を追加すればよいかわからない

10,000円の扱い、入力できる金額の下限と上限。そのどれを、どんな値で確認するかを決める力が必要です。

『ソフトウェアテストの教科書』で学ぶことを見る →

テストを書いた経験ができたら

結果は同じなのに、処理を整理するたびテストが壊れる

戻り値を守りたいのか、内部メソッドの呼び方を固定しているのか。テストが何に依存しているかを考える段階です。

『単体テストの考え方/使い方』で学ぶことを見る →

「newや引数・戻り値の意味からまだ難しい」場合は、先にJava基礎を補ってください。一方、JUnitの書き方を知らなかっただけなら、この短い例を動かしながら進められます。テストの本を買う前に、構文の不明点とケースを選ぶ判断の不明点を分けておきましょう。

『ソフトウェアテストの教科書』で、入力と期待値を選ぶ理由を学ぶ

「正常な値を1件試した後、何を足せばよいかわからない」人が最初に選ぶ本です。紹介しているのは増補改訂 第2版です。テストの基本から、同値分割・境界値、条件の組合せ、状態の変化を整理する考え方を学べます。

書籍「ソフトウェアテストの教科書[増補改訂 第2版]」の表紙
書影:SBクリエイティブ
PR テストを学ぶ若手向け

ソフトウェアテストの教科書[増補改訂 第2版]

布施昌弘・江添智之・永井努・三堀雅也 著

仕様から、試す入力と期待する結果を選べるようになる。

同値分割・境界値や条件の組合せなど、テストケースを選ぶ考え方を基礎から学ぶ1冊。何を試せばよいかわからない段階で選びます。

  • 正常な値を1件試した後、追加するケースに迷う
  • 入力と期待値を選んだ理由を説明したい

当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。

ここで学ぶこと:同じ結果になる範囲と、結果が切り替わる場所

送料の例では、受け付ける金額を「0〜9,999円」と「10,000〜1,000,000円」に分けられます。前者の送料は500円、後者は0円です。同じ扱いになる入力をまとめ、代表を選ぶ考え方が同値分割です。6,000円は前者の代表にはなりますが、送料無料の側はまだ確かめていません。

さらに、10,000円ちょうどは送料が変わる境目です。「以上」を「より大きい」と取り違えた実装は、6,000円でも20,000円でも発見できません。そこで9,999円・10,000円・10,001円を選び、境界の前後と境界そのものを確認します。

仕様の10,000円以上は送料無料という境界に対して、9,999円は送料500円、10,000円と10,001円は送料0円を期待する図。
入力を増やす理由は、送料の約束が切り替わる場所を確かめるため。

ここで必要なのは「境界値」という言葉を言えることより、どの仕様の境界なのかを指せることです。この例には送料無料の境界に加え、入力として受け付ける金額の境界もあります。下限の周辺は−1円・0円・1円、上限の周辺は999,999円・1,000,000円・1,000,001円です。

値を無闇に増やすより、「送料の切替」「入力できる下限」「入力できる上限」という3つの理由を先に分けると、確認の抜けを説明できます。どの値も必要だから追加するのであって、9件という件数に意味があるわけではありません。

ここで得ること:「正しく動く」の中身を他の人へ渡せる

レビューで「送料無料の条件は確認しましたか」と聞かれたときに、「JUnitが通りました」だけで終えず、「10,000円以上という仕様に対して、直前・ちょうど・直後を確認しました」と答えられることが目標です。これなら、レビューする人も何が未確認かを判断できます。

業務システムの金額条件は、後から担当者が変わっても読み直されます。期待値に理由があれば、担当者が「10,000円の送料はなぜ0円なのか」をコードの動きから推測し直す必要が減ります。テスト名にも、入力の条件と期待する結果を残しましょう。

本で扱う条件の組合せは、たとえば将来「会員なら小計に関係なく送料無料」と決まったときに役立ちます。金額の境界だけを増やすのではなく、会員かどうかと金額条件が組み合わさるからです。今のサンプルは金額だけに絞り、条件が増えたときに新しい考え方を持ち出します。

次に埋めること:選んだケースをJUnitで繰り返し実行する

この本は、JUnitの設定やAPIだけを順に覚える本としては選んでいません。本で学ぶのは、何を確認するかを決める考え方です。Javaでどう実行するかは、この記事のサンプルにケースを追加してつなげてください。

今の例なら、ケースを紙に書いた段階で「入力10,000円、期待する送料0円」と決められます。実行して返ってきた500円を、そのまま期待値にしてはいけません。実装が間違っていたとき、間違った結果まで正しいことにしてしまうためです。仕様が曖昧なら、テストを書く前にその仕様を確認します。

選んだケースをJUnitに書き、変更後にまとめて実行する

送料の境界を確かめる3件を、JUnitでは次のように書けます。今回はshippingFeeをテストクラスのフィールドに置き、各テストメソッドから使います。先ほどの1件ではメソッドの中で生成していた点との違いです。読めることを優先し、最初は1件ずつ分けています。クラス宣言とimportを含む完全なコードは、後のサンプルに入っています。

private final ShippingFee shippingFee = new ShippingFee();

@Test
    void 小計9999円の送料は500円() {
        assertEquals(500, shippingFee.calculate(9_999));
    }

    @Test
    void 小計10000円の送料は0円() {
        assertEquals(0, shippingFee.calculate(10_000));
    }

    @Test
    void 小計10001円の送料は0円() {
        assertEquals(0, shippingFee.calculate(10_001));
    }

テスト側にsubtotal >= 10_000 ? 0 : 500をもう一度書く必要はありません。仕様から決めた0円・500円と、本体が返した値を比較します。本体の条件式をコピーすると、同じ勘違いを両方へ写しても成功してしまいます。

範囲外を拒否する約束は、例外を期待する形にします。ここでassertEqualsに送料を渡すのではなくassertThrowsを使うのは、仕様が「何円を返すか」ではなく「受け付けない」だからです。

@Test
    void 負の小計は受け付けない() {
        assertThrows(IllegalArgumentException.class,
                () -> shippingFee.calculate(-1));
    }

() ->の右側は、JUnitに実行してもらう処理です。calculate(-1)が指定した例外、またはその派生型を出せば成功します。例外が出なかったり、それ以外の例外だったりすれば失敗します。

失敗したら、期待値を実際の値へ合わせる前に仕様を読む

本体の>= 10_000を一時的に> 10_000へ変えると、10,000円のケースが失敗します。これは「送料0円を期待したが、実際は500円だった」という意味です。期待値を500円に書き換えれば成功しますが、10,000円以上は無料という約束は破ったままです。

まず仕様が変わったのかを確認し、今回は変わっていないので本体を戻します。テストが失敗したこと自体を消すのではなく、仕様・期待値・実装のどれが食い違っているかを調べます。これが、失敗の表示を見て次の作業を判断する練習になります。

仕様変更では、新しいケースと、変わらない約束の両方を確認する

今度は、仕様そのものが「8,000円以上は送料無料」に変わったとします。7,999円・8,000円・8,001円のケースを追加し、送料500円・0円・0円を期待します。先に実行すると変更前の本体では8,000円と8,001円が失敗します。そこで本体を変更し、全件を実行します。

以前の9,999円のケースは、新仕様では送料0円に変わります。こうした期待値は仕様に合わせて更新します。一方、負の小計や100万円超を拒否する約束は変わっていません。そのケースも一緒に実行し、別の約束を壊していないかを確認します。

『単体テストの考え方/使い方』は、テストを直す負担に迷ってから読む

ケースを選んでテストを書けるようになり、「本体を整理すると大量のテストまで直す」「テストは増えたのに変更が怖い」と感じたら、次の候補が『単体テストの考え方/使い方』です。最初の本と役割が違い、残して育てる価値のあるテストを考えるために選びます。

書籍「単体テストの考え方/使い方」の表紙
商品画像:Amazon.co.jp
PR テストを学ぶ若手向け

単体テストの考え方/使い方

Vladimir Khorikov (著), 須田智之 (翻訳)

単体テストで「何を検証するか」の判断基準を学ぶ。

モックの使いどころや壊れにくいテストの考え方を整理できます。コード例はC#です。JUnitの操作入門ではなく、テストの設計と価値を深める本です。

  • テスト対象の切り分けに迷っている
  • 保守しやすい単体テストを書きたい

当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。

この本で学ぶ対象は、良いテストの性質、実装の整理に耐えられる確認、内部の手順へ依存しすぎない考え方などです。掲載コードはC#です。Javaで最初のJUnitテストを動かす手順を求めている段階では、まず前のサンプルを使ってください。

送料の例で、内部にisFreeShippingというメソッドを切り出したとしても、同じ小計から同じ送料を返すなら、今のテストは書き換えずに実行できます。呼び出し元から見える約束を確認しているからです。反対に、内部メソッドの名前や呼出順まで無条件に固定すると、結果を変えない整理でもテストが失敗することがあります。

ここで得たいのは、テストの成功件数を増やすこと以上に、「何が変わったとき、このテストには失敗してほしいか」を説明する力です。送料の約束が崩れたら失敗してほしい。一方、約束を保ったまま内部を読みやすくしただけなら、同じ確認を使い続けたい。この区別が本の内容を自分のコードへ当てはめる入口です。

ただし、どんな場面でも戻り値だけ見れば十分という意味ではありません。結果として何を守るのかは、対象の仕様によって変わります。まず料金のような小さな処理で判断の基準を持ち、扱う仕事が広がったときに本へ戻る使い方ができます。2冊目を読了することを、次の学習へ進む条件にはしません。

Springへ進む目安は、選んだケースを自分で説明し、実行できること

次の項目を、この送料の処理を使って確認してください。書き方を調べながらでもかまいません。大切なのは、自分が何を確認しているのかを説明できることです。

1〜3で止まるなら、最初の本でケースの選び方を補います。4〜5で止まるなら、サンプルを実行し、1件ずつ入力と期待値を変えてみてください。「本をもう1冊買う」ことと「実際にテストを動かす」ことを、同じ不足として扱わないようにします。

確認できたら、Springで画面・Javaの処理・保存をつなぐ学習へ進めます。今回JUnitで守れたのは送料の計算です。Webアプリへ組み込んだ後は、画面から値が届くか、処理の結果を表示できるかという確認が増えます。小さな計算を先に自動で確かめられると、問題が計算にあるのか、そのつなぎ方にあるのかを分けやすくなります。

最初は1冊で、テストケースを選ぶ理由を身につける

Javaのテスト入門で最初に勧めるのは、『ソフトウェアテストの教科書[増補改訂 第2版]』です。正常な入力を1件試した後に、なぜ境界や範囲外も確かめるのか。その判断を、自分の処理へ当てはめるために使ってください。

ケースを選べるようになったら、JUnitで入力・期待値・実行結果を結び付けます。『単体テストの考え方/使い方』は、その後、変更に耐えられるテストを育てるための1冊です。まずは、10,000円の送料をなぜ0円と確かめるのかを、自分の言葉で説明するところから始めましょう。

前の記事はこちら|クラス設計Javaのクラス設計を学ぶ本|どこに何を書くかを考える 前の記事はこちら。値と処理をどのクラスに置き、変更する場所をどう決めるかを学びます。 記事を読む 次の記事はこちら|SpringSpring Boot初心者におすすめの本3冊|現場で必要な力を身につける 次の記事はこちら。小さな処理を自動で確かめられたら、画面・Java・保存をつなぐ機能を作ります。 記事を読む

-Java, 技術書・書評
-, , ,