「書いたテスト、時間が経つと読めなくなる問題」──経験ありませんか?
この記事では、個人開発で取り組んだ Jestによる単体テストのリファクタリング体験をまとめます。 テストは壊れてはいないけど、読みにくい・書きにくい・再利用できない… そんな状態を少しずつ改善していった過程と、学びをまとめておきます。
前提と開発環境
今回の取り組みは以下のような環境で行いました:
- バックエンド:Node.js(Express)
- テストフレームワーク:Jest
- 設計思想:クリーンアーキテクチャを意識
- 対象のテスト:単体テスト(ユニットテスト)
層の構成(ざっくり)
src/
├── domain/ # エンティティ / リポジトリ / ValueObject
├── application/ # ユースケース / サービス / ファクトリー
├── infrastructure/ # DB・HTTP・各種外部接続
├── interface/ # コントローラーなど
テスト対象はこの各層に分離されており、疎結合な設計のおかげでテスト自体は書きやすい構造でした。
実際にリファクタリングしたポイント
1. テストデータの共通化と管理
- 重複していた モックオブジェクトを1箇所に集約
- 再利用可能な テストデータファクトリ関数を作成
// 例)Userエンティティのテスト用ファクトリ
export const createTestUser = (overrides = {}) => {
return new User({
id: 999999,
name: 'テストユーザー',
email: 'test@example.com',
...overrides,
});
};
得られた効果: テストごとにベタ書きしていたデータの重複が解消され、変更にも強くなりました。
2. テストヘルパー関数の導入
- Expressのコントローラーに対して
mockRequest()
やmockResponse()
を定義 - 例外発生時の
AppError
の検証も共通関数に抽出
export const expectAppError = (fn: () => any, expectedCode: string) => {
try {
fn();
} catch (e) {
expect(e).toBeInstanceOf(AppError);
expect(e.code).toBe(expectedCode);
}
};
得られた効果: 各テストファイルに散らばっていた同様の処理を共通化し、見通しがよく、意図が明確なテストになりました。
3. テスト構造と命名規則の統一
describe
/it
の階層を明確にし、一貫した構造へ- テスト名は「状態+動作+期待される結果」を意識
describe('TaskUseCase', () => {
describe('createTask', () => {
it('正常系: 有効なパラメータでタスクが作成される', () => {
// ...
});
it('異常系: titleが空ならエラーになる', () => {
// ...
});
});
});
得られた効果: 「何をテストしているのか」が読みやすくなり、後から読む人の負担が減りました。
4. AAAパターンの明確化(Arrange-Act-Assert)
- 各テストを
準備 → 実行 → 検証
の順で統一 - コード内にコメントを入れてセクションを明示
it('正常系: タスクを作成できる', async () => {
// Arrange: テストデータの準備
const input = createTestInput();
// Act: テスト対象の呼び出し
const result = await usecase.createTask(input);
// Assert: 結果の検証
expect(result).toEqual(expected);
});
得られた効果: レビュー時にも読みやすく、バグの原因特定がしやすいテスト構造になりました。
やってみて学んだこと・よかったこと
- すごく読みやすくなった。テスト実行時に何をテストしているのかすごくわかりやすくなった。
- AAAパターンや命名規則など、テストの設計原則は思った以上に効果がある
まとめ
- テストデータの共通化とヘルパー関数の抽出はコスパが高い改善だった
- 構造化・命名統一・AAAパターンの徹底で、読みやすく保守性の高いテストに
- テストがきれいになると、安心してリファクタや機能追加ができる土台になる