Nunit: Пожелание: Добавить поддержку индексаторов в PropertyConstraint

Созданный на 2 июн. 2017  ·  46Комментарии  ·  Источник: nunit/nunit

На данный момент можно тестировать только значения обычных свойств и нет возможности тестировать значения индексатора. Мне нужно написать тест, который выглядит примерно так:

`` С #
[TestFixture]
открытый класс TestClass
{
пример класса
{
частный словарьDictionary = новый словарь();

  public string this[string key]
  {
    get { return dictionary[key]; }
    set { dictionary[key] = value; }
  }
}

[Test]
public void Test()
{
  var obj = new Example
  {
    ["TEST1"] = "1",
    ["TEST2"] = "2",
  };

  Assert.That(obj, Has.Property("Item", "TEST1").EqualTo("1").And.Property("Item", "TEST2").EqualTo("2"));
}

}
`` ''

done help wanted enhancement normal

Самый полезный комментарий

А что касается уровня оплаты труда, поскольку все здесь получают годовую зарплату около $ 0, работая над NUnit, я думаю, что мы все имеем одинаковый уровень оплаты

Все 46 Комментарий

Мне нравится эта идея.

C # не поддерживает именованные индексированные свойства, но VB.NET поддерживает. Я думаю, что синтаксис .Property("Name", index1, ...) следует зарезервировать для именованных индексаторов, поэтому я бы не хотел, чтобы он использовался для индексатора по умолчанию. Я хотел бы уважать язык C #, не требуя, чтобы люди передавали параметр имени.

Что вы думаете о Has.Item("TEST1").EqualTo("1") или Has.Index("TEST1").EqualTo("1") ?

@ jnm2
Использование метода «Элемент» или «Индексатор» в качестве ярлыка удобно, но на самом деле можно изменить имя индексатора в C #: https://stackoverflow.com/questions/5110403/class-with-indexer-and-property- named -элемент

@ Ch0senOne Имя можно изменить. Чтобы быть яснее, я должен был сказать, что в C # невозможно создать индексатор, отличный от стандартного. Например, вы не можете объявить два индексатора или использовать имя из C #.
Подумав над этим, я боюсь, что Has.Item("value") будет слишком запутанным с Contains.Item . Так может быть Has.Index("value") ?

Поэтому я бы предпочел Has.Index("IndexValue") для индексатора по умолчанию и Has.Property("PropertyName", indexParams) для индексаторов не по умолчанию. Has.Property тоже будет работать для индексаторов по умолчанию, хотя идиоматическая вещь C #, которую нужно сделать, - это использовать Has.Index .


Или, если бы мы хотели быть милыми, мы могли бы сделать Has.Index["IndexValue"] и
Has.Property("PropertyName")[indexparams] ... 😁

На самом деле, мне интересно, могли бы мы обойтись без Has.Item["AtIndex"] используя синтаксис индексатора, чтобы не путать с Contains.Item("ItemValue") ... не уверен, что я об этом думаю!


Посмотрим, что думают другие члены команды @ nunit / framework.

Мы уже определили направление развития свойств, используя лямбды, а не строки. Кажется, на это лучше потратить время. Я говорю по телефону, поэтому не могу дать вам номер проблемы.

@CharliePoole https://github.com/nunit/nunit/issues/26?

Это имеет смысл для Has.Property . Мы могли сделать:

  • Has.Property(_ => _.PropertyName[index1, ...])
  • Has.Property(_ => _.PropertyName, index1, ...)

Я все еще думаю, что мы не должны ничего делать с Has.Property пока не потребуются свойства, отличные от значений по умолчанию, а пока просто укажите Has.Index(value1, ...) или Has.Index[value1, ...] или Has.Item[value1, ...] для гораздо более распространенные индексаторы по умолчанию.

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

@ jnm2 На ваше ключами . Например

this[string key1, string key2]

