66

我正在做一些单元测试,我想知道是否有任何方法可以测试列表是否按其包含的对象的属性排序。

现在我正在这样做,但我不喜欢它,我想要一个更好的方法。有人可以帮我吗?

// (fill the list)
List<StudyFeedItem> studyFeeds = 
    Feeds.GetStudyFeeds(2120, DateTime.Today.AddDays(-200), 20);   

StudyFeedItem previous = studyFeeds.First();

foreach (StudyFeedItem item in studyFeeds)
{
    if (item != previous)
    {
        Assert.IsTrue(previous.Date > item.Date);
    }

    previous = item;
}
4

22 回答 22

70

如果您使用的是 MSTest,您可能需要查看CollectionAssert.AreEqual

Enumerable.SequenceEqual可能是另一个在断言中使用的有用 API。

在这两种情况下,您都应该准备一个以预期顺序保存预期列表的列表,然后将该列表与结果进行比较。

这是一个例子:

var studyFeeds = Feeds.GetStudyFeeds(2120, DateTime.Today.AddDays(-200), 20);   
var expectedList = studyFeeds.OrderByDescending(x => x.Date);
Assert.IsTrue(expectedList.SequenceEqual(studyFeeds));
于 2009-12-21T14:03:15.363 回答
52

.NET 4.0 方法是使用该Enumerable.Zip方法压缩列表,使其自身偏移 1,从而将每个项目与列表中的后续项目配对。然后,您可以检查每对的条件是否成立,例如

var ordered = studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => new { a, b })
                        .All(p => p.a.Date < p.b.Date);

如果您使用的是较早版本的框架,您可以编写自己的 Zip 方法而不会有太多麻烦,如下所示(如果适用,枚举数的参数验证和处置留给读者):

public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
    this IEnumerable<TFirst> first,
    IEnumerable<TSecond> second,
    Func<TFirst, TSecond, TResult> selector)
{
    var e1 = first.GetEnumerator();
    var e2 = second.GetEnumerator();
    while (e1.MoveNext() & e2.MoveNext()) // one & is important
        yield return selector(e1.Current, e2.Current);
}
于 2009-12-21T14:04:40.100 回答
33

Nunit 2.5 引入了CollectionOrderedContraint和一个很好的语法来验证集合的顺序:

Assert.That(collection, Is.Ordered.By("PropertyName"));

无需手动订购和比较。

于 2013-10-23T13:58:56.983 回答
27

如果你的单元测试框架有辅助方法来断言集合的相等性,你应该可以做这样的事情(NUnit 风格):

var sorted = studyFeeds.OrderBy(s => s.Date);
CollectionAssert.AreEqual(sorted.ToList(), studyFeeds.ToList());

assert 方法适用于 any IEnumerable,但是当两个集合都是类型IList或“某物的数组”时,断言失败时抛出的错误消息将包含第一个不合适的元素的索引。

于 2009-12-21T14:02:50.107 回答
20

发布的涉及对列表进行排序的解决方案非常昂贵 - 确定列表是否已排序可以在 O(N) 中完成。这是一个将检查的扩展方法:

public static bool IsOrdered<T>(this IList<T> list, IComparer<T> comparer = null)
{
    if (comparer == null)
    {
        comparer = Comparer<T>.Default;
    }

    if (list.Count > 1)
    {
        for (int i = 1; i < list.Count; i++)
        {
            if (comparer.Compare(list[i - 1], list[i]) > 0)
            {
                return false;
            }
        }
    }
    return true;
}

通过更改为IsOrderedDescending可以轻松实现对应的。> 0< 0

于 2016-01-11T11:47:29.530 回答
11

Greg Beech的回答虽然很好,但可以通过在 Zip 本身中执行测试来进一步简化。所以而不是:

var ordered = studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => new { a, b })
                        .All(p => p.a.Date <= p.b.Date);

你可以简单地做:

var ordered = !studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => a.Date <= b.Date)
                        .Contains(false);

这为您节省了一个 lambda 表达式和一个匿名类型。

(在我看来,删除匿名类型也更容易阅读。)

于 2015-09-15T05:54:32.787 回答
9
if(studyFeeds.Length < 2)
  return;

for(int i = 1; i < studyFeeds.Length;i++)  
 Assert.IsTrue(studyFeeds[i-1].Date > studyFeeds[i].Date);

for还没死呢!

于 2009-12-21T14:00:21.857 回答
7

怎么样:

var list = items.ToList();
for(int i = 1; i < list.Count; i++) {
    Assert.IsTrue(yourComparer.Compare(list[i - 1], list[i]) <= 0);
} 

whereyourComparerYourComparer实现的实例IComparer<YourBusinessObject>。这确保了每个元素都小于枚举中的下一个元素。

于 2009-11-04T19:57:58.347 回答
6

基于 Linq 的答案是:

您可以使用SequenceEqual方法检查原始和订购的是否相同。

var isOrderedAscending = lJobsList.SequenceEqual(lJobsList.OrderBy(x => x));
var isOrderedDescending = lJobsList.SequenceEqual(lJobsList.OrderByDescending(x => x));

不要忘记导入System.Linq命名空间。

此外:

我重申这个答案是基于 Linq 的,您可以通过创建自定义扩展方法来提高效率。

另外,如果有人仍然想使用 Linq 并检查序列是按升序还是降序排序,那么您可以像这样实现更高的效率:

var orderedSequence = lJobsList.OrderBy(x => x)
                               .ToList();

var reversedOrderSequence = orderedSequence.AsEnumerable()
                                           .Reverse();

if (lJobsList.SequenceEqual(orderedSequence))
{
     // Ordered in ascending
}
else (lJobsList.SequenceEqual(reversedOrderSequence))
{
     // Ordered in descending
}
于 2016-01-11T11:43:51.273 回答
4

以下是我使用 Linq 的方法,我可以比较,可能不是最好的,但对我有用,而且它独立于测试框架。

所以调用看起来像这样:

    myList.IsOrderedBy(a => a.StartDate)

这适用于实现 IComparable 的任何东西,因此数字字符串和从 IComparable 继承的任何东西:

    public static bool IsOrderedBy<T, TProperty>(this List<T> list, Expression<Func<T, TProperty>> propertyExpression) where TProperty : IComparable<TProperty>
    {
        var member = (MemberExpression) propertyExpression.Body;
        var propertyInfo = (PropertyInfo) member.Member;
        IComparable<TProperty> previousValue = null;
        for (int i = 0; i < list.Count(); i++)
        {
            var currentValue = (TProperty)propertyInfo.GetValue(list[i], null);
            if (previousValue == null)
            {
                previousValue = currentValue;
                continue;
            }

            if(previousValue.CompareTo(currentValue) > 0) return false;
            previousValue = currentValue;

        }

        return true;
    }

希望这会有所帮助,我花了很长时间才解决这个问题。

于 2011-10-26T15:58:17.110 回答
4

您可以使用这样的扩展方法:

public static System.ComponentModel.ListSortDirection? SortDirection<T>(this IEnumerable<T> items, Comparer<T> comparer = null)
{
    if (items == null) throw new ArgumentNullException("items");
    if (comparer == null) comparer = Comparer<T>.Default;

    bool ascendingOrder = true; bool descendingOrder = true;
    using (var e = items.GetEnumerator())
    {
        if (e.MoveNext())
        {
            T last = e.Current; // first item
            while (e.MoveNext())
            {
                int diff = comparer.Compare(last, e.Current);
                if (diff > 0)
                    ascendingOrder = false;
                else if (diff < 0)
                    descendingOrder = false;

                if (!ascendingOrder && !descendingOrder)
                    break;
                last = e.Current;
            }
        }
    }
    if (ascendingOrder)
        return System.ComponentModel.ListSortDirection.Ascending;
    else if (descendingOrder)
        return System.ComponentModel.ListSortDirection.Descending;
    else
        return null;
}

