Je fais des tests unitaires et je veux savoir s'il existe un moyen de tester si une liste est ordonnée par une propriété des objets qu'elle contient.
En ce moment je le fais de cette façon mais je n'aime pas ça, je veux une meilleure façon. quelqu'un peut m'aider s'il vous plait?
// (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;
}
Si vous utilisez MSTest, vous voudrez peut-être jeter un œil à CollectionAssert.AreEqual .
Enumerable.SequenceEqual peut être une autre API utile à utiliser dans une assertion.
Dans les deux cas, vous devez préparer une liste qui contient la liste attendue dans l'ordre attendu, puis comparer cette liste au résultat.
Voici un exemple:
var studyFeeds = Feeds.GetStudyFeeds(2120, DateTime.Today.AddDays(-200), 20);
var expectedList = studyFeeds.OrderByDescending(x => x.Date);
Assert.IsTrue(expectedList.SequenceEqual(studyFeeds));
Une façon .NET 4.0 serait d'utiliser le Enumerable.Zip
méthode pour compresser la liste avec elle-même décalée d'une unité, qui associe chaque élément à l'élément suivant de la liste. Vous pouvez ensuite vérifier que la condition est vraie pour chaque paire, par ex.
var ordered = studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => new { a, b })
.All(p => p.a.Date < p.b.Date);
Si vous utilisez une version antérieure du framework, vous pouvez écrire votre propre méthode Zip sans trop de problèmes, quelque chose comme ceci (la validation des arguments et l'élimination des énumérateurs le cas échéant sont laissées au lecteur):
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);
}
Si votre framework de tests unitaires a des méthodes d'aide pour affirmer l'égalité des collections, vous devriez pouvoir faire quelque chose comme ça (version NUnit):
var sorted = studyFeeds.OrderBy(s => s.Date);
CollectionAssert.AreEqual(sorted.ToList(), studyFeeds.ToList());
La méthode assert fonctionne avec n'importe quel IEnumerable
, mais lorsque les deux collections sont de type IList
ou "tableau de quelque chose", le message d'erreur renvoyé lorsque l'assertion échoue contiendra l'index de la première sortie. élément de lieu.
Nunit 2.5 a introduit CollectionOrderedContraint et une belle syntaxe pour vérifier l'ordre d'une collection:
Assert.That(collection, Is.Ordered.By("PropertyName"));
Pas besoin de commander et de comparer manuellement.
Les solutions publiées impliquant le tri de la liste sont coûteuses - déterminer si une liste IS triée peut être effectuée en O (N). Voici une méthode d'extension qui vérifiera:
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;
}
Un IsOrderedDescending
correspondant pourrait être facilement implémenté en changeant > 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
n'est pas encore mort!
Greg Beech réponse , bien qu'excellent, peut encore être simplifié en effectuant le test dans le Zip lui-même. Donc au lieu de:
var ordered = studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => new { a, b })
.All(p => p.a.Date < p.b.Date);
Vous pouvez simplement faire:
var ordered = !studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => a.Date < b.Date)
.Contains(false);
Ce qui vous permet d'économiser une expression lambda et un type anonyme.
(À mon avis, la suppression du type anonyme facilite également la lecture.)
Que diriez-vous:
var list = items.ToList();
for(int i = 1; i < list.Count; i++) {
Assert.IsTrue(yourComparer.Compare(list[i - 1], list[i]) <= 0);
}
où yourComparer
est une instance de YourComparer
qui implémente IComparer<YourBusinessObject>
. Cela garantit que chaque élément est inférieur à l'élément suivant dans l'énumération.
La réponse basée sur Linq est:
Vous pouvez utiliser la méthode SequenceEqual
pour vérifier si l'original et celui commandé sont identiques ou non.
var isOrderedAscending = lJobsList.SequenceEqual(lJobsList.OrderBy(x => x));
var isOrderedDescending = lJobsList.SequenceEqual(lJobsList.OrderByDescending(x => x));
N'oubliez pas d'importer System.Linq
espace de noms.
De plus:
Je répète que cette réponse est basée sur Linq, vous pouvez obtenir plus d'efficacité en créant votre méthode d'extension personnalisée.
De plus, si quelqu'un veut toujours utiliser Linq et vérifier si la séquence est ordonnée dans l'ordre croissant ou décroissant, vous pouvez obtenir un peu plus d'efficacité comme ça:
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
}
Vous pouvez utiliser une méthode d'extension comme celle-ci:
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;
}
Il permet de vérifier si la séquence est triée et détermine également la direction:
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
Voici comment je le fais avec Linq et moi comparable, ce n'est peut-être pas le meilleur mais ça fonctionne pour moi et c'est indépendant du framework de test.
Donc, l'appel ressemble à ceci:
myList.IsOrderedBy(a => a.StartDate)
Cela fonctionne pour tout ce qui implémente IComparable, donc les chaînes de nombres et tout ce qui hérite d'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;
}
J'espère que cela aide, cela m'a pris beaucoup de temps pour travailler celui-ci.
La vérification d'une séquence peut avoir quatre résultats différents. Same
signifie que tous les éléments de la séquence sont identiques (ou que la séquence est vide):
enum Sort {
Unsorted,
Same,
SortedAscending,
SortedDescending
}
Voici un moyen de vérifier le tri d'une séquence:
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;
}
}
J'utilise l'énumérateur directement au lieu d'une boucle foreach
car j'ai besoin d'examiner les éléments de la séquence sous forme de paires. Il rend le code plus complexe mais est également plus efficace.
D'une manière ou d'une autre, vous devrez parcourir la liste et vous assurer que les éléments sont dans l'ordre que vous souhaitez. Comme la comparaison d'éléments est personnalisée, vous pouvez envisager de créer une méthode générique pour cela et de passer une fonction de comparaison - de la même manière que le tri de la liste utilise des fonctions de comparaison.
Quelque chose LINQ-y serait d'utiliser une requête triée distincte ...
var sorted = from item in items
orderby item.Priority
select item;
Assert.IsTrue(items.SequenceEquals(sorted));
L'inférence de type signifie que vous auriez besoin d'un
where T : IHasPriority
Cependant, si vous avez plusieurs éléments de la même priorité, alors pour une affirmation de test unitaire, vous feriez probablement mieux de simplement boucler avec l'index de liste comme Jason l'a suggéré.
Vous pouvez utiliser lambda en extension:
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;
}
Utilisation:
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);
}
Vous pouvez d'abord créer une version ordonnée et non ordonnée de la liste:
var asc = jobs.OrderBy(x => x);
var desc = jobs.OrderByDescending(x => x);
Comparez maintenant la liste d'origine avec les deux:
if (jobs.SequenceEqual(asc) || jobs.SequenceEquals(desc)) // ...
Qu'en est-il de quelque chose comme ça, sans trier la liste
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.");
Bien que les réponses d'AnorZaken et de Greg Beech soient très agréables, car elles ne nécessitent pas l'utilisation d'une méthode d'extension, il peut être bon d'éviter parfois Zip (), car certains énumérables peuvent être coûteux à énumérer de cette manière.
Une solution peut être trouvée dans 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);
Une chose un peu moche à propos de la solution ci-dessus, c'est le double de moins que les comparaisons. Des comparaisons flottantes comme celle-ci me rendent mal à l'aise car c'est presque comme une comparaison d'égalité à virgule flottante. Mais cela semble fonctionner pour le double ici. Les valeurs entières seraient également très bien. La comparaison en virgule flottante peut être évitée en utilisant des types nullables, mais le code devient alors un peu plus difficile à lire.
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);
Voici une version générique plus légère. Pour tester l'ordre décroissant, remplacez la comparaison> = 0 par <= 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;
}
Tests: