Nunit: Функция экземпляров на тестовый случай

Созданный на 24 нояб. 2017  ·  112Комментарии  ·  Источник: nunit/nunit

Кажется, что ментальная модель каждого состоит в том, что для каждого тестового примера создается экземпляр при распараллеливании в фикстуре (https://github.com/nunit/nunit/issues/2105, https://github.com/nunit/nunit/issues / 2252, https://github.com/nunit/nunit/issues/2573), хотя этого никогда не было. Я стараюсь решить эту проблему, всегда используя статические классы и определяя одноразовый вложенный класс фикстур, который в некоторых отношениях обеспечивает более богатый опыт ( пример ), но это может быть не то, что мы хотим для всех. Небольшой недостаток здесь в терминологии: статический класс - это то, что NUnit считает приспособлением, а реальное приспособление - это вложенный класс.

Не вариант делать экземпляр для каждого теста по умолчанию, потому что это нарушает непараллельные фикстуры, которые полагаются на то, что один тест может получить доступ к состоянию из другого теста. Люди, использующие атрибут [Order] , вероятно, единственные люди, на которых он повлияет, но он полностью нарушит любой тест, зависящий от порядка, который я могу придумать.

Мне нравится предложение Чарли:

  • Атрибут уровня сборки, который позволяет задать способ создания тестовых приспособлений: отдельный экземпляр или экземпляр для каждого приспособления.
  • Единый экземпляр будет работать так же, как сейчас
  • Экземпляр для каждого прибора создаст новый прибор для каждого теста.
  • ЛИБО выполнить одноразовые действия по настройке и демонтажу для каждого теста (т. Е. Больше не «один раз») ИЛИ заставить OneTimeSetUp и OneTimeTearDown помечаться как ошибки.

Возможные расширения:

  • Сделайте также этот класс уровня, позволяя отдельным приспособлениям иметь разный жизненный цикл.
  • Иметь третий параметр уровня сборки, который автоматически запускает отдельные экземпляры для любого приспособления, содержащего один или несколько тестов, которые выполняются параллельно. (Сложно всегда знать заранее)

Однако я бы посоветовал сначала заняться основами.

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

Я за ограждения. OneTimeSetUp и OneTimeTearDown должны быть помечены как ошибки, если прибор выполняет экземпляр для каждого тестового примера. Может, если они не статичны?

done feature normal docs releasenotes

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

есть ли шанс, что мы сможем это рассмотреть? кажется, что мы были очень близки

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

В настоящее время вы __can__ разделяете состояние между предоставленными параллельными тестами (1) вы устанавливаете состояние в конструкторе или однократно и (2) не меняете его ни в одном тесте. В некоторых случаях вы даже можете обойтись, если в тесте или настройке для состояния установлено постоянное значение, при условии, что вы никогда не устанавливаете для него какое-либо другое значение. Последнее неприятно, хотя я думаю, что у нас есть несколько тестов NUnit, которые это делают.

Я считаю, что мы не сможем изменить значение по умолчанию в ближайшее время, если вообще когда-либо. В настоящее время наличие изменяемого состояния не гарантирует ошибки. Это ошибка, только если вы действительно измените ее, и мы не узнаем об этом, если не проведем анализ кода - чего не будем делать!

Если вы не измените состояние экземпляра в ходе тестирования, настройки или разборки, вы не сломаетесь. Если вы это сделаете, вы уже сломаны, пока ваши тесты могут выполняться параллельно сами с собой. Я не могу придумать, что ты не сломлен. Это оправдание использования опции Auto, если мы считаем, что сложность, возникающая в результате для пользователя, стоит результата «разумных значений по умолчанию».

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

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

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

@BlythMeister Не волнуйтесь! Мы не рассматриваем никаких изменений, которые могли бы привести к поломке существующего кода.

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

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

С точки зрения XP (и я надеюсь, что все знают, что это всегда моя точка зрения) у нас есть три истории, которые строятся одна на другой ...

  1. Пользователь может указать поведение экземпляров для каждого тестового примера глобально для всех фикстур в сборке.

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

  3. Пользователи могут указывать поведение экземпляров на тестовый пример для фикстур, содержащих тесты, которые могут выполняться параллельно.

Я бы хотел, чтобы в этом выпуске полностью реализовано (1). Я думаю, мы должны просто выбросить (2) из ​​головы - или, по крайней мере, из части, которая пишет код - на данный момент. Я думаю, что (3) возможен, но есть некоторые подводные камни, с которыми мы уже столкнулись в других вопросах. (Прибор не знает, будут ли тестовые примеры запускаться параллельно!) Давайте поговорим об одном JustInTime, который будет после того, как (1) и (2) будут завершены и объединены. FWIW, я думаю, что у нас есть тенденция связывать себя узлами, пытаясь думать на несколько ходов вперед. Я упомянул (2) и (3), чтобы мы никоим образом не блокировали их, чего, кажется, достаточно легко избежать.

Думаю, тогда мы все согласны. Однако перед этим у меня есть несколько приоритетов NUnit, поэтому я оставлю это другим, чтобы они прокомментировали и, возможно, заберут.

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

Я согласен с @CharliePoole, что это должен быть атрибут. Тесты должны быть разработаны с учетом экземпляра для каждого теста или экземпляра для каждого устройства, поэтому они всегда должны выполняться таким образом. Я также предпочитаю использовать только вариант 1. Я думаю, что если вам нужен такой стиль тестирования, вы должны выбрать его для всего вашего набора тестов. Разрешение вам участвовать на более низких уровнях приводит к сложности, которая, как мне кажется, не дает хорошей рентабельности.

Просто любопытно, есть ли какие-нибудь планы по внедрению этой функции?

@rprouse Это все еще помечено как требующее дизайна, но похоже, что обсуждение уже охватило все.

@CharliePoole согласился, что это может уйти от дизайна. @pfluegs, мы еще не начали работать над этим, кроме проектирования, поэтому пока нет планов.

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

Меня только что сильно укусила эта проблема - +1 за исправление.

Если нам когда-нибудь понадобится больше возможностей, хотим ли мы:

`` С #
[сборка: FixtureCreation (FixtureCreationOptions.PerTestCase)]

And:
```diff
namespace NUnit.Framework
{
+   public enum FixtureCreationOptions
+   {
+       Singleton, // = default
+       PerTestCase
+   }
}

Мое собственное мнение об этом немного изменилось, по крайней мере, в контексте новой структуры.

Теперь я думаю, что должно быть два атрибута уровня класса, по одному для каждого поведения. Я бы заставил TestFixture работать с экземпляром для каждого теста, а Shared Fixture работал бы, как сейчас NUnit. Очевидно, что это серьезное изменение в контексте NUnit, но общий подход, возможно, стоит рассмотреть.

@CharliePoole [TestFixture(FixtureOptions.PerTestCase)] кажется приятным синтаксисом, но мне также очень нужен атрибут уровня сборки, чтобы я мог участвовать в каждом тестовом проекте. Как бы мы хотели, чтобы атрибут уровня сборки выглядел?

Правильно, поэтому я высказал оговорку WRT, что я буду делать на новом фреймворке. Однако в случае NUnit вы не захотите изменять семантику TestFixture и я не могу придумать действительно подходящего названия для поведения экземпляров в каждом случае. Если бы у вас было два имени, все, что вам нужно было бы сделать для получения желаемого поведения на всех приборах, - это выполнить глобальное редактирование исходного файла.

Мне не нравятся глобальные настройки на уровне сборки, потому что они сбивают с толку некоторых разработчиков, которые могут не знать, что установлено в файле AssemblyInfo. В настоящее время мы разрешаем несколько, поэтому я полагаю, что нет большого вреда в добавлении еще одного. У меня нет мыслей об имени атрибута уровня сборки, кроме того, что он должен сообщать, что мы устанавливаем параметр (по умолчанию?) Для всех приспособлений в сборке.

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

@ nunit / framework-team Что вы думаете? Для первоначального ознакомления с функцией:

  • да / нет для настройки на уровне сборки?
  • да / нет для настройки уровня прибора?
  • да / нет для настройки на уровне метода?
  • да / нет для настройки на уровне тестового случая?

Хорошее замечание о классах фикстур без атрибутов.

Я бы сказал да, да, нет, нет

Я согласен с Чарли - с пояснением, что «уровень приспособлений» означает «уровень класса». (В отличие, например, от метода источника тестового примера. 😊)

Что помогает! Таким образом, проблема с [TestFixture(FixtureCreationOptions.PerTestCase)] заключается в том, что в одном классе разрешено несколько атрибутов TestFixture, и они могут противоречить друг другу. Может быть, лучше и проще иметь новый именованный атрибут, применимый к сборкам и классам?

`` С #
[сборка: ShareFixtureInstances (ложь)]

[ShareFixtureInstances (false)]
открытый класс SomeFixture
{
// ...
}

Framework API:

```diff
namespace NUnit.Framework
{
+   [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
+   public sealed class ShareFixtureInstancesAttribute : PropertyAttribute
+   {
+       public bool ShareFixtureInstances { get; }

+       public ShareFixtureInstancesAttribute(bool shareFixtureInstances);
+   }
}

Звучит отлично. Каково это поведение наследования - если у меня есть атрибут в базовом классе с его собственными тестами, а затем он также наследуется от этого класса? 😊

Предположительно, поскольку это атрибут «класса» - он должен быть в реальном классе, из которого запускаются рассматриваемые тесты?

Кроме того, я не совсем в восторге от названия - поскольку прибор не всегда является классом в терминах nunit, я не думаю (например, все тесты в источнике testcases - если только "набор" и "приспособление" не имеют более сильную семантику в смысле, чем я думаю, в терминах Nunit!)

Как насчет чего-то вроде InstancePerTest ... но лучше назвать ...

Договорились об уровне сборки и класса, но не глубже. Я также предпочитаю что-то вроде предложения ClassInstancePerTest(bool) . Я также предполагаю, что атрибут на уровне класса переопределит уровень сборки, и я ожидаю, что он будет унаследован. Я думаю, что наследование важно, потому что вам обычно нужен экземпляр для каждого теста на основе членов класса в сочетании с параллелизмом. Следовательно, вы бы хотели, чтобы это автоматически применялось к производным классам.

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

Узнав об этом. Я, как и @ jnm2 , [TearDown] . :( Нет последовательного способа отличить ошибки теста от ошибок каркаса. Это действительно болезненный вариант использования.

Если я могу вмешаться в дизайн, я говорю с

Имя PerTestInstance , InstancePerTest или что-то подобное звучит намного лучше, чем ShareFixtureInstances . Мне слегка не нравится слово Class в имени атрибута, поскольку, когда я тестирую код F #, я не думаю, что экземпляры типов являются классами . Но я уверен, что смогу с этим жить! :)

О, мальчик, не могу дождаться этой функции!

@ kkm000 Я согласен на использование слова "класс". NUnit обычно использует имена, отличные от реализации. Даже в этом случае люди делают предположения, но лучше избегать таких вещей.

Примеры того, о чем я говорю: Test, TestFixture, SetUpFixture, Worker (не поток) и т. Д. В принципе, класс __could__ представляет отдельный тест, а не приспособление ... Я не говорю, что мы это делаем или даже должен, но __мы могли __.

@ kkm000

Спасибо за комментарий! Вы вообще не вторгаетесь. У нас есть цель дизайна - быть независимыми от языка там, где это возможно.
Помимо сосредоточения внимания на C #, еще кое-что о слове class : что, если однажды мы будем поддерживать фикстуры, которые не являются классами?

[InstancePerTestCase] обойдёт проблему, но может потерять интуитивную ясность.

Сегодня приборы и классы не сильно расходятся, но если они расходятся, я почти уверен, что этот атрибут имеет отношение к приборам, а не к классам.

Наборы - это общая организационная концепция, в то время как фикстуры - это вещи, которые содержат логику настройки / разрыва и состояние тестирования и передаются в методы тестирования (в настоящее время как аргумент this методов экземпляра). Приспособления не обязательно должны быть частью организационной иерархии. Представим себе, что фикстуры расходятся с концепцией классов и расходятся с концепцией тестовых наборов:

`` С #
общедоступный статический класс SomeTestSuite // Не приспособление: нет логики настройки / разрыва и состояния тестирования
{
[Test] // Отдельный тест, а не набор параметров;
// прибор вводится через параметр, а не через неявный параметр this
public static void SomeTest (прибор SomeFixture)
{
fixture.SomeService.SomeProperty = true;
fixture.SomeAction ();
fixture.AssertSomething ();
}
}

[TestFixture]
публичная структура SomeFixture: IDisposable
{
общедоступный SomeService SomeService {получить; } // Состояние

public SomeFixture() // This was almost legal syntax in C# 6, but I'm making a point 😁
{
    // Setup
}

public void Dispose()
{
    // Teardown
}

public void SomeAction()
{
    // Helper method
}

public void AssertSomething()
{
    // Helper method
}

}
`` ''

В приведенном выше примере [FixtureInstancePerTestCase] или [SharedFixtureInstances(false)] не ориентированы на классы и не ориентированы на иерархию (наборы): они сосредоточены на фикстурах, а фикстуры - это повторно используемые логика и состояние настройки / разрыва. В частности, он сосредоточен на том, создается ли новый SomeFixture для каждого тестового примера или же передается в каждый тест.

Поскольку этот атрибут будет контролировать, будут ли разные ITest использовать экземпляры ITest.Fixture, а управляемая вещь - это то, что мы украшаем с помощью [TestFixture] (другой атрибут с Fixture в имени), это похоже на _consistent_ вариант использовать слово Fixture.

(Я привожу моменты, которые кажутся важными для обсуждения, но я не особо заинтересован в том, чем в конечном итоге становится это имя.)

@ jnm2 Вы совершенно правы в том, что концепции набора и

Насколько мне известно, xunit.net был первым (единственным?) Фреймворком, который выделил прибор отдельно от иерархии наследования тестов.

Когда я создавал параметризованные тесты, я мог бы представить TestCaseSource как отдельное «приспособление». Я этого не делал и думаю, что изменения, необходимые для его повторения, сейчас сломаются.

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

Итак, подводя итоги, вот что я думаю ...

  1. Нам нужен экземпляр для каждого тестового примера поведения.

  2. Это не может быть значением по умолчанию из-за обратной совместимости.

  3. Разборный светильник - отличная идея, но это тоже отдельная тема.

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

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

Какую бы форму они ни принимали, фикстуры являются многоразовыми определениями настройки / разрыва / состояния. Я считаю, что это значение слова. Экземпляры фикстур (вхождения setup / teardown / state) - это то, что наш новый атрибут решает делиться или не делиться между тестовыми примерами. Я думаю, что FixtureInstances где-нибудь в названии внесет максимальную ясность.

Всем это кажется правильным? Если нет, то какие еще моменты мы можем рассмотреть?

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

Я думаю, что полное имя (что бы вы ни набирали) должно включать как Fixture, так и Instance. Как это делается, зависит от того, говорите ли вы об имени атрибута или о перечислении, используемом при его создании. Не уверен, остановились ли вы на одном или другом.

Да, хорошее замечание.

Если мы выполняем перечисление, это увеличивает время набора. Это имеет большее значение для атрибутов, ориентированных на классы, чем для атрибутов, ориентированных на сборку. Мы также должны избегать добавления перечисления в качестве параметра к TestFixtureAttribute потому что несколько TestFixtureAttributes разрешены в одном классе, и параметр одного может противоречить параметру другого. Мы можем справиться с этим с помощью ошибки времени тестирования, но обычно неплохо сделать недопустимые состояния непредставимыми.

Давайте рассмотрим несколько вариантов, и мы сможем настроить их оттуда.
Мы ищем что-то, что сочетает в себе интуитивность, согласованность со стилем NUnit и необходимую гибкость. (По большей части эти вещи уже должны совпадать. 😄)


Предложение А

`` С #
[сборка: FixtureOptions (InstancePerTestCase = true)]

[FixtureOptions (InstancePerTestCase = false)]
класс SomeFixture
{
}

Pro: extending later if there's ever more options for fixture handling (doubtful?)
Pro: two names so there's less stuffed into a single name
Con: `[FixtureOptions]` would be legal but highly misunderstandable syntax. (Does it reset the assembly-wide setting to the default, `false`, or does it have no effect, e.g. `null`?)


### Proposal B

```c#
[assembly: FixtureOptions(FixtureCreationOptions.PerTestCase)]

[FixtureOptions(FixtureCreationOptions.Singleton)]
class SomeFixture
{
}

public enum FixtureCreationOptions
{
    Singleton, // = default
    PerTestCase,
    // Doubtful there will be other options?
    // Maybe Pool, where we reset and reuse between nonconcurrent tests?    
}

Плюс: недопустимые состояния непредставимы
Плюс: расширение позже, если будет еще больше возможностей для работы с приспособлениями (сомнительно?)
Против: избыточность при вводе имени атрибута и имени перечисления

Предложение C

`` С #
[сборка: FixtureInstancePerTestCase]

[FixtureInstancePerTestCase (false)]
класс SomeFixture
{
}
`` ''

Плюс: недопустимые состояния непредставимы
Плюсы: сложно понять неправильно (но у меня здесь слепое пятно, потому что я не новый пользователь)
Минус: длинное имя
Может быть, против: очень конкретное имя, которое может указывать как параметр, так и значение. (Но у нас также есть [NonParallelizable] которые мне нравятся.)


(Альтернативные предложения приветствуются!)

Есть кое-что, что нужно сказать о наличии двух атрибутов: одного для сборки и одного для самого класса.

Pro: проще реализовать два простых атрибута, чем один атрибут с различным поведением в контексте. (Урок, извлеченный из ParallelizableAttribute)
Pro: вероятно, будет проще для понимания пользователями, поскольку каждый может быть назван соответствующим образом. Например, уровень сборки может включать слово «По умолчанию».
Против: два атрибута, которые следует запомнить.

Подходите, используя каждую из ваших альтернатив:

`` С #
[сборка: DefaultFixtureOptions (InstancePerTestCase = true)]

[FixtureOptions (InstancePerTestCase = false)]
класс SomeFixture
{
}

[сборка: DefaultFixtureOptions (FixtureCreationOptions.PerTestCase)]

[FixtureOptions (FixtureCreationOptions.Singleton)]
класс SomeFixture
{
}

[сборка: DefaultFixtureInstancePerTestCase]

[FixtureInstancePerTestCase (false)]
класс SomeFixture
{
}
`` ''

Другие комментарии:

  • Мне A и B нравятся больше, чем C
  • Вы не должны называть поведение общих приборов «Синглтоном», потому что это не так. : smile: Если вы унаследуете, вы получите несколько экземпляров базового прибора.
  • Если вы используете отдельные атрибуты на уровне сборки и приспособления, вы можете фактически удалить приспособление из имени на уровне приспособления.
  • Мы вообще не указали, как SetUpFixtures вписывается в это. Они проблематичны и могут потребовать ошибки времени выполнения, если на них используется атрибут. Фактически, одно из преимуществ совершенно отдельного атрибута состоит в том, что нам не нужно с этим иметь дело. (Это также преимущество свойства TestFixture, BTW)
  • Нам нужно предупредить людей, что они не применимы к статическим классам. Это очевидно для всех нас, но есть люди, которые не сразу поймут это.

Кстати, мне часто хотелось, чтобы мы использовали DefaultTestCaseTimeOut для уровней сборки и фикстур и оставили Timeout только для методов.

Хорошо, спасибо, что заговорили о такой возможности.

Что меня сейчас беспокоит, так это то, что это теоретически может иметь смысл, хотя мы и не идем в этом направлении:

c# [DefaultFixtureOptions(InstancePerTestCase = true)] class SomeFixture { [FixtureOptions(InstancePerTestCase = false)] public void SomeTest() { } }

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

Или:

`` С #
[сборка: DefaultFixtureOptions (InstancePerTestCase = true)]

// Почему это тоже не следует рассматривать по умолчанию?
[DefaultFixtureOptions (InstancePerTestCase = true)]
общедоступный абстрактный класс BaseFixture
{
}

общедоступный запечатанный класс SomeFixture: BaseFixture
{
}
`` ''

Как нам объяснить, когда и когда не использовать Default ?

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

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

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

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

Что касается того, когда использовать "Default" ... мне кажется, что вы бы __not__ использовали его на любом TestFixture, и вы бы использовали его только на уровне сборки. Базовые классы не являются исключением, потому что атрибут фактически находится в производном классе в силу наследования. Таким образом, установка конкретного поведения на базе не является значением по умолчанию. Скорее это настройка для этого класса и всех, что от него производных. Противоречивые спецификации должны либо вызывать ошибку, либо игнорироваться, как мы это делаем сейчас во всех случаях унаследованных атрибутов. То есть размещение атрибутов A в базовом классе и B в производном классе не отличается от размещения A и B непосредственно в базовом классе. Мы разработали его таким образом, потому что для пользователя было бы удивительно делать что-нибудь еще.

WRT setup fixtures, кстати, я думаю, что он должен генерировать ошибку времени выполнения, чтобы указать там любой вариант создания экземпляра. SetUpFixtures не являются TestFixtures.

То есть размещение атрибутов A в базовом классе и B в производном классе не отличается от размещения A и B непосредственно в базовом классе. Мы разработали его таким образом, потому что для пользователя было бы удивительно делать что-нибудь еще.

О боже, я ожидал, что [Apartment] или [Parallelizable] заменят базовый класс или настройку базового метода!

Для меня все это имеет смысл.

Можем ли мы исключить необходимость перечисления, такого как FixtureCreationOptions ? PerTestCase , PerFixture , гипотетический ReusedPerTestCase ?

Я склоняюсь к простоте:

`` С #
[сборка: DefaultFixtureOptions (InstancePerTestCase = true)]

[FixtureOptions (InstancePerTestCase = false)]
класс SomeFixture
{
}

Hypothetical other creation options would then be added this way:
```c#
[FixtureOptions(InstancePerTestCase = false, ReuseInstances = true)]

@ nunit / framework-team Каковы ваши предпочтения?

Касательно

[assembly: DefaultFixtureOptions(InstancePerTestCase = true)]

Может быть, только я, но обычно я понимаю атрибуты как указание на то, что отмеченный объект обладает определенной чертой, которая иначе не присутствует; аргументы атрибута, если таковые имеются, уточняют указанную черту. Возьмем, например, Serializable - объект сериализуемый, но без декорирования - нет; InternalsVisibleTo - внутренние элементы, видимые для чего-то определенного, и аргументы уточняют это, в противном случае внутренние не видны никому и т. Д. Некоторые атрибуты не соответствуют шаблону, но обычно это низкий уровень, например, CompilationRepresentation, MethodImpl. Обычно они идут с обязательными аргументами конструктора.

Теперь рассмотрим
c# [assembly: DefaultFixtureOptions]
Это правильно построенный C #, поскольку присвоение InstancePerTestCase необязательно. Что он выражает семантически?

Мне [assembly: DefaultFixtureInstancePerTestCase] кажется более естественным (за исключением его полуторного названия, но это всего лишь мнение, не оправданное с эстетической точки зрения). Один атрибут, одна черта. Возможно, для этого не нужны никакие параметры или необязательно настраиваемые свойства.

Кроме того, как пользователь я нахожу удивительным, что есть два атрибута с разными именами, один применим только на уровне сборки ( DefaultSomething ), а другой только на уровне типа (всего Something ) . Естественно, я не понимаю, какой был «усвоенный урок», который @CharliePoole упомянул относительно атрибута Parallelizable , но с этой стороны острова пара атрибутов, позволяющих узнать, что делать (с точки зрения поверхностного мышления этого пользователя), вызывает недоумение.

@ kkm000 Я согласен с вашим комментарием об атрибуте, который выражает одну черту.

WRT два разных атрибута для «одного и того же» признака на разных уровнях, я __would__ согласен, если я думал, что они на самом деле были одинаковыми. Но я так не думаю. В качестве примера я воспользуюсь тайм-аутом.

Если для метода тестирования задано значение «Тайм-аут», это означает: «Время ожидания этого метода тестирования истечет, если для его выполнения потребуется больше, чем указанное количество миллисекунд». Однако, когда тайм-аут помещается в приспособление или сборку, он имеет другое значение: «Тайм-аут по умолчанию для всех тестовых случаев, содержащихся в этом элементе, составляет указанное количество миллисекунд». Это значение по умолчанию, конечно, может быть отменено на более низких уровнях. Два довольно разных значения, которые, кстати, имеют две разные реализации. Первый применяет тайм-аут к тесту, в котором он появляется. Второй сохраняет значение по умолчанию в контексте теста, где оно будет найдено и использоваться по умолчанию при обнаружении содержащихся тестовых примеров. ParallelizableAttribute работает аналогично.

В этом конкретном случае, помещенном в прибор, атрибут означает «Создать экземпляр этого прибора один раз для каждого тестового примера». На уровне сборки это означает «Создавать экземпляры тестовых приспособлений в этой сборке один раз для каждого приспособления, если не указано иное».

В этом конкретном случае, помещенном в прибор, атрибут означает «Создать экземпляр этого прибора один раз для каждого тестового примера». На уровне сборки это означает «Создавать экземпляры тестовых приспособлений в этой сборке один раз для каждого приспособления, если не указано иное».

Предположим, вы сделали ошибку в последнем предложении - было ли _ «Создавать экземпляры тестовых устройств в этой сборке один раз для каждого тестового примера ~ fixture ~, если не указано иное». _ Предполагалось? Или я серьезно не понимаю масштаб и цель?

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

две отдельные реализации

В gedankenworld единорогов и радуг реализация не влияет на сторону, обращенную к пользователю. Но мы (и вы, разработчик NUnit, и я, один из его пользователей) все инженеры, а разработка - это компромисс. Если бы использование одного атрибута для разных областей на самом деле привело бы к внутренней неуклюжести и бремени обслуживания, я бы ни за что не пожалел. :)

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

@ kkm000 Да, я намеревался «один раз на тестовый пример».

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

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

Я допускаю, что проблема, о которой я упоминал с Timeout / DefaultTimeout, немного более серьезна, поскольку тайм-аут для прибора может легко означать, что тайм-аут применяется ко всему устройству, что, конечно, не так. В этом отношении тайм-аут сборки может применяться ко всей сборке, а это не так.

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

Если подумать, было бы лучше, если бы Timeout назывался TestCaseTimeout!

Я доволен синглом [FixtureInstancePerTestCase] . Кажется, это наименьшая умственная нагрузка на пользователей.

@ nunit / framework-team Можете ли вы помочь нам определиться с направлением, просматривая https://github.com/nunit/nunit/issues/2574#issuecomment -426347735 вниз?

[FixtureInstancePerTestCase] - тоже мой наименее нелюбимый вариант. 😄 Единственным недостатком является отсутствие расширяемости.

Мне не нравится проблема [FixtureOptions] - я думаю, что это слишком запутанно. Я лично предпочел бы один атрибут как для сборки, так и для класса - я понимаю, что раньше это вызывало у нас проблемы с Timeout и Paralellizable - однако, когда у этого атрибута в названии есть «Fixture», это кажется мне менее двусмысленным.

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

Есть какие-нибудь обновления по этому поводу?

Все обновления по этому вопросу будут публиковаться.

Кто-нибудь может решить эту проблему, пожалуйста ??
В настоящее время мы должны обернуть весь наш тестовый код следующим образом:
csharp [Test] public void TestExample(){ using(var tc = new MyTestContext()){ //code goes here } }
Используя эту функцию, мы можем инициализировать тестовый контекст при настройке и удалять при разрыве, оставаясь при этом потокобезопасным.

Привет, мы сталкиваемся с этой проблемой и в нашей компании. Мы переходим с фреймворка, который поддерживает эту функцию (Py.Test), так что это своего рода подводный камень. Мы были бы очень признательны, если бы вы могли показать нам исходный код, который управляет этим экземпляром, чтобы мы могли его разветвить и внести необходимые изменения.

@MChiciak
Если эта проблема не будет исправлена ​​в ближайшее время, мы рассматриваем возможность перехода на xUnit, поскольку в xUnit это поведение по умолчанию: «xUnit.net создает новый экземпляр тестового класса для каждого запущенного теста» https: // xunit .github.io / документы / общий-контекст

@LirazShay , @MChiciak Я замечаниями @CharliePoole . Итак, я создал свои тестовые поля, которые инициализируются в SetUp [ThreadStatic] и все работает нормально.

@dfal
Спасибо за хороший обходной путь!

Мне бы это понравилось. В настоящее время у меня есть существующий набор тестов с тысячами тестов, выполняемых на nunit, которые нельзя легко распараллелить из-за общего состояния. Если бы эта опция была предоставлена, мы могли бы гораздо легче распараллелить наш набор тестов.

В качестве мысленного эксперимента представьте, что мы просто изменили принцип работы TestFixture. Что бы это сломалось?

  1. Любые тесты, которые используют порядок и полагаются на изменения состояния, сделанные одним тестом, чтобы повлиять на другие тесты. Это очень плохая практика в случае (предположительно) независимых модульных тестов. Мы всегда предупреждали людей не делать этого, и я был бы не против взломать любые тесты (конечно, с сообщением), которые полагались на наличие единственного экземпляра таким образом.

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

  3. Любой уровень фикстуры OneTimeSetUp теперь будет запускаться несколько раз. Это исключит любой выигрыш в эффективности от однократной настройки и в некоторых случаях может изменить семантику. В частности, если инициализация каким-либо образом повлияла на глобальное состояние (например, настройка базы данных), это может привести к ошибкам. Мне эта поломка кажется неприемлемой.

Я нашел этот мысленный эксперимент полезным при размышлениях о том, как реализовать создание экземпляров фикстур для каждого тестового примера. Очевидно, что это должна быть новая функция, отличная от функции по умолчанию, если она введена в NUnit 3. Для NUnit 4 или любой другой новой инфраструктуры (например, TestCentric) новое поведение легко может быть по умолчанию.

в качестве примечания, я очень просто заменил некоторые MSTest TestClass и TestMethod на TestFixture и Test соответственно и обнаружил, что NUnit и MSTest имеют здесь разную семантику. MSTest создает новые экземпляры для каждого теста, где, как я уверен, всем, кто это читает, известно, NUnit повторно использует экземпляры TestFixture.

Было бы неплохо, если бы я мог написать TestFixture(lifeCycle = OneTestPerInstance)] или аналогичный, чтобы облегчить эту миграцию.

_edit: _ На самом деле, только что портировав тесты для использования метода Setup , оказалось, что существующий код инициализатора кода / до / разрыва был изрядно замучен под MSTest, и теперь он представляет собой простые конструкторы инициализатора свойств, которые намного приятнее и законченнее, поэтому я рад, что сделал перевод. Все-таки с точки зрения совместимости было бы неплохо иметь.

@CharliePoole

Любые тесты, которые используют порядок и полагаются на изменения состояния, сделанные одним тестом, чтобы повлиять на другие тесты.

Ааа, да, сломайте эти тесты, пожалуйста !!! 🤣 Это худшее злоупотребление фреймворком модульного тестирования, о котором я могу думать.

Любой уровень приспособлений OneTimeSetUp

Ух, я всегда думал, что это запускается один раз для каждого процесса ... Мне всегда было интересно, почему это не статично.

Но да, производительность также очень важна.

Эта функция обсуждается уже более 2 лет, я не хочу показаться оскорбительным, но есть ли какие-либо конкретные и реалистичные планы по ее практической реализации в ближайшем будущем?

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

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

Лучший способ «решить» эту проблему - сделать ваш тестовый класс потокобезопасным. Как и в любом хорошем многопоточном приложении, безопасность потоков должна иметь первостепенное значение.
Если вы используете, например, поля, которые вы настраиваете в методе настройки, а затем используете во многих тестовых методах, ваш класс не является потокобезопасным. это не недостаток NUnit. Вы написали небезопасный синглтон.

Создавая подкласс внутри вашего TestFixture, который содержит контекстную информацию, которую вы создаете как часть каждого теста, вы затем убедитесь, что ваш одноэлементный TestFixture является потокобезопасным ... таким образом ... вы "решаете" проблему.
Так что вместо того, чтобы тратить время на «переход на другую структуру», я мог бы предложить вам потратить это время на то, чтобы ваш синглтон TestFixtures стал потокобезопасным… вы, вероятно, обнаружите, что это гораздо более быстрое упражнение.

Например, когда я впервые столкнулся с тем фактом, что NUnit TestFixture является синглтоном, я преобразовал свои 100+ тестовых инструментов с более чем 500 тестами, чтобы они были потокобезопасными (и мне пришлось перейти с RhinoMocks на Moq, поскольку RhinoMocks также не является потокобезопасным), и это заняло мне около 4 недель (если честно, Rhino -> Moq занял большую часть этого времени!)

В принципе, вы на 100% правы, но когда у вас есть существующий набор тестов, состоящий из ~ 7000 + модульных тестов, просто переписать их для обеспечения потоковой безопасности - это огромные вложения.

Не уверен, что это больше инвестиции, чем переход на другой фреймворк (может быть какой-то инструмент для автоматизации этого), но я не думаю, что этот запрос функции должен быть отклонен в принципе, ИМО

Я не выступаю за то, чтобы не обращать внимания на эту функцию.

Но такое большое изменение в том, как работает nunit, на мой взгляд, следует учитывать либо с точки зрения обратной совместимости (что может быть довольно сложно), либо с точки зрения новой основной версии (также немалой задачи).

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

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

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

Это проект с открытым исходным кодом, полностью укомплектованный волонтерами. Как только функция принята, она ждет, пока кто-то из команды или за ее пределами возьмет ее и сделает. Нет никакого «начальника», который бы поручил кому-либо работу. На самом деле хорошо, что никто этого не сделал, если у них нет времени, чтобы это сделать. Таким образом, он доступен для всех, кто способен и заинтересован в этом.

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

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

Я согласен с тем, что он существует уже некоторое время, и я бы хотел увидеть это сам.

@BlythMeister Я думаю, что переключатель немного упрощает это - как вы, вероятно, знаете, но другие не могут.

Конечно, это выключатель, но это не просто включение лампочки. Это больше похоже на переключение вашего радио с AM на FM ... в игру вступают совершенно новые схемы.

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

Извините, что вы правы, мое объяснение было расплывчатым в том, почему "переключатель" не прост!

@BlythMeister Я действительно сделал именно то, что вы описываете - создал одноразовый объект для отслеживания состояния во время теста, а затем избавился от него после завершения теста - чтобы управлять своим тестовым жгутом Selenium. Одна из основных проблем такого подхода заключается в том, что государство не продолжает демонтировать. В результате невозможно выполнить действия для неудачных тестов (захватить снимки экрана, журналы или другие части состояния) условно, потому что тестовое состояние не разрешается внутри самого метода тестирования и может быть оценено только во время разрыва. Таким образом, хотя объект состояния - это мощный способ сделать тесты потокобезопасными, он работает только тогда, когда вам не нужно ничего делать с результатом теста во время разборки.

Полезно помнить, что работа с результатами теста - это не то, для чего мы разработали TearDown. Конечно, я знаю, что многие люди используют это таким образом, и я понимаю мотивацию, но есть много сложных угловых случаев, которые по сути сводятся к тому факту, что TearDown является частью теста, и он не закончен, пока не закончится.

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

@CharliePoole

  1. Любой уровень фикстуры OneTimeSetUp теперь будет запускаться несколько раз. Это исключит любой выигрыш в эффективности от однократной настройки и в некоторых случаях может изменить семантику. В частности, если инициализация каким-либо образом повлияла на глобальное состояние (например, настройка базы данных), это может привести к ошибкам. Мне эта поломка кажется неприемлемой.

Почему мы не можем реализовать функцию Instance-per-test-case и по-прежнему запускать OneTimeSetUp только один раз (не забудьте вызвать основной поток или что-то в этом роде)?

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

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

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

Как насчет того, чтобы разрешить только статические методы OneTimeSetUp и OneTimeTearDown когда включена функция "экземпляр на тестовый пример"? Это заставит элементы, инициализированные в этих методах, быть статическими, таким образом, очевидно, что они будут совместно использоваться экземплярами фикстур.

Хотя основная мотивация этой проблемы с Github - это распараллеливание, я также хотел бы добавить в качестве мотивации _принцип наименьшего удивления_ и _ избежания ошибок, вызванных влиянием тестов друг на друга. (Прошу прощения, если я пропустил то, что кто-то уже упомянул об этом.)

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

Думаю, здесь хорошо работает принцип наименьшего удивления. Неудивительно, что новый прибор работает иначе.

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

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

@fschmied Кстати, если бы мы создавали новый фреймворк (или, может быть, даже NUnit 4), я бы сказал иначе ... Я думаю, нам следовало бы сделать так, чтобы тесты мешали друг другу в NUnit 2 и 3. Однако , пока мы вносим изменения в NUnit 3, я думаю, что это правила совместимости.

Я предпочитаю заменять TestFixture совершенно новым типом приспособлений, которые работают по-другому.
[...]
Думаю, здесь хорошо работает принцип наименьшего удивления. Неудивительно, что новый прибор работает иначе.

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

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

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


[1] Джеймс Ньюкирк написал следующее :

Я думаю, что одна из самых больших ошибок, допущенных при написании NUnit V2.0, заключалась в том, что мы не создавали новый экземпляр класса тестовых приспособлений для каждого содержащегося в нем метода тестирования. Я говорю «мы», но думаю, что это моя вина. Я не совсем понял, почему JUnit создает новый экземпляр тестового устройства для каждого метода тестирования.

Но он также пишет это:

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

:)

Да, мы с Джимом много лет спорили об этом. : smile: Единственное, в чем мы согласны, - это последнее предложение. Джим ушел, чтобы создать новую структуру, прежде чем реализовывать свой предпочтительный подход. Я в основном делаю то же самое, и SetUp / TearDown - это то, что я, вероятно, буду делать по-другому.

Я постоянно удивляюсь тому, сколько достаточно опытных пользователей NUnit не знают об использовании одного и того же экземпляра для всех тестовых случаев. Возможно, он был более широко известен, когда впервые появился NUnit, потому что он был очевидным отличием от JUnit. Теперь это все забыто, и новые люди приходят в NUnit с ожиданиями, которые NUnit не соответствует. Что-то думать о.

так .. это готово? или все еще в стадии проектирования?

прямо сейчас в .NET приземляется либо XUnit без TestContext, либо NUnit с шаблонными параллельными тестами

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

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

Итак, вы хотели бы поработать над этим?

конечно, я только начал работать в
https://github.com/avilv/nunit/tree/instance-per-test

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

дай мне знать, если я уйду отсюда

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

Что касается дизайна, я думаю, что атрибут, производный от IApplyToContext имеет смысл. Думаю, я бы предпочел что-то более общее, чем просто InstancePerTestCase, возможно

C# [FixtureLifeCycle(LifeCycle.InstancePerTestCase]

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

ЧарлиПул, ты слишком много, большое спасибо за отзывы и помощь

Я думаю, я понимаю, как все устроено / что что делает и т. д. медленно, но верно

вот мое предложение для перечисления LifeCycle (я также обновил свою ветку с вашим предложением)

    /// <summary>
    /// Specifies the lifecycle for a fixture.
    /// </summary>
    public enum LifeCycle
    {
        /// <summary>
        /// A single instance is created and for all test cases.
        /// </summary>
        SingleInstance,

        /// <summary>
        /// A new instance is created for each test case for fixtures that are marked with <see cref="ParallelizableAttribute"/>.
        /// </summary>
        InstancePerTestCaseForParallelFixtures,

        /// <summary>
        /// A new instance is created for each test case.
        /// </summary>
        InstancePerTestCase
    }

@ jnm2 Что вы думаете об этом подходе - вы в первую очередь "спонсор" этой функции. Я немного не уверен в InstancePerTestCaseForParallelFixtures , что может быть слишком много для начальной реализации. Как обычно, после реализации мы застреваем на этом.

Если мы __do__ принимаем связь с параллелизмом, она должна быть контекстной, а не атрибутной. То есть любой тест может выполняться параллельно, поэтому он больше похож на `InstancePerTestCaseWhenCasesAreParallel. Параллельные приспособления особо не нуждаются в этой функции. И, может быть, лучше оставить выбор за пользователем.

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

Я согласен, я понимаю вариант использования его только в параллельных сценариях, поскольку именно здесь текущая модель NUnit по умолчанию вас укусит, но, надеюсь, это будет жизненный цикл по умолчанию в будущем (nunit 4?), поскольку я думаю, что он действительно должен иметь был таким с самого начала

возможно, нам следует оставить его в SingleInsance / InstancePerTestCase

Другой вопрос, следует ли нам обрабатывать здесь шаблон IDisposable, который в значительной степени сделает SetUp / TearDown избыточным, но будет казаться более естественным, как стиль XUnit.

Мы уже располагаем любым Test Fixture, реализующим IDisposable. Вам просто нужно убедиться, что он продолжает работать.

отлично, сделаю.

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

DisposeFixtureCommand заботится о вызове Dispose.

ИМО, ваша основная проблема - убедиться, что весь командный поток для тестового устройства вызывается __ для каждого теста__. Изменения, которые я вижу до сих пор, правильно создают поток команд, который, кажется, выполняет свою работу, но ключевой вопрос заключается в том, как обеспечить его запуск для каждого тестового примера. (Обратите внимание, что структура команд для фикстур отличается от структуры для тестовых случаев.) Я думаю, что compositeworkitem должен быть чувствителен к контексту и правильно вызывать команду (как вы это сделали), но я не уверен, что у него будет шанс сделать это для __every__ test case. (Я только читаю код, поэтому могу ошибаться.)

@CharliePoole похоже, что вы правы, я немного нарисовал график того, что именно выполняет тестовый метод:
image

в настоящее время я создаю экземпляр тестового объекта в цикле RunChildren, но этого недостаточно для команд Retry / Repeat, не говоря уже о том, есть ли другие обертки, которые я пропустил / кому-то нужны новые.
он должен быть намного ближе к TestMethodCommand

я обновил последние изменения на
https://github.com/avilv/nunit/tree/instance-per-test-2

удален InstancePerTestCaseWhenCasesAreParallel
реализован FixtureLifeCycle путем добавления MakeTestCommand в SimpleWorkItem и пропуска его в CompositeWorkItem MakeOneTimeSetUpCommand

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

есть ли шанс, что это будет рассмотрено до следующей версии? Я все еще готов к улучшениям / изменениям, конечно

Мне этот подход кажется хорошим, и я бы хотел использовать его в следующем выпуске, если мы сможем это сделать!

есть ли шанс, что мы сможем это рассмотреть? кажется, что мы были очень близки

Интересно, есть ли какие-либо подвижки по этой функции? Похоже, @avilv добрался до этого довольно близко. Долгое время ждали появления такой возможности и с нетерпением ждали ее использования!

Спасибо всем!

Эта функция сейчас работает?

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

@janissimsons Извините, но я больше не

Оцените всю работу, проделанную для объединения # 3427. @Rprouse есть идеи, когда мы можем ожидать этого в выпуске?

Как мы используем новую функцию? Может ли кто-нибудь написать краткое описание новых атрибутов и их правильного использования?

Я планирую выпустить в ближайшие пару недель. Я хотел подождать пока. NET 5 является окончательной и перед выпуском проведите с ней небольшое заключительное тестирование. Сомневаюсь, что будут какие-то проблемы, но это так близко, что я подумал, что лучше подождать.

@rprouse Есть

@janissimsons очень скоро. Мы решаем некоторые проблемы с нашими сборками .NET 5.0 и пару других проблем, а затем я выпущу. Вы можете отслеживать статус на этой доске проекта, https://github.com/nunit/nunit/projects/4

Это прекрасная новость!

Правильно ли я понимаю, что это сделает возможным в NUnit следующее?

Скомбинируйте все это:

  • Тесты параметризованы
  • Параллельно запускайте тесты.
  • Уметь параллельно запускать параметризованные варианты одного и того же теста.
  • Тесты могут управляться данными

Пробовал кое-что в прошлом здесь https://github.com/jawn/dotnet-parallel-parameterized-tests

Будет ли новый выпуск по-прежнему совместим с .NET Full Framework 4.7.2 или хотя бы 4.8?

С наилучшими пожеланиями,
DR

@jawn Я верю, что все это сработает, да. Я протестировал все эти вещи, но NUnit имеет много функций, поэтому могут быть крайние случаи с некоторыми комбинациями функций. Не стесняйтесь загрузить наш ночной пакет NuGet и протестировать его! Мы хотели бы больше тестирования и отзывов перед выпуском.

@drauch да, версия 3.13 по-прежнему будет поддерживать .NET 3.5.

@avilv Отличная работа !!
кстати, с помощью какого инструмента вы создали эту красивую блок-схему?
Спасибо

@rprouse Не могли бы вы также обновить документацию по использованию этой новой функции? Спасибо!

@janissimsons Я планирую обновить документацию к релизу. Краткая версия заключается в том, чтобы добавить [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] в ваш тестовый класс, вероятно, вместе с [Parallelizable(ParallelScope.All)] и экземпляр вашего тестового класса будет создан для каждого теста, который запускается внутри класса. Это позволяет использовать переменные-члены в классе в параллельных тестовых прогонах, не беспокоясь о том, что другие тесты изменят переменные.

@LirazShay Схема на https://docs.microsoft.com/en-us/visualstudio/modeling/map-dependencies-across-your-solutions ReSharper имеет аналогичную функцию.

@ jnm2 Большое спасибо !!!
У меня есть VS Enterprise, и я не знал об этой функции, которая может быть очень полезной.
Большое спасибо за совет

Правильно ли я, что это было выпущено и является частью выпуска 3.13?

Правильно ли я, что это было выпущено и является частью выпуска 3.13?

Я считаю, что было обнаружено и исправлено несколько проблем, особенно в отношении применения атрибутов на уровне сборки в # 3720, и сейчас мы ждем 3.13.1 ..

Выпущен NUnit 3.13.1.
Остались еще вопросы?

Нет, поэтому его закрыли @andrewlaser

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