【问题标题】:How to change the return value of a method in a unit test mid execution如何在单元测试执行中更改方法的返回值
【发布时间】:2016-01-10 19:08:09
【问题描述】:

我正在尝试设计一个单元测试来测试重试循环模式。我能想到的唯一方法是在测试中途更改嵌入在重试循环中心的方法返回的内容。

例如...我想在测试的前 5 秒内为特定方法抛出异常。然后阻止该异常被抛出,并在该点之后实际响应一些有效数据。

前 5 秒:

service.MethodToRetry(Arg.Any<string>()).ThrowsForAnyArgs(new Exception());

然后异常条件被移除,MethodToRetry() 正常完成。

这是可能的还是我完全错误的方式?我正在使用 xunit 和 nsubstitute 在 c# 中工作。

【问题讨论】:

  • 你在使用 NSubstitute 吗?
  • @JoelRamosMichaliszen 是的,使用 NSubstitute

标签: c# unit-testing xunit.net nsubstitute


【解决方案1】:

注意:这里的测试是为了演示 NSubstitute 的行为。在实际测试中,我们不会测试替代品。 :)

测试重试的一种方法是存根多次返回(如果您需要条件而不是针对特定数量的调用失败,这可能不适用于您的情况,但我认为我会从最简单的方法开始):

    [Test]
    public void StubMultipleCalls() {
        Func<string> throwEx = () => { throw new Exception(); };
        var sub = Substitute.For<IThingoe>();
        // Stub method to fail twice, then return valid data
        sub.MethodToRetry(Arg.Any<string>())
           .Returns(x => throwEx(), x => throwEx(), x => "works now");

        // The substitute will then act like this:
        Assert.Throws<Exception>(() => sub.MethodToRetry("")); 
        Assert.Throws<Exception>(() => sub.MethodToRetry("")); 
        Assert.AreEqual("works now", sub.MethodToRetry(""));
        // Will continue returning last stubbed value...
        Assert.AreEqual("works now", sub.MethodToRetry(""));
        Assert.AreEqual("works now", sub.MethodToRetry(""));
    }

另一种选择是在对调用进行存根时放入条件:

    [Test]
    public void StubWithCondition() {
        var shouldThrow = true;

        var sub = Substitute.For<IThingoe>();
        sub.MethodToRetry(Arg.Any<string>()).Returns(x => {
            if (shouldThrow) {
                throw new Exception();
            }
            return "works now";
        });

        Assert.Throws<Exception>(() => sub.MethodToRetry(""));
        shouldThrow = false; // <-- can alter behaviour by modifying this variable
        Assert.AreEqual("works now", sub.MethodToRetry(""));
    }

作为此方法的修改版本,您还可以替换用于存根的回调:

    [Test]
    public void ReplaceLambda() {
        Func<string> methodToRetry = () => { throw new Exception(); };

        var sub = Substitute.For<IThingoe>();
        sub.MethodToRetry(Arg.Any<string>()).Returns(x => methodToRetry());

        Assert.Throws<Exception>(() => sub.MethodToRetry(""));
        methodToRetry = () => "works now";
        Assert.AreEqual("works now", sub.MethodToRetry(""));
    }

理想情况下,我们会尽量避免测试中依赖于时间的逻辑,但如果确实有必要,我们可以在 5 秒后更新第二个示例中的条件,以获得您问题中提到的行为。

【讨论】:

    【解决方案2】:

    首先,我没有看到任何具体的实现,所以我会概括地说。

    想法:

    1. 因为您想测试“重试循环模式”,我假设您有“5 秒等待”部分的逻辑。这个逻辑应该是一个可注入的调用,所以在你的测试中你可以检查它是否被调用。 (http://nsubstitute.github.io/help/received-calls/)
    2. 等待部分不应出现在您的测试中,因为您的方法必须在重试步骤而不是测试中等待。

    【讨论】:

    • 我看过这篇文章,但我很难将它应用于我的特定问题 - 我需要证明它会暂时失败,并且在满足特定条件后,成功。这对我来说涉及在测试用例执行的中途更改已执行方法的状态。
    • “暂时失败”是什么意思?你的意思是有些情况下你的方法会在内部抛出异常,在这些情况下你会返回一个不同的返回值?
    • 我需要在测试开始时抛出异常的方法,重试几次,然后切换到返回正确的结果来验证重试逻辑。
    • 我想我现在明白了。因此,考虑#1,您可以将调用子化以引发异常,并且在给定数量的调用之后(这只是一个示例,这是您的条件发生变化的部分),您可以将子函数更改为不在您的再次测试并调用您的方法。
    • 没错!不管条件是什么,我都能应付。我只需要知道如何更新存根方法,从在测试开始时抛出异常到在结束时返回结果。
    猜你喜欢
    • 1970-01-01
    • 2014-05-10
    • 2014-11-08
    • 2021-07-01
    • 2022-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-26
    相关资源
    最近更新 更多