Autofixture: NSubstitute.ReceivedCalls() 使用 AutoFixture.AutoNSubstitute 返回错误值

创建于 2018-03-28  ·  10评论  ·  资料来源: AutoFixture/AutoFixture

我们有很多使用“Class.ReceivedCalls()”和“var tmp = Class.Received(int).Property;”的测试检查呼叫计数。 在 AutoFixture 的 v3 中,它正确报告了调用计数,尤其是属性,它不再这样做了。 方法的调用次数似乎还可以。
鉴于我们有以下代码:

    public interface IRunSpeed
    {
        int Speed { get; }
    }

    public class GetToDaChoppa
    {
        private readonly IRunSpeed _runSpeed;

        public GetToDaChoppa(IRunSpeed runSpeed)
        {
            _runSpeed = runSpeed ?? throw new ArgumentNullException(nameof(runSpeed));
        }

        public void DoItNow()
        {
            var runningSpeed = _runSpeed.Speed;
        }
    }

鉴于我们有以下测试:

        [Fact]
        public void DoItNow_WithOutAutoNSubstitute()
        {
            // Arrange
            var runSpeed = Substitute.For<IRunSpeed>();
            runSpeed.Speed.Returns(2);

            var sut = new GetToDaChoppa(runSpeed);

            //Act
            sut.DoItNow();

            //Assert
            var tmp = runSpeed.Received(1).Speed;
            Assert.Single(runSpeed.ReceivedCalls());
        }

        [Theory, AutoNSubstituteData]
        public void DoItNow_UsingAutoNSubstitute(
            [Frozen] IRunSpeed runSpeed,
            GetToDaChoppa sut)
        {
            // Arrange
            runSpeed.Speed.Returns(49);

            //Act
            sut.DoItNow();

            //Assert
            var tmp = runSpeed.Received(1).Speed;
            Assert.Single(runSpeed.ReceivedCalls());

我的 AutoNSubstituteDataAttribute 看起来像这样:

        public class AutoNSubstituteDataAttribute : AutoDataAttribute
        {
            public AutoNSubstituteDataAttribute()
                : base(() => new Fixture()
                           .Customize(new AutoNSubstituteCustomization()))
            {
            }
        }

第一个测试“DoItNow_WithOutAutoNSubstitute”工作正常。 但是第二个测试 'DoItNow_UsingAutoNSubstitute' 为 'ios.Received(1).Speed;' 返回 2。
它还为“runSpeed.ReceivedCalls();”返回 2。
因此,我们目前无法将我们的解决方案升级到 v4,因为我们每个解决方案立即有超过 1000 个失败的测试。 关于问题可能是什么或在哪里寻找修复的任何指导?

question

所有10条评论

感谢您解决问题。 其实这个问题和AutoFixture没有关系,我们也受到了影响😕

此问题主要是由 xUnit 引起的,如果您按如下方式重写测试,它将通过:
```c#
[理论,AutoNSubstituteData]
public void DoItNow_UsingAutoNSubstitute_CreateManually(IFixture fixture)
{
// 安排
var runSpeed = fixture.Freeze();
var sut = fixture.Create();
runSpeed.Speed.Returns(49);

//Act
sut.DoItNow();

//Assert
var tmp = runSpeed.Received(1).Speed;
Assert.Single(runSpeed.ReceivedCalls());

}
```

当您运行测试时,xUnit 会尝试为您格式化测试名称。 如果它发现该类型未知并且没有ToString()重载,它会使用结构检查并递归获取属性值。 如果您检查测试名称,您将看到以下内容:
image

您可能已经注意到,xUnit 获取了Speed属性值以很好地向您展示测试参数。 由于这是通常的调用,NSubstitute 将其视为调用。

很奇怪,你在 v3 中没有这个问题,因为在这方面没有任何改变。 我刚刚测试了 xUnit2 + AutoFixture v3并且仍然有问题。 可能您也升级了 xUnit。


至于解决方法,我有好消息和坏消息。 好消息是,这将在 NSubsitute 的下一个主要版本中得到解决,因为它开始覆盖ToString()方法以返回代理 ID。 结果,xUnit 不再触及属性:
image

坏消息是 NSubsitute v4 还没有发布。

我可以建议你:

  • 推迟迁移直到 NSubsitute v4 发布;
  • 使用 NSubsitute 的最新master源代码,进行本地构建并使用您自己的NSubsitute.dll而不是 NuGet 包。 v4 发布后,可以切换到 NuGet 包。
  • 如果您也更新了 xUnit 版本(因为这可能解释了您之前没有此问题的原因),请还原该更改并使用先前版本的 xUnit。

对于给您带来的不便,我深表歉意,但不幸的是,我们无法在 AutoFixture 方面采取任何措施来解决此问题。

刚刚测试了这个。 最后,我们断言 ReceivedCalls 的测试又返回了一个成功的结果。
仍然失败的是 Received(x) 上的断言,如上面提到的 Received(1).Speed Daniel。

snip_20180328181559

你也有帽子的答案/解决方案吗?

你们在做同一个项目吗? :) 如果是这样,你能澄清一下你是如何解决这个问题的吗?

