Jest / React Testing Library / Playwright を活用した Webアプリの自動テスト導入支援事例

Jest / React Testing Library / Playwright を活用した Webアプリの自動テスト導入支援事例
2024/10/11に公開

はじめに

Webアプリケーション開発において、自動テストの導入は「不具合を減らす」ためだけでなく、「開発のスピードと安心感を両立するための仕組み」として重要です。

しかし、実際にはこういった悩みを抱える現場も多くあります:

  • どこからテストを書けばいいかわからない
  • テストが動かなくなったときのメンテナンスが不安
  • 自動テストを導入したいが、開発で手一杯

本記事では、こうした悩みを持つBtoCサービス運営企業様に対して、弊社がどのように自動テストの構築・導入・定着までを支援したか、そのプロセスを具体的にご紹介します。


本記事では、自動テストが十分に整備されていないWebアプリケーション開発現場に対して、弊社がどのようにテスト基盤を構築・運用支援していったかをご紹介します。

ご依頼いただいたのは、BtoC向けのWebサービスを自社開発・運営しているお客様です。機能改善要望が非常に多く、エンジニアの手が足りない状況で、自動テストの整備が後回しになっていました。その結果、手動テストに頼ったリリースフローとなり、月に2回しか本番リリースができず、ユーザーの声に迅速に応えることが難しい状態でした。

テスト不足に起因する不具合や手戻りも多く、開発効率と品質の両面で課題を抱えていました。

支援内容

弊社でテストコードを実装する支援をさせて頂きました。

テスト観点の洗い出し

自動テストは、すべてを網羅する必要はありません。「どこをテストすれば安全か」「どこが壊れやすいか」といった観点の整理が最初の一歩です。

特に、以下のような判断軸が有効です。

  • 変更が頻繁な箇所か? → テストがあることで安心して修正できる
  • バグが致命的な影響を及ぼす箇所か? → 信頼性担保のため優先的にテストすべき
  • 外部との連携があるか? → モックや分離の工夫が必要になる
  • UI上の操作が複雑か? → 自動テストによりヒューマンエラーを防げる

こうした軸をもとに、必要な範囲を絞ってテストを整備することで、無理なく・効果的なテスト戦略を実現します。

自動テストは、すべてを網羅する必要はありません。「どこをテストすれば安全か」「どこが壊れやすいか」といった観点の整理が最初の一歩です。

まずはテストの全体像を整理するために、以下の2軸でテスト観点を洗い出しました。

  • ヒアリングベースの分析:過去に発生した不具合や「ここは壊れると影響が大きい」という現場の実感をもとに重点領域を特定
  • マニュアル・コードベースの調査:利用者マニュアルやソースコードを読み込み、仕様と実装から必要な観点を逆算

この過程で、お客様にとって「守るべき機能」がどこかを共有認識にできたことが、後工程の優先順位づけに活きました。

テストの実装方針検討

テストコードの命名規則について

テストコードの読みやすさと保守性を高めるために、命名には以下のようなルールを意識しました。

  • 何をテストしているか明確に:関数名・振る舞い・条件・期待結果を含める(例:shouldShowErrorMessageWhenEmailIsInvalid
  • ファイル構成:対象ファイルと同階層に .test.tsx / .spec.ts 形式で配置
  • describe + it 構文で意図を分かりやすく
RegisterForm.test.tsx
describe('RegisterForm', () => {
  it('should show error message when email is invalid', async () => {
    ...
  })
})

モック活用の考え方

テスト対象が外部依存(API通信、日時、ランダム値など)を含む場合、安定性と速度の観点からモックが重要です。

  • ユニットテストでは基本的にモックを積極活用:対象のロジックのみを純粋に検証できる
  • E2Eテストでは実際のバックエンドとつなぐ or 本番に近いダミー環境と連携:より実態に近い挙動を再現

活用例:jest.mock() を使ってAPIモジュールの戻り値を差し替えるなど、目的に応じた設計を行いました。

テストにはさまざまなレベル(単体・結合・E2Eなど)があり、それぞれに適した目的と技術があります。
ここでは、自動テスト導入を初めて検討する方でもイメージしやすいように、以下のように分けて方針を整理しました。

  • 単体テスト:関数やUIコンポーネントなど、最小単位のロジックが正しく動作するかを確認するテスト。
  • シナリオテスト(E2Eテスト):ユーザーがブラウザで操作する流れをまるごと再現する、画面操作の自動化テスト。

両者は役割が異なるため、バグの検出範囲や保守性にも違いがあります。
単体テストで細かなロジックの精度を高めつつ、シナリオテストでユーザー目線の動作確認を行うことで、バランスのよいテスト体制を実現します。

整理した観点に対して、以下のようにテストの粒度・技術選定を行いました。

単体テスト

  • 使用技術:Jest + React Testing Library
  • 対象領域:入力バリデーションやフォーム処理など、ロジックが集中するUIコンポーネント

例)UIコンポーネント

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import RegisterForm from './RegisterForm'

test('フォームが正しくバリデーションされる', async () => {
  render(<RegisterForm />)
  await userEvent.type(screen.getByLabelText('メールアドレス'), 'invalid-email')
  await userEvent.click(screen.getByRole('button', { name: '登録' }))
  expect(screen.getByText('正しいメールアドレスを入力してください')).toBeInTheDocument()
})

例)関数のテスト(純粋なロジック)

utils/calculateTax.ts
export const calculateTax = (price: number, rate: number): number => {
  return Math.round(price * rate)
}
utils/calculateTax.test.ts
import { calculateTax } from './calculateTax'

describe('calculateTax', () => {
  it('税率10%で税込価格を計算できる', () => {
    expect(calculateTax(1000, 1.1)).toBe(1100)
  })

  it('税率8%で税込価格を計算できる', () => {
    expect(calculateTax(500, 1.08)).toBe(540)
  })
})

例)fetch を含む非同期処理のテスト(API呼び出し)

lib/api.ts
export const fetchUser = async (userId: string) => {
  const res = await fetch(`/api/users/${userId}`)
  if (!res.ok) throw new Error('ユーザー取得に失敗しました')
  return res.json()
}
lib/api.test.ts
import { fetchUser } from './api'

describe('fetchUser', () => {
  beforeEach(() => {
    global.fetch = vi.fn()
  })

  it('ユーザー情報を正常に取得できる', async () => {
    const mockUser = { id: '1', name: 'Taro' }
    ;(fetch as jest.Mock).mockResolvedValue({
      ok: true,
      json: async () => mockUser,
    })

    const result = await fetchUser('1')
    expect(result).toEqual(mockUser)
  })

  it('レスポンスがエラーだった場合に例外を投げる', async () => {
    ;(fetch as jest.Mock).mockResolvedValue({ ok: false })

    await expect(fetchUser('1')).rejects.toThrow('ユーザー取得に失敗しました')
  })
})

シナリオテスト(E2E)

  • 使用技術:Playwright
  • 対象領域:画面遷移やボタン操作、フォーム送信などユーザー体験に直結する操作
  • 補足対応:テスト用バックエンド環境の用意、およびAllureなどのレポーティングツール導入による可視化
login.spec.ts
import { test, expect } from '@playwright/test'

test('ログイン成功後にダッシュボードへ遷移する', async ({ page }) => {
  await page.goto('https://example.com/login')
  await page.fill('input[name="email"]', 'user@example.com')
  await page.fill('input[name="password"]', 'password123')
  await page.click('button[type="submit"]')
  await expect(page).toHaveURL('https://example.com/dashboard')
  await expect(page.getByText('ようこそ')).toBeVisible()
})

実装優先順位と進行体制

