オンラインストアの割引を計算するための複雑な機能を開発したと想像してみてください。コードは正常に動作しますが、1 か月後、同僚がアプリケーションの別の部分に小さな変更を加えると、突然、計算が間違った結果を出し始めます。このような問題を時間内に検出するにはどうすればよいですか?答えは簡単です。ユニットテストです。
ユニットテストとは
ユニットテストは、小さなコードのセクション、通常は個々の関数またはメソッドの自動チェックです。これらはチェックポイントとして機能し、コードが意図したとおりに動作していることを常に確認します。
基本的な考え方は簡単です。既知の入力データを使用して関数を呼び出し、結果が期待どおりであることを確認するコードを記述します。結果が正しい場合、テストは成功します。そうでない場合、テストは失敗し、エラーを報告します。
ユニットテストが必要な理由
初心者の開発者は、コードを手動でチェックできるのに、なぜテストを書くのに時間を費やすのですか?とよく疑問に思います。いくつかの理由があります。
最初の理由は、リファクタリングへの自信です。テストカバレッジがあると、何かが壊れた場合、テストがすぐにそれを報告することを知っているので、コードを安全に改善できます。これは、アクロバットのためのセーフティネットのようなものです。
2つ目の理由は、ドキュメントです。よく書かれたテストは、関数がどのように使用されるべきか、どのような入力データを受け取るか、そして何を返すかを示しています。これは常に最新の状態にあるライブドキュメントです。
3つ目の理由は、長期的に時間を節約できることです。確かに、テストを書くには時間がかかりますが、将来的にデバッグの時間を節約できます。自動テストは数秒で開始され、すべての機能をチェックしますが、手動テストには数時間かかる場合があります。

簡単な例
JavaScriptの実例を見てみましょう。メールアドレスを検証する機能があるとします。
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}今度は、人気のある Jest フレームワークを使用して、ユニットテストを書きます。
describe('isValidEmail', () => {
test('must return true for a valid email', () => {
expect(isValidEmail('user@example.com')).toBe(true);
});
test('must return false for email without @', () => {
expect(isValidEmail('userexample.com')).toBe(false);
});
test('must return false for email without domain', () => {
expect(isValidEmail('user@')).toBe(false);
});
test('must return false for an empty string', () => {
expect(isValidEmail('')).toBe(false);
});
});各テストは特定のシナリオをテストします。誰かが正規表現を変更して誤って検証を中断した場合、テストはすぐにそれを検出します。
優れたテストを書くための原則
優れたユニットテストは独立している必要があります。つまり、他のテストの結果やテストの順序に依存しない必要があります。各テストは独自のデータを生成し、1 つの特定のもののみをチェックします。
テストは迅速に実行できる必要があります。すべてのテストの実行に数分かかる場合、開発者はそれらを実行する頻度が低くなります。ユニットテストは、コードを変更するたびに実行できるように、ミリ秒単位で実行する必要があります。
テストは明確でなければなりません。テストが失敗した場合、何が正しく機能していないのかをすぐに理解できる必要があります。テストにはわかりやすい名前を付け、エラーメッセージは明確にしてください。
AAAパターン
テストを構造化するための一般的なアプローチは、AAAパターンです。つまり、Arrange、Act、Assert(準備、行動、検証)です。
test('must correctly calculate the 10% discount', () => {
// 整理 — データの準備
const price = 1000;
const discount = 10;
// Act — アクションの実行
const result = calculateDiscount(price, discount);
// アサート — 結果の検証
expect(result).toBe(900);
});この構造により、初めてコードを見る人にとっても、テストが読みやすく理解しやすくなります。
境界値のテスト
ユニットテストの最も重要なタスクの1つは、境界値のチェックです。これらは、空の配列、ゼロ値、最大数、特殊文字など、許容値の限界にある状況です。
describe('calculateAge', () => {
test('must return 0 for a newborn', () => {
const today = new Date();
expect(calculateAge(today)).toBe(0);
});
test('must process a leap year', () => {
const birthDate = new Date('2000-02-29');
expect(calculateAge(birthDate)).toBeGreaterThan(0);
});
test('should throw an error for a date in the future', () => {
const futureDate = new Date('2030-01-01');
expect(() => calculateAge(futureDate)).toThrow();
});
});限界のケースは、多くの場合、本番環境でのバグの原因となります。