上面建议的选项应该有助于解决这两个问题。 如果他们没有 - 澄清场景。

实际上不是同一个项目,而是在同一家公司。
我们一直在 xunit 1.9 + nsubstitute 2 / 3 上使用了很长时间,最终设法获得了我们的内部程序集(nuget 包,因此返回仅一个引用的旧版本的简单方法并不像听起来那么容易将)与 xunit2 兼容。 现在我们有这两个问题。

我们已经尝试了一些东西,最后认为它一定与 autofixture 有关 - 因为冻结属性和硬编码的测试执行计数 - 或类似的东西。
丹尼尔打开这个案例后,他把它的链接发给了我。

就像你上面描述的那样,我用新编译的 nsubstitute 项目中的参考替换了 nuget 3.1 参考。 在那之后,我现在有 118 个而不是 178 个失败的测试,因为 Receiced(x) 仍在评估错误的调用次数。

我的项目目前基于 .net 4.5.2 和 xunit2,引用了 nsubstitute 和 autofixture 4.2 的当前 repo 版本。 由于相同的两个原因,所有测试都失败了 - 在单元测试中仍然有一些接收到的呼叫失败。
我想当我再次回到办公室(复活节外出)时,我必须再次深入研究它。
也许@dklinger同时可以描述场景。

@dklinger @evilbaschdi请在有机会检查后跟进 - 很有趣,为什么即使在应用补丁后仍然会看到问题。

重新打开问题以表明我们仍在进行调查。

好的,所以我已经测试了过去几个小时。 首先:感谢您的回复、您的解释和建议 - 帮助很大。
我已经测试了 NSubstitute 的 v4。 它适用于我上面的示例代码,但不能完全解决我们现实世界项目中的问题。 问题是,它解决了错误的 ReceivedCalls-count 问题,仅适用于 SUT-Calls 方法,而不是属性。

我们现在工作的代码示例:

        [Theory, AutoNSubstituteData]
        public void DoItNow_UsingAutoNSubstitute(
            [Frozen] IRunSpeed runSpeed,
            GetToDaChoppa sut)
        {
            // Arrange
            runSpeed.Speed.Returns(49);

            //Act
            sut.DoItNow();

            //Assert
            var tmp = runSpeed.Received(1).Speed;
            Assert.Equal(1, runSpeed.ReceivedCalls().Count());
        }

但是如果我改变方法“GetToDaChoppa.DoItNow();” 要成为属性“GetToDaChoppa.DoItNow”,ReceivedCallsCount 再次为 +1:

        [Theory, AutoNSubstituteData]
        public void DoItNow_UsingAutoNSubstitute(
            [Frozen] IRunSpeed runSpeed,
            GetToDaChoppa sut)
        {
            // Arrange
            runSpeed.Speed.Returns(49);

            //Act
            var x = sut.DoItNow;

            //Assert
            var tmp = runSpeed.Received(1).Speed;
            Assert.Equal(1, runSpeed.ReceivedCalls().Count());
        }

我认为它再次与 xUnit 命名有关,因为我们为工作方法实现获得了这个命名:
image

