TypeScriptでクラスをユニットテストしやすくする設計ポイント
生徒
「先生、TypeScriptで書いたクラスをテストするときに、どうやったらテストしやすいコードになりますか?」
先生
「とても大事な質問ですね。TypeScriptでは、設計の工夫をすることでクラスをユニットテストしやすくできますよ。」
生徒
「ユニットテストって何ですか?」
先生
「ユニットテストとは、プログラムの部品(クラスや関数)を小さな単位で確認するテストです。クラスが正しく動いているかを確かめる方法なんです。」
生徒
「なるほど!じゃあ、TypeScriptでどう書けばテストしやすくなるんですか?」
先生
「それでは、ポイントを順番に見ていきましょう!」
1. ユニットテストしやすい設計とは?
ユニットテストしやすいクラスを作るためには、クラスができるだけ単純な責任を持つことが大切です。これを「単一責任の原則」と呼びます。たとえば、あるクラスがデータを保存することと表示することを同時に担当していると、テストが複雑になってしまいます。役割を分けることで、それぞれの処理を独立してテストできるようになります。
2. コンストラクタで依存を受け取る
ユニットテストをやりやすくする代表的な工夫が依存性注入(DI: Dependency Injection)です。依存性注入とは、クラスが自分で他のクラスを作るのではなく、外から渡してもらう仕組みのことです。こうすることで、テストするときに本物のクラスではなく「テスト用のダミークラス(モック)」を渡せるようになります。
class UserService {
private db: Database;
constructor(db: Database) {
this.db = db;
}
getUser(id: number): string {
return this.db.findUser(id);
}
}
// 実際のアプリで使うとき
const realDb = new Database();
const userService = new UserService(realDb);
このように書いておくと、テストのときには本物のDatabaseクラスではなく、テスト専用のものを渡すことができます。
3. モックを使ったテストの例
モック(mock)とは、テスト専用に用意した偽物のオブジェクトです。本物のデータベースを使わなくても、モックを渡せばクラスの動作を確認できます。
// テスト用のモックデータベース
class MockDatabase {
findUser(id: number): string {
return "テストユーザー";
}
}
// モックを使ってUserServiceをテスト
const mockDb = new MockDatabase();
const userService = new UserService(mockDb as any);
console.log(userService.getUser(1));
テストユーザー
このようにモックを使うことで、外部環境に依存せずにテストできるようになります。たとえば実際のデータベースが動いていなくても、クラス単体の動作確認ができるのです。
4. クラスを小さく分けて設計する
ユニットテストしやすいコードを書くためには、クラスを小さく分けることが重要です。ひとつのクラスに多くの処理を詰め込んでしまうと、テスト対象が大きくなり、テストが複雑になります。小さなクラスに分けて、それぞれ独立して確認できるようにすると、失敗したときの原因も追いやすくなります。
5. インターフェースを活用する
TypeScriptではインターフェースを使ってクラスの型を定義できます。テストしやすい設計のためには、この仕組みを活用するのが効果的です。インターフェースを定義しておけば、モックを作るときにも型を揃えやすくなります。
interface IDatabase {
findUser(id: number): string;
}
class UserService {
private db: IDatabase;
constructor(db: IDatabase) {
this.db = db;
}
getUser(id: number): string {
return this.db.findUser(id);
}
}
このようにインターフェースを使うと、テスト用のモックも簡単に作ることができます。コードの見通しが良くなり、ユニットテストしやすい環境が整います。
6. 外部に依存しない設計を心がける
テストが難しくなる大きな原因は、クラスが外部の環境(データベースやネットワークなど)に依存してしまうことです。こうした依存を減らすためには、外部と直接やりとりする部分を切り離しておき、テストしたいクラスはできるだけ「純粋な処理」だけを担当させるとよいです。
たとえば、「計算」や「文字列の整形」などの処理は、外部に依存しない純粋なロジックです。こうした部分を独立させておけば、テストはとてもシンプルになります。
まとめ
ユニットテストを意識したTypeScriptクラス設計の重要性
今回の記事では、TypeScriptでクラスをユニットテストしやすくするための設計ポイントについて深く学びました。 特に「依存性注入」「モックの活用」「インターフェースの利用」「クラスの責務を小さく保つ」「外部依存を減らす」といった考え方は、堅牢で保守しやすいソフトウェアを作るための基本でもあります。 ユニットテストがしやすいコードというのは、すなわち“変更に強く、壊れにくく、読みやすいコード”です。 そのため、テストしやすさを意識して設計することは、品質向上だけでなく長期的な開発効率にも大きく貢献します。
依存性注入はTypeScriptの設計でもとても重要な役割を果たし、クラス内部で依存オブジェクトを直接生成するのではなく、コンストラクタで受け取ることによって、テスト用のモックオブジェクトを簡単に差し替えられるようになります。 また、インターフェースを使って依存の型を抽象化しておくことで、モックの設計が楽になり、テストの見通しが良くなります。 さらに、クラスの責務をひとつに絞り、小さくまとめることでテスト対象の範囲が明確になり、ユニットテストの精度も上がります。
サンプルコード:依存性注入とモックの活用をまとめて確認
以下のサンプルは、設計のポイントを踏まえた「テストしやすいクラス」のひとつの形です。 インターフェースで依存を抽象化し、外部環境に依存せずテスト可能な構造を整えています。
// データ取得の抽象インターフェース
interface IDataFetcher {
fetch(id: number): string;
}
// 実際の処理クラス
class DataService {
constructor(private fetcher: IDataFetcher) {}
getData(id: number): string {
return this.fetcher.fetch(id);
}
}
// テスト用モック
class MockFetcher implements IDataFetcher {
fetch(id: number): string {
return "モックデータ:" + id;
}
}
// モックを使ってテスト
const mock = new MockFetcher();
const service = new DataService(mock);
console.log(service.getData(10));
モックデータ:10
このように依存性を注入しておくことで、本物の外部サービスが動いていなくてもロジックの確認ができます。 テストコードでは実データベースやAPI通信を行わないため、テストが軽く・速く・正確になります。 これは特に大規模開発や長期の運用で大きく効果を発揮します。
テストしやすいクラス構造の判断基準
テストしやすいコードになっているかどうかを判断する基準として、以下のようなポイントがあります。
- クラスが複数の責務を持っていないか(単一責任の原則)
- 外部依存(ネットワークやデータベース)とロジック部分が分離されているか
- 依存はコンストラクタで受け取るようにしているか
- インターフェースを使って依存先を抽象化しているか
- モックが容易に作れる構造になっているか
これらを意識するだけで、ユニットテストのしやすさは大きく変わります。 テストがしやすいということは、開発者全員が安心して機能拡張や修正ができるということでもあります。 バグの発生率も下がり、結果的に開発スピードや品質の向上にもつながっていきます。
先生と生徒の振り返り会話
生徒
「ユニットテストしやすい設計って、こんなに大切なんですね。依存性注入ってすごく便利です。」
先生
「そのとおり。依存を外から渡すだけでテストしやすさが一気に上がります。特にモックを使えるのが大きいですね。」
生徒
「クラスを小さく分ける理由もよくわかりました。小さいほうがテストもしやすいし、読みやすいです。」
先生
「クラスが大きすぎるとテスト範囲が広くなってしまいますからね。役割ごとに整理するだけで設計が整いますよ。」
生徒
「インターフェースで依存を抽象化すると、モックが簡単に作れるのも驚きでした!」
先生
「その理解があれば、テストしやすいコードはもう書けますよ。実際の開発でも意識して使ってみてくださいね。」