Мне интересно, есть ли какие-либо обновления по этой проблеме или текущий способ тестирования индексаторов для типа, который не наследует IEnumerable. Полагаю, я мог бы просто проверить, не выбрасывается ли исключение KeyNotFoundException. Что еще я упускаю из виду, чтобы проверить наличие ключа в индексаторе?

@ crush83 Да, мой синтаксис index1, ... был предназначен для указания, что мы должны учитывать их, что бы мы ни делали.

Мне не нравится терминологический ключ, если мы не говорим об объекте, который на самом деле является словарем или ключевой коллекцией. Это вещи с индексаторами по умолчанию, но не все вещи с индексаторами по умолчанию имеют ключи. (Поскольку вы используете эту терминологию, возможно, вы найдете наш синтаксис Contains.Key полезным? Можем ли мы его улучшить?)

Не было никакого прогресса, кроме того, что вы видите здесь. Нам нужно предложение. Чтобы разместить что-то там, моим текущим фаворитом является Has.Item(params object[] indexerArgs) который будет вызывать индексатор по умолчанию независимо от его имени.
Сможет ли это сделать все, о чем просили до сих пор?

+1

@ nunit / framework-team Поддержите ли вы следующий новый API?

namespace NUnit.Framework
{
    public abstract class Has
    {
        public static ResolvableConstraintExpression Property(string name);

+       public static ResolvableConstraintExpression Item(params object[] indexerArgs);
    }
}

namespace NUnit.Framework.Constraints
{
    public class ConstraintExpression
    {
        public ResolvableConstraintExpression Property(string name);

+       public ResolvableConstraintExpression Item(params object[] indexerArgs);
    }
}

Я бы, вероятно, оставил класс IndexerConstraint внутренним, пока не появится причина добавить его в API.

Звучит неплохо.

Еще один голос за, я просто хотел эту функцию для консольного теста. 🙂

@ jnm2 Команда все еще хочет

Я быстро рассмотрел проблему, и, поскольку я точно не понимаю, должен ли поддерживаемый API выглядеть так - это:

/// obj from the initial post example
Assert.That(obj, Has.Item("TEST1").EqualTo(1).And.Item("TEST2").EqualTo(2));

либо это:

Assert.That(obj, Has.Item("TEST1", 1).And.Item("TEST2", 2));

?

В вашем примере используется params object[] поэтому вы выбрали вариант 2.

Предполагая, что опт. 2, потребуется ли массив, как если бы предоставлен только один объект, утверждающий, что ключ существует, 2 параметра - ключ и значение? Другие комбинации (более или менее ожидаемые параметры) терпят неудачу?

@Poimen Да, и мы будем признательны за помощь!

Массив необходим, потому что индексаторы могут принимать более одного аргумента. Например:

public string this[int x] => "First indexer";

public string this[string x] => "Second indexer";

public string this[int x, int y] => "Third indexer";

Вы должны использовать Has.Item(42).EqualTo("First indexer") , Has.Item("42").EqualTo("Second indexer") и Has.Item(1, 2).EqualTo("Third indexer") .

Видишь ли, ты узнаешь что-то новое каждый день - я предположил, что это опечатка, ой: see_no_evil: Раньше я не имел удовольствия использовать многоключевой индексатор ...

Хорошо, круто, на этой неделе я посмотрю, как что-нибудь собрать.

@ jnm2 Я создал PR по этому выпуску.

Единственное, что меня беспокоит, это сообщение об ошибке, которое создается кодом:

Has.Item(42)

Сообщение об ошибке немного "взломано", но я не уверен, какой самый чистый способ решить эту проблему. «Взлом»:
https://github.com/nunit/nunit/pull/3609/commit/96154ca6402c692cec5e0ae6947f9922e72799fe#diff -ceeee4b8399633f181b6bccd5a39eb32R72

@Poimen Извините, я не

