統合テストは恐ろしいものではありません。これらは、アプリケーションを完全にテストするための重要な部分です。
テストについて話すとき、私たちは通常、コードの小さなチャンクを分離してテストする単体テストについて考えます。ただし、アプリケーションはその小さなコードチャンクよりも大きく、アプリケーションのほとんどの部分が単独で機能しません。これは、統合テストがその重要性を証明する場所です。統合テストは、単体テストが不十分な場所をピックアップし、単体テストとエンドツーエンドテストの間のギャップを埋めます。
統合テストを作成する必要があることを知っているのに、なぜそれを行わないのですか? つぶやきこの記事では、APIベースのアプリケーションの例を使用して、読み取り可能で構成可能な統合テストを作成する方法を学習します。
JavaScript /を使用しますが Node.js この記事のすべてのコード例について、説明したほとんどのアイデアは、任意のプラットフォームでの統合テストに簡単に適合させることができます。
単体テストは、特定のコード単位に焦点を当てています。多くの場合、これは特定の方法またはより大きなコンポーネントの機能です。
これらのテストは個別に実行され、通常、すべての外部依存関係がスタブまたはモックされます。
つまり、依存関係は事前にプログラムされた動作に置き換えられ、テストの結果がテスト対象のユニットの正確さによってのみ決定されるようにします。
あなたはユニットテストについてもっと学ぶことができます ここに 。
単体テストは、優れた設計で高品質のコードを維持するために使用されます。また、コーナーケースを簡単にカバーすることもできます。
ただし、欠点は、単体テストではコンポーネント間の相互作用をカバーできないことです。ここで統合テストが役立ちます。
単体テストがコードの最小単位を分離してテストすることによって定義されている場合、統合テストは正反対です。
統合テストは、相互作用する複数のより大きなユニット(コンポーネント)をテストするために使用され、場合によっては複数のシステムにまたがることもあります。
統合テストの目的は、次のようなさまざまなコンポーネント間の接続と依存関係のバグを見つけることです。
テストしているコンポーネントに複雑なロジックがない場合(例:最小限のコンポーネント 循環的複雑度 )、統合テストは単体テストよりもはるかに重要になります。
この場合、単体テストは主に優れたコード設計を実施するために使用されます。
単体テストは関数が適切に記述されていることを確認するのに役立ちますが、統合テストはシステムが全体として適切に機能していることを確認するのに役立ちます。したがって、単体テストと統合テストはそれぞれ独自の補完的な目的を果たし、包括的なテストアプローチには両方が不可欠です。
ユニットテストと統合テストは、同じコインの両面のようなものです。コインは両方なしでは無効です。
したがって、統合テストと単体テストの両方を完了するまで、テストは完了しません。
単体テスト用のテストスイートのセットアップは非常に簡単ですが、統合テスト用のテストスイートのセットアップは多くの場合より困難です。
たとえば、統合テストのコンポーネントには、データベース、ファイルシステム、電子メールプロバイダー、外部支払いサービスなど、プロジェクト外の依存関係があります。
場合によっては、統合テストでこれらの外部サービスとコンポーネントを使用する必要があり、スタブ化されることもあります。
それらが必要な場合、それはいくつかの課題につながる可能性があります。
統合テストには、次のような厳密なルールはありません。 ユニットテスト 。それにもかかわらず、統合テストを作成するときに従うべきいくつかの一般的な指示があります。
テストの順序や依存関係によってテスト結果が変わることはありません。同じテストを複数回実行すると、常に同じ結果が返されます。テストでインターネットを使用してサードパーティのサービスに接続している場合、これを実現するのは難しい場合があります。ただし、この問題はスタブとモックで回避できます。
より細かく制御できる外部依存関係の場合、統合テストの前後に手順を設定すると、テストが常に同じ状態から開始して実行されるようになります。
モンテカルロシミュレーション時系列
考えられるすべてのケースをテストするには、単体テストがはるかに優れたオプションです。
統合テストは、モジュール間の接続に重点を置いているため、テスト 幸せなシナリオ モジュール間の重要な接続をカバーするため、通常はこれが最適な方法です。
テストの1つのクイックビューは、何がテストされているか、環境がどのようにセットアップされているか、何がスタブされているか、いつテストが実行されているか、何がアサートされているかを読者に通知する必要があります。アサーションは単純で、比較とロギングを改善するためにヘルパーを利用する必要があります。
テストを初期状態にすることは、可能な限り単純で理解しやすいものでなければなりません。
テストではサードパーティのサービスを使用できますが、テストする必要はありません。そして、あなたがそれらを信頼しないのなら、あなたはおそらくそれらを使うべきではありません。
プロダクションコードはクリーンでわかりやすいものにする必要があります。 テストコードと製品コードの混合 接続できない2つのドメインが結合されます。
失敗したテストは、適切なログがなければあまり価値がありません。
テストに合格すると、追加のログは必要ありません。しかし、それらが失敗した場合、広範なロギングが不可欠です。
ロギングには、すべてのデータベースクエリ、APIリクエストとレスポンス、およびアサートされているものの完全な比較が含まれている必要があります。これにより、デバッグが大幅に容易になります。
ここでのガイドラインに従う簡単なテストは、次のようになります。
const co = require('co'); const test = require('blue-tape'); const factory = require('factory'); const superTest = require('../utils/super_test'); const testEnvironment = require('../utils/test_environment_preparer'); const path = '/v1/admin/recipes'; test(`API GET ${path}`, co.wrap(function* (t) { yield testEnvironment.prepare(); const recipe1 = yield factory.create('recipe'); const recipe2 = yield factory.create('recipe'); const serverResponse = yield superTest.get(path); t.deepEqual(serverResponse.body, [recipe1, recipe2]); }));
上記のコードは、保存されたレシピの配列を応答として返すことを期待するAPI(GET /v1/admin/recipes
)をテストしています。
テストは、単純なものであっても、多くのユーティリティに依存していることがわかります。これは、優れた統合テストスイートに共通しています。
ヘルパーコンポーネントを使用すると、わかりやすい統合テストを簡単に作成できます。
統合テストに必要なコンポーネントを確認しましょう。
包括的なテストスイートには、フロー制御、テストフレームワーク、データベースハンドラー、バックエンドAPIへの接続方法などのいくつかの基本的な要素が含まれています。
JavaScriptテストの最大の課題の1つは、非同期フローです。
コールバックはできます 大混乱をもたらす コードと約束では十分ではありません。ここでフローヘルパーが役立ちます。
待っている間 非同期/待機 完全にサポートされるために、同様の動作を持つライブラリを使用できます。目標は、非同期フローを持つ可能性のある、読みやすく、表現力があり、堅牢なコードを作成することです。
何 コードをブロックしないようにしながら、コードを適切に記述できるようにします。これは、coジェネレーター関数を定義し、結果を生成することによって行われます。
別の解決策は使用することです 青い鳥 。 Bluebirdは、配列、エラー、時間などの処理などの非常に便利な機能を備えたPromiseライブラリです。
CoとBluebirdのコルーチンは、ES7のasync / awaitと同様に動作します(続行する前に解決を待機します)。唯一の違いは、エラーの処理に役立つpromiseを常に返すことです。
テストフレームワークの選択は、個人的な好みに帰着します。私の好みは、使いやすく、副作用がなく、出力が読みやすく、パイプ処理されやすいフレームワークです。
JavaScriptにはさまざまなテストフレームワークがあります。この例では、 テープ 。私の意見では、テープはこれらの要件を満たすだけでなく、MochaやJasminなどの他のテストフレームワークよりもクリーンでシンプルです。
テープはに基づいています Test Anything Protocol(TAP) 。
TAPには、ほとんどのプログラミング言語用のバリエーションがあります。
Tapeはテストを入力として受け取り、それらを実行してから、結果をTAPとして出力します。次に、TAPの結果をテストレポーターにパイプするか、raw形式でコンソールに出力することができます。テープはコマンドラインから実行されます。
Tapeには、テストスイート全体を実行する前にロードするモジュールの定義、小さくて単純なアサーションライブラリの提供、テストで呼び出す必要のあるアサーションの数の定義など、いくつかの優れた機能があります。モジュールを使用してプリロードすると、テスト環境の準備が簡単になり、不要なコードを削除できます。
ファクトリライブラリを使用すると、静的フィクスチャファイルを、テスト用のデータを生成するためのはるかに柔軟な方法に置き換えることができます。このようなライブラリを使用すると、面倒で複雑なコードを記述せずに、モデルを定義し、それらのモデルのエンティティを作成できます。
JavaScriptには 工場の女の子 このために-からインスピレーションを得たライブラリ 似たような名前の宝石 、もともとはRuby onRails用に開発されました。
const factory = require('factory-girl').factory; const User = require('../models/user'); factory.define('user', User, { username: 'Bob', number_of_recipes: 50 }); const user = factory.build('user');
開始するには、factory_girlで新しいモデルを定義する必要があります。
名前、プロジェクトのモデル、新しいインスタンスの生成元のオブジェクトで指定されます。
あるいは、新しいインスタンスが生成されるオブジェクトを定義する代わりに、オブジェクトまたはPromiseを返す関数を提供することもできます。
モデルの新しいインスタンスを作成するとき、次のことができます。
例を見てみましょう。
const factory = require('factory-girl').factory; const User = require('../models/user'); factory.define('user', User, (buildOptions) => { return ' [email protected] ' }); const user1 = factory.build('user'); // {'name': 'Mike', 'surname': 'Dow', 'email': ' [email protected] '} const user2 = factory.build('user', {name: 'John'}, {email: ' [email protected] '}); // {'name': 'John', 'surname': 'Dow', 'email': ' [email protected] '}
本格的なHTTPサーバーを起動して実際のHTTPリクエストを作成し、数秒後にそれを破棄するだけで(特に複数のテストを実行する場合)、完全に非効率的であり、統合テストに必要以上に時間がかかる可能性があります。
スーパーテスト は、新しいアクティブサーバーを作成せずにAPIを呼び出すためのJavaScriptライブラリです。これは、TCPリクエストを作成するためのライブラリであるSuperAgentに基づいています。このライブラリを使用すると、新しいTCP接続を作成する必要はありません。 APIはほぼ瞬時に呼び出されます。
約束をサポートするSuperTestは 約束通りのスーパーテスト 。このようなリクエストがpromiseを返すと、ネストされた複数のコールバック関数を回避できるため、フローの処理がはるかに簡単になります。
const express = require('express') const request = require('supertest-as-promised'); const app = express(); request(app).get('/recipes').then(res => assert(....));
SuperTestはExpress.jsフレームワーク用に作成されましたが、わずかな変更を加えるだけで、他のフレームワークでも使用できます。
場合によっては、コードの依存関係を模倣したり、スパイを使用して関数のロジックをテストしたり、特定の場所でスタブを使用したりする必要があります。ここで、これらのユーティリティパッケージのいくつかが役に立ちます。
SinonJS は、テスト用のスパイ、スタブ、モックをサポートする優れたライブラリです。また、曲げ時間、テストサンドボックス、拡張アサーション、偽のサーバーやリクエストなど、その他の便利なテスト機能もサポートしています。
場合によっては、コードの依存関係をモックする必要があります。モックしたいサービスへの参照は、システムの他の部分で使用されます。
この問題を解決するには、 依存性注入 または、それがオプションでない場合は、Mockeryのようなモックサービスを使用できます。
嘲笑 外部依存関係を持つコードをモックするのに役立ちます。正しく使用するには、テストやコードをロードする前にMockeryを呼び出す必要があります。
const mockery = require('mockery'); mockery.enable({ warnOnReplace: false, warnOnUnregistered: false }); const mockingStripe = require('lib/services/internal/stripe'); mockery.registerMock('lib/services/internal/stripe', mockingStripe);
この新しい参照(この例ではmockingStripe
)を使用すると、テストの後半でサービスをモックするのが簡単になります。
const stubStripeTransfer = sinon.stub(mockingStripe, 'transferAmount'); stubStripeTransfer.returns(Promise.resolve(null));
Sinonライブラリの助けを借りて、モックを作成するのは簡単です。ここでの唯一の問題は、このスタブが他のテストに伝播することです。サンドボックス化するには、sinonサンドボックスを使用できます。これを使用すると、後のテストでシステムを初期状態に戻すことができます。
const sandbox = require('sinon').sandbox.create(); const stubStripeTransfer = sandbox.sinon.stub(mockingStripe, 'transferAmount'); stubStripeTransfer.returns(Promise.resolve(null)); // after the test, or better when starting a new test sandbox.restore();
次のような機能のために他のコンポーネントが必要です。
抽象化と拡張性は、効果的な統合テストスイートを構築するための重要な要素です。テストのコアからフォーカスを取り除くすべて(データの準備、アクション、およびアサーション)は、グループ化してユーティリティ関数に抽象化する必要があります。
Pythonでロボットをプログラムする方法
ここには正しい道も間違った道もありませんが、すべてがプロジェクトとそのニーズに依存するため、いくつかの重要な品質は、優れた統合テストスイートに共通しています。
次のコードは、レシピを作成し、副作用として電子メールを送信するAPIをテストする方法を示しています。
外部の電子メールプロバイダーをスタブ化して、実際に電子メールを送信せずに電子メールが送信されたかどうかをテストできるようにします。このテストでは、APIが適切なステータスコードで応答したかどうかも確認します。
const co = require('co'); const factory = require('factory'); const superTest = require('../utils/super_test'); const basicEnv = require('../utils/basic_test_enivornment'); const path = '/v1/admin/recipes'; basicEnv.test(`API POST ${path}`, co.wrap(function* (t, assert, sandbox) { const chef = yield factory.create(‘chef’); const body = { chef_id: chef.id, recipe_name: ‘cake’, Ingredients: [‘carrot’, ‘chocolate’, ‘biscuit’] }; const stub = sandbox.stub(mockery.emailProvider, 'sendNewEmail').returnsPromise(null); const serverResponse = yield superTest.get(path, body); assert.spies(stub).called(1); assert.statusCode(serverResponse, 201); }));
上記のテストは、毎回クリーンな環境で開始されるため、繰り返し可能です。
セットアップに関連するすべてがbasicEnv.test
内に統合されるシンプルなセットアッププロセスがあります。関数。
1つのアクション(単一のAPI)のみをテストします。また、単純なアサートステートメントを通じてテストの期待を明確に示しています。また、テストには、スタブ/モックによるサードパーティのコードは含まれていません。
新しいコードを本番環境にプッシュするとき、開発者(および他のすべてのプロジェクト参加者)は、新しい機能が機能し、古い機能が壊れないことを確認したいと考えています。
これは、テストなしで達成するのは非常に困難であり、不十分に行われると、フラストレーション、プロジェクトの疲労、そして最終的にはプロジェクトの失敗につながる可能性があります。
ユニットテストと組み合わせた統合テストは、防御の最前線です。
2つのうち1つだけを使用するだけでは不十分であり、カバーされていないエラーのために多くのスペースが残ります。常に両方を利用することで、新しいコミットが堅牢になり、すべてのプロジェクト参加者に自信を与え、信頼を得ることができます。