Dev Study
← 解説「テストを書く理由 — 手動確認は改修のたびに破綻する」に戻る

サンプルコードで身につける: テストを書く理由 — 手動確認は改修のたびに破綻する

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1手動確認と自動テストの作業量の違い

手動確認のコストは画面数に比例して増え、改修のたびに全部やり直しになります。自動テストは一度書けば何度でも数秒で再実行できます。機能が増えるほど差は開く一方です。

// ── 手動確認だけの場合 ──
// 画面が3個のころ  : 改修のたびに3画面を触って確認(まだ耐えられる)
// 画面が30個のころ : 改修のたびに30画面 × 各パターン…(現実的に不可能)
// → 「たぶん大丈夫」で出荷して、別の画面が壊れていた、が起きる

// ── 自動テストがある場合 ──
// 改修する → ng test → 数秒後に全項目の結果が出る
//   ✓ 合計金額の計算  ✓ 一覧の表示  ✓ 検索の絞り込み …
// → 壊した箇所があれば、その場で ✗ と教えてくれる

2はじめてのテストコードを眺めてみる

テストコードは「〜したら〜になるはず」を書き下したものです。it に日本語で期待を書き、expect で答え合わせをします。この2行の組み合わせがすべての基本です。

// テストは「期待の文章 + 答え合わせ」でできている
it('1 + 2 は 3 になる', () => {
  expect(1 + 2).toBe(3);
});

// it(説明, () => { ... })  … 「〜のはず」という期待をひとつ書く
// expect(実際の値).toBe(期待値) … 一致しなければテスト失敗

// 実務のテストも、対象が関数や画面部品に変わるだけで形は同じ

33層のテストはそれぞれ何を見るのか

同じ「カート機能」でも、層によって検証する範囲が違います。単体テストは計算だけ、コンポーネントテストは表示だけ、E2Eは購入までの流れ全体を見ます。役割分担を掴みましょう。

// 「カート機能」を3つの層でテストすると…

// 1. 単体テスト: 合計金額の計算関数だけを検証(ミリ秒で終わる)
//    expect(totalPrice([100, 200])).toBe(300);

// 2. コンポーネントテスト: カート画面の表示を検証
//    「商品を2件渡したら、2行表示され合計が出る」

// 3. E2Eテスト: 実際のブラウザで通しの流れを検証
//    「商品を選ぶ → カートに入れる → 購入完了画面が出る」

// 下の層で細かく、上の層は代表的な流れだけ、が基本の配分

4テストがあるとリファクタリングが怖くなくなる

中身の実装を書き換えても、入出力が同じなら同じテストがそのまま通ります。テストが通る限り壊れていないと言えるので、コードの改善に安心して踏み切れます。これがテストの実務での最大の御利益です。

// 改修前: for文で書かれた合計計算
export function totalPrice(prices: number[]): number {
  let sum = 0;
  for (const p of prices) {
    sum += p;
  }
  return sum;
}

// 改修後: reduce にリファクタリング
// export function totalPrice(prices: number[]): number {
//   return prices.reduce((sum, p) => sum + p, 0);
// }

// どちらの実装でも、このテストはそのまま通る
it('価格の合計を返す', () => {
  expect(totalPrice([100, 250])).toBe(350);
});
// → テストが通る = 挙動を壊していない、と機械が保証してくれる

5まずは ng test を実行してみる

ng new で作ったプロジェクトには最初からテストが1本入っています。何も書かなくても ng test を実行すれば動くので、まず既存のテストが通る様子を見てみるのが第一歩です。

# ng new 直後のプロジェクトでもテストは実行できる
ng test

# 出力例(App コンポーネントのテストが最初から入っている):
#  ✓ App > should create the app
#
#  Tests  1 passed (1)

# この状態から、自分のテストを1本ずつ足していけばよい