@ jnm2 Появляется сообщение об ошибке:

  Expected: indexer [System.Int32]
  But was:  "not found"

То, как формируются сообщения об ошибках, StringifyArguments function немного "хакерское".

Итак, мой плохо сформулированный вопрос больше касается того, существует ли более чистый способ создания желаемого вывода сообщения.

@ jnm2 Думаю, я обратился к комментариям к обзору (спасибо за них). На этикетке написано awaiting:contributor .

Я не уверен, что мне нужно еще что-то сделать, чтобы вы знали, что я "готов": +1:

Я думаю, мы должны сделать это как можно ближе к сообщению, которое отображается, когда Has.Property не может найти свойство. Затем мы должны заменить property на default indexer и заменить имя свойства на accepting arguments [42] или что-то в этом роде. Я склоняюсь к мысли, что мы должны использовать один из существующих помощников по форматированию, например MsgUtils.FormatCollection чтобы отображать значения аргументов, а не перечислять типы аргументов. Например, один из аргументов может иметь значение null и работать с диапазоном типов.

Нет, это здорово! Такой комментарий в самой PR-ветке был бы идеальным.

Учитывая утверждение:
`` С #
Assert.That (тестировщик, Has.Item ("вау"));


It producers the message:

Ожидается: индексатор по умолчанию принимает аргументы <>
Но было: «не найдено»


Given the assertion:
```c#
Assert.That(tester, Has.Item("wow").EqualTo(1));

Он создает сообщения:

Default indexer accepting arguments < "wow" > was not found on NUnit.Framework.Constraints.IndexerConstraintTests+NamedIndexTester.

Это сгенерировано с помощью функции MsgUtils.FormatCollection .

Это также более точно соответствует сообщениям о свойствах с добавленными деталями.

Второй случай мне очень нравится. Первый случай все еще кажется мне семантически неправильным, потому что он показывает типы аргументов вместо значений аргументов. У нас есть только значения аргументов, и каждое значение аргумента может соответствовать диапазону типов аргументов в индексаторе. Неправильно делать вид, будто должен существовать индексатор с аргументом System.String потому что аргумент IEnumerable<char> будет работать точно так же.

IEnumerable<char> - дурацкий вариант использования ... но, конечно, я понял.

Итак, вернемся к - учитывая:
`` С #
Assert.That (тестировщик, Has.Item ("вау"));


producers the message:

Ожидается: индексатор по умолчанию принимает аргументы <"вау">
Но было: «не найдено»


For numbers it goes into the usual suffix - given:
```c#
Assert.That(tester, Has.Item(42d));

производители:

  Expected: Default indexer accepting arguments < 42.0d >
  But was:  "not found"

Мне тогда кажется, что сообщение «Ожидается» отлично. Сообщение But was: "not found" немного похоже на то, что в нем говорится об экземпляре строки с содержимым not found , но если PropertyConstraint ведет себя таким же образом, для меня предпочтительнее оставить их такими же. Изменение PropertyConstraint по умолчанию выходит за рамки этого PR. Мы все еще могли бы внести изменения, если бы вы захотели, и я и кто-то еще в команде фреймворка одобрили это изменение.

Да, я пытался заставить его этого не делать, но не смог найти решения с сообщением об ошибке. Хотелось бы, чтобы это было просто:

But was: not found

но возврат ConstraintResult помещает туда строковые кавычки.

Для справки по версии собственности:
`` С #
Assert.That (тестировщик, Has.Property ("NoItDoesNotHaveThis"));

produces:

Ожидается: свойство NoItDoesNotHaveThis
Но было:
`` ''
Таким образом, нет кавычек - но он возвращает тип, а не значение.

Я не был уверен, что возвращение перегрузки ConstraintResult будет решением?

Я думаю, мы должны придерживаться того, что делает PropertyConstraint для единообразия. Отображение представления по умолчанию того, что представляет собой значение actual - не самое худшее, когда появляется сообщение о том, что не удалось найти свойство.

