【问题标题】:Is there any way to mock Azure CloudQueueClient or CloudQueue?有什么方法可以模拟 Azure CloudQueueClient 或 CloudQueue?
【发布时间】:2016-07-21 09:10:02
【问题描述】:

我正在为我的代码编写单元测试,并且遇到了一个方法,该方法在尝试创建队列并将 AddMessage 添加到队列时会引发 StorageException。我想测试异常处理是否正常。为了做到这一点,我有一个为 CloudQueue 使用模拟的想法,但后来发现这个类是密封的。有什么方法可以在不实际更改生产代码的情况下测试异常处理(或强制 StorageException)?

【问题讨论】:

  • 我看到的一种方法是您需要将 CloudQueue 包装在一个接口中,然后模拟该接口以抛出您想要处理的所需异常。但这需要您更改代码。我没有看到任何其他方式。
  • 是的,我想到了这个方法。但是我不想仅仅为了测试目的而重构我的代码。所以我想没有办法涵盖这两个结果,是吧?

标签: c# .net azure moq


【解决方案1】:

处理这个问题的最简单方法是使用CloudQueueClient(这是@tyrion)建议的接口,上面..但也是ICloudQueue的接口

public interface ICloudQueueClientWrapper
{
    ICloudQueueWrapper GetQueueReference(string queueName);
}

// ----------------

public class CloudQueueClientWrapper : ICloudQueueClientWrapper
{
    private readonly Lazy<CloudQueueClient> _cloudQueueClient;

    public CloudQueueClientWrapper(string connectionStringName)
    {
        connectionStringName.ShouldNotBeNullOrWhiteSpace();

        _cloudQueueClient = new Lazy<CloudQueueClient>(() =>
        {
            // We first need to connect to our Azure storage.
            var storageConnectionString = CloudConfigurationManager.GetSetting(connectionStringName);
            var storageAccount = CloudStorageAccount.Parse(storageConnectionString);

            // Create the queue client.
            return storageAccount.CreateCloudQueueClient();
        });
    }

    public ICloudQueueWrapper GetQueueReference(string queueName)
    {
        queueName.ShouldNotBeNullOrWhiteSpace();

        var cloudQueue = _cloudQueueClient.Value.GetQueueReference(queueName);
        return new CloudQueueWrapper(cloudQueue);
    }

    // Add more methods here which are a one-to-one match against the underlying CQC.
}

这是第一个接口和包装器...注意这会返回一个ICloudQueue 实例...所以现在就这样做...

public interface ICloudQueueWrapper
{
    Task AddMessageAsync(CloudQueueMessage message);
}

public class CloudQueueWrapper : ICloudQueueWrapper
{
    private readonly CloudQueue _cloudQueue;

    public CloudQueueWrapper(CloudQueue cloudQueue)
    {
        cloudQueue.ShouldNotBeNull();

        _cloudQueue = cloudQueue;
    }

    public async Task AddMessageAsync(CloudQueueMessage message)
    {
        message.ShouldNotBeNull();

        await _cloudQueue.AddMessageAsync(message);
    }
}

好的...现在让我们尝试在一些单元测试中使用它:)

    [Theory]
    [MemberData(nameof(StockIds))]
    public async Task GivenSomeData_DoFooAsync_AddsDataToTheQueue(string[] stockIds)
    {
        // Arrange.
        var cloudQueue = Mock.Of<ICloudQueueWrapper>();
        var cloudQueueClient = Mock.Of<ICloudQueueClientWrapper>();
        Mock.Get(cloudQueueClient).Setup(x => x.GetQueueReference(It.IsAny<string>()))
            .Returns(cloudQueue);
        var someService = new SomeService(cloudQueueClient);

        // Act.
        await someService.DoFooAsync(Session);

        // Assert.
        // Did we end up getting a reference to the queue?
        Mock.Get(cloudQueueClient).Verify(x => x.GetQueueReference(It.IsAny<string>()), Times.Once);

        // Did we end up adding something to the queue?
        Mock.Get(cloudQueue).Verify(x => x.AddMessageAsync(It.IsAny<CloudQueueMessage>()), Times.Exactly(stockids.Length));
    }

【讨论】:

    【解决方案2】:

    过去,我们曾使用 Microsoft Fakes 框架对类似的 Azure SDK 类进行单元测试。前面有一点学习曲线,但效果很好。

    https://msdn.microsoft.com/en-us/library/hh549175.aspx

    【讨论】:

    • 谢谢,我会调查的。
    【解决方案3】:

    @Pure.Krome 的解决方案是一个很好的解决方案 - 我只想指出他对 CloudQueueClientWraper 的实现可能存在的问题:

    public class CloudQueueClientWrapper : ICloudQueueClientWrapper
    {
        private readonly Lazy<CloudQueueClient> _cloudQueueClient;
    
        public CloudQueueClientWrapper(string connectionStringName)
        {
            connectionStringName.ShouldNotBeNullOrWhiteSpace();
    
            _cloudQueueClient = new Lazy<CloudQueueClient>(() =>
            {
                // We first need to connect to our Azure storage.
                var storageConnectionString = CloudConfigurationManager.GetSetting(connectionStringName);
                var storageAccount = CloudStorageAccount.Parse(storageConnectionString);
    
                // Create the queue client.
                return storageAccount.CreateCloudQueueClient();
            });
        }
    
        public ICloudQueueWrapper GetQueueReference(string queueName)
        {
            queueName.ShouldNotBeNullOrWhiteSpace();
    
            var cloudQueue = _cloudQueueClient.Value.GetQueueReference(queueName);
            return new CloudQueueWrapper(cloudQueue);
        }
    
        // Add more methods here which are a one-to-one match against the underlying CQC.
    }
    

    Lazy&lt;T&gt; 缓存异常!因此,如果一个线程(或者更确切地说是Task)尝试通过执行valueFactory 委托来创建.Value 失败并出现异常,那么对.Value 的所有后续调用都将返回相同的异常。

    docs 说:

    异常缓存 当您使用工厂方法时,异常会被缓存。也就是说,如果工厂方法在线程第一次尝试访问 Lazy 对象的 Value 属性时抛出异常,则每次后续尝试都会抛出相同的异常。这确保了对 Value 属性的每次调用都会产生相同的结果,并避免不同线程获得不同结果时可能出现的细微错误。 Lazy 代表一个实际的 T,否则它会在较早的时候初始化,通常是在启动期间。在那个早期点的失败通常是致命的。如果有可能发生可恢复的故障,我们建议您将重试逻辑构建到初始化例程(在本例中为工厂方法)中,就像您不使用延迟初始化时一样。

    作为“解决方法” - 放弃内置的 Lazy&lt;T&gt; 并实现自己的。在@mariusGundersen 的this GitHub 问题上建议了一种优雅的方法

    public class AtomicLazy<T>
    {
        private readonly Func<T> _factory;
    
        private T _value;
    
        private bool _initialized;
    
        private object _lock;
    
        public AtomicLazy(Func<T> factory)
        {
            _factory = factory;
        }
    
        public AtomicLazy(T value)
        {
            _value = value;
            _initialized = true;
        }
    
        public T Value => LazyInitializer.EnsureInitialized(ref _value, ref _initialized, ref _lock, _factory);
    }
    

    【讨论】:

      猜你喜欢
      • 2016-07-18
      • 2023-04-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-18
      • 1970-01-01
      • 1970-01-01
      • 2015-12-07
      • 2011-01-03
      相关资源
      最近更新 更多