Как заставить модульные тесты работать быстро?


40

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

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

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

Изменить: в качестве ответа на несколько ответов мы уже используем CI и сервер сборки, вот как я знаю, что тесты не пройдены. Проблема (на самом деле симптом) в том, что мы продолжаем получать сообщения о неудачных сборках. Частичные тесты - это то, что большинство людей делают, но не все. Что касается тестов, они на самом деле довольно хорошо сделаны, они используют подделки для всего, и IO вообще не существует.


8
Получить лучшее оборудование? Аппаратное обеспечение дешево по сравнению с временем программиста.
Брайан Оукли

18
Вы уже подразумевали решение в своем вопросе: запускайте только те тесты, которые имеют отношение к фрагменту кода, который был изменен. Периодически запускайте весь набор тестов, как часть цикла QA / Release. Тем не менее, от 2 до 3 минут не похоже на много времени, поэтому, возможно, ваша команда разработчиков слишком часто проверяет.
Роберт Харви

3
Первый эталон, чтобы выяснить, откуда взялись затраты на производительность. Есть ли несколько дорогих тестов, или это просто количество тестов? Некоторые установки дороги?
CodesInChaos

13
Блин, хотелось бы, чтобы наши тесты были всего 2-3 минуты. Для запуска всех наших модульных тестов требуется 25 минут, а у нас еще нет интеграционных тестов.
Изката

4
2-3 минуты? Jeez. Наши могут бегать часами ...
Родди из замороженного гороха

Ответы:


51

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

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

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

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

Отличное руководство по одному из способов такой настройки можно найти здесь (специфично для git, но должно работать для других систем контроля версий): http://nvie.com/posts/a-successful-git-branching-model/


15
Это. Если разработчики «перестали беспокоиться о запуске их (модульных тестов) перед выполнением регистрации», то вы хотите, чтобы настройки CI запускали их после регистрации.
Carson63000

+1: дальнейшим улучшением будет модульность тестов. Если определенный модуль / файл не изменился с момента последнего запуска, нет причин для повторного запуска тестов, которые отвечают за его тестирование. Вроде как make-файл не перекомпилирует все только потому, что один файл изменился. Это может потребовать некоторой работы, но, вероятно, даст вам более чистые тесты.
Лев

Будет ли методология ветвления работать с TFS? Мы пишем C # с TFS, а ветвление в TFS менее дружественно, чем в git. Я верю, что эта идея будет отвергнута, поскольку мы никогда не разветвляемся.
Зив

У меня нет личного опыта работы с TFS; Тем не менее, мне удалось найти это руководство от Microsoft, которое, похоже, демонстрирует сходную стратегию ветвления с той, что в посте: msdn.microsoft.com/en-us/magazine/gg598921.aspx
Майк

33

Большинство юнит-тестов должно занимать менее 10 миллисекунд каждый или около того. «Почти тысяча тестов» - ничто, и запуск может занять несколько секунд.

Если это не так, то вам следует прекратить написание сильно связанных интеграционных тестов (если это не то, что нужно коду) и начать писать хорошие модульные тесты (начиная с хорошо отделенного кода и правильного использования fakes / mocks / stubs / etc). Эта связь будет влиять на качество теста и время, необходимое для их написания, так что дело не только в сокращении времени выполнения теста.


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

2
Вы правы, что это интеграционные тесты.
Том Сквайрс

9
Этот ответ не является продуктивным. Во-первых, оно устанавливает необоснованные ожидания. В самой структуре модульного тестирования есть накладные расходы; То, что каждый тест занимает менее миллисекунды, не означает, что тысячи тестов должны занимать менее нескольких секунд. То, что весь набор тестов OP завершается за 2-3 минуты, является в большинстве случаев очень хорошим признаком.
Rwong

