1. はじめに
次に参画するプロジェクトでは、テストでJUnitを使っているそうです。
これまで自分がやってきた「テスト」では機能ごとに試験項目書を作り、期待値と実施結果の欄を埋め、エビデンスのスクリーンショットを貼り付けていました。
正直なところ、現時点でJUnitは一行も書いたことがありません。名前を知っている位の状態です。
参画前の予習として、JUnitとは何なのか、何のために使われるのか、周辺には何があるのかを調べました。実践の記録ではなく、調べて分かったことの整理として書きます。
2. この記事はこんな方におすすめ
- 手動テストや試験項目書ベースの現場にいて、これから単体テストコードに触れる方
- Javaは書けるが、JUnitは名前しか知らないという方
- チームがJUnitを使っていて、まず全体像だけでも掴んでおきたい方
- 「テストを自動化すると何が変わるのか」を、一度言葉にしておきたい方
未経験者が予習段階でまとめたものなので、実務上の勘所や現場ごとの運用には踏み込んでいません。そこはご容赦ください。
3. JUnitとは何か
JUnitは、Java向けのテスティングフレームワークです。主に単体テストで使われます。
ソースコードの中に「このメソッドにこの値を渡したら、この結果になるはず」という検証コードを書いておき、コマンドやIDEからまとめて実行して、合否まで自動で判定する。ざっくり言えばそういう仕組みです。
自分がこれまでやってきた項目書ベースでの試験と比較すると、違いがはっきりしました。
| 試験項目書ベース | JUnit | |
|---|---|---|
| 記述先 | Excel等のドキュメント | ソースコード(テストクラス) |
| 実行 | 人が手順どおり操作する | コマンド・IDE・CIが自動実行する |
| 合否判定 | 人が目視で期待値と照合する | アサーションが自動判定する |
| 主な守備範囲 | 画面・機能・業務シナリオ | クラス/メソッド単位のロジック |
ただし、どちらかが優れているという話ではありません。JUnitが扱えるのはロジック単位の検証で、画面遷移の自然さや業務シナリオとしての妥当性は、やはり人が見るしかない領域です。置き換えではなく、担当範囲が違うということでしょう。
4. 何が書けるのか
最小の例
class CalculatorTest {
@Test
void 足し算の結果が正しいこと() {
Calculator calc = new Calculator();
assertEquals(5, calc.add(2, 3));
}
}
@Test を付けたメソッドが、1つのテストケースにあたるそうです。assertEquals は引数を2つ取っていて、1つ目が期待値、2つ目が実際に動かした結果。この2つが一致しなければ失敗、とのこと。
項目書の「期待値」欄と「実施結果」欄が、そのまま引数になっているだけとも言えます。
テスト名は別途指定できる
レポートに表示される名前を、メソッド名とは別に指定できます。
class BankAccountTest {
@Test
@DisplayName("残高不足の場合、出金は失敗すること")
void withdrawFailsWhenBalanceIsInsufficient() {
// 検証処理
}
}
項目書の「試験項目名」にあたる部分です。メソッド名は規約に沿って英語で書きつつ、日本語の項目名を残せる。
入力値だけを変えたケースをまとめる(パラメータ化テスト)
同じ観点で入力値だけが違うケースは、1つのメソッドに畳み込めます。以下は「1〜100だけを受け付けるバリデータ」を想定した例です。
@ParameterizedTest
@ValueSource(ints = {-1, 0, 101})
void 範囲外の値は例外になること(int input) {
assertThrows(IllegalArgumentException.class, () -> validator.check(input));
}
項目書だと「同一観点・入力値違い」で行数が膨らんでいた、あの部分です。境界値のケースを1行ずつ書き起こしていた作業がこれに置き換わるのだとしたら、効いてくる場面は多そうだと思いました。
異常系も書ける
上の例に出てきた assertThrows は、「指定した例外が投げられること」自体を検証します。項目書の異常系区分が、手順ではなく動く形で残る、ということになります。
複数の検証をまとめて評価する
複数のアサーションを一度に評価して、失敗したものをまとめて報告する仕組みもあります。
assertAll(
() -> assertEquals("田中", user.getLastName()),
() -> assertEquals(30, user.getAge())
);
1つ目の失敗で止まらないので、原因の切り分けが早くなります。「1項目NGでも残りは流す」という、これまで人が都度判断していたことが、仕組みの側に置かれている感じです。
構造化とタグ付け
テストを入れ子にして「正常系」「異常系」といった階層で整理したり、ラベルを付けて実行対象を絞り込んだりもできるとのこと。項目書の章立てや「区分」欄に近い考え方だと思います。
5. 何が変わるのか
調べていて分かったのは、JUnitの意義は「テストが自動で動くこと」そのものより、そこから派生する話のほうが大きいということでした。
まず、リグレッションテストのコストが下がります。改修のたびに全項目を人手で流し直す必要がなくなり、実施回数に比例して増えていた工数が、実行時間だけに置き換わります。
次に、CI/CDに乗せられる点です。コードがプッシュされるたびに自動でテストを走らせ、失敗したものはマージさせない。品質の担保が、人の注意力ではなく仕組みの側に移るということです。
また、テストコードが仕様の手がかりになるという点も挙げられます。よく書かれたテストは「このクラスがどう振る舞うべきか」を示す資料になり、しかもドキュメントと違って、実装とズレたときに失敗という形で表に出てきます。
もっとも、テスト自体が間違っていたらその限りではないので、そこは過信しないほうがよさそうです。
6. 周辺のツール
JUnit単体で完結するわけではなく、実際には他のツールと組み合わせて使われることが多いようです。ここでは名前と役割だけ控えておきます。
JUnitと同じ位置づけのもの
- TestNG … JUnitと並ぶJava向けのテスティングフレームワーク
- Spock … Groovy製。Given-When-Thenの構文で書ける
組み合わせて使われるもの
| ツール | 役割 | レイヤー |
|---|---|---|
| Mockito | DBや外部APIなど、単体テストで実際には呼びたくない依存を差し替える | 単体 |
| AssertJ | アサーションを流暢な記法で書けるようにする | 単体 |
| JaCoCo | カバレッジ計測 | 単体 |
| Spring Boot Test | Spring環境でのテスト支援 | 結合 |
| Testcontainers | Dockerで実際のDBなどを起動し、本番に近い状態でテストする | 結合 |
| Selenium / Playwright | ブラウザ操作によるE2Eテスト | E2E |
7. 生まれた疑問
一通り調べたところで、いくつか疑問も出てきました。
- テストコードの品質は、誰がどうレビューするのか
- ドキュメントとしての試験項目書は、別レイヤーのテストとして残すのか
- どこまでテストを書くかの線引きは、どこに置くのか
いずれもツールの使い方ではなく、チームの方針の話です。プロジェクトごとに答えが違う部分で、事前に正解を用意しておく類のものでもないでしょう。参画後に、現場の運用として意識して見ていきたいところです。
8. まとめ
- JUnitは、テストの記述・実行・判定をコードとして自動化するJavaのフレームワーク。試験項目書の「期待値」と「実施結果」をコードに落とし込んだもの、という理解が入口としては分かりやすいと思います。
- 解決しているのは「テストの実施」ではなく「テストの再実施」。1回流すだけなら手動でもコストは大きく変わりません。差がつくのは2回目以降です。
- 手動テストの置き換えではなく、役割分担。ロジックはJUnitに任せ、人は業務シナリオの妥当性という、人にしか判断できない領域に集中できます。
- 周辺ツールとの組み合わせで使われるので、名前と役割だけでも先に押さえておくと、現場の会話には入りやすくなりそうです。