它可以检查序列是否已排序并确定方向:

var items = new[] { 3, 2, 1, 1, 0 };
var sort = items.SortDirection();
Console.WriteLine("Is sorted? {0}, Direction: {1}", sort.HasValue, sort);
//Is sorted? True, Direction: Descending
于 2016-01-11T11:49:56.650 回答
2

检查一个序列可以有四种不同的结果。Same表示序列中的所有元素都相同(或序列为空):

enum Sort {
  Unsorted,
  Same,
  SortedAscending,
  SortedDescending
}

这是一种检查序列排序的方法:

Sort GetSort<T>(IEnumerable<T> source, IComparer<T> comparer = null) {
  if (source == null)
    throw new ArgumentNullException(nameof(source));
  if (comparer == null)
    comparer = Comparer<T>.Default;

  using (var enumerator = source.GetEnumerator()) {
    if (!enumerator.MoveNext())
      return Sort.Same;
    Sort? result = null;
    var previousItem = enumerator.Current;
    while (enumerator.MoveNext()) {
      var nextItem = enumerator.Current;
      var comparison = comparer.Compare(previousItem, nextItem);
      if (comparison < 0) {
        if (result == Sort.SortedDescending)
          return Sort.Unsorted;
        result = Sort.SortedAscending;
      }
      else if (comparison > 0) {
        if (result == Sort.SortedAscending)
          return Sort.Unsorted;
        result = Sort.SortedDescending;
      }
    }
    return result ?? Sort.Same;
  }
}

我直接使用枚举器而不是foreach循环,因为我需要成对检查序列的元素。它使代码更复杂,但也更有效。

于 2016-01-11T11:56:19.097 回答
1

LINQ-y 将使用单独的排序查询......

var sorted = from item in items
 orderby item.Priority
 select item;

Assert.IsTrue(items.SequenceEquals(sorted));

类型推断意味着你需要一个

 where T : IHasPriority

但是,如果您有多个具有相同优先级的项目,那么对于单元测试断言,您可能最好按照 Jason 的建议使用列表索引进行循环。

于 2009-11-04T20:14:43.460 回答
1

您将不得不以一种或另一种方式遍历列表并确保项目按您想要的顺序排列。由于项目比较是自定义的,您可以考虑为此创建一个通用方法并传入一个比较函数 - 与排序列表使用比较函数的方式相同。

于 2009-12-21T13:59:19.677 回答
1

您可以先创建列表的有序和无序版本:

var asc = jobs.OrderBy(x => x);
var desc = jobs.OrderByDescending(x => x);

现在将原始列表与两者进行比较:

if (jobs.SequenceEqual(asc) || jobs.SequenceEquals(desc)) // ...
于 2016-01-11T11:47:14.037 回答
0
var studyFeeds = Feeds.GetStudyFeeds(2120, DateTime.Today.AddDays(-200), 20);
var orderedFeeds = studyFeeds.OrderBy(f => f.Date);

for (int i = 0; i < studyFeeds.Count; i++)
{
    Assert.AreEqual(orderedFeeds[i].Date, studyFeeds[i].Date);
}
于 2009-12-21T13:59:22.593 回答
0

像这样的东西怎么样,没有排序列表

    public static bool IsAscendingOrder<T>(this IEnumerable<T> seq) where T : IComparable
    {
        var seqArray = seq as T[] ?? seq.ToArray();
        return !seqArray.Where((e, i) =>
            i < seqArray.Count() - 1 &&
            e.CompareTo(seqArray.ElementAt(i + 1)) >= 0).Any();
    }
于 2014-11-07T10:43:57.867 回答
0
Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert.AreEqual(
  mylist.OrderBy((a) => a.SomeProperty).ToList(),
  mylist,
  "Not sorted.");
于 2015-04-09T13:43:46.400 回答
0

这是一个更轻量级的通用版本。要测试降序,请将 >= 0 比较更改为 <= 0。

