Вы размещаете модульные тесты в том же или другом проекте?

142

Вы помещаете модульные тесты в один и тот же проект для удобства или помещаете их в отдельную сборку?

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

Леора
источник

Ответы:

103

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

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

Я действительно не знаю никаких плюсов. Наличие лишнего проекта (или 10) не является недостатком.

Изменить: дополнительная информация о сборке и доставке

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

Подводя итог, вот общая идея ежедневной сборки, тестирования и доставки битов и других файлов:

  1. Производственная сборка запускается, производственные файлы помещаются в определенный «производственный» каталог.
    1. Только сборка производственных проектов.
    2. Скопируйте скомпилированные биты и другие файлы в "производственный" каталог.
    3. Скопируйте биты и другие файлы в каталог кандидатов на выпуск, также известный как каталог рождественских выпусков, который будет «Release20081225».
  2. Если производственная сборка завершается успешно, запускается сборка модульного теста.
    1. Скопируйте производственный код в каталог "tests".
    2. Сборка модульных тестов в каталог "tests".
    3. Запустите модульные тесты.
  3. Отправляйте разработчикам уведомления о сборках и результаты модульных тестов.
  4. Когда релиз-кандидат (например, Release20081225) принят, отправьте эти биты.
Джейсон Джексон
источник
16
ИМО, перечисленные вами минусы не всегда применимы. Профи для одного и того же проекта - это более простая группировка проверенных классов + тестов - эти небольшие удобства имеют большое значение при написании тестов. Здесь побеждают личные предпочтения, и иногда ваши очки важны, но не всегда.
orip 07
7
Сначала вы должны спросить, нужно ли вам удалять тесты при отправке. Если да, то вам нужен отдельный проект. Если нет, используйте другие плюсы и минусы, чтобы решить. Люди, которые считают, что они не могут развернуть тесты, всегда по умолчанию приходят к выводу «отдельный проект».
orip
7
Я не вижу никаких преимуществ в отправке непроизводственного кода, такого как модульные тесты, и есть множество недостатков. Доставка модульных тестов означает, что вы имеете дело с большим количеством бит, которые необходимо распределить. Модульные тесты также имеют отдельный набор зависимостей. Теперь вы отправляете NUnit, Rhino или Moq и т. Д. Это еще больше раздувание. Размещение модульных тестов в отдельном проекте требует совсем немного усилий, и это единовременные затраты. Меня вполне устраивает вывод о том, что модульные тесты не следует отправлять.
Джейсон Джексон
5
В качестве альтернативы подходу «отдельный проект» для удаления модульных тестов из производственного кода рассмотрите возможность использования настраиваемого символа и директив компилятора, например #if. Этот настраиваемый символ можно переключать с помощью параметров командной строки компилятора в сценариях программного обеспечения CI. См. Msdn.microsoft.com/en-us/library/4y6tbswk.aspx .
Rich C
24
.NET отстает от разработки тестов в отдельном проекте, и я готов поспорить, что это скоро изменится. Нет никаких причин, по которым процесс сборки не может игнорировать тестовый код в сборке выпуска. Я использовал несколько генераторов веб-приложений, которые делают это. Я думаю, что абсолютно лучше включать кодовые файлы модульных тестов вместе с кодовыми файлами, которые они описывают, приправленные в той же файловой иерархии. Google рекомендует это, называемое «фрактальной» файловой организацией. Почему тесты отличаются от встроенных комментариев и документации readme? Компилятор охотно игнорирует последнее.
Брэндон Арнольд
114

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

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

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

Джон Скит
источник
+1: Я только что столкнулся с модульными тестами в том же проекте, что и основной код, и трудно найти тесты среди реального кода - даже несмотря на то, что было соблюдено соглашение об именах.
Fenton
33
Для менее опытного читателя это означает, что вы добавляете файл [assembly:InternalsVisibleTo("UnitTestProjectName")]в свой проект AssemblyInfo.cs.
Маргус
Очень полезное дополнение Маргус. Спасибо Джону за упоминание InternalsVisibleTo в этом контексте.
Bjørn Otto Vasbotten
1
@jropella: если вы подписываете сборку prod, вы в любом случае можете сделать внутреннюю часть видимой только для подписанных сборок. И, конечно, если вы запускаете какой-либо код с полным доверием, у них есть доступ через отражение ...
Джон Скит
2
@jropella: Reflection в любом случае не заботится о InternalsVisibleTo ... но вы бы не увидели, что подписанная сборка доверяет неподписанной сборке с помощью InternalsVisibleTo, потому что это предотвращается во время компиляции.
Джон Скит,
70

