Сравните два объекта List‹string[]› в модульном тесте C#

Я пытаюсь создать модульный тест, который сравнивает два списка массивов строк.

Я попытался создать два одинаковых объекта List<string[]>, но когда я использую CollectionAssert.AreEqual(expected, actual);, тест не проходит:

[TestMethod]
public void TestList()
{
    List<string[]> expected = new List<string[]> {
        new string[] { "John", "Smith", "200" },
        new string[] { "John", "Doe", "-100" }
    };

    List<string[]> actual = new List<string[]> {
        new string[] { "John", "Smith", "200" },
        new string[] { "John", "Doe", "-100" }
    };

    CollectionAssert.AreEqual(expected, actual);
}

Я также пробовал Assert.IsTrue(expected.SequenceEqual(actual));, но это тоже не помогло.

Оба эти метода работают, если я сравниваю два списка строк или два массива строк, но они не работают при сравнении двух списков массивов строк.

Я предполагаю, что эти методы не работают, потому что они сравнивают два списка ссылок на объекты вместо строковых значений массива.

Как я могу сравнить два объекта List<string[]> и сказать, действительно ли они одинаковы?


person Tot Zam    schedule 17.09.2017    source источник
comment
Попробуйте это: expected.Zip(actual, (e, a) => e.SequenceEqual(a)).All(x => x).   -  person Enigmativity    schedule 18.09.2017
comment
Из любопытства... вы бы считали списки равными, если бы в них были одни и те же элементы, но в другом порядке? Кроме того, стоит отметить, что строковый массив — ужасная замена объекту.   -  person David    schedule 18.09.2017
comment
@David Для этого конкретного теста мне подойдет решение, которое требует, чтобы элементы были в одном порядке, а также решение, которое игнорирует порядок. И я согласен, что объекты обычно лучше, чем строковые массивы. Этот код является частью более широкой картины и должен быть в этом формате.   -  person Tot Zam    schedule 18.09.2017
comment
@Enigmativity Хороший, или !expected.Zip(actual, (e, a) => e.SequenceEqual(a)).Contains(false). Но если два внешних списка имеют разное количество, Zip просто остановится, когда в одном списке больше не будет записей, поэтому я думаю, что в этом случае вы можете получить ложное срабатывание.   -  person Jeppe Stig Nielsen    schedule 18.09.2017
comment
@JeppeStigNielsen Является ли .Contains(false) более эффективным, чем .All(x => x)?   -  person Tot Zam    schedule 18.09.2017
comment
@JeppeStigNielsen - отличное замечание по поводу того, что .Zip не сравниваются полные длины.   -  person Enigmativity    schedule 18.09.2017
comment
@TotZam - в этом примере разница в эффективности незначительна.   -  person Enigmativity    schedule 18.09.2017
comment
@Enigmativity Тогда есть ли причина использовать этот код вместо вашего кода?   -  person Tot Zam    schedule 18.09.2017
comment
@TotZam - я бы выбрал удобочитаемость. Если подумать об этом еще немного, нет никакой разницы в производительности. Оба ярлыка на false.   -  person Enigmativity    schedule 18.09.2017
comment
.Contains(false) отрицание не более (и не менее) эффективно, чем .All(x => x). Оба потребляют исходный код, пока не найдут запись false. Первый сравнивает каждый элемент с false с помощью компаратора равенства по умолчанию для bool. Второй вызывает делегат predicate, который оборачивает статический метод IL, выходящий из лямбда-стрелки x => x, и проверяет возвращаемые значения. Поскольку среда выполнения будет выполнять встраивание в любом случае, я думаю, что оба будут одинаково быстрыми (я не измерял). Согласен с @Enigmativity.   -  person Jeppe Stig Nielsen    schedule 18.09.2017


Ответы (3)


Это не удается, потому что элементы в вашем списке являются объектами (string[]), и поскольку вы не указали, как CollectionAssert.AreEqual должен сравнивать элементы в двух последовательностях, он возвращается к поведению по умолчанию, которое заключается в сравнении ссылок. Если бы вы изменили свои списки, например, на следующие, вы бы обнаружили, что тест проходит, потому что теперь оба списка ссылаются на одни и те же массивы:

var first = new string[] { "John", "Smith", "200" };
var second = new string[] { "John", "Smith", "200" };

List<string[]> expected = new List<string[]> { first, second};
List<string[]> actual = new List<string[]> { first, second};

Чтобы избежать ссылочных сравнений, вам нужно сообщить CollectionAssert.AreEqual, как сравнивать элементы, вы можете сделать это, передав IComparer при вызове:

CollectionAssert.AreEqual(expected, actual, StructuralComparisons.StructuralComparer);
person Jason Boyd    schedule 17.09.2017

CollectionAssert.AreEqual(expected, actual); терпит неудачу, потому что сравнивает ссылки на объекты. expected и actual относятся к разным объектам.

Assert.IsTrue(expected.SequenceEqual(actual)); не работает по той же причине. На этот раз содержимое expected и actual сравнивается, но сами элементы являются разными ссылками на массивы.

Возможно, попробуйте сгладить обе последовательности, используя SelectMany:

var expectedSequence = expected.SelectMany(x => x).ToList();
var actualSequence = actual.SelectMany(x => x).ToList();
CollectionAssert.AreEqual(expectedSequence, actualSequence);

Как правильно заметил Enigmativity в своем комментарии, SelectMany может дать положительный результат, когда количество массивов и/или их элементов различается, но сведение списков приведет к равному количеству элементов. Это безопасно только в том случае, когда у вас всегда одинаковое количество массивов и элементов в этих массивах.

person Kapol    schedule 17.09.2017
comment
Вы справились с объяснением ссылок на объекты, но подход .SelectMany создает целую кучу ложных срабатываний, и нет необходимости в .Select(y => y) - person Enigmativity; 18.09.2017
comment
Спасибо за комментарий. Я пропустил это. Я отредактировал свой пост. - person Kapol; 18.09.2017

Лучшим решением было бы проверить как элементы в каждой подколлекции, так и количество предметов в каждой соответствующей подколлекции.

Попробуйте с этим:

bool equals = expected.Count == actual.Count &&
              Enumerable.Range(0, expected.Count).All(i => expected[i].Length == actual[i].Length &&
                                                           expected[i].SequenceEqual(actual[i]));
Assert.IsTrue(equals);

Это проверит, что:

  • Оба списка имеют одинаковую длину
  • Все пары подколлекций в обоих списках имеют одинаковую длину.
  • Предметы в каждой паре подколлекций одинаковы.

Примечание: использование SelectMany не является хорошей идеей, так как это может создать ложное срабатывание, у вас есть те же элементы во втором списке, но разбросанные по разным подколлекциям. Я имею в виду, что два списка будут считаться одинаковыми, даже если во втором из них будут одинаковые элементы в одной подколлекции.

person Sergio0694    schedule 17.09.2017