自動テスト導入を成功させるには、「どこから始めるか」「どのタイミングでCIに組み込むか」も重要です。

  • スモールスタートの考え方

    • 初期はユニットテストから着手し、1画面・1コンポーネントから始めるのが効果的です
    • テスト対象は「よく使われるが壊れやすい」「複雑でミスが起きやすい」機能から選定
    • まずはローカルでテストが動く状態にし、チーム内でのレビューや習熟を進めることが最優先です
  • CI(継続的インテグレーション)への組み込みタイミング

    • 単体テストが最低限整備できた段階で、CIに自動テストの実行を組み込みます
    • Pull Request 時にテストが通っていることをマージ条件に設定するなど、開発フローとの接続を意識
    • E2Eテストはまず staging 環境での定期実行から始め、必要に応じて本番前チェックにも拡張可能です

  • テスト観点のリスクレベルに応じて、単体テスト→E2Eテストの順に優先順位を設定
  • 実装は段階的に行い、お客様側の開発者にもレビュー・相談に入っていただきながらスキルトランスファーを同時並行で実施
  • 最終的には、自社内でテストコードを自律的に運用・メンテナンスできる状態を目指し、ドキュメントも整備

運用フェーズの対応と改善

自動テストを導入すればすべてが解決するわけではありません。
導入後には、仕様変更に伴うテストの更新や、テストそのものが壊れてしまう場面も想定されます。

テスト導入後、実際に以下のような課題と向き合いました。

  • テスト導入後、仕様変更に伴うテストの失敗が徐々に増加 → 定期的に見直しの場を設け、対応方針を議論
  • 安定性重視の方針へ転換
    • 不安定なE2Eをユニットテストへ置き換え
    • 外部APIなどはモックで差し替え

こうした工夫により、現場にとって扱いやすく、維持コストが現実的なテスト設計に育てていきました。

自動テスト導入のメリット

  • リリース頻度が向上:月2回 → 毎日デプロイ可能な体制へ
  • 初期実装負荷の軽減:弊社が実装初期を担当することで、既存エンジニアは機能開発に集中
  • テスト文化の定着:観点整理・技術選定を通じて、チーム内にテストを書く習慣と知識が定着
  • 品質向上:事前に不具合を潰せるようになり、ユーザー体験の安定にも寄与

スケジュール・支援期間

最初の1ヶ月でシステム要件の理解、テスト観点の洗い出し、テストの実装方針の策定及びご依頼主様との調整。

その後、半年程度をかけて自動テストの実装を行いました。ある程度、自動テストが整った段階で日々の開発業務に自動テストを組み込みました。

今回のご依頼主様からは別プロジェクトでの開発依頼も頂いておりましたので、そちらを対応しつつ、自動テストの稼働状況の監視を半年程度行いました。

今後の展開

今回の支援を通じて、自動テストの構築と導入フェーズを完了し、継続運用の基盤を整備することができました。

今後は以下のような取り組みを視野に入れています。

  • E2Eテストの安定性向上と再編成
    • より堅牢なE2E体制に向け、共通操作のユーティリティ化やログ収集基盤との連携を検討
  • テストカバレッジの可視化と拡張
    • 未網羅のロジックやクリティカルパスを特定し、カバレッジ計測ツールと組み合わせて継続的に拡張
  • 仕様変更へのテスト更新体制の確立
    • テスト観点の見直しや再設計を開発サイクルに組み込み、属人化しない運用を実現
  • テスト教育と社内展開の促進
    • 開発メンバー以外にもレビューや観点出しに関わってもらえるよう、軽量なワークショップやルール共有を推進

これらを段階的に進めることで、単なるテスト導入にとどまらず、チーム文化として品質意識を根付かせる支援へとつなげていく方針です。

まとめ

自動テストは、単なる品質担保のためのコストではなく、「開発の自由度を広げるための基盤」です。

今回の支援では、観点整理からツール選定・実装・運用フェーズの工夫までを一貫して伴走することで、「現場に合った形で、継続可能な自動テスト」を実現しました。

今後も、開発チームの成長フェーズや制約条件に寄り添いながら、開発と運用がバランス良く回るテスト戦略を支援していきます。

Xでシェア
Facebookでシェア
LinkedInでシェア

関連する技術ブログ

お問い合わせ

開発・セキュリティ・品質を包括的に見直し、
持続可能なプロダクト開発をサポートします。

お問い合わせ