Mattermostプラグインでの複雑な統合テストのためのテストコンテナの活用

この記事は投稿者によって寄稿されました ヘスス・エスピノ、Mattermostのプリンシパルエンジニア。

進化し続けるソフトウェア開発環境において、堅牢で信頼性の高いプラグイン統合を確保することは簡単なことではありません。 Mattermostにとって、プラグインテストをモックのみに頼ることが制限となり、テストが脆弱になり、統合の問題が見落とされました。 Testcontainersは、分離されたDocker環境を提供するオープンソースツールで、複雑な統合テストを実現可能にするだけでなく、効率的にします。 

このブログ記事では、MattermostがTestcontainersを採用してテスト戦略を全面的に見直し、自動化の進行、精度の向上、最小限のオーバーヘッドでのシームレスなプラグイン統合を実現した方法について詳しく説明します。

2400x1260 Mattermost プラグインでの複雑な統合テストに testcontainers を活用する

以前のアプローチ

これまで、Mattermostはプラグインのテストにモックに大きく依存していました。 このアプローチにはメリットがありましたが、大きな欠点もありました。 テストは脆弱で、コードベースに変更が加えられると壊れることがよくありました。 これにより、開発者はコードの変更を反映するためにモックを常に更新する必要があったため、テストの開発と保守が困難になりました。

さらに、モックの使用は、テストの統合の側面がほとんど見落とされることを意味していました。 このテストでは、システムのさまざまなコンポーネントが相互にどのように相互作用するかが考慮されていなかったため、本番環境で予期しない問題が発生する可能性があります。 

さらに、以前のアプローチでは、自動化された方法での適切な統合テストができませんでした。 自動化が進んでいなかったため、テストプロセスに時間がかかり、人為的ミスが発生しやすくなっていました。 これらの課題により、Mattermostのテスト戦略の転換が必要となり、複雑な統合テストに Testcontainers を採用することになりました。

統合テストに対するMattermostのアプローチ

Go用のTestcontainers

Mattermostは、Testcontainers for Goを使用して、プラグインの分離されたテスト環境を作成します。 このテスト環境には、Mattermostサーバー、PostgreSQLサーバー、および場合によってはAPIモックサーバーが含まれます。 その後、プラグインはMattermostサーバーにインストールされ、通常のAPI呼び出しまたはPlaywrightなどのエンドツーエンドのテストフレームワークを通じて、必要なテストを実行します。

Mattermostサーバー専用のTestcontainersモジュールを作成しました。このモジュールは PostgreSQL を依存関係として使用し、テスト環境が本番環境を密接にミラーリングすることを保証します。 私たちのモジュールを使用すると、開発者はMattermostサーバーに必要なプラグインを簡単にインストールおよび構成できます。

システムの分離性を向上させるために、Mattermostモジュールには、サーバー用のコンテナとPostgreSQLデータベース用のコンテナが含まれており、これらは内部Dockerネットワークを介して接続されています。

さらに、Mattermostモジュールは、データベースへの直接アクセス、Goクライアントを介したMattermost APIへの直接アクセスを可能にするユーティリティ機能、および管理者がユーザー、チャネル、チームを作成したり、構成を変更したりできるようにする一部のユーティリティ機能を公開しています。 この機能は、API呼び出し、ユーザー/チーム/チャネルの作成、構成の変更、さらにはSQLクエリの実行など、テスト中に複雑な操作を実行するのに非常に役立ちます。 

このアプローチは、テストを設定し、期待する動作を検証するためのすべてを準備するための強力なツールセットを提供します。 テスト コンテナ インスタンスの使い捨ての性質と組み合わせることで、分離されたままでシステムを理解しやすくなります。

この包括的なテストアプローチにより、Mattermostサーバーとそのプラグインのすべての側面が徹底的にテストされ、信頼性と機能が向上します。 しかし、使用のコード例を見てみましょう。

次のようなプラグインを使用して、Mattermost環境のセットアップを開始できます。

pluginConfig := map[string]any{}
options := []mmcontainer.MattermostCustomizeRequestOption{
  mmcontainer.WithPlugin("sample.tar.gz", "sample", pluginConfig),
}
mattermost, err := mmcontainer.RunContainer(context.Background(), options...)
defer mattermost.Terminate(context.Background()

Mattermostインスタンスが初期化されたら、次のようなテストを作成できます。

func TestSample(t *testing.T) {
    client, err mattermost.GetClient()
    require.NoError(t, err)
    reqURL := client.URL + "/plugins/sample/sample-endpoint"
    resp, err := client.DoAPIRequest(context.Background(), http.MethodGet, reqURL, "", "")
    require.NoError(t, err, "cannot fetch url %s", reqURL)
    defer resp.Body.Close()
    bodyBytes, err := io.ReadAll(resp.Body)
    require.NoError(t, err)
    require.Equal(t, 200, resp.StatusCode)
    assert.Contains(t, string(bodyBytes), "sample-response") 
}

ここでは、Mattermostインスタンスをいつ分解して再作成するかを決定できます。 テストごとに1回ですか? 一連のテストごとに一度ですか? それはあなた次第であり、あなたのニーズとテストの性質に厳密に依存します。

Node.js用テストコンテナ

Mattermost は、Go 用の Testcontainers を使用するだけでなく、 Testcontainers for の Node.js を活用してテスト環境をセットアップしています。 ご存じない方のために説明すると、Testcontainers for Node.jsは、Testcontainers for Goと同様の機能を提供するNode.jsライブラリです。 Testcontainers for Startups Node.js、 Testcontainers for Goで行ったのと同じ方法で環境を設定できます。 これにより、JavaScriptを使用して Playwright テストを作成し、Testcontainersによって作成された分離されたMattermost環境でそれらを実行することができ、プラグインのユーザーインターフェイスと直接対話する統合テストを実行できます。 コードは GitHub で入手できます。  

このアプローチは、Testcontainers for Goと同じ利点を提供し、よりインターフェースベースのテストツール(この場合はPlaywright)を使用することができます。 Node.js と Playwright の実装に関するコードを少し示しましょう。

各テストのコンテナを開始および停止します。

test.beforeAll(async () => { mattermost = await RunContainer() })
test.afterAll(async () => { await mattermost.stop(); })

次に、Mattermostインスタンスを実行中の他のサーバーと同様に使用して、Playwrightテストを実行できます。

test.describe('sample slash command', () => {
  test('try to run a sample slash command', async ({ page }) => {
    const url = mattermost.url()
    await login(page, url, "regularuser", "regularuser")
    await expect(page.getByLabel('town square public channel')).toBeVisible();
    await page.getByTestId('post_textbox').fill("/sample run")
    await page.getByTestId('SendMessageButton').click();
    await expect(page.getByText('Sample command result', { exact: true })).toBeVisible();
    await logout(page)
  });  
});

これら 2 つのアプローチを使用すると、他の合成環境をモックしたり使用したりすることなく、API とインターフェイスをカバーする統合テストを作成できます。 また、Testcontainersインスタンスを再利用するかどうかを意識的に決定するため、絶対的に分離してテストできます。 また、高度な分離を達成できるため、エンドツーエンドのテストを行う際に汚染された環境によって引き起こされる不安定さを回避できます。

使用例

現在、このアプローチを 2 つのプラグインに使用しています。

1。 Mattermost AIコパイロット

この統合は、AI 大規模言語モデル (LLM) を使用してユーザーの日常業務を支援し、スレッドや会議の概要作成、コンテキストベースの問い合わせなどを提供します。

このプラグインは豊富なインターフェースを備えているため、Testcontainers for Node と Playwright のアプローチを使用して、インターフェースを通じてシステムを適切にテストできるようにしました。 また、このプラグインは API を介して AI LLM を呼び出す必要があります。 このリソースを大量に消費するタスクを回避するために、任意の API をシミュレートする別のコンテナである API モックを使用します。

このアプローチにより、サーバー側のコードだけでなく、開発中に何も壊れていないことを確認できるため、インターフェイス側にも自信を持つことができます。

2。 Mattermost MS Teamsプラグイン

この統合は、MS TeamsとMattermostをシームレスに接続し、両方のプラットフォーム間でメッセージを同期するように設計されています。

このプラグインでは、主にAPI呼び出しを行う必要があるため、Testcontainers for Goを使用し、Goで書かれたクライアントを使用してAPIを直接ヒットしました。 この場合も、プラグインはサードパーティのサービスであるMicrosoftの Microsoft Graph API に依存しています。 そのために、APIモックも使用しており、サードパーティのサービスに依存せずにプラグイン全体をテストできます。

Microsoft Graph の呼び出しを適切に処理していることを確認するために、同じ Testcontainers インフラストラクチャを使用して、実際の Teams API との統合テストがまだいくつかあります。

Testcontainersライブラリを使用する利点

統合テストに Testcontainers を使用すると、次のような利点があります。

  • 分離: 各テストは独自の Docker コンテナーで実行されるため、テストは互いに完全に分離されます。 このアプローチにより、テストが互いに干渉するのを防ぎ、各テストが白紙の状態から開始されます。
  • 再現性: テスト環境は自動的に設定されるため、テストの再現性は高くなります。 つまり、開発者はテストを複数回実行して同じ結果を得ることができ、テストの信頼性が向上します。
  • 使いやすさ: Testcontainersは、Dockerコンテナのセットアップと破棄の複雑さをすべて処理するため、使いやすいです。 これにより、開発者はテスト環境の管理ではなく、テストの作成に集中できます。

Testcontainersでテストが簡単に

Mattermostがプラグインの複雑な統合テストにTestcontainersライブラリを使用していることは、Testcontainersのパワーと汎用性の証です。

Mattermostは、十分に分離された再現性のあるテスト環境を作成することにより、プラグインが徹底的にテストされ、高い信頼性が保証されることを保証します。

さらに詳しく