6
@ rwong - извините, я называю ерунду. Метрика, которую я получил, состояла из двух доступных мне профессиональных проектов: один с ~ 300 тестами, другой с ~ 30000 тестами и просмотром времени выполнения тестов. Набор тестов, занимающий 2-3 минуты для <1000 тестов, является ужасным и признаком того, что тесты недостаточно изолированы.
Теластин

2
@rwong В том же духе, что и в Telastyn, у меня есть кое-какие данные: даже несмотря на то, что тестов было несколько больше идеального, каркас теста ( py.test) выполнял кучу магии в фоновом режиме, и все было в чистом коде Python («100x»). медленнее, чем С "), выполнение около 500 тестов в моем проекте занимает менее 6 секунд на медленном нетбуке нескольких лет. Эта цифра примерно линейна по количеству тестов; хотя при запуске возникают некоторые накладные расходы, они амортизируются во всех тестах, а накладные расходы на тест составляют O (1).

16

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

  1. Проверьте время выполнения и найдите все самые медленные тесты, а затем проанализируйте, почему они требуют столько времени для выполнения .
  2. У вас есть 100 проектов, может быть, вам не нужно каждый раз создавать и тестировать их? Не могли бы вы запустить все юнит-тесты только на ночных сборках? Создайте несколько «быстрых» конфигураций сборки для ежедневного использования . CI-сервер будет выполнять только ограниченный набор проектов юнит-тестов, связанных с «горячими» частями вашего текущего процесса разработки .
  3. Смоделируйте и изолируйте все, что можете , избегайте дискового / сетевого ввода-вывода, когда это возможно
  4. Когда невозможно изолировать такие операции, возможно, у вас есть интеграционные тесты? Может быть, вы могли бы запланировать интеграционные тесты только для ночных сборок ?
  5. Проверьте все случайные синглтоны, которые хранят ссылки на экземпляры / ресурсы и которые потребляют память, это может привести к снижению производительности при выполнении всех тестов.

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

  1. Gated commit Некоторые CI-серверы могут быть настроены для выполнения сборки и тестирования перед передачей кода в исходное хранилище. Если кто-то фиксирует код без предварительного запуска всех тестов, который также содержит неудачные тесты, он будет отклонен и возвращен автору.
  2. Сконфигурируйте сервер CI для параллельного выполнения тестов : с использованием нескольких машин или процессов. Примерами являются pnunitи конфигурация CI с несколькими узлами.
  3. Плагин для непрерывного тестирования для разработчиков, который автоматически запускает все тесты во время написания кода.

12

0. Слушайте своих программистов.

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

1. Сделайте ваши тесты на 100% надежными.

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

2. Измените ваши системы, чтобы гарантировать, что все тесты проходят все время.

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

3. Измените свою культуру, чтобы оценить 100% прохождения тестов.

Преподайте урок, что задача не «выполнена» до тех пор, пока 100% тестов не пройдут и она не будет объединена с основной / официальной / версией / какой-либо веткой.

4. Сделайте тесты быстрыми.

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

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

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

Быстрые тесты могут быть трудными (об этом спрашивал ОП, верно!). Разделение является ключевым. С издевательствами и подделками все в порядке, но я думаю, что вы можете добиться большего успеха путем рефакторинга, чтобы лишние издевательства / подделки стали ненужными. См. Блог Арло Белши, начиная с http://arlobelshee.com/post/the-no-mocks-book .

5. Сделайте тесты полезными.

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


2
ПОЛНОСТЬЮ согласен, особенно с пунктами 3 и 1. Если разработчики не запускают тесты, то тесты нарушаются, среда нарушается или оба. Точка 1 - это минимум. Ложные неудачи хуже, чем пропущенные тесты. Потому что люди учатся принимать неудачи. Как только отказ терпится, он распространяется, и требуются огромные усилия, чтобы вернуться к 100% прохождению и ОЖИДАЮ 100% прохождения. Начните исправлять это сегодня .
Билл IV

