私はいくつかの単体テストを行っていますが、リストに含まれるオブジェクトのプロパティによってリストが順序付けられているかどうかをテストする方法があるかどうかを知りたいです。
今私はこのようにしていますが、好きではありません。もっと良い方法が欲しいです。誰かが私を助けてくれますか?
// (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;
}
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));
.NET 4.0の方法は、 Enumerable.Zip
リスト自体を1オフセットしたZipを圧縮するメソッド。各アイテムをリスト内の後続のアイテムとペアにします。その後、条件が各ペアに当てはまることを確認できます。
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);
}
ユニットテストフレームワークにコレクションの等価性をアサートするヘルパーメソッドがある場合、次のようなことができるはずです(NUnitフレーバー):
var sorted = studyFeeds.OrderBy(s => s.Date);
CollectionAssert.AreEqual(sorted.ToList(), studyFeeds.ToList());
Assertメソッドは任意のIEnumerable
で機能しますが、両方のコレクションのタイプがIList
または「何かの配列」である場合、assertが失敗したときにスローされるエラーメッセージには最初のout-場所の要素。
Nunit 2.5に導入された CollectionOrderedContraint およびコレクションの順序を検証するためのNice構文:
Assert.That(collection, Is.Ordered.By("PropertyName"));
手動で注文して比較する必要はありません。
リストのソートに関連して投稿されたソリューションは高価です-ソートされたリストISが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
に変更することで簡単に実装できます。
if(studyFeeds.Length < 2)
return;
for(int i = 1; i < studyFeeds.Length;i++)
Assert.IsTrue(studyFeeds[i-1].Date > studyFeeds[i].Date);
for
はまだまだ死んでいません!
Greg Beech answer は優れていますが、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);
これにより、1つのラムダ式と1つの匿名型が保存されます。
(私の意見では、匿名型を削除すると読みやすくなります。)
どうですか:
var list = items.ToList();
for(int i = 1; i < list.Count; i++) {
Assert.IsTrue(yourComparer.Compare(list[i - 1], list[i]) <= 0);
}
ここで、yourComparer
はYourComparer
のインスタンスで、 IComparer<YourBusinessObject>
。これにより、すべての要素が列挙内の次の要素よりも小さくなります。
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
}
次のような拡張メソッドを使用できます。
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
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;
}
これがお役に立てば幸いです、私はこれを解決するのに何年もかかりました。
シーケンスのチェックには、4つの異なる結果があります。 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
ループの代わりに列挙子を直接使用しています。コードがより複雑になりますが、より効率的です。
何らかの方法でリストを調べて、アイテムが希望の順序になっていることを確認する必要があります。アイテムの比較はカスタムであるため、このための汎用メソッドを作成して比較関数を渡すことを検討できます-リストの並べ替えが比較関数を使用するのと同じ方法です。
LINQ-yの何かは、別のソートされたクエリを使用することです...
var sorted = from item in items
orderby item.Priority
select item;
Assert.IsTrue(items.SequenceEquals(sorted));
型推論は、あなたが必要になることを意味します
where T : IHasPriority
ただし、同じ優先度のアイテムが複数ある場合、単体テストのアサーションでは、Jasonが提案したように、リストインデックスでループするのがおそらく最善です。
拡張でラムダを使用できます:
public static bool IsAscending<T>(this IEnumerable<T> self, Func<T, T, int> compareTo) {
var list = self as IList<T> ?? self.ToList();
for (int i = 1; i < list.Count; i++) {
if (compareTo(list[i - 1], list[i]) > 0) {
return false;
}
}
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);
}
});
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);
}
リストの順序付きバージョンと順序なしバージョンを最初に作成できます。
var asc = jobs.OrderBy(x => x);
var desc = jobs.OrderByDescending(x => x);
元のリストと両方を比較します。
if (jobs.SequenceEqual(asc) || jobs.SequenceEquals(desc)) // ...
リストをソートせずに、このようなものはどうですか
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();
}
Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert.AreEqual(
mylist.OrderBy((a) => a.SomeProperty).ToList(),
mylist,
"Not sorted.");
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);
上記の解決策について少しlittleいのは、二重の小なり比較です。このような浮動比較は、浮動小数点の等値比較とほとんど同じであるため、気分が悪くなります。しかし、ここでは二重に機能するようです。整数値でも問題ありません。浮動小数点の比較は、null許容型を使用することで回避できますが、コードが少し読みにくくなります。
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);
これは、より軽量な汎用バージョンです。降順をテストするには、> = 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;
}
テスト: