← トップページに戻る

TDD実践ガイド

🧭第13章 実装を導くテスト(TODOリスト作成)

Kent Beckの「実装を導くテスト」の概念をTODOアプリの新機能追加に適用します。 新しいTODOアイテムを追加する機能を実装しながら、実装を導くテストの手法を学びます。

重複をすべて除去するまでは、まだ項目を「済」にできない。コードに重複はないが、データに重複がある。 それは、仮実装で返している部分だ。
— Kent Beck『テスト駆動開発』第13章より

📋TODOリスト

  • • TODOリストコンポーネントの作成
  • TODOアイテムの追加機能
  • • 一覧表示機能

🎯仮実装から本実装へのアプローチ

これまでは仮実装を本実装に導く方法として、単にベタ書きの値を変数に置き換える程度でした。 今回はTODOアイテムの追加という、より複雑な実装に挑戦します。

🔴1. レッド:失敗するテストを書く(TODOアイテム追加フォーム)

まず、新しいTODOアイテムを追加するためのフォームコンポーネントのテストを作成します。components/TodoForm.test.tsx を作成しましょう。

components/TodoForm.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import TodoForm from './TodoForm'; // まだ存在しない

describe('TodoForm コンポーネントのテスト', () => {
  it('入力フィールドと送信ボタンが表示されること', () => {
    render(<TodoForm onSubmit={jest.fn()} />);
    
    // テキスト入力フィールドの存在確認
    expect(screen.getByRole('textbox')).toBeInTheDocument();
    expect(screen.getByDisplayValue('')).toBeInTheDocument();
    
    // 送信ボタンの存在確認
    expect(screen.getByRole('button', { name: /todo を追加/i })).toBeInTheDocument();
  });
});

このテストは失敗します(レッド)。TodoForm.tsx が存在しないためです。

🟢2. グリーン:テストをパスさせる最小限のコードを書く

components/TodoForm.tsx を作成し、 テストをパスさせる最小限のコードを書きます。

components/TodoForm.tsx
"use client";

import React from 'react';

interface TodoFormProps {
  onSubmit: (text: string) => void;
}

export default function TodoForm({ onSubmit }: TodoFormProps) {
  return (
    <form>
      <input type="text" defaultValue="" />
      <button type="submit">TODO を追加</button>
    </form>
  );
}

これは仮実装です。フォームは表示されますが、実際の機能はまだ実装されていません。

🧪3. 実装を導くテストの追加

次に、フォームの実際の動作をテストする新しいテストを追加します。

components/TodoForm.test.tsx
it('フォーム送信時に入力値でonSubmitが呼ばれること', () => {
  const mockOnSubmit = jest.fn();
  render(<TodoForm onSubmit={mockOnSubmit} />);
  
  const input = screen.getByRole('textbox');
  const submitButton = screen.getByRole('button', { name: /todo を追加/i });
  
  // テキストを入力
  fireEvent.change(input, { target: { value: 'Next.js のテストを学ぶ' } });
  
  // フォームを送信
  fireEvent.click(submitButton);
  
  // onSubmitが正しい値で呼ばれることを確認
  expect(mockOnSubmit).toHaveBeenCalledWith('Next.js のテストを学ぶ');
  expect(mockOnSubmit).toHaveBeenCalledTimes(1);
});

🔵4. 実装を導くステップでの本実装

これらのテストを通すために、実際の機能を実装します。

components/TodoForm.tsx
"use client";

import React, { useState } from 'react';

interface TodoFormProps {
  onSubmit: (text: string) => void;
}

export default function TodoForm({ onSubmit }: TodoFormProps) {
  const [inputValue, setInputValue] = useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (inputValue.trim()) {
      onSubmit(inputValue.trim());
      setInputValue(''); // 送信後にクリア
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="新しいTODOを入力..."
      />
      <button type="submit">TODO を追加</button>
    </form>
  );
}

🔗5. TODOリストコンポーネントとの統合

次に、TodoForm を使用するTodoList コンポーネントのテストを作成します。

components/TodoList.test.tsx
it('フォーム送信時に新しいTODOが追加されること', () => {
  render(<TodoList />);
  
  const input = screen.getByRole('textbox');
  const submitButton = screen.getByRole('button', { name: /todo を追加/i });
  
  // 新しいTODOを追加
  fireEvent.change(input, { target: { value: '新しいTODOアイテム' } });
  fireEvent.click(submitButton);
  
  // 追加されたTODOが表示されることを確認
  expect(screen.getByText(/新しいTODOアイテム/i)).toBeInTheDocument();
});

💡実装を導くテストの考察

この章では、Kent Beckの「実装を導くテスト」の手法を実践しました:

  • 段階的実装: 最初は仮実装から始めて、テストに導かれながら本実装へ
  • テスト主導の設計: テストが実装の方向性を決定
  • 機能の分割: 大きな機能を小さなテスト可能な単位に分割
  • エッジケースの考慮: 実装後に追加のテストでエラーハンドリングを改善

TODOリストの更新

  • TODOリストコンポーネントの作成
  • TODOアイテムの追加機能
  • 一覧表示機能

🚀コミットとCIの確認

git add . git commit -m 'feat: Implement TodoForm and TodoList with TDD approach (Chapter 13)' git push

次の章では、学習用テストと回帰テストの実践を通じて、TODOアイテムの変更処理を実装します。