Javaの単体テストはWebアプリでも必要?画面・Controller・Service・DAOの役割で考える

本記事は、Schooの「Java入門 中級【2026年版】」第3回「単体テストによるビジネスロジック検証」を学ぶ前後に、Webアプリケーションでも単体テストを行うのかを整理したい方向けの記事です。
単体テストというと、計算クラスやコンソールアプリだけの話に見えるかもしれません。しかし、Webアプリケーションでも単体テストは行います。Webアプリでは、画面、Controller、Service、DAOのように層を分け、それぞれの役割に合わせて確認する内容を考えます。
Schoo コースページ:Java入門 中級【2026年版】
Webアプリでも単体テストは行う
Webアプリは、画面、Controller、Service、DAO、データベースなど、複数の部品がつながって動きます。単体テストでは、その全体をまとめて動かすのではなく、確認したい部品を取り出してテストします。
画面全体を動かすテストとは役割が違う
単体テストは、ブラウザでボタンを押して画面全体の動きを確認するテストとは役割が違います。画面から確認するテストは、入力から表示までの流れを見るのに向いています。一方、単体テストは、特定の処理が仕様どおりに判断しているかを細かく確認するのに向いています。
Webアプリを層に分けてテスト対象を考える
Webアプリのテストを考えるときは、まず処理を層に分けます。画面、Controller、Service、DAOは、それぞれ担当していることが違います。担当が違うため、テストで確認する内容も変わります。
画面:入力と表示を確認する層
画面は、利用者が値を入力したり、処理結果を見たりする場所です。画面の確認では、入力欄が表示されているか、ボタンを押せるか、エラーメッセージが見えるかなどを確認します。
ただし、画面からすべての境界値や異常値を確認しようとすると手間が大きくなります。たとえば送料計算で 4999、5000、5001 を毎回ブラウザから入力して確認するのは効率がよくありません。
Controller:画面から来た値をServiceへ渡す層
Controllerは、画面やリクエストから届いた値を受け取り、必要な形にしてServiceへ渡す層です。Controllerの確認では、「必要な値を受け取れているか」「正しいServiceを呼び出しているか」「結果に応じてどの画面へ返すか」を見ます。
class OrderController {
private final OrderService orderService;
OrderController(OrderService orderService) {
this.orderService = orderService;
}
String confirm(int amount) {
int fee = orderService.calculateShippingFee(amount);
return "送料は" + fee + "円です";
}
}
このControllerの中心は、送料を計算することではありません。画面から来た amount をServiceへ渡し、Serviceから返ってきた結果を画面向けの文字列にすることです。
Service:ビジネスロジックを確認する層
Serviceは、業務上の判断や計算を担当する層です。送料をいくらにするか、会員ランクで割引率をどう変えるか、在庫不足のときに注文できないようにするか、といった判断をここに置きます。
class OrderService {
int calculateShippingFee(int amount) {
if (amount < 0) {
throw new IllegalArgumentException("amount must be 0 or more");
}
if (amount >= 5000) {
return 0;
}
return 500;
}
}
このServiceは、購入金額に応じて送料を決めています。これは画面表示ではなく、業務上のルールに基づく判断です。単体テストでは、この判断が仕様どおりかを直接確認します。
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
class OrderServiceTest {
@Test
void testCalculateShippingFee() {
OrderService service = new OrderService();
assertEquals(500, service.calculateShippingFee(3000));
assertEquals(500, service.calculateShippingFee(4999));
assertEquals(0, service.calculateShippingFee(5000));
assertEquals(0, service.calculateShippingFee(10000));
}
@Test
void testCalculateShippingFeeThrowsExceptionWhenAmountIsNegative() {
OrderService service = new OrderService();
assertThrows(
IllegalArgumentException.class,
() -> service.calculateShippingFee(-1)
);
}
}
Serviceの単体テストでは、画面やControllerを通さず、メソッドに値を渡して戻り値や例外を確認します。これにより、業務ルールの境界値や異常値を細かく確認できます。
実際の要件からテストケースを作る
WebアプリのServiceをテストするときは、実際の要件をテストケースに変換します。ここでは、在庫数と注文数から注文できるかを判定するServiceを例にします。
在庫数が注文数以上なら注文できる。在庫数が注文数より少ない場合は注文できない。注文数が0以下、または在庫数が0未満の場合は例外とする。
この要件では、在庫数と注文数の関係が重要です。特に、在庫数と注文数が同じ場合に注文できるか、注文数が在庫数を1つ超えた場合に注文できないかを確認します。
class StockService {
boolean canOrder(int stock, int quantity) {
if (stock < 0) {
throw new IllegalArgumentException("stock must be 0 or more");
}
if (quantity <= 0) {
throw new IllegalArgumentException("quantity must be 1 or more");
}
return stock >= quantity;
}
}
このServiceに対して、テストケース仕様書の内容をJUnitコードにすると次のようになります。
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class StockServiceTest {
@Test
void testCanOrderWhenStockIsEnough() {
StockService service = new StockService();
assertTrue(service.canOrder(10, 3)); // TC-001
assertTrue(service.canOrder(10, 10)); // TC-002
}
@Test
void testCannotOrderWhenQuantityIsGreaterThanStock() {
StockService service = new StockService();
assertFalse(service.canOrder(10, 11)); // TC-003
}
@Test
void testCanOrderThrowsExceptionWhenValueIsInvalid() {
StockService service = new StockService();
assertThrows(IllegalArgumentException.class, () -> service.canOrder(10, 0)); // TC-004
assertThrows(IllegalArgumentException.class, () -> service.canOrder(-1, 1)); // TC-005
}
}
このように、要件、テストケース、テストコードを対応させると、何のためにそのテストを書いているのかが分かりやすくなります。
DAO:データベースとのやり取りを確認する層
DAOは、データベースとのやり取りを担当する層です。SQLを実行してデータを取得する、登録する、更新する、削除するといった処理を扱います。
class OrderDAO {
Order findById(int id) {
// SQLを実行して、注文データを取得する
}
}
DAOの確認では、送料計算のような業務ルールではなく、「指定したIDのデータを取得できるか」「SQLの条件が正しいか」「取得結果をオブジェクトに詰められているか」を見ます。
つまり、Webアプリの単体テストでは、層ごとに確認する対象を分けます。Serviceではビジネスロジック、ControllerではServiceへのつなぎ方、DAOではデータベース操作を確認します。
確認問題
次の処理のうち、Serviceの単体テストで確認しやすいものを考えてください。
- ログイン画面のボタンの色
- 購入金額に応じた送料計算
- 会員ランクによる割引率の判定
- 検索結果が画面に何件表示されるか
- 在庫数が足りない場合に注文できないようにする判定
演習後の確認:こちらのページを使って、自分で考えた内容を見直せます。
まとめ
- Webアプリでも単体テストは行う
- 画面、Controller、Service、DAOは層ごとに役割が違う
- Controllerは、リクエストを受け取りServiceへつなぐ層
- Serviceは、ビジネスロジックを置く層
- DAOは、データベースとのやり取りを担当する層