Я не понимаю частых возражений против развертывания тестов с производственным кодом. Возглавлял команду на небольшом микрокапитале (выросла с 14 до 130 человек). У нас было около полдюжины Java-приложений, и мы нашли ЧРЕЗВЫЧАЙНО полезным развернуть тесты в полевых условиях, чтобы выполнить их на определенноммашина, которая проявляла необычное поведение. Случайные проблемы возникают в полевых условиях, и возможность провести несколько тысяч модульных тестов с нулевыми затратами была бесценной и часто диагностировала проблемы за считанные минуты ... включая проблемы с установкой, нестабильные проблемы с ОЗУ, проблемы, связанные с машиной, нестабильные сетевые проблемы, и т. д. и т. д. Я считаю невероятно ценно проводить тесты в полевых условиях. Кроме того, случайные проблемы возникают в случайное время, и приятно, когда модульные тесты уже ждут выполнения в любой момент. Место на жестком диске дешевое. Точно так же, как мы пытаемся объединить данные и функции (объектно-ориентированный дизайн), я думаю, что есть что-то принципиально ценное в хранении кода и тестов вместе (функция + тесты, которые проверяют функции).

Я хотел бы провести свои тесты в том же проекте на C # /. NET / Visual Studio 2008, но я еще не изучил этого достаточно, чтобы достичь этого.

Одним из больших преимуществ хранения Foo.cs в том же проекте, что и FooTest.cs, является то, что разработчикам постоянно напоминают, когда в классе отсутствует тест-аналог! Это способствует более совершенным методам кодирования, основанным на тестировании ... дыры более очевидны.


источник
17
Я понимаю, насколько это будет полезно для интеграционных тестов, но для модульных тестов? Если вы напишете их правильно, они не должны зависеть от машины.
Джоэл Макбет
+1 Согласен. Поскольку я занимаюсь в основном .NET, тестирование доставки с производственным кодом также имеет приятный побочный эффект сокращения этапа непрерывной интеграции компиляции и тестирования: 1) только половина проектов для сборки, 2) все тестовые сборки вместо основного приложения так что проще параметризовать ваш тестовый исполнитель. Конечно, при использовании Maven этот аргумент не работает. Наконец, мои клиенты более технические люди, и им очень нравится иметь возможность запускать тесты самостоятельно, поскольку это документирует текущее состояние в соответствии со спецификацией.
mkoertgen 04
Я думаю, что имеет смысл проводить интеграционное тестирование в стиле TDD / BDD, то есть писать тестовый код, который можно легко автоматизировать с помощью стандартных средств запуска тестов, таких как NUnit, xUnit и т. Д. Конечно, вы не должны путать модульное тестирование с интеграционным тестированием. Просто убедитесь, что вы знаете, какой набор тестов следует запускать быстро и часто для проверки сборки (BVT), а какой - для интеграционного тестирования.
mkoertgen 04
1
Люди возражают против этого, потому что это не то, к чему они привыкли. Если бы их первый опыт модульного тестирования заключался в том, что они были в производственном коде, на самом деле, с определенным синтаксисом на языке программирования, который указывал, какой тест связан с производственным методом, они бы поклялись, что это был бесценный аспект рабочего процесса разработки и совершенно безумно помещать их в отдельный проект. Большинство разработчиков такие. Последователи.
zumalifeguard
20

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

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

Также приятно иметь модульные тесты, близкие к написанному вами коду. Когда вы пишете метод, вы можете легко найти соответствующие модульные тесты, потому что они находятся в том же проекте. Когда вы создаете сборку, которая включает unitTests, любые ошибки в unitTest выдадут вам ошибку compilereerror, поэтому вы должны поддерживать свой unittest в актуальном состоянии только для сборки. Наличие unittest в отдельном проекте может привести к тому, что некоторые разработчики забудут собрать проект unittest и на какое-то время пропустят неработающие тесты.

