インテグレーションテストの作成

Flex Gateway ポリシー開発キット (PDK) には、カスタムポリシーのインテグレーション回帰を検出するための自動化テストフレームワークが用意されています。

PDK が提供するインテグレーションテストフレームワークは、Docker コンテナのローカルモードで実行されている Flex Gateway のみをサポートします。接続モードで動作する Flex Gateway や別のプラットフォームにポリシーをデプロイする場合でも、まず Docker コンテナのローカルモードで実行されている Flex Gateway を使用して PDK でポリシーをテストしてください。

ポリシーの開発とテストのみが目的であれば、Windows の Docker で Flex Gateway をローカルモードで実行することもできます。ただし、Flex Gateway は本番環境では Windows をサポートしていません。

ポリシーをテストするには、次のタスクを実行します。

始める前に

次の準備が整っていることを確認します。

インテグレーションテストのディレクトリ構造

デフォルトでは、​<root-directory>/tests​ ディレクトリには以下のファイルが保存されます。

tests
├── common.rs
├── requests.rs
└── common
    └── logging.yaml
    └── registration.yaml

/tests​ ディレクトリにはインテグレーションテストが保存されます。デフォルトでは以下のファイルが保存されます。

  • requests.rs​ ファイル: 個別のテストモジュールと実行ファイル。

  • commons.rs​ ファイル: すべてのインテグレーションテストモジュールに含める必要がある機能、API 設定、ポリシー設定が含まれたテストモジュール。

ローカルモードでの Flex Gateway インスタンスの登録

make test​ コマンドを使用してカスタムポリシーのデバッグを開始するには、ローカルモードで Flex Gateway を登録します。

make test​ コマンドが動作するためには、​<root-directory>/tests/common​ ディレクトリに Flex Gateway の ​registration.yaml​ ファイルが存在する必要があります。このディレクトリで登録コマンドを実行してこのファイルを作成するか、または以前に登録してある Flex Gateway の ​registration.yaml​ ファイルをこのディレクトリに移動します。

Flex Gateway の登録については、以下を参照してください。

登録コマンドのみを実行してください。Docker の開始コマンドは、​make test​ コマンドがこのステップを完了するため、実行しないでください。

ポリシーをテストするデバイスごとに異なる登録ファイルを作成してください。

登録ファイルはリモートリポジトリへの保存では無視され、プロジェクトディレクトリのルートにある ​.gitignore​ ファイルにリストされます。

カスタムポリシーを設定する

各テスト関数では、その要求の API とポリシーの設定が必要です。

カスタムポリシーを設定する手順は、次のとおりです。

  1. show-policy-ref-name​ を実行してポリシー ID を表示します。

    make show-policy-ref-name
  2. 結果をコピーします。

  3. commons.rs​ の ​{{project-name}}​ を自分のポリシー ID で上書きします。例:

    pub const POLICY_NAME: &str = "<custom-policy-id>";
  4. ポリシーの設定パラメーターを定義します。

    設定パラメーターは、​definition/gcl.yaml​ ファイルにあります。

テスト関数を作成する

既存のテストモジュール (​/tests/requests.rs​ など) に新しいテスト関数を配置して、新しいテストモジュールを追加します。

PDK テスト関数は、​#[pdk_test]​ 属性がつけられた​非同期​関数です。

use pdk_test::pdk_test;

#[pdk_test]
async fn say_hello() {

}
ボディが空であるため、この関数がエラーとなることはありません。​make test​ を実行してすべてが正常に動作することを確認します。

インテグレーションテストを実行する

/tests​ フォルダーのインテグレーションテストを実行するには、​make test​ コマンドを実行します。

make test

インテグレーションテストに機能を追加するたびにこのテストを実行して、正しく設定されていることを確認してください。

make test​ コマンドは、インテグレーションテストを実行する前にポリシーをコンパイルします。

インテグレーションテスト用にサービスを設定する

PDK インテグレーションテストには、Docker ネットワーク内で実行されるサービスの複合体が含まれます。サービスインスタンスは ​TestComposite​ オブジェクトに登録できます。すべてのサービスには、設定、ホスト名、インスタンスがあります。

Cargo.toml​ ファイルの ​[dev-dependencies]​ セクションにある ​pdk-test​ クレートは PDK のカスタムポリシーのインテグレーションテストを作成するためのすべての機能を提供します。

