【问题标题】:FakeItEasy expectation fail against HashSet comparisonsFakeItEasy 期望对 HashSet 比较失败
【发布时间】:2014-08-11 19:28:32
【问题描述】:

我在带有 NUnit 2.6.3 和 FakeItEasy 1.23.0 的 Mac OS X 10.9.4 上使用 Xamarin Studio 5.2。

当我对此代码运行测试时:

using System;
using ValueSet = System.Collections.Generic.HashSet<uint>;
using NUnit.Framework;
using FakeItEasy;

namespace SetTest
{
    [TestFixture]
    class TestFixture
    {
        [Test]
        public void CallsUsersWithSetAndReducedSet()
        {
            var values = new ValueSet { 1, 2, 3 };

            var setUser = A.Fake<SetUser>();

            ClassUnderTest testInstance = new ClassUnderTest();

            using (var scope = Fake.CreateScope())
            {
                testInstance.RunWith(setUser);

                using (scope.OrderedAssertions())
                {
                    A.CallTo(() => setUser.Use(A<ValueSet>.That.IsEqualTo(values))).MustHaveHappened(Repeated.Exactly.Once);
                    A.CallTo(() => setUser.Use(A<ValueSet>.That.Matches(set =>
                        set.Count == 2 && set.Contains(1)))).MustHaveHappened(Repeated.Exactly.Once);
                }
            }
        }
    }

    public class SetUser
    {
        public virtual void Use(ValueSet set)
        {
        }
    }

    class ClassUnderTest
    {
        public static void Main(string[] arguments)
        {
        }

        public void RunWith(SetUser setUser)
        {
            var values = new ValueSet { 1, 2, 3 };
            setUser.Use(values);

            values.Remove(3);
            setUser.Use(values);
        }
    }
}

我得到以下错误输出:

FakeItEasy.ExpectationException: Assertion failed for the following call: SetTest.SetUser.Use(1[System.UInt32]>) 预计只找到一次,但在调用中找到 #0 次: 1. SetTest.SetUser.Use(set: System.Collection.Generic.HashSet1[System.UInt32]) 重复2次

我不明白导致此故障的原因以及如何解决它。 要通过这种类型的测试需要什么?

【问题讨论】:

  • 我在任何地方都看不到 values 被传递到任何对象中,那么您的验证器如何匹配?如果另一个ValueSet 在您未提供的代码中创建,那么ThatIsEqualTo() 可能正在测试引用相等性?如何为 ValueSet 定义相等性?
  • 有两个 ValueSet 变量。一个在测试方法中,另一个在被测类的 RunWith 方法中。他们都设置了相同的值,所以从这个角度来看,期望似乎应该过去了。

标签: fakeiteasy


【解决方案1】:

@Tim Long 在他的评论中走在了正确的轨道上。
这里有更多细节,以及响应您 2014-08-11 03:25:56 的 cmets 的更新:

第一个MustHaveHappened失败的第一个原因:

根据FakeItEasy argument constraints documentationThat.IsEqualTo 测试“使用 object.Equals 的对象相等性”。这就是导致意外行为的原因。

没有将values 传递给方法不一定是问题,或者如果ValueSet.Equals 执行了值比较则不会是问题,但ValueSetHashSet&lt;uint&gt;,所以你可以看到from that class's method documentation它没有——它使用object.Equalstests for reference equality。因此,您的 IsEqualTo 断言失败。如果您使用更复杂的匹配器来对HashSet 执行值类型比较,也许更接近您在第二个A.CallTo 中使用的匹配器,或者使用That.Contains 的匹配器,我认为您会获得更好的成功。

你可能会考虑使用That.IsSameSequenceAs,但如果这样做要小心:HashSet 不保证枚举中元素的顺序,所以即使集合具有相同的元素,你也可能得到一个失败。

第一个MustHaveHappened失败的第二个原因:

RunWith 在对setUser.Use 的调用之间更改values 集的内容。因此,在两次调用中使用相同的集合,首先使用 3 个元素,然后当它只有 2 个元素时。这意味着在第一次调用MustHaveHappened 时,该集合只有2 个元素,因此比较失败。您可以通过writing an argument formatter for the ValueSet 更清楚地看到这一点。这将提供更多信息。

不匹配的原因是当调用伪造的方法时,FakeItEasy 会捕获参数。但是,对于引用类型,例如 ValueSet (HashSet),只保留对参数的引用。因此,如果对象稍后被修改,特别是在测试的执行和验证阶段之间,对象将看起来与伪造调用时不同。请参阅@jimmy_keen 对MustHaveHappened fails when called twice on the same object 的回答。 FakeItEasy Issue 306 - Verifying multiple method calls with reference parameters 有更多讨论。

在这种情况下,通常的方法是按照他的建议进行操作 - 提供代码以在调用时捕获传入参数的重要状态,然后稍后查询保存的状态。

你也许可以使用这样的东西:

[Test]
public void CallsUsersWithSetAndReducedSet()
{
    var capturedValueSets = new List<List<uint>>();

    var setUser = A.Fake<SetUser>();
    A.CallTo(() => setUser.Use(A<ValueSet>._)) // matches any call to setUser.Use
        .Invokes((ValueSet theSet) => capturedValueSets.Add(theSet.ToList()));

    ClassUnderTest testInstance = new ClassUnderTest();

    testInstance.RunWith(setUser);

    Assert.That(capturedValueSets, Has.Count.EqualTo(2),
        "not enough calls to setUser.Use");
    Assert.That(capturedValueSets[0], Is.EquivalentTo(new uint[] {1, 2, 3}),
        "bad set passed to first call to setUser.Use");
    Assert.That(capturedValueSets[1], Has.Count.EqualTo(2) & Has.Member(1),
        "bad set passed to second call to setUser.Use");
}

可以看到,每次调用Use,我们都会将ValueSet参数的内容添加到capturedValueSets。那么最后我们

  1. 通过检查capturedValueSets 的长度,确保进行了 2 次调用
  2. 确保第一次调用 Use 时,该集合包含元素 1、2 和 3。Is.EquivalentTo 检查这两个列表但忽略顺序
  3. 确保第二次调用 Use 时,该集合有 2 个元素,其中一个是 1

通过依次检查两个捕获的值集,所有关于范围和有序断言的位都变得不必要了。

【讨论】:

  • ThatIsEqualTo 是一个错字,我在粘贴代码后尝试调整代码以使用我认为可以正确比较值的东西。知道正确的调用方法令人困惑。
  • 我按照预期更改了比较,所以它现在调用 That.IsSameSequenceAs(values)。如果我注释掉 RunWith 方法中的第二个期望和最后两行,则测试通过。当我重新启用一切时,测试失败并出现以下错误:
  • FakeItEasy.ExpectationException:以下调用的断言失败:SetTest.SetUser.Use() 预计只找到一次,但在调用中找到它 #0 次:1:SetTest。 SetUser.Use(set: System.Collections.HashSet`1[System.UInt32]) 重复 2 次​​span>
  • 扩展答案有帮助吗?如果没有,我很乐意继续努力。
  • 好的,我尝试了你的建议并且有效。谢谢
猜你喜欢
  • 2021-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-25
相关资源
最近更新 更多