确保两个不同类型的枚举类型是等价的

9

我有一个 NUnit 单元测试,其中我有两个不同类型的集合,我想要断言它们是等价的。

class A { public int x; }
class B { public string y; }

[Test]
public void MyUnitTest()
{
    var a = GetABunchOfAs(); // returns IEnumerable<A>
    var b = GetABunchOfBs(); // returns IEnumerable<B>

    Assert.IsPrettySimilar(a, b, (argA, argB) => argA.ToString() == argB);
}

这里的 Assert.IsPrettySimilar 是这样定义的:

public static void IsPrettySimilar<T1, T2>(
    IEnumerable<T1> left, 
    IEnumerable<T2> right, 
    Func<T1, T2, bool> predicate)
{
    using (var leftEnumerator = left.GetEnumerator())
    using (var rightEnumerator = right.GetEnumerator())
    {       
        while (true)
        {
            var leftMoved = leftEnumerator.MoveNext();

            if (leftMoved != rightEnumerator.MoveNext()) 
            {
                Assert.Fail("Enumerators not of equal size");
            }

            if (!leftMoved)
            {
                break;
            }

            var isMatch = predicate(leftEnumerator.Current, 
                                rightEnumerator.Current);

            Assert.IsTrue(isMatch);
        }           
    }
}

我的问题是,使用NUnit的现有方法是否有更习惯用法的方式来完成上述操作?我已经查看了CollectionAssert,但没有任何匹配我想要做的事情。
在这种情况下,我对“等效”的描述如下:
1)集合必须具有相同的大小
2)集合必须按照相同的逻辑顺序排列
3)必须使用某些谓词来确定匹配项之间的等价性。
3个回答

1

我知道你已经查看了CollectionAssert,但是我发现在我的测试中使用这样的策略非常有用。

覆盖对象的ToString和Equals方法可以使此测试通过。

[TestFixture]
public class Class1
{

    [Test]
    public void MyUnitTest()
    {
        var a = GetABunchOfAs(); // returns IEnumerable<A>
        var b = GetABunchOfBs(); // returns IEnumerable<B>

        CollectionAssert.AreEqual(a, b.OrderBy(x => x.y));

    }

    public List<A> GetABunchOfAs()
    {
        return new List<A>
        {
            new A() {x = 1},
            new A() {x = 2},
            new A() {x = 3},
            new A() {x = 4}
        };
    }

    public List<B> GetABunchOfBs()
    {
        return new List<B>
        {
            new B() {y = "4"},
            new B() {y = "1"},
            new B() {y = "2"},
            new B() {y = "3"},

        };
    }
}

public class A
{
    public int x;
    public override bool Equals(object obj)
    {
        return obj.ToString().Equals(x.ToString());
    }

    public override string ToString()
    {
        return x.ToString();
    }
}

public class B
{
    public string y;

    public override string ToString()
    {
        return y;
    }

    public override bool Equals(object obj)
    {
        return obj.ToString().Equals(y);
    }

}

我故意没有按顺序放置GetABunchOfBs,然而测试仍然通过。


1

让我们思考一下你想要测试什么。你并不是想测试第一个序列中的对象与第二个序列中的对象是否相同。它们可能非常不同。因此,这里的词 similar 非常模糊。你真正想要测试的是,第一个序列的某些投影是否等于第二个序列的另一些投影。而 NUnit 已经有了这样的功能:

 CollectionAssert.AreEqual(bunchOfAs.Select(a => a.ToString()),
                           bunchOfBs.Select(b => b));

因此,您正在投影两个序列以获取特定数据,然后可以为这两个投影提供良好的名称,这将使其他人能够轻松阅读您的测试。这里有一些隐藏的业务逻辑,在代码中没有解释 - 您不解释为什么要进行这样的投影。因此,投影结果的良好名称将解释您的意图。例如:

 var expectedNames = employees.Select(u => u.Login);
 var actualNames = customers.Select(c => c.Name);
 CollectionAssert.AreEqual(expectedNames, actualNames);

那对我来说比较简洁

 Assert.IsPrettySimilar(employees, customers, (e, c) => u.Login == c.Name);

这是正确的,我正在测试投影是否符合我的预期。然而,在我的场景中,我的两种类型分别有7个和4个属性。你提供的两个例子没有考虑顺序或计数,这是我需要考虑的事情。 - Matthew
@Matthew,但是 CollectionAssert.AreEqual 验证项的数量和顺序 - 当一个序列缺少项 (即比另一个短) 或当某些项不相等时,它将抛出断言异常 (它的内部实现 EnumerablesEqual 几乎与您的扩展方法相同,除了使用谓词)。 - Sergey Berezovskiy
1
啊,好的,这看起来可能是我想要的,让我调查一下。 - Matthew
1
这看起来像是我想要的,但是我可能不会像我的例子那样使用它,因为我发现谓词更符合我编写的测试的习惯用语,但这是一个很好的替代方案。 - Matthew

1

看起来Sergey的答案是我正在寻找的(即查看NUnit是否已经有了我想要的功能)。然而,这是我最终采用的解决方案,更接近我想要的实现。

public static class EnumerableAssert
{
    public static void AreEquivilent<TExpected, TActual>(IEnumerable<TExpected> expected, IEnumerable<TActual> actual, Func<TExpected, TActual, bool> predicate)
    {
        if (ReferenceEquals(expected, actual))
        {
            return;
        }

        using (var expectedEnumerator = expected.GetEnumerator())
        using (var actualEnumerator = actual.GetEnumerator())
        {
            while (true)
            {
                var expectedMoved = expectedEnumerator.MoveNext();

                if (expectedMoved != actualEnumerator.MoveNext())
                {
                    Assert.Fail("Expected and Actual collections are of different size");
                }

                if (!expectedMoved)
                {
                    return;
                }

                Assert.IsTrue(predicate(expectedEnumerator.Current, actualEnumerator.Current));
            }
        }
    }
}

这是我发现的唯一正确的答案,适用于涉及的对象可以在测试目的下等效但不相等,并且无法以合理方式进行投影的情况 - 例如,想要比较非平凡等价性的Roslyn语法节点。 - Joel Aelwyn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接