C# C-如何在生产者/消费者场景中对私有方法进行单元测试?

C# C-如何在生产者/消费者场景中对私有方法进行单元测试?,c#,multithreading,unit-testing,C#,Multithreading,Unit Testing,我试图弄清楚,在生产者/消费者场景中,是否有一些好的实践方法来单元测试业务逻辑 我们在应用程序中使用的设计是,几个公共方法接受来自外部系统的请求,将它们放在一个任务队列中,然后有另一个线程负责处理队列中的任务。问题在于,public方法不做任何复杂的事情,只需将新任务排入队列并设置手动重置事件,这样其他线程就可以开始处理新项目,所有需要测试的复杂代码都在私有方法中 我知道我可以将这些私有方法更改为内部方法,但我不喜欢这样,因为这样每个开发人员都可以直接调用这些方法而不是公共方法,从而完全绕过任务

我试图弄清楚,在生产者/消费者场景中,是否有一些好的实践方法来单元测试业务逻辑

我们在应用程序中使用的设计是,几个公共方法接受来自外部系统的请求,将它们放在一个任务队列中,然后有另一个线程负责处理队列中的任务。问题在于,public方法不做任何复杂的事情,只需将新任务排入队列并设置手动重置事件,这样其他线程就可以开始处理新项目,所有需要测试的复杂代码都在私有方法中

我知道我可以将这些私有方法更改为内部方法,但我不喜欢这样,因为这样每个开发人员都可以直接调用这些方法而不是公共方法,从而完全绕过任务队列

那么有没有办法测试这些私有方法呢?也许是小规模的重构或重新设计?谢谢

我们使用的设计框架:

订单服务:

1公共方法

public void OrderReceived(OrderDto orderDto, Action<Exception> callback)
{
    try
    {
        taskManager.ProcessWorkItem(() => OrderReceivedImpl(orderDto, callback));
    }
    catch (Exception ex)
    {
        Logger.Error(ex);

        callback(ex);
    }
}
2单独线程处理任务的方法:

private void ProcessWorkItemQueue()
{
    while (true)
    {
        var waitResult = WaitHandle.WaitAny(new WaitHandle[] { newWorkItemReceived, stopEventReceived });

        if (waitResult == 0)
        {
            while (true)
            {
                if (taskQueue.Count == 0) break;

                Action action;

                taskQueue.TryDequeue(out action);                   

                try
                {
                    action.Invoke();
                }
                catch (Exception ex)
                {
                    Logger.Error(ex);
                }

                if (stopEventReceived.WaitOne(1)) return;
            }
        }
        else
        {
            return;
        }
    }
}

您应该测试私有方法将根据给定参数更改的状态

如果该私有方法使用外部资源数据库、文件系统和其他API,那么您应该将它们抽象并作为依赖项传递给类,在单元测试中,您可以对它们进行模拟,并根据模拟的依赖项断言逻辑

另一种方法是遵循单一责任原则。有了这个原则,班级应该只有一个改变的理由。 此时此刻,我看到了下一个原因

当您将更改处理收到的订单的业务逻辑时,您需要更改您的类。 当您更改处理队列、集合或其他方式中不同订单的方式时,需要更改类。
因此,您可以将订单处理移到类之外,并将其作为依赖项传递给队列类。

我将它们更改为内部,并移到独立库。之后,我将做一个TestClass,它可以查看内部结构并在那里实现一些公共的methodsExtensionsTesting,代理调用这些方法,并在unitTests中调用它们,这样您就可以直接检查它们了

如果不想更改当前实现,请使用模拟框架,如Moq,并创建taskManager的模拟。可以设置您的模拟对象,以便对ProcessWorkItem的调用只需立即调用操作,并允许私有方法保持私有。

可能会将您的业务逻辑注入OrderService ala策略模式。比如:

public interface IOrderReceiverStrategy
{
    void OrderReceived(OrderDto orderDto, Action<Exception> callback);
}

public class OrderReceiverStrategy : IOrderReceiverStrategy
{
    public void OrderReceived(OrderDto orderDto, Action<Exception> callback)
    {
        try
        {
            // some business logic

            callback(null);
        }
        catch (Exception ex)
        {
            Logger.Error(ex);

            callback(ex);
        }
    }
}

public class OrderService
{
    public OrderService(IOrderReceiverStrategy strategy) { }


    public void OrderReceived(OrderDto orderDto, Action<Exception> callback)
    {
        try
        {
            taskManager.ProcessWorkItem(() => _strategy.OrderReceived(orderDto, callback));
        }
        catch (Exception ex)
        {
            Logger.Error(ex);

            callback(ex);
        }
    }
}

然后,您可以通过单元测试OrderReceiverStrategy来验证您的业务逻辑。

我看不出在声明这些内部方法时存在任何问题。将整个OrderService实现放在一个单独的库中,并从主应用程序中引用它。主应用程序只能使用公共部分。顺便问一下,你如何让你的开发人员不把这种方法从私有改为公有?你为什么要写这些东西?这不是一个解决了的问题吗?你想达到什么目标?
private void ProcessWorkItemQueue()
{
    while (true)
    {
        var waitResult = WaitHandle.WaitAny(new WaitHandle[] { newWorkItemReceived, stopEventReceived });

        if (waitResult == 0)
        {
            while (true)
            {
                if (taskQueue.Count == 0) break;

                Action action;

                taskQueue.TryDequeue(out action);                   

                try
                {
                    action.Invoke();
                }
                catch (Exception ex)
                {
                    Logger.Error(ex);
                }

                if (stopEventReceived.WaitOne(1)) return;
            }
        }
        else
        {
            return;
        }
    }
}
public interface IOrderReceiverStrategy
{
    void OrderReceived(OrderDto orderDto, Action<Exception> callback);
}

public class OrderReceiverStrategy : IOrderReceiverStrategy
{
    public void OrderReceived(OrderDto orderDto, Action<Exception> callback)
    {
        try
        {
            // some business logic

            callback(null);
        }
        catch (Exception ex)
        {
            Logger.Error(ex);

            callback(ex);
        }
    }
}

public class OrderService
{
    public OrderService(IOrderReceiverStrategy strategy) { }


    public void OrderReceived(OrderDto orderDto, Action<Exception> callback)
    {
        try
        {
            taskManager.ProcessWorkItem(() => _strategy.OrderReceived(orderDto, callback));
        }
        catch (Exception ex)
        {
            Logger.Error(ex);

            callback(ex);
        }
    }
}