有没有办法模拟Azure CloudQueueClient或CloudQueue?

时间:2016-07-21 09:10:02

标签: c# .net azure moq

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

3 个答案:

答案 0 :(得分:6)

处理此问题的最简单方法是使用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));
    }

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

@ 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<T>缓存异常!因此,如果一个线程(或更确切地说是Task)通过执行.Value委托创建valueFactory的尝试失败并带有Exception,则对.Value的所有后续调用都将返回相同的结果例外。

docs说:

  

异常缓存使用工厂方法时,将缓存异常。也就是说,如果工厂方法在线程第一次尝试访问Lazy对象的Value属性时引发异常,则在以后的每次尝试中都会引发相同的异常。这样可以确保对Value属性的每次调用都产生相同的结果,并避免了如果不同的线程获得不同的结果可能会引起的细微错误。惰性代表实际的T,否则通常会在启动过程中在某个较早的时间点对其进行初始化。在此之前的故障通常是致命的。如果有可能发生可恢复的故障,我们建议您将重试逻辑构建到初始化例程(在本例中为factory方法)中,就像您不使用惰性初始化一样。

作为“解决方法”-放弃内置Lazy<T>并实施您自己的。 @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);
}