カテゴリ: TypeScript 更新日: 2026/01/28

TypeScriptでモジュールのテストを簡潔に書くための構造設計!初心者でもわかる実践ガイド

TypeScriptでモジュールのテストを簡潔に書くための構造設計
TypeScriptでモジュールのテストを簡潔に書くための構造設計

先生と生徒の会話形式で理解しよう

生徒

「TypeScriptでモジュールのテストを書きたいんですけど、どうすればいいですか?」

先生

「モジュールのテストを簡潔に書くには、モジュールの設計段階からテストしやすい構造にしておくことが大切なんです。」

生徒

「テストしやすい構造って、具体的にどういうことですか?」

先生

「それでは、実際のコード例を見ながら、テストしやすいモジュールの設計方法を学んでいきましょう!」

1. モジュールのテストとは?

1. モジュールのテストとは?
1. モジュールのテストとは?

モジュールのテストとは、作成したTypeScriptのモジュールが正しく動作するかを確認する作業のことです。例えば、お菓子を作る工場があるとします。工場で作ったお菓子が美味しいか、形が綺麗かを確認するのが品質チェックですよね。プログラムも同じで、作った機能が正しく動くかをテストで確認します。

TypeScriptでは、モジュールという単位でコードを分けて管理することができます。モジュールは、関連する機能をまとめた箱のようなものです。この箱の中身がちゃんと動くかを確認するのがモジュールのテストです。

テストしやすい設計とは、後からテストを書く時に苦労しないように、最初からモジュールを整理整頓して作ることを指します。これにより、バグを見つけやすくなり、コードの品質が向上します。

2. テストしやすいモジュールの基本原則

2. テストしやすいモジュールの基本原則
2. テストしやすいモジュールの基本原則

テストしやすいモジュールを作るには、いくつかの重要な原則があります。これらを理解することで、TypeScriptプログラミングの質が大きく向上します。

単一責任の原則

一つのモジュールは一つの役割だけを持つべきです。例えば、料理で考えると、包丁は切る専用、鍋は煮る専用というように、それぞれの道具には専門の役割があります。モジュールも同じで、ユーザー情報を管理するモジュールなら、ユーザー情報のことだけを扱うようにします。

依存関係を明確にする

モジュールが他のモジュールに依存している場合、その関係を明確にすることが大切です。依存関係とは、あるモジュールが動くために別のモジュールが必要という関係のことです。これを明確にすることで、テストを書く時にどのモジュールを準備すればいいかがわかりやすくなります。

3. 実践:テストしやすいモジュールの設計例

3. 実践:テストしやすいモジュールの設計例
3. 実践:テストしやすいモジュールの設計例

ここでは、ユーザー管理システムを例に、テストしやすいモジュールの設計方法を見ていきます。

悪い例:テストしにくいモジュール

まず、テストしにくいモジュールの例を見てみましょう。以下のコードは、複数の責任が一つのモジュールに混在しています。


// userService.ts(テストしにくい例)
export class UserService {
    saveUser(name: string, email: string) {
        // データベースに直接アクセス
        const db = new Database();
        db.connect();
        db.insert({ name, email });
        
        // メール送信も同じ関数で実行
        const mailer = new EmailSender();
        mailer.send(email, "登録完了");
    }
}

この例では、ユーザー保存、データベース操作、メール送信が全て一つの関数に混在しています。これではテストを書く時に、データベースやメール送信システムも一緒に準備しなければならず、大変面倒です。

良い例:テストしやすいモジュール

次に、同じ機能をテストしやすく設計した例を見てみます。


// interfaces.ts
export interface IDatabase {
    save(data: any): void;
}

export interface IEmailSender {
    send(to: string, message: string): void;
}

// userService.ts(テストしやすい例)
export class UserService {
    constructor(
        private database: IDatabase,
        private emailSender: IEmailSender
    ) {}
    
    saveUser(name: string, email: string) {
        this.database.save({ name, email });
        this.emailSender.send(email, "登録完了");
    }
}

この設計では、データベースとメール送信機能を外部から渡すようにしています。インターフェースという型定義を使うことで、実際の実装を差し替えられるようになっています。インターフェースとは、「このような機能を持っていますよ」という約束事のようなものです。

4. テスト用のモジュールを作成する

4. テスト用のモジュールを作成する
4. テスト用のモジュールを作成する

テストしやすい設計ができたら、実際にテストを書いてみます。テストでは、本物のデータベースやメール送信システムの代わりに、テスト用の簡易版を使います。


// userService.test.ts
class MockDatabase implements IDatabase {
    savedData: any = null;
    
    save(data: any): void {
        this.savedData = data;
    }
}

class MockEmailSender implements IEmailSender {
    sentEmail: { to: string; message: string } | null = null;
    
    send(to: string, message: string): void {
        this.sentEmail = { to, message };
    }
}

// テストコード
const mockDb = new MockDatabase();
const mockEmail = new MockEmailSender();
const userService = new UserService(mockDb, mockEmail);

userService.saveUser("田中太郎", "tanaka@example.com");

