#c# #unit-testing #mstest
#c# #модульное тестирование #мстест
Вопрос:
Я написал небольшой метод расширения, который находит индексы данной строки в любом IEnumerable.
public static IEnumerable<int> FindIndexesOf(this IEnumerable<string> itemList, string indexesToFind)
{
if (itemList == null)
throw new ArgumentNullException("itemList");
if (indexesToFind == null)
throw new ArgumentNullException("indexToFind");
List<string> enumerable = itemList as List<string> ?? itemList.ToList();
for (int i = 0; i < enumerable.Count(); i )
{
if (enumerable[i] == indexesToFind)
yield return i;
}
}
Как вы можете видеть выше, исключение ArgumentNullException выдается, если ItemList имеет значение null. Просто и ясно.
При запуске моего unittest в приведенном выше методе я ожидаю и исключение типа ArgumentNullException, потому что ItemList имеет значение null . Однако тест выдает false, потому что исключение не генерируется.
Как это возможно? Логика кажется довольно ясной. Смотрите тест ниже.
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void FindIndexesOfTest2()
{
string[] items = null;
IEnumerable<int> indexes = items.FindIndexesOf("one");
}
Где я ошибаюсь в своей логике; почему он не выдает исключение ArgumentNullException?
Комментарии:
1. Вы запускали код в отладчике и проверяли, действительно ли параметр равен null?
2. У меня есть, да. На самом деле это null .
Ответ №1:
Проблема в том, что использование счетчиков yield
оценивается лениво.
Поскольку вы не выполняете итерацию по возвращенной коллекции, метод фактически не выполнен.
Правильный способ сделать это — разделить метод на две части:
public static IEnumerable<int> FindIndexesOf(this IEnumerable<string> itemList, string indexesToFind)
{
if (itemList == null)
throw new ArgumentNullException("itemList");
if (indexesToFind == null)
throw new ArgumentNullException("indexToFind");
return FindIndexesOfImpl(itemList, indexesToFind);
}
private static IEnumerable<int> FindIndexesOfImpl(this IEnumerable<string> itemList, string indexesToFind)
{
List<string> enumerable = itemList as List<string> ?? itemList.ToList();
for (int i = 0; i < enumerable.Count(); i )
{
if (enumerable[i] == indexesToFind)
yield return i;
}
}
Здесь первый метод будет выполняться при его вызове и возвращать лениво вычисляемый перечислитель, который этого не сделал, пока вы не выполните итерацию по нему.
Хотя я бы посоветовал вам также изменить последний метод здесь, чтобы он был действительно лениво оценен. Тот факт, что метод кэширует все itemList
только для того, чтобы иметь возможность использовать индексы, не нужен, и вы можете фактически переписать его без него:
public static IEnumerable<int> FindIndexesOfImpl(this IEnumerable<string> itemList, string indexesToFind)
{
var index = 0;
foreach (var item in itemList)
{
if (item == indexesToFind)
yield return index;
index ;
}
}
Вы также можете использовать методы расширения LINQ для этого, хотя это включает в себя создание временного объекта для каждого элемента, не уверенный, стоит ли оно того, я бы выбрал тот, который чуть выше здесь:
public static IEnumerable<int> FindIndexesOfImpl(this IEnumerable<string> itemList, string indexesToFind)
{
return itemList
.Select((item, index) => new { item, index })
.Where(element => element.item == indexesToFind)
.Select(element => element.index);
}
С помощью этого последнего метода вы можете переместить его обратно в основной метод, потому что вы больше не используете yield
:
public static IEnumerable<int> FindIndexesOf(this IEnumerable<string> itemList, string indexesToFind)
{
if (itemList == null)
throw new ArgumentNullException("itemList");
if (indexesToFind == null)
throw new ArgumentNullException("indexToFind");
return itemList
.Select((item, index) => new { item, index })
.Where(element => element.item == indexesToFind)
.Select(element => element.index);
}
Комментарии:
1. Работает, ты лучший!
2. Обратите внимание, что на самом деле может быть лучше просто создать список в методе и вернуть его, содержащий результаты, вместо того, чтобы использовать здесь перечислитель, поскольку вы на самом деле
itemList
все равно кэшируете все. Есть другой способ написать этот последний метод, хотя и без необходимости использования кэша, я также опубликую его.3. Какой из этих двух вы бы порекомендовали?
4. Я бы рекомендовал либо тот, который использует
foreach
иindex
переменную, либо последний, который возвращается только к 1 методу.5. Да, но какой из них? Каковы преимущества для них обоих?