И как вы можете не согласиться с # 5?!? в дополнение к 1 и 3, или, черт возьми, 2 и 4 тоже! Во всяком случае, отличный ответ всем вокруг.
четверг в ночь на

4

Пара минут в порядке для модульных тестов. Однако имейте в виду, что существует 3 основных типа тестов:

  1. Модульные тесты - тестируйте каждый «блок» (класс или метод) независимо от остальной части проекта
  2. Интеграционные тесты - тестируют проект в целом, обычно совершая вызовы в программу. Некоторые проекты, которые я видел, сочетают это с регрессионными тестами. Здесь гораздо меньше насмешек, чем юнит-тестов
  3. Регрессионные тесты - тестирование завершенного проекта в целом, поскольку набор тестов является конечным пользователем. Если у вас есть консольное приложение, вы должны использовать консоль для запуска и тестирования программы. Вы никогда не подвергаете эти тесты внутренним компонентам, и любой конечный пользователь вашей программы должен (теоретически) иметь возможность запускать ваш набор регрессионных тестов (даже если они никогда не будут)

Они перечислены в порядке скорости. Модульные тесты должны быть быстрыми. Они не поймают каждую ошибку, но они устанавливают, что программа вменяемая. Модульные тесты должны выполняться за 3 минуты или меньше или приличное оборудование. Вы говорите, что у вас есть только 1000 модульных тестов, и они занимают 2-3 минуты? Ну, это, наверное, хорошо.

Вещи, чтобы проверить:

  • Убедитесь, что ваши модульные и интеграционные тесты разделены. Интеграционные тесты всегда будут медленнее.

  • Убедитесь, что ваши модульные тесты работают параллельно. У них нет причин не делать это, если они являются настоящими юнит-тестами

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

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


3

Обучите своих разработчиков Личному процессу программного обеспечения (PSP), помогая им понять и улучшить свою производительность, используя больше дисциплины. Написание кода не имеет ничего общего с ударом пальцами по клавиатуре, а затем нажмите кнопку компиляции и регистрации.

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

  1. введите код без раздумий
  2. нажмите build / compile
  3. исправьте синтаксис, чтобы он компилировался
  4. запустить тесты, чтобы увидеть, имеет ли смысл то, что вы написали

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


2
Я полностью согласен с вашим списком, но абсолютно не с тем, что «запускать сборку только два раза в день лучше, чем 50 раз».
Док Браун

3

Один из возможных способов: разделить ваше решение. Если решение имеет 100 проектов, то оно совершенно неуправляемо. Тот факт, что два проекта (скажем, A и B) используют некоторый общий код из другого проекта (например, Lib), не означает, что они должны быть в одном решении.

Вместо этого вы можете создать решение A с проектами A и Lib, а также решение B с проектами B и Lib.


2

Я в похожей ситуации. У меня есть юнит-тесты, которые проверяют связь с сервером. Они тестируют поведение с таймаутами, отменой соединений и т. Д. Весь набор тестов длится 7 минут.

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

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

Так что делать?

  1. Смена тестов обычно не очень эффективна.
  2. Запускайте только соответствующие тесты перед вашей фиксацией.
  3. Выполняйте все свои тесты каждый день (или несколько раз в день) на сервере сборки. Это также даст вам возможность создавать хорошие отчеты о покрытии и анализе кода.

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


1
Что касается тестов, которые общаются с серверами: если это общение с сервером, на самом деле это не модульный тест, а нечто большее. На вашем месте я бы выделил модульные тесты (которые должны выполняться быстро) и, по крайней мере, запускал их перед каждым коммитом. Таким образом, вы по крайней мере получите быстрый материал (вещи, которые не должны общаться с сервером), прежде чем код будет зафиксирован.
Майкл Кохне

@MichaelKohne Я знал, что кто-то заметит это. Я знаю, что это не совсем модульные тесты, но они служат той же цели, речь идет только о том, как вы их называете.
Sulthan

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

