【问题标题】:Verify methods are called in order验证方法按顺序调用
【发布时间】:2015-12-30 14:06:10
【问题描述】:

我有以下方法:

public async Task DeleteAmendment(int amendmentHeaderId, int userId)
{
    // Delete the corresponding version records.
    await _amendmentVersionService.DeleteForAmendmentAsync(amendmentHeaderId);

    // Delete the corresponding lifecycle records.
    await _amendmentLifecycleService.DeleteForAmendmentAsync(amendmentHeaderId);

    // Delete the amendment header record itself.
    await _amendmentHeaderService.DeleteAsync(amendmentHeaderId, userId);
}

我正在尝试验证方法是否按顺序调用。

我已经尝试设置回调(见下文)

AmendmentVersionService.Setup(x => x.DeleteForAmendmentAsync(It.IsAny<int>()))
    .Callback(() => ServiceCallbackList.Add("AmendmentVersionService"));

AmendmentLifecycleService.Setup(x => x.DeleteForAmendmentAsync(It.IsAny<int>()))
    .Callback(() => ServiceCallbackList.Add("AmendmentLifecycleService"));

AmendmentHeaderService.Setup(x => x.DeleteAsync(It.IsAny<int>(), It.IsAny<int>()))
    .Callback(() => ServiceCallbackList.Add("AmendmentHeaderService"));

但列表只包含字符串“AmendmentVersionService”

有什么想法吗?

【问题讨论】:

  • 为什么不按顺序调用它们?如果您在它们中的每一个中使用 await,则执行将等待每个完成,然后再调用下一个
  • 您能否向我们展示您调用 DeleteAmendment 的其余测试?
  • 执行顺序很重要,我想确保如果其他开发人员将来切换顺序,测试会中断
  • 如果您有一个无效的 DeleteAmendment 并且没有 await ,它会起作用吗? (即:如果您删除异步

标签: c# nunit moq verify


【解决方案1】:

实现相同目标但概念不同的一种方法是进行 3 次测试,每次调用一次。它有点脏,但作为备用解决方案,它可以让你走出困境

第一次通话:

  • 设置调用 2 以引发自定义类型 TestException 的异常。
  • 仅断言调用 1 已执行
  • 期待TestException 被抛出

第二次通话:

  • 设置调用 3 以引发自定义类型 TestException 的异常。
  • 断言调用 1 和 2 已执行
  • 期待TestException 被抛出

第三次通话:

  • 将所有调用设置为正常执行。
  • 断言调用 1、2 和 3 已执行

【讨论】:

    【解决方案2】:

    您可以使用延续(如下),但如果您确实需要保证这些事情按顺序发生,那么它们不应该是异步操作。通常,您希望异步操作能够同时运行;

    public async Task DeleteAmendment(int amendmentHeaderId, int userId)
    {
        Task.Run(() =>
        {
            // Delete the corresponding version records.
            await _amendmentVersionService.DeleteForAmendmentAsync(amendmentHeaderId);
         }).ContinueWith(_ => 
         {
            // Delete the corresponding lifecycle records.
            await _amendmentLifecycleService.DeleteForAmendmentAsync(amendmentHeaderId);
         }).ContinueWith(_ => 
        {
            // Delete the amendment header record itself.
            await _amendmentHeaderService.DeleteAsync(amendmentHeaderId, userId);
        });
    }
    

    【讨论】:

    • 我认为这并没有真正帮助他:他想通过单元测试确保以正确的顺序执行延续,而不是在实现中
    • 那么它将保持不变或使用.Wait。无论哪种方式,如果顺序很重要,您都不会真正运行异步操作,而是要测试它们并确保按顺序调用它们,这就是您必须这样做的方式,因为您无法保证它们将完成的顺序。
    • 如果顺序很重要,你并没有真正运行异步操作我强烈反对。异步不等于并行。 但是这不是重点。他希望进行测试以确保没有人弄乱代码并颠倒调用的顺序,无论是通过 await 还是 ConitnueWith 完成。
    • 我同意这一点,但您仍然无法知道异步操作何时完成,因为一个可能需要比另一个更长的时间,所以如果在测试下一个之前的测试结果很重要,那么您会必须控制完成的顺序。
    • 是的。您无法确定第二次调用是在第一次调用之后进行的,还是作为第一次调用返回的结果
    【解决方案3】:

    您的问题是,您将永远无法知道某个方法是否由于前一个方法的完成(等待)而被执行,或者您是否幸运地没有遭受竞争条件(在没有等待的情况下进行的调用,或者没有继续)

    您可以实际测试它的唯一方法是将默认的 TaskScheduler 替换为您自己的实现,这不会将后续任务排队。如果后续任务被调用,那么您的代码是错误的。如果不是,则意味着该任务确实是前一个任务完成的结果。

    我们在Testeroids 中完成了这项工作,这是一个我和朋友构建的测试框架。

    通过这样做,您的自定义 TaskScheduler 可以在单个线程中按顺序执行任务(以真正突出您可能遇到的时间线问题)并记录哪些任务已安排以及哪些较旧。

    如果你想做到那么彻底,你需要付出很多努力,但至少你明白了。

    为了替换默认的 TaskScheduler,您可以从我们在 Testeroids 上所做的工作中获得灵感。

    https://github.com/Testeroids/Testeroids/blob/c5f3f02e8078db649f804d94c37cdab3df89fed4/solution/src/app/Testeroids/TplTestPlatformHelper.cs

    【讨论】:

      【解决方案4】:

      感谢斯蒂芬·布里克纳 ...

      我所有的调用都是同步的,这使得 Moq 工作中的回调就像做梦一样。

      感谢您的所有帮助,非常感谢!

      【讨论】:

        猜你喜欢
        • 2014-03-21
        • 2023-03-22
        • 1970-01-01
        • 2018-05-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多