如何为Nunit CollectionAssert测试创建一个IComparer?

11

我希望在NUnit中创建以下测试,针对以下情况进行测试:我们希望测试新的计算方法生成的结果与旧系统类似。已定义所有值之间的可接受差异(或者更确切地说是重新定义等式)。

 abs(old_val - new_val) < 0.0001

我知道可以循环遍历新列表中的每个值,并将其与旧列表中的值进行比较并测试上述条件。

如何使用Nunit的CollectionAssert.AreEqual方法(或一些CollectionAssert方法)实现这一点?

4个回答

13

当前的答案已经过时。自从 NUnit 2.5 以来,CollectionAssert.AreEqual 提供了一个重载版本,它接受一个 System.Collections.IComparer

这里是一个最小实现:

public class Comparer : System.Collections.IComparer
{
  private readonly double _epsilon;

  public Comparer(double epsilon)
  {
    _epsilon = epsilon;
  }

  public int Compare(object x, object y)
  {
    var a = (double)x;
    var b = (double)y;

    double delta = System.Math.Abs(a - b);
    if (delta < _epsilon)
    {
      return 0;
    }
    return a.CompareTo(b);
  }
}


[NUnit.Framework.Test]
public void MyTest()
{
  var a = ...
  var b = ...
  NUnit.Framework.CollectionAssert.AreEqual(a, b, new Comparer(0.0001));
}

这也适用于MsTest!Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert.AreEqual(a, b, new Comparer(0.0001)); - Andrei Krasutski

10

在NUnit框架中有一种方法,允许我对集合进行容差检查。请参考Equal Constraint。其中使用了AsCollectionWithin扩展方法。不过我并不完全确定这个声明的含义。

如果您希望将要比较的数组视为简单的集合,请使用AsCollection修饰符,这将导致按元素逐个进行比较,而不考虑数组的秩或维度。

 [Test]
 //[ExpectedException()]
 public void CheckLists_FailsAt0()
 {
    var expected = new[] { 0.0001, 0.4353245, 1.3455234, 345345.098098 };
    var result1 = new[] { -0.0004, 0.43520, 1.3454, 345345.0980 };
    Assert.That(result1, Is.EqualTo(expected).AsCollection.Within(0.0001), "fail at [0]"); // fail on [0]    
    }

[Test]
//[ExpectedException()]
public void CheckLists_FailAt1()
{
    var expected = new[] { 0.0001, 0.4353245, 1.3455234, 345345.098098 };
    var result1a = new[] {  0.0001000000 , 0.4348245000 , 1.3450234000 , 345345.0975980000  };                      
    Assert.That(result1a, Is.EqualTo(expected).AsCollection.Within(0.0001), "fail at [1]"); // fail on [3]        
    }

[Test]    
public void CheckLists_AllPass_ForNegativeDiff_of_1over10001()
{
    var expected = new[] { 0.0001, 0.4353245, 1.3455234, 345345.098098 };
    var result2 = new[] {  0.00009900 , 0.43532350 , 1.34552240 , 345345.09809700 };
    Assert.That(result2, Is.EqualTo(expected).AsCollection.Within(0.0001)); // pass      
 }

 [Test]
 public void CheckLists_StillPass_ForPositiveDiff_of_1over10001()
 {
    var expected = new[] { 0.0001, 0.4353245, 1.3455234, 345345.098098 };
    var result3 = new[] {  0.00010100 ,  0.43532550  , 1.34552440 , 345345.09809900 };
    Assert.That(result3, Is.EqualTo(expected).AsCollection.Within(0.0001)); // pass
 }

1
哎呀,我真是见了鬼 - 我从来没有见过那种构造。很不错的发现! - Ben

0

NUnit没有定义任何委托对象或接口来执行自定义检查列表,并确定预期结果是否有效。

但我认为最好和最简单的选择是编写一个小的静态方法来实现您的检查:

    private const float MIN_ACCEPT_VALUE = 0.0001f;

    public static void IsAcceptableDifference(IList collection, IList oldCollection)
    {
        if (collection == null)
            throw new Exception("Source collection is null");
        if (oldCollection == null)
            throw new Exception("Old collection is null");
        if (collection.Count != oldCollection.Count)
            throw new Exception("Different lenghts");

        for (int i = 0; i < collection.Count; i++)
        {
            float newValue = (float)collection[i];
            float oldValue = (float)oldCollection[i];

            float difference = Math.Abs(oldValue - newValue);
            if (difference < MIN_ACCEPT_VALUE)
            {
                throw new Exception(
                    string.Format(
                        "Found a difference of {0} at index {1}",
                        difference,
                        i));
            }
        }
    }

谢谢你的方法。NUnit确实提供了集合比较功能。请看我的回答。 - Ahmad
自 NUnit 2.5 开始,CollectionAssert.AreEqual 有一个重载方法,可以接受 System.Collections.IComparer - Sebastian Negraszus

0

您问如何使用CollectionAssert方法实现所需的测试而无需遍历列表。我相信这很明显,但是遍历正是这种方法要做的事情...

对于您确切的问题,简短的答案是您不能使用CollectionAssert方法来实现您想要的功能。但是,如果您真正想要的是一种比较浮点数列表并断言它们相等的简单方法,请继续阅读。

方法Assert.AreEqual( double expected, double actual, double tolerance )使您不必编写单个项目断言。使用LINQ,您可以像这样做:

double delta = 0.0001;
IEnumerable<double> expectedValues;
IEnumerable<double> actualValues;

// code code code

foreach (var pair in expectedValues.Zip(actualValues, Tuple.Create))
{
    Assert.AreEqual(pair.Item1, pair.Item2, delta, "Collections differ.");
}

如果你想变得更加高级,你可以将其提取为自己的方法,捕获AssertionException,对其进行处理并重新抛出以获得更清晰的界面。
如果你不关心哪些项目不同:
var areEqual = expectedValues
    .Zip(actualValues, Tuple.Create)
    .Select(tup => Math.Abs(tup.Item1 - tup.Item2) < delta)
    .All(b => b);

Assert.IsTrue(areEqual, "Collections differ.");

接近了,但仍需要循环 - NUnit确实提供了集合比较的功能。请看我的答案。 - Ahmad
不错的发现。然而,我的一些小挑剔坚持认为,在你的方法中也肯定存在循环 - 只是让你知道而已。 - Ben
这个解决方案是不正确的,因为它认为数组[1,2,3]和[1,2,3,4,5]相等。如果Zip的输入长度不同,则结果的长度将与最短的输入相同。 - Collin K
自 NUnit 2.5 开始,CollectionAssert.AreEqual 有一个重载方法,可以接受 System.Collections.IComparer - Sebastian Negraszus

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