サポートされるサービス

PDK のテストライブラリは以下のサービスをサポートします。

サービス 説明

Flex

Flex Gateway サービスインスタンス。

httpmock

Rust の httpmock​ サーバーへのサービスバインド。

サポートされるサービスにより、インテグレーションテストの大半のユースケースがカバーされます。異なる種別のサービスを定義することはできません。

httpmock サービスを設定する

httpmock​ は、Rust で要求のモックを作成できるようにするモックサーバーです。 httpmock​ サービスは、​HttpMockConfig​ 値を使用して設定します。テスト関数から​結果​を返すようにするのがベストプラクティスです。Rust の anyhow​ クレートは、異なるデフォルトエラー種別を返せるようにします。

httpmock​ サービスの設定は、次のコード例を参照してください。

use pdk_test::{pdk_test, TestComposite};
use pdk_test::services::httpmock::HttpMockConfig;

#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {

    // Configure HttpMock service
    let backend_config = HttpMockConfig::builder()
        .hostname("backend")
        .port(80) // Port where the service will accept requests
        .build();

    // Register HTTPBin service and start the docker network
    let composite = TestComposite::builder()
        .with_service(backend_config)
        .build()
        .await?;

    Ok(())
}
Docker エンジンが正しく設定されていれば、このテストは合格します。​make test​ を実行して設定が正しいことを確認してください。

代替 httpmock Docker イメージを設定する

提供されるデフォルトの代わりに別のソースの ​httpmock​ Docker イメージを使用するには、設定ビルダーで提供される ​image_name()​ メソッドを使用して明示的に定義します。

代替 ​httpmock​ Docker イメージを設定するには、次の例のように ​HttpMockConfig::builder()​ 定義を更新します。

// Configure HttpMock service
let backend_config = HttpMockConfig::builder()
    .image_name("myrepo/httpmock") // Custom image name setting
    .hostname("backend")
    .port(80) // Port where the service will accept requests
    .build();

サービスのハンドルを取得する

サービスの複合体で設定されているすべてのサービスインスタンスには、カスタムインタラクション用のハンドルがあります。このハンドルはサービスエンドポイントを使用できるようにするためのソケットを返します。

サービスのハンドルを見つけるには、次のコード例を参照してください。

use pdk_test::{pdk_test, TestComposite};
use pdk_test::services::httpmock::{HttpMock, HttpMockConfig};

#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {

    // Configure HTTPMock service
    let backend_config = HttpMockConfig::builder()
        .hostname("backend")
        .build();

    // Register HTTPMock service and start the docker network
    let composite = TestComposite::builder()
        .with_service(backend_config)
        .port(80) // Port where the service accepts requests
        .build()
        .await?;

    // Get the httpmock handle
    let httpmock: HttpMock = composite.service()?;

    Ok(())
}
httpmock​ インスタンスが正しく設定されていれば、このテストは合格します。​make test​ を実行して設定が正しいことを確認してください。

HTTP エンドポイント要求

エンドポイントをモックするには httpmock When/Then API​ を使用します。

エンドポイントをモックしたら、HTTP クライアントを使用してエンドポイント要求を実行します。

reqwest​ クレートは、最も包括的で柔軟な Rust HTTP クライアントを提供します。

エンドポイントをモックして要求を実行するには、次のコード例を参照してください。

use pdk_test::{pdk_test, TestComposite};
use pdk_test::services::httpmock::{HttpMock, HttpMockConfig};

#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {

    // Configure HttpMock service
    let backend_config = HttpMockConfig::builder()
        .hostname("backend")
        .port(80) // Port where the service will accept requests
        .build();

    // Register HTTPBin service and start the docker network
    let composite = TestComposite::builder()
        .with_service(backend_config)
        .build()
        .await?;

    // Get the httpmock handle
    let httpmock: HttpMock = composite.service()?;

    // Connect the mock server
    let mock_server = httpmock::MockServer::connect_async(httpmock.socket()).await;

    // Configure the endpoint mocks
    mock_server.mock_async(|when, then| {
        when.path_contains("/hello");
        then.status(202).body("World!");
    }).await;

    let base_url = mock_server.base_url();

    // Hit the endpoint
    let response = reqwest::get(format!("{base_url}/hello")).await?;

    // Assert on response
    assert_eq!(response.status(), 202);
    assert_eq!(response.text().await?, "World!");

    Ok(())
}
make test​ を実行して設定が正しいことを確認してください。