Возврат нового класса, производного от ConstraintResult, - это то, как другие ограничения настраивают это, да.

Хорошо, отлично - звучит как - учитывая:
`` С #
Assert.That (тестировщик, Has.Item (42d));


producers:

Ожидается: индексатор по умолчанию принимает аргументы <42.0d>
Но было:
`` ''

Это соответствует сообщению об ограничении свойства.

@ nunit / framework-team и все остальные, я надеюсь на быстрое решение этого вопроса, поскольку отличный PR @Poimen (https://github.com/nunit/nunit/pull/3609) готов к слиянию в противном случае.

@Dreamescaper обратил мое внимание на мою озабоченность выше в этой цепочке, которую я потерял из виду: я боюсь, что люди будут писать и читать Assert.That(collection, Has.Item("x")); думая, что Has.Item означает Contains.Item .
Мы могли бы подумать о добавлении чего-нибудь в сообщение об ошибке, но это не поможет решить проблему его правильного понимания при чтении Has.Item в исходном коде:

  Expected: Default indexer accepting arguments < 42.0d > (Did you mean to use Contains.Item?)

С другой стороны, мне не нравятся Has.Index(42) или Has.Index(42).EqualTo(43) которые я предложил вместо Item . У экземпляра есть элемент по индексу. Вы бы не сказали, что у экземпляра есть индекс и что сам индекс равен 43. Индекс - это то, что вы передаете, 42.

Некоторые варианты

  • Используйте Has.Item . Откажитесь от проблем, которые могут возникнуть при чтении исходного кода. Возможно, добавьте некоторые смягчающие меры, которые помогут при написании исходного кода, например, XML-документы и подсказки в сообщениях об ошибках.
  • Используйте Has.Index несмотря на неудобное смешение терминологии.
  • Вместо этого используйте Has.ItemAt . Has.ItemAt(42).EqualTo(43) .

Подождите, я просто влюбился в последнее предложение, поскольку оно имеет параллель с методом LINQ ElementAt, которому присваивается индекс и который возвращает элемент.

Все довольны Has.ItemAt ? Предложения приветствуются, но опять же, я хотел бы сразу же отметить время, которое

+1 за Has.ItemAt :)

Я согласен с вами, что Has.Item потенциально сбивает с толку. Мне нравится Has.ItemAt но, поскольку вам нравится синтаксис LINQ, почему бы не использовать Has.ElementAt ?

Я не уверен насчет ElementAt, так как это не прямой эквивалент.
Например, для Dictionary<string,string> LINQ ElementAt (0) вернет первую пару, а Has.ItemTo (0) .EqualTo ("...") завершится ошибкой, поскольку нет индексатора, принимающего целые числа.

@CharliePoole Это действительно хорошая мысль. Похоже, поиск будет не таким интуитивным, но я не знаю, почему я так думаю. Я думаю, что предпочитаю Item потому что мы будем вызывать индексатор, предположительно, для какой-то коллекции / словаря / поиска, тогда как ElementAt предназначен для работы с запросами и другими перечислениями, которые не обязательно являются коллекциями. . Элементы коллекции обычно называют предметами.

@Dreamescaper Как вы думаете, Has.ItemAt(4) может заставить людей думать, что он работает так же, как ElementAt , перечисляя, а не вызывая индексатор?

@ jnm2
Вероятно, мог бы, но я не знаю лучшего варианта (и я думаю, что это все же гораздо лучший вариант, чем Has.Item).

@ jnm2 Я немного запутался. Этот выпуск начался с вопросов о свойствах и до сих пор носит такое название. Как Has.Property и Has.ItemAt будут связаны, взаимодействовать или сравниваться друг с другом в глазах пользователя?

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

Что касается NUnit 4.0, было бы неплохо рассмотреть, как термины используются в API, и немного поправить. Слово «член» часто используется как эквивалент «элементу», но может также означать свойство, метод или переменную.

