【问题标题】:Azure Functions testing when using imperative binding使用命令式绑定时的 Azure Functions 测试
【发布时间】:2018-01-11 20:59:18
【问题描述】:

到目前为止,我已经能够为 Azure Functions 设置单元测试并且效果很好。但是,对于我当前的项目,我需要使用动态或命令式绑定。 https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-csharp#imperative-bindings

这导致我的单元测试出现我似乎无法解决的问题。

我的函数如下所示:

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.ServiceBus.Messaging;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace My.Functions
{
    public static class MyFunc
    {
        [FunctionName("my-func")]
        public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequestMessage req,
            Binder binder)
        {
            dynamic data = await req.Content.ReadAsAsync<object>();
            byte[] bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
            MemoryStream stream = new MemoryStream(bytes, writable: false);

            var sbMsg = new BrokeredMessage(stream) { ContentType = "application/json" };
            var attributes = new Attribute[]
            {
                new ServiceBusAccountAttribute("some-sb-account"),
                new ServiceBusAttribute("some-queue-or-topic", AccessRights.Send)
            };
            var outputSbMessage = await binder.BindAsync<IAsyncCollector<BrokeredMessage>>(attributes);
            await outputSbMessage.AddAsync(sbMsg);

            return req.CreateResponse(HttpStatusCode.OK, "OK");
        }
    }
}

在函数代码接近尾声时,我将这个绑定器配置为保存一个 BrokeredMessages 列表。这是通过调用活页夹上的 BindAsync 来完成的。

属性是动态设置的,包含服务总线连接和主题名称。当部署到 Azure 时,这一切都很好,所以功能方面一切都很好。 到目前为止一切顺利。

但是,我正在努力让我的测试运行。为了能够调用该函数,我需要提供参数。 HttpTrigger 这很常见,但是对于 Binder 我不知道提供什么。

为了测试,我使用这种方法:

[TestMethod]
public void SendHttpReq()
{
    // Setup
    var httpRequest = GetHttpRequestFromTestFile("HttpRequest");
    var sbOutput = new CustomBinder();

    // Act
    var response = SendToServicebus.Run(httpRequest, sbOutput);

    // Assert
    Assert.AreEqual(sbOutput.Count(), 1);

    // Clean up
}

我使用从 Binder 继承的 CustomBinder,因为在 'BindAsync' 上的函数中只有一个 Binder 实例失败,抛出“对象引用未设置为对象的实例”。看来 binder 的构造函数实际上并没有被调用的意思。

在 CustomBinder 中,我重写 BindAsync 以返回 BrokeredMessages 的通用列表。

public class CustomBinder : Binder
{
 public override async Task<TValue> BindAsync<TValue>(Attribute[] attributes, CancellationToken cancellationToken = new CancellationToken())
 {
  return (TValue)((object)(new List<BrokeredMessage>()));
  }
}

投掷失败也不足为奇:

InvalidCastException:无法将“System.Collections.Generic.List'1[Microsoft.ServiceBus.Messaging.BrokeredMessage]”类型的对象转换为“Microsoft.Azure.WebJobs.IAsyncCollector`1[Microsoft.ServiceBus.Messaging. BrokeredMessage]'。

我找不到 IAsyncCollector 的实现,所以也许我需要以不同的方式处理这个问题?

我的实际目标是能够验证代理消息列表,因为该函数将输出到 Azure 服务总线。

【问题讨论】:

  • IBinder接口,但比BinderAFAIK更受限制。有你需要的方法吗?为什么不也模拟IAsyncCollector
  • 我尝试了 IBinder,但它不包含提供属性数组的方法,而这正是我所需要的。
  • 我试图实现的目标是能够读取从函数返回的代理消息列表。我想在运行函数后验证消息内容是否符合预期。
  • 这就是为什么你可以模拟IAsyncCollector。不过,验证BrokeredMessage 可能具有挑战性,甚至不确定您是否可以从中读取消息内容。
  • 在非绑定方案中,您在签名中使用 ICollector,然后很容易对此进行测试。对于 Binder 来说,这确实是一个挑战。

标签: c# unit-testing azure-functions


【解决方案1】:

正如 cmets 中提到的,我同意嘲笑它是有道理的。您明确希望单元测试您自己的代码逻辑。只考虑您自己的业务逻辑,您可能会假设实际的实际远程操作binder.BindAsync(...)(您无法控制)按预期工作。

在单元测试中模拟它应该像这样工作:

using FluentAssertions;
using Microsoft.Azure.WebJobs;
using Microsoft.ServiceBus.Messaging;
using Xunit;

[Fact]
public async Task AzureBindAsyncShouldRetrunBrokeredMessage()
{
    // arrange           
    var attribute = new ServiceBusAccountAttribute("foo");
    var mockedResult = new BrokeredMessage()
    {
        Label = "whatever"
    };

    var mock = new Mock<IBinder>();
    mock.Setup(x => x.BindAsync<BrokeredMessage>(attribute, CancellationToken.None))
        .ReturnsAsync(mockedResult);

    // act
    var target = await mock.Object.BindAsync<BrokeredMessage>(attribute);

    // assert
    target.Should().NotBeNull();
    target.Label.Should().Be("whatever");
}

我了解您的担忧可能是完整的集成测试。您似乎想测试整个链条。在这种情况下,进行单元测试可能会很困难,因为您依赖于外部系统。如果是这种情况,您可能希望在其之上创建一个单独的集成测试,方法是设置一个单独的实例。

考虑到您的函数设置为HttpTrigger,以下应该可以工作:

# using azure functions cli (2.x), browse to the output file
cd MyAzureFunction/bin/Debug/netstandard2.0

# run a new host/instance if your function
func host start 

接下来,只需对托管端点执行一个 http 请求:

$ [POST] http://localhost:7071/api/HttpTriggerCSharp?name=my-func

在这种情况下,您有一个干净且独立的集成设置。

无论哪种方式,我都想主张要么采用模拟单元测试的路线,要么为其设置单独的集成测试设置。

希望这会有所帮助...

【讨论】:

  • 谢谢朱利安。我提到了单元测试,但我真正喜欢测试的是函数的输出是否正确。所以也许术语集成测试会更好。我假设 Binder 的 BindAsync 函数可以正常工作,因为这也已通过在 Azure 中运行的测试得到证明,但我所追求的是由 binder 表示的函数的输出,因此我可以验证列表中的消息代理消息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-16
  • 1970-01-01
  • 2016-10-31
  • 1970-01-01
相关资源
最近更新 更多