适用于:Azure 逻辑应用(标准)
备注
此功能为预览版,受 Azure 预览版补充使用条款的约束。
单元测试是使应用或解决方案在整个软件开发生命周期内保持可靠且准确的基本做法。 单元测试可帮助你高效、系统地验证解决方案中的关键组件。
对于标准逻辑应用工作流,可以使用 Visual Studio Code 和 Azure 逻辑应用(标准)扩展创建单元测试。 此功能允许你使用工作流定义创建单元测试,并将它们定制为逻辑应用解决方案支持的方案-所有这些方案无需连接到任何外部服务、系统或 API。 此方法使你无需与外部服务、系统或 API 交互即可测试工作流,并提供以下优势:
通过在部署到其他环境之前识别和解决潜在问题来提高工作流质量。
在简化单元测试与开发流程集成的同时,确保工作流行为的一致性和准确性。
本指南演示如何从工作流创建单元测试定义。 此定义模拟来自每个工作流作的外部调用,而无需更改工作流逻辑。 为工作流创建单元测试时,将获得包含以下文件夹的单元测试项目:
一个文件夹,包含工作流中每个可模拟操作的强类型类。
为每个单元测试定义准备的文件夹。 此文件夹包含包含示例类和方法的 C# 文件。 使用此类和方法设置自己的断言,确认工作流的行为符合预期,并确保工作流在更大的 Azure 生态系统中可靠且可预测。
一个 Azure 帐户和订阅。 如果没有订阅,可以注册 Azure 帐户。
Visual Studio Code 中的标准逻辑应用项目,其中包含至少一个用于创建单元测试的工作流定义。
有关 Visual Studio Code 设置和项目创建的详细信息,请参阅 使用 Visual Studio Code 创建标准逻辑应用工作流。
此版本目前仅支持 C# 来创建单元测试。
此版本不支持非模拟操作。 请确保模拟工作流执行路径中的所有操作。
此版本不支持以下操作类型:
- 集成帐户操作
- 数据映射器操作
- 自定义代码操作
- XML操作
- Liquid 操作
- EDI 编码和解码操作
以下列表包含有关标准工作流单元测试的基本但重要概念:
逻辑应用单元测试
注入模拟对象的受控工作流执行。 这些对象表示工作流触发器或依赖于外部服务或系统的动作。
可模拟操作
依赖于外部服务或系统的工作流操作。 可以将这些操作转换为模拟操作,用于创建和执行单元测试。
在 Visual Studio Code 中,打开标准逻辑应用项目。
在您的项目中,展开工作流定义文件夹。
从 workflow.json 文件的快捷菜单中,选择 “打开设计器”。
在设计器工具栏上,选择“ 创建单元测试”。
提供用于单元测试、单元测试类和 C# 文件的名称。
现在,项目工作区中会显示一个名为 “测试 ”的新文件夹。 此文件夹具有以下结构:
文件夹或文件 DESCRIPTION Tests
|| <logic-app-name
>在 Tests
文件夹中,向逻辑应用项目添加单元测试时,将显示一个 <logic-app-name
> 文件夹。Tests
|| <logic-app-name
>
||| <workflow-name
>在 < logic-app-name
> 文件夹中,为工作流添加单元测试时,将显示一个 <workflow-name
> 文件夹。Tests
|| <logic-app-name
>
||| <workflow-name
>
||||MockOutputs
<operation-name-outputs
>|||||。cs
在< workflow-name
>文件夹中,MockOutputs
文件夹包含一个C#(.cs)文件,该文件包含了工作流中每个连接器操作的强类型类。 每个 .cs 文件名都使用以下格式:
<operation-name
>[Trigger\|Action
]Output.cs
如果连接器操作具有 动态协定,则每个 动态类型会出现一个类。 动态类型是指基于为该参数提供的值具有不同输入和输出的作参数。 可以使用这些类扩展单元测试并从头开始创建新的模拟。Tests
|| <logic-app-name
>
||| <workflow-name
>
|||| <unit-test-name
>
||||| <unit-test-name
>.cs
在 < workflow-name
> 文件夹中,该 <unit-test-name
> 文件夹包含一个 <unit-test-name
>.cs
文件。 使用此文件(其中包含示例 C# 类和方法)来运行和断言结果。 可以编辑此文件以匹配特定的测试方案。
此单元测试类提供一个框架,用于通过模拟触发器和动作来测试标准逻辑应用工作流。 此类允许你测试工作流,而无需实际调用外部服务或 API。
典型的单元测试类使用以下结构:
[TestClass]
public class <unit-test-name>
{
public TestExecutor TestExecutor;
[TestInitialize]
public void Setup()
{
this.TestExecutor = new TestExecutor("<workflow-name>/testSettings.config");
}
// Add test methods here.
// Add helper methods here.
}
此方法使用测试设置配置文件的路径实例化 TestExecutor
类。 该方法在每次测试执行之前运行,并创建一个新实例 TestExecutor
。
[TestInitialize]
public void Setup()
{
this.TestExecutor = new TestExecutor("<workflow-name>/testSettings.config");
}
以下部分介绍可在单元测试类中使用的示例测试方法。
以下方法演示如何使用静态模拟数据测试工作流。 在此方法中,可以完成以下任务:
- 在模拟的操作上设置属性值。
- 使用配置的模拟数据执行工作流。
- 确认执行成功。
[TestMethod]
public async Task <workflow-name>_<unit-test-name>_ExecuteWorkflow_SUCCESS_Sample1()
{
// PREPARE mock: Generate mock trigger data.
var triggerMockOutput = new WhenMessagesAreAvailableInAQueuePeeklockTriggerOutput();
// Sample that shows how to set the properties for triggerMockOutput
// triggerMockOutput.Body.Id = "SampleId";
var triggerMock = new WhenMessagesAreAvailableInAQueuePeeklockTriggerMock(outputs: triggerMockOutput);
// Generate mock action data.
var actionMockOutput = new CallExternalAPIActionOutput();
// Sample that shows how to set the properties for actionMockOutput
// actionMockOutput.Body.Name = "SampleResource";
// actionMockOutput.Body.Id = "SampleId";
var actionMock = new CallExternalAPIActionMock(name: "Call_External_API", outputs: actionMockOutput);
// ACT: Create the UnitTestExecutor instance. Run the workflow with mock data.
var testMock = new TestMockDefinition(
triggerMock: triggerMock,
actionMocks: new Dictionary<string, ActionMock>()
{
{actionMock.Name, actionMock}
});
var testRun = await this.TestExecutor
.Create()
.RunWorkflowAsync(testMock: testMock).ConfigureAwait(continueOnCapturedContext: false);
// ASSERT: Confirm successful workflow execution and that the status is 'Succeeded'.
Assert.IsNotNull(value: testRun);
Assert.AreEqual(expected: TestWorkflowStatus.Succeeded, actual: te
stRun.Status);
}
以下方法演示如何将动态模拟数据与回调方法配合使用。 此方法提供两个选项来动态生成模拟数据:
- 定义单独的回调方法。
- 使用 内联 lambda 函数。
这两种方法都允许基于单元测试执行上下文创建动态响应。
[TestMethod]
public async Task <workflow-name>_<unit-test-name>_ExecuteWorkflow_SUCCESS_Sample2()
{
// PREPARE: Generate mock trigger data.
var triggerMockOutput = new WhenMessagesAreAvailableInAQueuePeeklockTriggerOutput();
// Sample that shows how to set triggerMockOutput properties.
// triggerMockOutput.Body.Flag = true;
var triggerMock = new WhenMessagesAreAvailableInAQueuePeeklockTriggerMock(outputs: triggerMockOutput);
// PREPARE: Generate mock action data.
// OPTION 1: Define a callback class.
var actionMock = new CallExternalAPIActionMock(name: "Call_External_API", onGetActionMock: CallExternalAPIActionMockOutputCallback);
// OPTION 2: Define inline with a lambda function.
/*var actionMock = new CallExternalAPIActionMock(name: "Call_External_API", onGetActionMock: (testExecutionContext) =>
{
return new CallExternalAPIActionMock(
status: TestWorkflowStatus.Succeeded,
outputs: new CallExternalAPIActionOutput {
// If this account contains a JObject Body,
// set the properties you want here:
// Body = "something".ToJObject()
}
);
});*/
// ACT: Create the UnitTestExecutor instance. Run the workflow with mock data.
var testMock = new TestMockDefinition(
triggerMock: triggerMock,
actionMocks: new Dictionary<string, ActionMock>()
{
{actionMock.Name, actionMock}
});
var testRun = await this.TestExecutor
.Create()
.RunWorkflowAsync(testMock: testMock).ConfigureAwait(continueOnCapturedContext: false);
// ASSERT: Confirm successful workflow execution and that the status is 'Succeeded'.
Assert.IsNotNull(value: testRun);
Assert.AreEqual(expected: TestWorkflowStatus.Succeeded, actual: testRun.Status);
}
以下部分介绍示例测试方法使用的方法。 帮助程序方法显示在类定义中的测试方法下。
以下方法动态生成模拟数据。 方法名称根据测试方法中用于静态或动态模拟数据的模拟动作名称而有所不同。 可以编辑此方法以基于测试方案要求返回不同的模拟响应,或者将其用作模板来创建自己的动态回调方法。
public CallExternalAPIActionMock CallExternalAPIActionMockOutputCallback(TestExecutionContext context)
{
// Sample mock data: Dynamically change the mocked data for 'actionName'.
return new CallExternalAPIActionMock(
status: TestWorkflowStatus.Succeeded,
outputs: new CallExternalAPIActionOutput {
// If this account contains a JObject Body,
// set the properties you want here:
// Body = "something".ToJObject()
}
);
}