【问题标题】:Unit testing Void method which calls another void, starts Task()单元测试调用另一个 void 的 Void 方法,启动 Task()
【发布时间】:2015-05-12 00:06:29
【问题描述】:

我正在寻找一些关于为下面的代码编写单元测试的建议。除了实现之外(这不是我的代码,但我的任务是为它追溯编写一些测试)有人可以建议我如何测试它吗?我没有使用 nUnit 或类似的框架;我正在使用 Visual Studio 中内置的测试工具。

我对编写单元测试还很陌生,但我想我至少应该测试以下内容:

  • 有效响应传递到SaveFormBrokerResponse() 方法
  • 测试catch()抛出的有效异常
  • 正在测试已启动的Task,但不知道该怎么做

我已经从这个函数中删除了一点,主要是与一些对象的实例化和填充有关:

public void SaveResponse(IForm form, bool isLive, HttpRequestBase request)
{
    try
    {
        var response = new FormBrokerResponses();
        // Initialize some vars on response

        using (var memory = new MemoryStream())
        {
            var serializer = new DataContractSerializer(typeof(FormKeyValue[]));
            serializer.WriteObject(memory, request.Form.AllKeys.Select(r => new FormKeyValue(r, request.Form[r])).ToArray());
            memory.Flush();
            memory.Seek(0, SeekOrigin.Begin);
            response.Values = Encoding.UTF8.GetString(memory.ToArray());
        }

        _dataHandler.SaveFormBrokerResponses(response);
    }
    catch (Exception ex)
    {
        throw new Exception("boom explosions");
    }

    Task.Factory.StartNew(() => DispatchFormResponseViaEmail(form, isLive, request.Form.AllKeys.ToDictionary(r => r, r => (object)request.Form[r])));
}

我意识到测试 void 实现是棘手且有问题的,并且这里存在一些集成测试问题,但这表示我(目前)无法更改实现并且需要为我拥有的内容编写测试。

【问题讨论】:

    标签: c# unit-testing testing visual-studio-2013


    【解决方案1】:

    你不能。您创建了一个触发异步操作的方法,然后不向调用者公开任何观察该操作的完成/结果的方法。有很多方法可以做到这一点(返回任务、接受回调、事件等),但您需要做 一些事情 让调用者能够观察异步操作的结果.如果该方法没有公开任何内容,则调用者无法可靠地执行任何操作。

    【讨论】:

    • 哈,那么,感谢您澄清这一点。完全有道理。我想鉴于此,有没有一种方法可以测试调用任务的方式?换句话说,是否有一种方法可以测试任务是否以符合某些期望的参数开始?我可能对测试框架提出了很多要求,但有点了解可用的选项。谢谢!
    • @trnelson 如果您将该信息公开给方法的调用者,通过返回该信息、使用回调等,那么是的。其他任何东西都是超级脆弱的,依赖于可能发生变化的内部实现细节,因此失败的测试更有可能是错误的问题指示。
    • 绝对有道理。再深入一点,任务方法DispatchFormResponseViaEmail 也是一个void,它在失败时调用Trace.TraceError()。我想归根结底是,调用此方法的一般假设是它会通过,但失败最终会出现在日志消息中,以便稍后进行诊断。这似乎没有什么可用于测试的。
    【解决方案2】:

    如果允许您对代码进行轻微修改,我会执行以下操作,这只是一个小改动:

    public void SaveResponse(IForm form, bool isLive, HttpRequestBase request)
    {
        try
        {
            var response = new FormBrokerResponses();
            // Initialize some vars on response
    
            using (var memory = new MemoryStream())
            {
                var serializer = new DataContractSerializer(typeof(FormKeyValue[]));
                serializer.WriteObject(memory, request.Form.AllKeys.Select(r => new FormKeyValue(r, request.Form[r])).ToArray());
                memory.Flush();
                memory.Seek(0, SeekOrigin.Begin);
                response.Values = Encoding.UTF8.GetString(memory.ToArray());
            }
    
            _dataHandler.SaveFormBrokerResponses(response);
        }
        catch (Exception ex)
        {
            throw new Exception("boom explosions");
        }
    
        Dispatch(form,isLive,request);
    }
    
    virtual void Dispatch(IForm form, bool isLive, HttpRequestBase request){
        Task.Factory.StartNew(() => DispatchFormResponseViaEmail(form, isLive, request.Form.AllKeys.ToDictionary(r => r, r => (object)request.Form[r])));
    }
    

    我不知道这个类叫什么,所以假设这个类被命名为 DutClass,你现在可以派生该类的不同实现,如下所示:

    public class UnitTestClass : DutClass{
        override Dispatch(){
            //don't do anything or set a state variable that this method was called
        }
    }
    

    然后,不是测试 DutClass,而是测试 UnitTextClass,它具有不同的 Dispatch 方法实现并且根本不启动 Task。然后你可以测试这个方法是否被调用,测试异常等等。

    【讨论】:

    • 啊,有趣。因此,实际上您正在创建一个模拟对象,它在所有方面都继承自原始对象,除了您已经覆盖了 Dispatch() 方法这一事实,然后您可以验证它的执行?
    • 我很好奇,如果DispatchFormResponseViaEmail() 方法是同一个类中的私有方法,是否有可能以某种方式仍然做这样的事情,但只覆盖该私有方法,然后像那样测试?
    • 私有方法也可以是虚拟的,所以这个技巧本身有效,是的。但问题是您无法控制它何时完成,可能是当单元测试本身已经完成时,因此如果您检查状态变量检查方法是否运行,它将给出一个失败的测试。试一试,通过尝试最终并不总是有效的东西,我学到了很多东西。
    • 我已将此标记为答案,因为它最接近地回答了这个问题。感谢您对重构的建议;很有帮助!
    猜你喜欢
    • 2015-12-15
    • 1970-01-01
    • 2017-05-11
    • 1970-01-01
    • 1970-01-01
    • 2022-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多