Flex サービスインスタンスを設定する

Flex​ サービスは、​TestComposite​ で ​FlexConfig​ 構造のインスタンスを登録して設定します。​FlexConfig​ インスタンスを完了するには、​ApiConfig​ および ​PolicyConfig​ インスタンスも定義する必要があります。

FlexConfig​ 構造には次のプロパティがあります。

プロパティ コンテンツ

version

テストする Flex Gateway バージョン。

image_name

Flex Gateway Docker イメージ名 (デフォルトは「mulesoft/flex-gateway」)。

hostname

Flex​ サービスのホスト名。デフォルトでは、「local-flex」です。

config_mounts

Flex​ サービスが設定ファイルを見つけるためのローカルディレクトリのマップ。 POLICY_DIR​ 定数と ​COMMON_CONFIG_DIR​ 定数は、デフォルトでは ​common.rs​ モジュールで定義されます。テストで追加の設定が必要な場合、テスト関数の特定の設定が含まれるディレクトリを追加します。

with_api

Flex​ サービスインスタンスにデプロイされた API の ​ApiConfiguration​。複数の API を設定するには、複数回コールします。

ports

Flex​ サービスが要求をリスンするポートのリスト。​config_mount​ ディレクトリに含まれるいずれかの設定ディレクトリで API を公開する場合、そのポートと ​spec.address​ プロパティで設定されたポートが一致している必要があります。

ApiConfig​ には次のプロパティがあります。

プロパティ コンテンツ

name

API の名前。

upstream

アップストリームサービスへの参照。

path

アップストリーム要求の送信先パス。

port

API が要求をリスンするポート。

policies

API に適用される ​PolicyConfig​ インスタンスのリスト。

PolicyConfig​ には次のプロパティがあります。

プロパティ コンテンツ

name

適用するポリシー。

configuration

ポリシーのパラメーター。

次のコード例では、完全な ​Flex​ サービスを定義しています。

mod common;
use common::*;

use pdk_test::{pdk_test, TestComposite};
use pdk_test::port::Port;
use pdk_test::services::flex::FlexConfig;

// Port where Flex listens for requests
const FLEX_PORT: Port = 8081;

#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {

    // Configure an upstream service for the API
    let httpmock_config = HttpMockConfig::builder()
        .port(80)
        .version("latest")
        .hostname("backend")
        .build();

    // Configure a policy for the API
    let policy_config = PolicyConfig::builder()
        .name(POLICY_NAME)
        .configuration(serde_json::json!({"source": "http://backend/blocked", "frequency": 60}))
        .build();

    // Configure the API to deploy to the Flex
    let api_config = ApiConfig::builder()
        .name("ingress-http")
        .upstream(&httpmock_config)
        .path("/anything/echo/")
        .port(FLEX_PORT)
        .policies([policy_config])
        .build();

    // Configure the Flex service
    let flex_config = FlexConfig::builder()
        .version("1.6.1")
        .hostname("local-flex")
        .config_mounts([
            (POLICY_DIR, "policy"),
            (COMMON_CONFIG_DIR, "common")
        ])
        .with_api(api_config)
        .build();

    let composite = TestComposite::builder()
        .with_service(flex_config)
        .with_service(httpmock_config)
        .build()
        .await?;
}

代替 Flex Docker イメージを設定する

提供されるデフォルトの代わりに別のソースの ​flex​ Docker イメージを使用するには、設定ビルダーで提供される ​image_name()​ メソッドを使用して明示的に定義します。

代替 Flex Docker イメージを設定するには、次の例のように ​FlexConfig::builder()​ 定義を更新します。

// Configure the Flex service
let flex_config = FlexConfig::builder()
    .image_name("myrepo/flex-gateway") // Custom image name setting
    .version("1.6.1")
    .hostname("local-flex")
    .config_mounts([
        (POLICY_DIR, "policy"),
        (COMMON_CONFIG_DIR, "common")
    ])
    .with_api(api_config)
    .build();

make test​ を実行して設定が正しいことを確認してください。

Flex サービスエンドポイント要求

