我在Mac OS X 10.9.4上使用Xamarin Studio 5.2,NUnit 2.6.3和FakeItEasy 1.23.0。
当我为此代码运行测试时:
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:以下调用的断言失败:SetTest.SetUser.Use(1 [System.UInt32]&gt;)预计只找到一次,但在调用中找到#0次: 1. SetTest.SetUser.Use(设置:System.Collection.Generic.HashSet1 [System.UInt32])重复2次
我不明白造成这种失败的原因以及解决方法。 让这种类型的测试通过需要什么?
答案 0 :(得分:2)
MustHaveHappened
失败的第一个原因:根据FakeItEasy argument constraints documentation,That.IsEqualTo
测试“使用object.Equals的对象相等”。这就是造成意外行为的原因。
未将values
传递给方法不一定是个问题,或者如果ValueSet.Equals
执行了值比较,则不会,ValueSet
是HashSet<uint>
,因此,您可以看到from that class's method documentation它没有使用object.Equals
,tests for reference equality。因此,您的IsEqualTo
断言失败。如果您使用更复杂的匹配器对HashSet
执行值类型比较,可能更接近您在第二个A.CallTo
中使用的内容,或者使用That.Contains
的内容,我认为您会有更好的成功。
您可能会考虑使用That.IsSameSequenceAs
,但是如果这样做则要小心:HashSet
不保证枚举中元素的顺序,所以即使集合具有相同的元素,你可能会失败。
MustHaveHappened
失败的第二个原因: RunWith
更改values
之间调用的内容setUser.Use
。因此,在两个调用中使用相同的集合,首先使用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
。然后在最后我们
capturedValueSets
Use
时,该集合包含元素1,2和3. Is.EquivalentTo
检查两个列表但忽略顺序Use
,该集合有2个元素,其中一个是1 通过依次检查两个捕获的值集,有关范围和有序断言的所有位变得不必要。