Мне удобнее всего использовать ItemAt over ElementAt отчасти потому, что может показаться, что мы делегируем или используем тот же особый подход, что и Enumerable.ElementAt . Что меня сильно поразило в ElementAt(int index) это меньше деталей того, как это работает, и больше тот факт, что есть прецедент для суффиксов At в BCL, особенно связанных с параметром с именем index .

Если вы используете это с ILookup, каждый индекс ILookup возвращает несколько TElements, а не один элемент / элемент. Has.EntryAt может быть чем-то, что работает с коллекциями, словарями и поиском по нескольким элементам по индексу. Моя первая реакция такова, что Has.ItemAt который возвращает IEnumerable<TElement> из ILookup, вероятно, достаточно понятен.

Что вы все думаете? Трудно, когда вы видите, что работает более одной вещи, но предпочтение большинства здесь также может помочь указать, какое имя будет легче всего найти людям. Люди, кроме @ nunit / framework-team, я не хотел исключать вас. Если вы смотрите и испытываете сильное чувство, это всегда полезно!

@CharliePoole Has.Property("X") утверждает, что экземпляр имеет непараметрическое свойство с именем X . Has.ItemAt("X") будет утверждать, что экземпляр имеет индексатор по умолчанию (параметризованное свойство с нерелевантным общеизвестным именем), который может принимать строковый параметр.

Has.Property("X").EqualTo(3) утверждает, что экземпляр имеет непараметрическое свойство с именем X которое возвращает 3 если вы вызываете его непараметрический метод доступа get . Has.ItemAt("X").EqualTo(3) будет утверждать, что экземпляр имеет индексатор по умолчанию (параметризованное свойство с нерелевантным общеизвестным именем), который возвращает 3 если вы передадите ему конкретную строку "X" get аксессор.

Что делает имя индексатора по умолчанию несущественным, так это то, что синтаксис C #, VB и F # может вызывать индексаторы по умолчанию без указания имени. (Отсюда и термин «по умолчанию».) Язык, в котором не было концепции индексаторов по умолчанию, но имел концепцию именованных индексаторов, должен был бы указать имя. Имя индексатора по умолчанию обычно Item , но индексатор по умолчанию может иметь произвольное имя и по-прежнему быть индексатором по умолчанию, потому что эти языки по-прежнему могут вызывать его без указания имени. В C # это можно сделать с помощью атрибута [IndexerName("ArbitraryName")] .

@ jnm2 Я действительно понимаю весь язык C # и т. д. об индексаторах ... однако ...

Я думаю, что есть вероятность путаницы между собственностью

  • __being__ индексатор фактического объекта
  • __returning__ объект, у которого есть индексатор
  • __ возврат__ коллекции с элементами в ней

Я подчеркиваю, что __you__ не будет сбит с толку, и я, вероятно, только буду притворяться, когда разговариваю с вами (сократический метод, понимаете: wink :), но я подозреваю, что многие могут смутить это, и вы будете объяснять это людям достаточно много.

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

PS: Я совсем не чувствовал себя исключенным. : смайлик:

@CharliePoole Хорошая мысль. Можем ли мы отложить это соображение до тех пор, пока не остановимся на названии, поскольку похоже, что оно не повлияет на выбор между Has.ItemAt / Has.ElementAt / Has.EntryAt / etc? Или есть момент, который я упустил, и вы поддерживаете конкретный выбор наименования, исходя из этой потенциальной путаницы?

Пока склоняюсь к ItemAt . @Dreamescaper , ты тоже здесь? @CharliePoole Я не был уверен, что вы предпочитаете ElementAt или просто помогаете нам продумать процесс.