console.log(mockDb.savedData);
console.log(mockEmail.sentEmail);
Mock(モック)とは?
Mockとは、テスト用に作る「偽物」のことです。本物のデータベースを使うと時間がかかったり、設定が大変だったりするので、テストでは簡単に動く偽物を使います。映画の撮影で使う小道具のようなものだと考えてください。

実行結果は以下のようになります。


{ name: '田中太郎', email: 'tanaka@example.com' }
{ to: 'tanaka@example.com', message: '登録完了' }

このように、本物のデータベースやメール送信システムがなくても、モジュールの動作を確認できるようになりました。

5. モジュール分割のコツ

5. モジュール分割のコツ
5. モジュール分割のコツ

テストしやすいモジュール設計のために、適切にモジュールを分割することが重要です。ここでは、実践的な分割方法を紹介します。

機能ごとにファイルを分ける

大きなプログラムを書く時は、機能ごとにファイルを分けると管理しやすくなります。例えば、以下のようなファイル構成が考えられます。


src/
  ├─ models/
  │   └─ User.ts(ユーザーのデータ構造)
  ├─ services/
  │   └─ UserService.ts(ユーザー管理の処理)
  ├─ database/
  │   └─ Database.ts(データベース操作)
  └─ utils/
      └─ EmailSender.ts(メール送信)

エクスポートとインポートを整理する

TypeScriptでは、exportでモジュールの機能を外部に公開し、importで他のモジュールから読み込みます。必要なものだけをエクスポートすることで、モジュールの境界が明確になります。


// models/User.ts
export interface User {
    id: number;
    name: string;
    email: string;
}

export function createUser(name: string, email: string): User {
    return {
        id: Math.floor(Math.random() * 10000),
        name,
        email
    };
}

// services/UserService.ts
import { User, createUser } from '../models/User';
import { IDatabase } from '../interfaces/IDatabase';

export class UserService {
    constructor(private database: IDatabase) {}
    
    registerUser(name: string, email: string): User {
        const user = createUser(name, email);
        this.database.save(user);
        return user;
    }
}

6. テストカバレッジを意識する

6. テストカバレッジを意識する
6. テストカバレッジを意識する

テストカバレッジとは、プログラムのどれくらいの部分がテストされているかを示す指標です。カバレッジが高いほど、テストで確認できている範囲が広いということになります。

テストしやすいモジュール設計をすることで、自然とテストカバレッジを高めやすくなります。各モジュールが独立しているため、それぞれのモジュールに対して個別にテストを書くことができるからです。

注意点
テストカバレッジが高いからといって、必ずしもバグがないわけではありません。大切なのは、重要な機能や複雑な処理に対して、適切なテストを書くことです。

7. 実践的なテスト設計のポイント

7. 実践的なテスト設計のポイント
7. 実践的なテスト設計のポイント

最後に、日々の開発で役立つテスト設計のポイントをまとめます。

ポイント1:小さく分けて考える

大きな機能を一度にテストしようとせず、小さな部品に分けてテストします。パズルを組み立てる時に、一つ一つのピースが正しい形かを確認するイメージです。

ポイント2:外部依存を減らす

モジュールが外部のシステムに依存していると、テストが難しくなります。先ほど紹介したインターフェースを使った設計で、依存関係を管理しやすくしましょう。

ポイント3:テストを先に書く

実は、コードを書く前にテストを先に書く方法もあります。これをテスト駆動開発と呼びます。テストを先に書くことで、自然とテストしやすい設計になります。

関連記事:
カテゴリの一覧へ
新着記事
New1
TypeScript
TypeScriptでパスエイリアスを設定する方法!baseUrlとpathsでコードをスッキリ整理
New2
JavaScript
JavaScriptのfor文の書き方を初心者向けにやさしく解説
New3
JavaScript
JavaScriptの関数でよくあるエラーとその解決法まとめ
New4
JavaScript
JavaScriptのイベント処理でよくあるエラーとその対処法
人気記事
No.1
Java&Spring記事人気No1
JavaScript
JavaScriptのインストール方法まとめ!Windows・Mac・Linux別にステップ解説
No.2
Java&Spring記事人気No2
JavaScript
JavaScriptのマウスイベントの使い方(click, mouseoverなど)
No.3
Java&Spring記事人気No3
JavaScript
JavaScriptのtoStringとString関数の違いを初心者向けに解説
No.4
Java&Spring記事人気No4
JavaScript
JavaScriptの純粋関数(pure function)と副作用の違いを理解しよう
No.5
Java&Spring記事人気No5
JavaScript
JavaScriptプログラムの実行方法まとめ!ブラウザ・Node.js・コンソールの使い方
No.6
Java&Spring記事人気No6
JavaScript
JavaScriptで文字列をforループで1文字ずつ処理する方法!初心者向け解説
No.7
Java&Spring記事人気No7
TypeScript
TypeScript学習におすすめの無料教材・リファレンスサイト【初心者向け】
No.8
Java&Spring記事人気No8
JavaScript
JavaScriptのDOMContentLoadedとloadイベントの違いを理解しよう