1

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

Учитесь писать правильные тесты.

Вы говорите, что у вас есть почти тысяча тестов, и у вас есть 120 проектов. Предполагая, что не более половины из этих проектов являются тестовыми проектами, у вас есть 1000 тестов для 60 проектов с производственным кодом. Это дает вам около 16-17 тестов пр. проект !!!

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

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

Для сравнения, над проектом .NET, над которым я сейчас работаю, мы можем выполнить примерно около 500 модульных тестов менее чем за 10 секунд (а это даже не было измерено на машине с высокими техническими характеристиками). Если бы это были ваши цифры, вы бы не боялись запускать их локально.

Научитесь управлять структурой проекта.

Вы разделили решение на 120 проектов. Это по моим меркам ошеломляющее количество проектов.

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

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

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

Спросите себя, действительно ли вам нужно 120 проектов?

PS Вы можете проверить NCrunch. Это плагин Visual Studio, который автоматически запускает тест в фоновом режиме.


0

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

Например, тестирование базы данных обычно занимает некоторое время для инициализации и завершения.

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

Что можно сделать, это:

1) Определите важные тесты. Те для самых важных частей библиотек и те, которые, скорее всего, потерпят неудачу после изменений. Только те тесты должны выполняться всегда при компиляции. Если какой-то код часто ломается, его тесты должны быть обязательными, даже если для их выполнения требуется много времени, с другой стороны, если какая-то часть программного обеспечения никогда не вызывала проблемы, вы можете безопасно пропустить тесты для каждой сборки.

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


0

Проблемы, которые я видел:

а) Использование МОК для создания тестовых элементов. 70 секунд -> 7 секунд, удалив контейнер.

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

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

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

e) Удалить зависимости. Небольшой тестовый исполняемый файл сократит время загрузки. Используйте интерфейсную библиотеку и контейнеры IOC для запуска вашего окончательного решения, но ваши основные тестовые проекты должны иметь только определенную интерфейсную библиотеку. Это обеспечивает разделение, облегчает тестирование, а также уменьшает размер отпечатка.


0

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

Вещи, которые съедают время сборки:

  • Загрузка пакетов (Nuget для C #, Maven для Java, Gem для Ruby и т. Д.)
  • Копирование большого количества файлов в файловой системе (пример: файлы поддержки GDAL)
  • Открытие соединений с базой данных (некоторые требуют более секунды на соединение для согласования)
  • Код на основе отражения
  • Автогенерированный код
  • Использование исключений для управления потоком программ

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

Есть способы решения проблем:

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

Когда ваше решение содержит более 100 проектов, у вас есть комбинация кода библиотеки, тестов и кода приложения. Каждая из библиотек может быть ее собственным решением со связанными тестами. Jet Brains Team City - это сервер сборки CI, который работает как сервер Nuget, и я уверен, что он не единственный. Это дает вам возможность перемещать те библиотеки, которые, вероятно, не часто меняются, в свои собственные решения / проекты и использовать Nuget для разрешения зависимостей для кода вашего приложения. Меньшие решения означают, что вы можете вносить изменения в библиотеку быстро и без суеты и пользоваться преимуществами основного решения.


-1

Может ли ваша тестовая среда работать где-нибудь? Если это возможно, используйте облачные вычисления для запуска тестов. Разделите тесты среди N виртуальных машин. Если время для запуска тестов на одной машине составляет T1 секунды, тогда время для их разделения, T2, может приблизиться к T2 = T1 / N. (Предполагается, что каждый тестовый пример занимает примерно одинаковое количество времени.) И вам нужно платить только за виртуальные машины, когда вы их используете. Таким образом, у вас нет кучки тестовых машин, сидящих где-то в лаборатории 24/7. (Я бы хотел сделать это там, где я работаю, но мы привязаны к конкретному оборудованию. Никаких виртуальных машин для меня.)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.