モックアップとスタブ
多くの場合、機能はデータベース、API、ファイルシステムなどの外部サービスに依存します。ユニットテストでは、実際の外部リソースを使用したくありません。これは遅く、信頼性が低いからです。代わりに、モックとスタブが使用されます。
// テストする機能
async function getUserData(userId) {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
// モックテスト
test('must receive user data', async () => {
// フェッチのモックを作成する
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ id: 1, name: 'Alexey' })
})
);
const userData = await getUserData(1);
expect(userData.name).toBe('Alexey');
expect(fetch).toHaveBeenCalledWith('/api/users/1');
});モックを使用すると、テストコードを分離し、関数が外部依存関係と正しく相互作用することを確認できます。
テストコードカバレッジ
コードカバレッジは、テスト中にコードが実行される割合を示します。ほとんどのテストツールは、カバレッジレポートを生成できます。
ただし、100%のカバレッジがバグの不在を保証するものではないことを理解することが重要です。カバレッジはコードが実行されたことを示しますが、すべての可能なシナリオでテストされたことを保証するものではありません。コードの重要な部分を合理的にカバーするようにしてください。
異なる言語でのテスト
ユニットテストの概念は汎用的ですが、ツールはプログラミング言語によって異なります。
Pythonでは、pytestとunittestのフレームワークが人気があります。これらは、テストを作成するためのシンプルな構文と、テスト用の多くの組み込みユーティリティを提供します。
JavaScriptとTypeScriptはJest、Mocha、Jasmineを使用します。Jestは、組み込みのモックサポートと使いやすいAPIで特に人気があります。
Vue.jsアプリケーションでは、Vue Test UtilsとJestを組み合わせて使用することが多く、コンポーネントを分離してテストしたり、レンダリングやユーザーとのインタラクションを確認したりできます。
Javaでは従来JUnitを使用し、PHPではPHPUnitを使用しています。これらのツールはそれぞれ、言語の特性に合わせて調整されていますが、基本原則は同じです。
TDD:テスト駆動開発
一部のチームは、メインコードを書く前にテストを書くアプローチであるTDD(テスト駆動開発)を実践しています。プロセスは次のようになります。まず、望ましい動作を記述する失敗するテストを記述し、次にテストを合格させるための最小限のコードを記述し、最後にテストを緑色のままにしてコードをリファクタリングします。
このアプローチは、アーキテクチャをよりよく考えるのに役立ち、すべてのコードがテストでカバーされていることを保証します。ただし、TDDには規律が必要であり、実験的なプロジェクトに常に適しているとは限りません。
ユニットテストが不要な場合
正直なところ、すべてのコードがユニットテストを必要とするわけではありません。単純なゲッターとセッター、簡単な書式設定関数、ロジックのないUIコンポーネントなどは、テストなしで実行したり、統合テストでカバーしたりできます。
ビジネスロジック、複雑なアルゴリズム、条件付きロジックを持つ関数、アプリケーションの動作に不可欠なコードのテストに焦点を当てます。ここでは、テストが最大の恩恵をもたらします。
テストやその他のプロフェッショナル開発の実践について、より深く学びたいですか?
アプリケーション コディック Python、JavaScript、その他多くのテクノロジーに関するインタラクティブなコースを提供しています。トレーニングは、実践的なタスクと実際の例を使用して、シンプルなものから複雑なものまで構築されています。
私たちの Telegramチャンネル、ここでは、プログラミングの学習の過程で、あらゆる質問に答えたり、経験を共有したり、サポートしたりする準備ができている開発者のアクティブなコミュニティを見つけることができます!
