如何使用私有实现对此代码进行单元测试?

时间:2013-11-28 12:17:23

标签: c# unit-testing

我知道有些问题与此类似,但似乎没有一种方法可以满足以下代码片段在单元测试方面的正确解决方法

public class InMemoryQueue : ICloudQueue
{
    private Queue<object> _inMemoryCollection = new Queue<object>();
    private readonly ServiceBusQueueSettings _settings;

    public InMemoryQueue(ServiceBusQueueSettings settings = null)
    {
        _settings = settings;

        if (_settings == null) _settings = new ServiceBusQueueSettings();
    }

    public async Task<IEnumerable<T>> ReceieveAsync<T>(int batchCount)
    {
        List<T> result = new List<T>();
        for (int i = 0; i < batchCount; i++)
        {
            _inMemoryCollection.Dequeue();
        }
        return result;
    }

    public async Task AddToAsync<T>(IEnumerable<T> items)
    {
        items.ToList().ForEach(x=>_inMemoryCollection.Enqueue(x));
    }
}

好的,我想将这个ICloudQueue实现单元化,以确保通过AddToAsync添加的项目保存在内存中,并且从内存中提取对RecieveAsync的任何调用。

然而 - 复杂的是数据保存在_inMemoryCollection中,这是一个私有变量。 如果_inMemoryCollection队列WASNT为private,则其中一个测试可能如下所示:

    [Test]
    public async Task Adding_To_Queue_Results_In_Message_Added_To_List()
    {
        //arrange
        var listToAdd = new List<object>() { new object() };
        var inMemoryQueue = new InMemoryQueue();

        //act
        await inMemoryQueue.AddToAsync(listToAdd);

        //assert
        Assert.IsTrue(inMemoryQueue._inMemoryCollection.Count == 1);
    }

我想我可以将_inMemoryCollection更改为内部,然后使用[InternalsVisibleTo]属性将其公开给单元测试。虽然我不情愿,因为这意味着改变实现只是为了满足单元测试。

这是我唯一的选择吗?

3 个答案:

答案 0 :(得分:3)

我认为您可以通过接收它们来检查添加到队列中的项目是否已排队(实际上这是您队列的预期行为):

[Test]
public void Can_Receive_Added_Items()
{
    //arrange        
    var listToAdd = new List<object> { new object() };
    var inMemoryQueue = new InMemoryQueue();

    //act
    inMemoryQueue.AddToAsync(listToAdd).Wait();

    //assert
    var queuedItems = inMemoryQueue.ReceieveAsync(listToAdd.Count).Result;
    CollectionAssert.AreEqual(listToAdd, queuedItems);
}

BTW目前,当您将项目排队时,您不会将项目添加到result列表,因此您的测试将失败。

答案 1 :(得分:1)

可以通过依赖注入(例如构造函数)提供InMemoryQueue内队列的真实实现,这将允许您测试它:

public class InMemoryQueue : ICloudQueue
{
    private IQueue<object> _queue;
    private readonly ServiceBusQueueSettings _settings;

    public InMemoryQueue(IQueue<object> queue, ServiceBusQueueSettings settings = null)
    {
        _queue = queue;
        _settings = settings;

        if (_settings == null) _settings = new ServiceBusQueueSettings();
    }

    public async Task<IEnumerable<T>> ReceieveAsync<T>(int batchCount)
    {
        List<T> result = new List<T>();
        for (int i = 0; i < batchCount; i++)
        {
            _queue.Dequeue();
        }
        return result;
    }

    public async Task AddToAsync<T>(IEnumerable<T> items)
    {
        items.ToList().ForEach(x => _queue.Enqueue(x));
    }
}

不幸的是,System.Collections.Generic.Queue<T>没有实现任何IQueue<T>接口,所以你必须介绍它:

public interface IQueue<T>
{
    void Enqueue(T item);
    T Dequeue();
    int Count { get; }
}

因此要使用InMemoryQueue测试Queue<T>,必须写一个包装器:

public class QueueWrapper<T> : IQueue<T>
{
    private Queue<T> queue; 
    public QueueWrapper()
    {
        queue = new Queue<T>();
    }
    public void Enqueue(T item)
    {
        queue.Enqueue(item);
    }

    public T Dequeue()
    {
        return queue.Dequeue();
    }

    public int Count
    {
        get
        {
            return queue.Count;
        }
    }
}

最终测试看起来:

public async void TestMethod()
{
    //arrange
    var listToAdd = new List<object>() { new object() };
    var queue = new QueueWrapper<object>();
    var inMemoryQueue = new InMemoryQueue(queue);

    //act
    await inMemoryQueue.AddToAsync(listToAdd);

    //assert
    Assert.IsTrue(queue.Count == 1);
}

答案 2 :(得分:0)

我认为唯一的方法就是改变你的测试方法。

您必须从流程角度编写测试。

首先执行添加,然后执行接收。您必须断言您收到的元素与您添加的元素相同。

如果您想确保只添加一个元素,请再次执行接收,确保您获得一个空列表。