public static bool IsAscendingOrder<T>(this IEnumerable<T> seq) where T : IComparable<T>
{
    var predecessor = default(T);
    var hasPredecessor = false;

    foreach(var x in seq)
    {
        if (hasPredecessor && predecessor.CompareTo(x) >= 0) return false;
        predecessor = x;
        hasPredecessor = true;
    }

    return true;
}

测试:

  • 新的 int[] { }.IsAscendingOrder() 返回true
  • 新的 int[] { 1 }.IsAscendingOrder() 返回true
  • 新的 int[] { 1,2 }.IsAscendingOrder() 返回true
  • 新 int[] { 1,2,0 }.IsAscendingOrder() 返回false
于 2015-10-18T22:25:47.040 回答
0

虽然 AnorZaken 和 Greg Beech 的答案非常好,因为它们不需要使用扩展方法,但有时最好避免使用 Zip(),因为以这种方式枚举某些可枚举项可能会很昂贵。

可以在 Aggregate() 中找到解决方案

double[] score1 = new double[] { 12.2, 13.3, 5, 17.2, 2.2, 4.5 };
double[] score2 = new double[] { 2.2, 4.5, 5, 12.2, 13.3, 17.2 };

bool isordered1 = score1.Aggregate(double.MinValue,(accum,elem)=>elem>=accum?elem:double.MaxValue) < double.MaxValue;
bool isordered2 = score2.Aggregate(double.MinValue,(accum,elem)=>elem>=accum?elem:double.MaxValue) < double.MaxValue;

Console.WriteLine ("isordered1 {0}",isordered1);
Console.WriteLine ("isordered2 {0}",isordered2);

上述解决方案有点难看的是双重小于比较。像这样的浮点比较让我感到不安,因为它几乎就像一个浮点相等比较。但它似乎在这里为双倍工作。整数值也可以。浮点比较可以通过使用可空类型来避免,但是代码变得有点难以阅读。

double[] score3 = new double[] { 12.2, 13.3, 5, 17.2, 2.2, 4.5 };
double[] score4 = new double[] { 2.2, 4.5, 5, 12.2, 13.3, 17.2 };

bool isordered3 = score3.Aggregate((double?)double.MinValue,(accum,elem)=>(elem>(accum??(double?)double.MaxValue).Value)?(double?)elem:(double?)null) !=null;
bool isordered4 = score4.Aggregate((double?)double.MinValue,(accum,elem)=>(elem>(accum??(double?)double.MaxValue).Value)?(double?)elem:(double?)null) !=null;

Console.WriteLine ("isordered3 {0}",isordered3);
Console.WriteLine ("isordered4 {0}",isordered4);
于 2015-10-22T01:32:44.197 回答
0

您可以在扩展中使用 lambda:

public static bool IsAscending<T>(this IEnumerable<T> self, Func<T, T, int> compareTo) {
  var list = self as IList<T> ?? self.ToList();
  if (list.Count < 2) {
    return true;
  }
  T a = list[0];
  for (int i = 1; i < list.Count; i++) {
    T b = list[i];
    if (compareTo(a, b) > 0) {
      return false;
    }
    a = b;
  }
  return true;
}

使用:

bool result1 = Enumerable.Range(2, 10).IsAscending((a, b) => a.CompareTo(b));

更多的:

var lst = new List<(int, string)> { (1, "b"), (2, "a"), (3, "s1"), (3, "s") };
bool result2 = lst.IsAscending((a, b) => {
  var cmp = a.Item1.CompareTo(b.Item1);
  if (cmp != 0) {
    return cmp;
  } else {
    return a.Item2.CompareTo(b.Item2);
  }
});
于 2019-08-28T08:44:25.970 回答
-2
var expectedList = resultA.ToArray();
var actualList = resultB.ToArray();
var i = 0;
foreach (var item in expectedList)
{
     Assert.True(expectedList[i].id == actualList[i].id);
     i++;
}
于 2022-02-28T02:11:50.573 回答