И вы можете удалить модульные тесты из производственного кода, используя теги компиляции (IF #Debug).

Тесты автоматической интеграции (сделанные в NUnit) должны быть в отдельном проекте, поскольку они не принадлежат ни одному проекту.

Jenspo
источник
1
Но это означает, что вы не можете запускать тесты на Releaseсборке, и некоторые «heisenbugs» могут провалиться.
hIpPy 06
3
Использование дружественных сборок ( msdn.microsoft.com/en-us/library/0tke9fxk.aspx ) InternalsVisibleToпозволяет вам тестировать внутренние методы из отдельного тестового проекта
ParoX
3
@hlpPy - вы можете настроить произвольные символы условной компиляции, и у вас может быть более двух конфигураций сборки. Например; у вас может быть Debug, Approvalи Release; и согласование компиляции со всеми оптимизациями выпуска. Кроме того, как правило, модульные тесты не очень хороши для обнаружения heisenbug (по моему опыту). Для них вам, как правило, требуются специальные интеграционные регрессионные тесты - и вы вполне можете разместить их рядом.
Eamon Nerbonne
Отдельный проект дает такую ​​же ошибку времени компиляции. Если вам нужно так глубоко погрузиться в код, это больше похоже на то, что вам нужно больше абстракций, чем взламывать модульные тесты в свой код. Использование условных обозначений в коде для переключения ветвей тоже нехорошо. Похоже, что путают тесты Unit и Integrations.
Ник Тернер
16

Проведя некоторое время в проектах TypeScript, где тесты часто помещаются в файл вместе с кодом, который они тестируют, я предпочел этот подход разделению:

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

Поэтому, когда я недавно начал новый проект .NET Core, я хотел посмотреть, можно ли имитировать эту структуру в проекте C # без отправки тестов или тестовых сборок с окончательной версией.

Добавление следующих строк в файл проекта пока работает хорошо:

  <ItemGroup Condition="'$(Configuration)' == 'Release'">
    <Compile Remove="**\*.Tests.cs" />
  </ItemGroup>
  <ItemGroup Condition="'$(Configuration)' != 'Release'">
    <PackageReference Include="nunit" Version="3.11.0" />
    <PackageReference Include="NUnit3TestAdapter" Version="3.12.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
  </ItemGroup>

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

Если вы все еще хотите иметь возможность модульного тестирования классов в их конфигурации выпуска, вы можете просто создать новую конфигурацию, производную от Releaseчего-то вроде ReleaseContainingTests.


Обновление: после использования этой техники в течение некоторого времени я также обнаружил, что полезно настроить ваши значки в VS Code, чтобы тесты (и другие вещи) немного выделялись на панели проводника:

Скриншот VS Code

Для этого используйте расширение Material Icon Theme и добавьте в настройки JSON VS Code что-то вроде следующего:

"material-icon-theme.files.associations": {
  "*.Tests.cs": "test-jsx",
  "*.Mocks.cs": "merlin",
  "*.Interface.cs": "yaml",
}
Джеймс Терли
источник
Именно то, что мы только что начали делать
Мэтью Стиплс
У вас это работает и для стандартных библиотек классов .net?
Zenuka
10

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

tvanfosson
источник
1
Тогда как вы проводите модульное тестирование внутренних классов? Или вы тестируете только публичные классы?
user19371 07
7
<assembly: InternalsVisibleTo = "TestProject" /> при необходимости, хотя обычно я тестирую только общедоступные интерфейсы.
tvanfosson
@tvanfosson, что ты делаешь со спецификациями (Specflow)? У вас есть отдельный проект или вы бы поместили их в проект модульного тестирования? +1.
w0051977
@ w0051977 Я никогда не использовал Specflow, поэтому не знаю
tvanfosson
@tvanfosson, а как насчет интеграционных тестов? Вы бы поместили их в отдельный проект для модульных тестов?
w0051977
10

Если используется фреймворк NUnit , есть дополнительная причина для помещения тестов в один и тот же проект. Рассмотрим следующий пример производственного кода, смешанного с модульными тестами:

public static class Ext
{
     [TestCase(1.1, Result = 1)]
     [TestCase(0.9, Result = 1)]
     public static int ToRoundedInt(this double d)
     {
         return (int) Math.Round(d);
     }
}

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

Обновление : я знаю, что такое использование TestCaseатрибута не было запланировано разработчиками NUnit, но почему бы и нет?

Андрей Адаменко
источник
2
Мне нравится эта идея; имея несколько примеров входов и ожидаемых выходов прямо здесь с кодом.
Preston McCormick
3

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

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

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

orip
источник
3

Я помещаю их в отдельные проекты. Имя сборки отражает имя пространств имен, как правило для нас. Итак, если есть проект под названием Company.Product.Feature.sln, он имеет выход (имя сборки) Company.Product.Feature.dll. Тестовый проект - Company.Product.Feature.Tests.sln, в результате чего получается Company.Product.Feature.Tests.dll.

Лучше всего хранить их в одном решении и контролировать вывод через Configuration Manager. У нас есть именованная конфигурация для каждой из основных веток (Разработка, Интеграция, Производство) вместо использования отладки и выпуска по умолчанию. После настройки конфигураций вы можете включить или исключить их, установив флажок «Сборка» в диспетчере конфигураций. (Чтобы получить Configuration Manager, щелкните решение правой кнопкой мыши и перейдите в Configuration Manager.) Обратите внимание, что я считаю, что CM в Visual Studio иногда содержит ошибки. Пару раз мне приходилось заходить в файлы проекта и / или решения, чтобы очистить созданные им цели.

Кроме того, если вы используете Team Build (и я уверен, что другие инструменты сборки .NET такие же), вы можете связать сборку с именованной конфигурацией. Это означает, что если вы, например, не создаете свои модульные тесты для «производственной» сборки, проект сборки также может знать об этом параметре и не создавать их, поскольку они были отмечены как таковые.

Кроме того, мы использовали XCopy для удаления с машины сборки. Сценарий просто не будет копировать что-либо с именем * .Tests.Dll из развертывания. Это было просто, но работало.

Джозеф Феррис
источник
1

Я бы сказал, держите их отдельно.

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

Себастьян К.
источник
Разве это не зависит от технологии, используемой для проведения тестов? Я имею в виду, что OpenCover считает строки кода покрытыми, если они запускаются тестом, включая строки самого теста, так что если тесты имеют неожиданные исключения, они также помечаются как непокрытые.
Isaac Llopis
0

Я действительно вдохновлен фреймворком модульного тестирования библиотеки Flood NN Роберта Лопеса. Он использует другой проект для каждого отдельного тестируемого класса и имеет одно решение, содержащее все эти проекты, а также основной проект, который компилирует и запускает все тесты.

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


источник
0

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

Зачем?

Во-первых, я очень стараюсь сохранить структуру папок основного проекта такой же, как и в тестовом проекте. Итак, если у меня есть файл, Providers > DataProvider > SqlDataProvider.csя создаю такую же структуру в своих проектах модульного тестирования, напримерProviders > DataProvider > SqlDataProvider.Tests.cs

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

Во-вторых, не всегда легко перейти от класса для тестирования к классу модульного тестирования. Это еще сложнее для JavaScript и Python.

Недавно я начал практиковать, что каждый созданный мной файл (например SqlDataProvider.cs) я создаю другой файл с суффиксом Test, напримерSqlDataProvider.Tests.cs

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

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

Специально для Js и Python вам не нужно будет импортировать ссылки с разных путей, вы можете просто использовать тот же путь к тестируемому целевому файлу.

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

Теоман Шипахи
источник
Я не согласен. Гораздо более организовано группирование предметов, чем тестирование отдельных классов. Если вы тестируете своих поставщиков; У вас будет IProvider, ISqlProvider и MockSqlConnection. В папке Provider у вас будет модульный тест для каждого провайдера. Вы можете так глубоко углубиться в модульные тесты, но тогда вы просто пишете тесты, а не код, и можете даже удалить pro и начать писать код для тестирования.
Ник Тернер
0

Как ответили другие - ставьте тесты в отдельные проекты

Одна вещь, о которой не упоминалось, - это то, что вы не можете работать nunit3-console.exeни с чем, кроме .dllфайлов.

Если вы планируете запускать тесты через TeamCity, это создаст проблему.

Допустим, у вас есть проект консольного приложения. При компиляции он возвращает исполняемый файл .exeв binпапку.

Из этого nunit3-console.exeвы не сможете запускать какие-либо тесты, определенные в этом консольном приложении.

Другими словами, консольное приложение возвращает exeфайл, а библиотека классов возвращает dllфайлы.

Меня это только что укусило сегодня, и это отстой :(

Колобский каньон
источник
0

Недавно я разворачивался в докере. Я не вижу смысла иметь отдельный проект, когда Dockerfile может легко скопировать каталог / src и покинуть каталог / test. Но я новичок в dotnet и, возможно, что-то упускаю.

MikeF
источник
-2

Отдельные проекты, хотя я спорю сам с собой, должны ли они использовать один и тот же файл svn. На данный момент я даю им отдельные репозитории svn, один из которых называется

«MyProject» - для самого проекта

и один позвонил

«MyProjectTests» - для тестов, связанных с MyProject.

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

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

Мой проект
| \ Багажник
| | \ Код
| \ Тесты
| \ Теги
| | \ 0,1
| | | \ Код
| | \ Тесты
| \ 0,2
| | \ Код
| \ Тесты
\Ветви
  \ MyFork
   | \ Код
    \Контрольная работа

Мне было бы интересно узнать, что другие люди думают об этом решении.

сампаблокупер
источник
6
Вам не нужны отдельные коммиты для кода теста и приложения. Они должны идти рука об руку, и коммиты будут включать и то, и другое, особенно при использовании дизайна в стиле * DD.
eddiegroves 03