Чем больше людей откликнется на Has.ItemAt / Has.ElementAt / Has.EntryAt / other, тем больше у нас будет уверенности в том, что люди сочтут интуитивно понятным. Я хотел бы просто назвать это для того, кто, по-видимому, будет лидером на следующий день или около того, поскольку мы пропустим выпуск 3.13 на такую ​​крошечную сумму, если мы займем гораздо больше времени.

@ jnm2 Просто пытаюсь обдумать это через себя и поощрять то же самое.

WRT выбор слов, мне было интересно, будет ли какая-то конкретная формулировка лучше всего работать с тремя типами выражений, которые я перечислил, а также есть ли какие-либо другие комбинации, о которых нужно беспокоиться. С тремя упомянутыми мною вариантами у вас будет

  • Has.ItemAt("foo").EqualTo("bar")
  • `Has.Property (" Bing "). With.ItemAt (" foo "). EqualTo (" bar ")
  • `Has.Property (" Bing "). With.One.EqualTo (" бар ")

Думаю, вы правы в том, что формулировка не имеет значения ни для каких вариантов.

В более долгосрочной перспективе я бы предпочел использовать фактическую индексацию с квадратными скобками. Но для этого потребуется фактическая компиляция выражения ограничения, а не моделирование как набор классов.

Я знаю, что это выше моей зарплаты: smile:, но подумал, что пойду сюда ...

Мне нравится ItemAt .

Я потратил время, как предложил @CharliePoole , обдумывая некоторые варианты использования. Я пришел к выводу, что ElementAt больше используется в ситуациях IEnumerable и языковые индексаторы могут обрабатывать больше, чем просто IEnumerable . Некоторое время я сидел на заборе ElementAt , затем подумал:
`` С #
Assert.That (..., Has.ElementAt ("один", "два"));

against:
```c#
Assert.That(..., Has.ItemAt("one", "two"));

ItemAt в приведенном выше примере кажется более естественным для выражения нескольких индексаторов значений.

Я также считал, что билет изначально предназначался для короткого замыкания для ограничения свойства, и ItemAt соответствует этому повествованию. (Однако это не мешает ему трансформироваться во что-то большее / лучшее / и т.д., просто точка зрения).

Я также предпочитаю ItemAt , так что давайте назовем это большинством и вынесем решение в пользу этого 😺

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

@Poimen Re: ваша зарплата, не стоит недооценивать степень, в которой мы все придумываем что-то, пока идем сюда. Спасибо за то, что поделились своими мыслями!

А что касается уровня оплаты труда, поскольку все здесь получают годовую зарплату около $ 0, работая над NUnit, я думаю, что мы все имеем одинаковый уровень оплаты

@Dreamescaper задал еще один хороший вопрос в PR. Независимо от того, положительный или отрицательный, это похоже на проверку счетчика, а не на наличие индексатора:

Assert.That(new[] { 1, 2, 2 }, Has.ItemAt(3));
Assert.That(new[] { 1, 2, 2 }, Has.No.ItemAt(3));

Кажется, безопаснее всего удалить ограничение Exists, пока у нас не будет больше времени подумать над этим. Я бы предпочел открыть отдельную задачу для Has.Indexer(typeof(Type1), typeof(Type2), ...) для проверки существования индексатора и Has.ItemAt(arg1, arg2, ...) для фактического запуска индексатора и утверждения вещей о результирующем значении.

Будет ли кто-нибудь возражать против первоначальной отправки Has.ItemAt(3) как разрешения ограничения и Has.ItemAt(3).EqualTo(4) как разрешения?

Для меня Has.Indexer имеет смысл. Я предполагаю, что вы также будете поддерживать Has.Indexer<T>() , Has.Indexer<T1, T2>() и т. Д. Для согласованности с другими ограничениями.

Спасибо за ответ! Я дал добро на запрос на перенос, чтобы Has.ItemAt (...) больше не саморазрешался, и я подал https://github.com/nunit/nunit/issues/3690 для отслеживания Has.Indexer чтобы предоставить эту возможность.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги