3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C#】 TUnitを用いたロジックアプリの単体テスト手法 【導入編】

3
Posted at

【概要】

Logic Apps に対して TUnit を用いた自動テストを導入する手法を紹介します。
本記事の手順を実施することで、従来の手動確認では見落としやすいパターンを検出でき、CI/CD パイプラインへの組み込みも容易になります。これにより、リファクタリング後のデグレード検知を効率的に自動化することができます。

【テスト環境】

  • 開発環境
    • 開発ツール:Visual Studio 2022 (17.x)
    • .NET SDK: .NET 8.0
    • テストフレームワーク: TUnit
    • 言語: C#
  • ロジックアプリ
    • Azure Logic Apps: Consumption

【注意点】

本記事の手法は Logic Apps Consumption プランで自動テストを行うために、テストフレームワークを一部ラップして管理 API を直接呼び出しています。
一方、Logic Apps Standard プランを利用する場合は、SDK が標準で対応しているためラップは不要で、そのまま利用可能です。

【Visual Studioの準備】

手順①:ソリューションとテスト対象プロジェクトの作成

まず、テスト対象のコードを含むプロジェクトと、それとは別のテストプロジェクトを格納するためのソリューションを作成します。

Visual Studioを開き、新しいプロジェクトの作成を選択します。
「コンソール アプリ」(C#)テンプレートを検索して選択し、「次へ」をクリックします。
プロジェクト名(例:UnitTestProject)を入力します。
ソリューション名(例:TUnitSolution)を入力し、「次へ」をクリックしてプロジェクトを作成します。
画像.png

手順②:NuGetパッケージのインストール

作成したテストプロジェクトにTUnitパッケージを追加します。

  1. ソリューションエクスプローラーでUnitTestProjectプロジェクトを右クリックし、「NuGet パッケージの管理...」を選択します。
  2. 「参照」タブに移動します。
  3. 検索ボックスに「TUnit」と入力します。
  4. TUnit パッケージを選択し、右側のペインで最新バージョン(必要に応じてプレリリース版も含む)を選んで「インストール」をクリックします。

画像 (1).png

  1. 続けて検索ボックスに「Newton」と入力し「Newtonsoft.Json」をインストールします。
  2. 最後に検索ボックスに「IPB」と入力し「IPB.LogicApp.Standard.Testing」をインストールします。

手順③: テストコードの作成

UnitTestProject を開き、以下のコードを追加します。

using IPB.LogicApp.Standard.Testing.Helpers;
using Newtonsoft.Json.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using TUnit;
using TUnit.Assertions;
namespace UTest.TUnit
{
    namespace UTest.TUnit
    {
        public sealed class ConsumptionLogicAppWrapper
        {
            private readonly string _subscriptionId;
            private readonly string _resourceGroup;
            private readonly string _logicAppName;
            private readonly string _bearerToken;

            public ConsumptionLogicAppWrapper(
                string subscriptionId,
                string resourceGroup,
                string logicAppName,
                string bearerToken
            )
            {
                _subscriptionId = subscriptionId;
                _resourceGroup = resourceGroup;
                _logicAppName = logicAppName;
                _bearerToken = bearerToken;
            }

            /// <summary>
            /// Logic App の callbackUrl を取得する (Consumption 用)
            /// </summary>
            public string GetCallbackUrl(string triggerName = "When_an_HTTP_request_is_received")
            {                  
                using var client = new HttpClient { BaseAddress = new Uri("https://management.azure.com") };
                client.DefaultRequestHeaders.Authorization =
                    new AuthenticationHeaderValue("Bearer", _bearerToken);

                var url = $"/subscriptions/{_subscriptionId}"
                        + $"/resourceGroups/{_resourceGroup}"
                        + $"/providers/Microsoft.Logic/workflows/{_logicAppName}"
                        + $"/triggers/{triggerName}/listCallbackUrl?api-version=2019-05-01";

                var req = new HttpRequestMessage(HttpMethod.Post, url)
                {
                    Content = new StringContent("{}", Encoding.UTF8, "application/json")
                };

                var res = client.Send(req);
                var body = res.Content.ReadAsStringAsync().Result;
                res.EnsureSuccessStatusCode();

                return JObject.Parse(body)["value"]!.ToString(); // SAS付きURLを返す
            }

            /// <summary>
            /// 実際に Logic App を起動する
            /// </summary>
            public HttpResponseMessage TriggerLogicApp(string callbackUrl, string jsonPayload)
            {
                using var http = new HttpClient();
                var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
                var res = http.PostAsync(callbackUrl, content).Result;
                return res;
            }
        }


        class TestClass
        {
            [Test]
            public async Task MsTest_Echo_GreenPath()
            {
                // クライアント設定オブジェクトの作成
                var authHelper = new AuthenticationHelper();
                authHelper.ClientId = "[Get this from an app registration in azure ad]";
                authHelper.ClientSecret = "[Get this from an app registration in azure ad]";
                authHelper.TenantId = "[Your azure ad tenant id]";
                var token = authHelper.GetBearerTokenFromAzureAD();

                // ロジックアプリへのコネクション
                var IPBWrapper = new ConsumptionLogicAppWrapper(
                    subscriptionId: "[Subscription id]",
                    resourceGroup: "[Resource Group Name]",
                    logicAppName: "[Logic App Name]",
                    bearerToken: token
                );

                var callbackUrl = IPBWrapper.GetCallbackUrl("When_an_HTTP_request_is_received");

                var payload = Newtonsoft.Json.JsonConvert.SerializeObject(new
                {
                    sum1 = 1,
                    sum2 = 5
                });

                var runRes = IPBWrapper.TriggerLogicApp(callbackUrl, payload);
                var runBody = runRes.Content.ReadAsStringAsync().Result;

                Console.WriteLine($"{(int)runRes.StatusCode} {runRes.ReasonPhrase}");
                Console.WriteLine(runBody);

                // ステータスコード
                await Assert.That(runRes.StatusCode).IsEqualTo(HttpStatusCode.OK);

                // 計算結果の確認
                var result = int.Parse(runBody);
                await Assert.That(result).IsEqualTo(6);
            }

        }
    }
}

【注意】
今回のコードはあくまで テスト用の簡易的な実装として、直接的な書き方をしています。
実装にあたり規模が大きくなったり、本番環境で利用する場合は、

HttpClient は HttpClientFactory を利用して管理する
設定値(ClientId, Secret など)は appsettings.json や Key Vault に移す
といった形で整備してください。

【ロジックアプリの設定】

手順①:アプリ登録(サービス プリンシパル)を作成

  1. Azure Portal → Microsoft Entra ID → アプリの登録 → 新規登録
  2. 名前:UnitTestApp...など。
  3. サポートされているアカウントの種類:既定のまま
  4. リダイレクトURI:不要(クライアント資格情報フローだけ使う)
  5. 登録後、次を控える アプリケーション(クライアント)ID(例:bfd42140-...)
  • ディレクトリ(テナント)
  • ID(例:e4c89f6f-...)

クライアント シークレットの作成

  1. アプリの詳細 → 証明書とシークレット → 新しいクライアント シークレット
  2. 期限を選んで作成
  3. 表示された“値(Value)”を必ずコピー(← ID ではなく 値。)

ここで参照した以下の値を必ず控えてください。
ClientId / ClientSecret(値)/ TenantId

手順②:RBAC(ロール)を割り当てる

テストコードからサービス プリンシパルに Azure RBAC を付与します。
Portal からの手順

  1. 対象スコープ(リソース グループ )を開く
  2. アクセス制御 (IAM) → ロールの割り当ての追加
  3. 役割:Contributor
  4. メンバーで、作成したアプリ(例:UnitTestApp)を検索して選択 → 追加

手順③:ロジックアプリを作成する(既に作成済みの場合はスキップ)

Logic App (Consumption) の選択

  1. 検索バーで 「Logic App」 と入力。

  2. 「Logic App (Consumption)」を選択して 作成 をクリック。
    画像 (2).png

  3. リソースグループを手順②で設定したものとし、任意のロジックアプリ名を入力してロジックアプリを作成する。

  4. テスト用にAPIで2つの整数を受け取り、足し算した結果をResponseするロジックアプリを作成する。
    画像 (3).png

【ロジックアプリテスト用コード】


{
    "definition": {
        "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
        "contentVersion": "1.0.0.0",
        "triggers": {
            "When_an_HTTP_request_is_received": {
                "type": "Request",
                "kind": "Http",
                "inputs": {
                    "schema": {
                        "type": "object",
                        "properties": {
                            "sum1": {
                                "type": "integer"
                            },
                            "sum2": {
                                "type": "integer"
                            }
                        }
                    }
                }
            }
        },
        "actions": {
            "Response": {
                "type": "Response",
                "kind": "Http",
                "inputs": {
                    "statusCode": 200,
                    "body": "@outputs('Compose')"
                },
                "runAfter": {
                    "Compose": [
                        "Succeeded"
                    ]
                }
            },
            "Compose": {
                "type": "Compose",
                "inputs": "@add(triggerBody()?['sum1'],triggerBody()?['sum2'])",
                "runAfter": {}
            }
        },
        "outputs": {},
        "parameters": {
            "$connections": {
                "type": "Object",
                "defaultValue": {}
            }
        }
    },
    "parameters": {
        "$connections": {
            "type": "Object",
            "value": {}
        }
    }
}

【テストの実行】

手順①:ロジックアプリの設定値をテストコードに反映する。

UnitTestProjectプロジェクトのテストコードを開き、以下の設定値をロジックアプリ設定時に控えたものに修正する。

// クライアント設定オブジェクトの作成
   var authHelper = new AuthenticationHelper();
   authHelper.ClientId = "[Get this from an app registration in azure ad]";
   authHelper.ClientSecret = "[Get this from an app registration in azure ad]";
   authHelper.TenantId = "[Your azure ad tenant id]";
   var token = authHelper.GetBearerTokenFromAzureAD();

// ロジックアプリへのコネクション
   var IPBWrapper = new ConsumptionLogicAppWrapper(
           subscriptionId: "[Subscription id]",
           resourceGroup: "[Resource Group Name]",
           logicAppName: "[Logic App Name]",
           bearerToken: token
           );

手順②:テストの実行

最後に、テスト エクスプローラーを使用してテストを実行します。

  1. Visual Studioのメニューから「テスト」 > 「テスト エクスプローラー」を開きます。
  2. テストが自動的に検出されて「MsTest_Echo_GreenPath()」が表示されます。
  3. テストを右クリックし、「実行」ボタンをクリックしてテストを実行します。
    画像 (4).png

【最後に】

以上で TUnit を用いた Logic Apps の自動テストが完了となります。
今回は最小構成のロジックアプリを対象にしたシンプルなテストを実装しましたが、実際の業務シナリオでは複数の API やデータベースとの依存関係を持つ状況が多いかと思います。
そのような場合は、API や DB 部分を Mock 化してテストを行う手法を用いることで外部依存を切り離しつつ柔軟なテストが可能になります。
また、単体テストは「変更後に一連の処理を確実に検証できること」で真価を発揮するので、今後 CI/CD パイプラインに単体テストを組み込む方法についても紹介していければと考えています。

【参考文献】

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?