Flex​ サービスは、ポートに応じた外部ベース URL でエンドポイントを表示します。これらの URL にアクセスするには、ポートごとにインデックス付けされた ​external_url()​ メソッドを Flex サービスインスタンスハンドルで指定する必要があります。例:

use pdk_test::{pdk_test, TestComposite, port::Port};
use pdk_test::services::flex::Flex;

const FLEX_PORT: Port = 8081;

async fn build_composite() -> anyhow::Result<TestComposite> {
    todo!("Configuration stuff comes here")
}

#[pdk_test]
async fn say_hello() -> anyhow::Result<()> {
    // Invoke a helper composite builder function for simplicity
    let composite = build_composite().await?;

    // Get the Flex service handle
    let flex: Flex = composite.service()?;

    // Get the URL for our configured port
    let flex_url = flex.external_url(FLEX_PORT).unwrap();

    let response = reqwest::get(format!("{flex_url}/hello")).await?;

    // Make assertions

    Ok(())
}

複数サービスの組み合わせ

通常、PDK のインテグレーションテストには、​Flex​ サービスと、1 つ以上のバックエンドサービスが含まれます。

次のコードは、カスタムポリシーの作成時に生成されるデフォルトのテストです。

mod common;

use httpmock::MockServer;
use pdk_test::{pdk_test, TestComposite};
use pdk_test::port::Port;
use pdk_test::services::flex::{FlexConfig, Flex};
use pdk_test::services::httpmock::{HttpMockConfig, HttpMock};

use common::*;

// Directory with the configurations for the `hello` test.
const HELLO_CONFIG_DIR: &str =  concat!(env!("CARGO_MANIFEST_DIR"), "/tests/requests/hello");

// Flex port for the internal test network
const FLEX_PORT: Port = 8081;

// This integration test shows how to build a test to compose a local-flex instance
// with a MockServer backend
#[pdk_test]
async fn hello() -> anyhow::Result<()> {
    // Configure an HttpMock service
    let httpmock_config = HttpMockConfig::builder()
        .port(80)
        .version("latest")
        .hostname("backend")
        .build();

    // Configure a policy for the API
    let policy_config = PolicyConfig::builder()
        .name(POLICY_NAME)
        .configuration(serde_json::json!({"source": "http://backend/blocked", "frequency": 60}))
        .build();

    // Configure the API to deploy to the Flex
    let api_config = ApiConfig::builder()
        .name("ingress-http")
        .upstream(&httpmock_config)
        .path("/anything/echo/")
        .port(FLEX_PORT)
        .policies([policy_config])
        .build();

    // Configure the Flex service
    let flex_config = FlexConfig::builder()
        .version("1.6.1")
        .hostname("local-flex")
        .config_mounts([
            (POLICY_DIR, "policy"),
            (COMMON_CONFIG_DIR, "common")
        ])
        .with_api(api_config)
        .build();

    // Compose the services
    let composite = TestComposite::builder()
        .with_service(flex_config)
        .with_service(httpmock_config)
        .build()
        .await?;

    // Get a handle to the Flex service
    let flex: Flex = composite.service()?;

    // Get an external URL to point the Flex service
    let flex_url = flex.external_url(FLEX_PORT).unwrap();

    // Get a handle to the HttpMock service
    let httpmock: HttpMock = composite.service()?;

    // Create a MockServer
    let mock_server = MockServer::connect_async(httpmock.socket()).await;

    // Mock a /hello request
    mock_server.mock_async(|when, then| {
        when.path_contains("/hello");
        then.status(202).body("World!");
    }).await;

    // Perform an actual request
    let response = reqwest::get(format!("{flex_url}/hello")).await?;

    // Assert on the response
    assert_eq!(response.status(), 202);

    Ok(())
}

サービスログを確認する

インテグレーションテストでエラーが発生した場合は、サービスログで原因を特定します。効率的にデバッグできるように、各テストフォルダーに専用のサービスログ (​<root-directory>/target/pdk-test/<module-name>/<test>/<service>.log​) があります。

たとえば、​say_hello​ テストには次のサービスログがあります。

<root-directory>
├── Cargo.lock
├── Cargo.toml
├── playground
├── src
├── tests
└── target
    └── pdk-test
        └── requests
            └── say_hello
                └── <service>.log