还有一个用于非工作属性实现:
image

看起来 xUnit 正在调用 SUT-property 来检索其值以将其用于测试名称,但它并没有为 SUT-methods 执行此操作。 我的第一个想法是它与我们的方法“GetToDaChoppa()”的返回值有关,该值是无效的。 但即使将其更改为“public int GetToDaChoppa()”,xUnit 仍然没有调用它来获取测试名称的返回值。 这只是使用属性的问题。

现在我又被困住了。 我完全同意这不是 AutoFixture 的问题。 但在您看来,比我们更了解所有数据包,您有什么建议?

  • 为 NSubstitute 打开一个问题?
  • 为 xUnit2 打开一个问题?
  • 还是全新的东西?

@dklinger感谢您的跟进! 您能否分享最后一个仍然失败的场景的MCVE代码? 只是为了确保没有遗漏任何东西并且我没有误解条件。

之后,我将尝试调查发生这种情况的原因以及如何解决该问题。

谢谢。

是的,当然。 这里是:

被测系统:

    public interface IRunSpeed
    {
        int Speed { get; }
        void Dude();
        int Dude2();
    }

    public class GetToDaChoppa
    {
        private readonly IRunSpeed _runSpeed;

        public GetToDaChoppa(IRunSpeed runSpeed)
        {
            _runSpeed = runSpeed ?? throw new ArgumentNullException(nameof(runSpeed));
        }

        public int DoItNow
        {
            get
            {
                var runningSpeed = _runSpeed.Speed;
                return 0;
            }
        }
    }

测试用例:

        [Theory, AutoNSubstituteData]
        public void DoItNowAsProperty_UsingAutoNSubstitute(
            [Frozen] IRunSpeed runSpeed,
            GetToDaChoppa sut)
        {
            // Arrange
            runSpeed.Speed.Returns(49);

            //Act
            var x = sut.DoItNow;

            //Assert
            var tmp = runSpeed.Received(1).Speed;
            Assert.Equal(1, runSpeed.ReceivedCalls().Count());
        }

目前我正试图赶上https://github.com/xunit/xunit/issues/1386https://github.com/AutoFixture/AutoFixture/issues/805上的讨论
我还尝试创建自己的 TheoryAttribute 覆盖 DisplayName 属性,但这也无济于事,因为 xUnit 在某些方面仍在内部使用自动生成的名称。 但这只是仅供参考,与上面的演示代码完全无关。

        public sealed class MyTheoryAttribute : TheoryAttribute
        {
            public MyTheoryAttribute([CallerMemberName] string memberName = null)
            {
                DisplayName = "MyTestCase";
            }
        }

image

你好,我又来了:)
确实是 xUnit2 调用了复杂类型的测试方法参数的所有属性和字段。 这行代码是: https :

谢谢您的支持。 我在 xUnit-Repo 重新打开了一个问题,讨论应该继续: https :

@dklinger感谢您提供详细的场景。 这确实非常棘手,很难以某种方式解决它。 从 AutoFixture 和 NSubsitute 的角度来看,代码是在 xUnit 内部深处还是在测试主体中调用没有区别。

通常,作为一种解决方法,您可以使用 NSubstitute 的明确功能:

runSpeed.ClearReceivedCalls();

此代码应在您验证确切调用次数的每个测试的序言中运行。 如果你有几个测试,它工作得很好,但是很明显,如果数千个测试受到影响,那将没有多大帮助😅

遗憾的是,NSubsitute + xUnit2 + AutoFixture 集成不能很好地工作并受到此类问题的困扰。 AutoFixture 产品旨在简化生活,而不是让它成为一场噩梦 😕 希望来自 xUnit 的人会建议您快速解决整个项目的问题。

如果您认为我们可以从我们这边做一些事情来改善这种情况,请告诉我。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

tomasaschan picture tomasaschan  ·  3评论

ploeh picture ploeh  ·  3评论

joelleortiz picture joelleortiz  ·  4评论

JoshKeegan picture JoshKeegan  ·  6评论

ploeh picture ploeh  ·  7评论