Есть ли такая вещь, как слишком много модульных тестов?

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

Будет ли это отношение становиться неуправляемым, поскольку мы увеличиваем охват кода?

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

Я доверяю комментарию своего технологического руководства. У него больше опыта, чем у меня, и у него лучшие инстинкты, когда дело доходит до разработки программного обеспечения. Но как команда из нескольких человек пишет тесты для такого неоднозначного стандарта; то есть, как я узнаю своих сверстников, и я разделяю ту же идею для «наиболее распространенных случаев использования»?

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

131 голос | спросил user2954463 3 Maypm17 2017, 20:37:17

11 ответов


172

Да, со 100% покрытием вы напишете несколько тестов, которые вам не нужны. К сожалению, единственный надежный способ определить, какие тесты вам не нужны, это написать их все, а затем подождать 10 лет или около того, чтобы посмотреть, какие из них никогда не сработали.

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

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

ответил Karl Bielefeldt 3 Maypm17 2017, 21:29:16
60

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

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

ответил Frank Hileman 3 Maypm17 2017, 21:06:08
35

Ответы на ваши вопросы

  

Есть ли такая вещь, как слишком много модульных тестов?

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

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

  

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

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

  

Будет ли это отношение становиться неуправляемым, поскольку мы увеличиваем покрытие кода?

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

  

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

Это верно для «единичных» тестов в строгом смысле. Здесь «единица» является чем-то вроде метода или класса. Точка «единичного» тестирования должна тестировать только одну конкретную единицу кода, а не всю систему. В идеале вы бы удалили всю оставшуюся часть системы (используя удваивание или еще что-то).

  

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

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

  

Он предложил протестировать 4-5 случаев использования, которые наиболее часто используются с рассматриваемым классом, а не исчерпывающим тестированием каждой функции.

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

  

Для меня 100% -ное охват тестирования - это высокая цель, но даже если мы достигнем 50%, мы бы знали, что 100% из этих 50% были покрыты.

Я не знаю, что такое «охват единичного тестирования». Я предполагаю, что вы имеете в виду «покрытие кода», то есть после выполнения набора тестов каждая строка кода (= 100%) была выполнена хотя бы один раз.

Это хорошая метрика в шарике, но далеко не лучший стандарт, за который можно стрелять. Просто выполнение строк кода - это не вся картина; например, это не учитывает разные пути через сложные вложенные ветви. Это скорее метрика, которая указывает на палец на фрагменты кода, которые слишком мало тестируются (очевидно, если класс содержит 10% или 5% кода, то что-то не так); с другой стороны, 100% -ное покрытие не скажет вам, были ли вы достаточно проверены или были ли вы правильно протестированы.

Тестирование интеграции

Это сильно меня раздражает, когда люди постоянно говорят о тестировании unit по умолчанию. По моему мнению (и опыту) тестирование unit отлично подходит для библиотек /API; в более ориентированных на бизнес областях (где мы говорим о случаях использования, как в вопросе под рукой), они не обязательно являются лучшим вариантом.

Для общего кода приложения и в среднембизнес (где зарабатывать деньги, ударять по срокам и удовлетворять удовлетворенность клиентов важно, и вы в основном хотите избежать ошибок, которые либо прямо на лице пользователя, либо могут привести к реальным бедствиям - мы не говорим Здесь запускается ракета НАСА), интеграционные или функциональные тесты более полезны much .

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

Чтобы сохранить его коротким (ish), тест интеграции /функции выполняет весь стек приложения. В веб-приложении он будет действовать как браузер, нажимая через приложение (и нет, очевидно, что это не , чтобы быть упрощенным, там есть очень мощные фреймворки - проверьте http://cucumber.io для примера).

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

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

Очевидно, что тесты функций /интеграции имеют свою собственную червь (например, производительность, избыточное тестирование сторонних фреймворков, так как вы обычно не используете двойники, которые, как мне кажется, сложнее писать, по моему опыту ...) но в любой день я бы воспользовался 100% -ным положительным тестированием приложений на 100% -ном тестировании кода (не в библиотеке!).

ответил AnoE 4 Mayam17 2017, 01:04:13
24

Да, возможно слишком много модульных тестов. Если у вас есть 100% -ый охват модульными тестами и нет интеграционных тестов, например, у вас есть явная проблема.

Некоторые сценарии:

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

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

  2. У вас может быть разумное покрытие для каждого совершения с 20% тестов, оставшихся на 80% для интеграции или по крайней мере отдельных тестовых проходов; основные негативные эффекты, которые вы видите в этом сценарии, являются медленными изменениями, поскольку вам приходится ждать большое время для выполнения тестов.

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

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

ответил Bruno Guardia 3 Maypm17 2017, 22:23:40
19

Имейте в виду, что каждый тест имеет стоимость, а также выгоду. Недостатки включают:

  • должен быть записан тест;
  • тест выполняется (как правило, очень небольшое количество) времени для запуска;
  • тест должен поддерживаться с помощью кода - тесты должны меняться при изменении API-интерфейсов;
  • вам может потребоваться изменить свой дизайн, чтобы написать тест (хотя эти изменения, как правило, к лучшему).

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

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

ответил Solomonoff's Secret 3 Maypm17 2017, 22:40:48
12

Да, есть такая вещь, как слишком много модульных тестов.

При хорошем тестировании каждый модульный тест:

  • Потенциальное бремя обслуживания, тесно связанное с API

  • Время, которое может быть потрачено на что-то еще

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

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

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

Прагматика для большинства кодов указывает:

  1. Удостоверьтесь, что у вас есть 100% -ый охват входных точек (все как-то проверено) и нацелены на то, чтобы быть ближе к 100% -ному охвату кода путей «без ошибок».

  2. Проверьте любые релевантные минимальные /максимальные значения или размеры

  3. Проверьте все, что вы считаете смешным особым случаем, особенно «нечетные» значения.

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

Для более сложных алгоритмов также рассмотрим:

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

Например, проверьте алгоритм сортировки с некоторым рандомизированным вводом, а проверка данных сортируется в конце, сканируя его.

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

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

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

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

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

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

В сочетании с предварительными условиями тестирования отладочного кода, post-conditions, включая инварианты на всех уровнях, вы получаете максимальное покрытие теста от минимальной реализации теста.

ответил Persixty 3 Maypm17 2017, 20:54:24
3

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

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

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

Следующими наиболее ценными тестами являются те, которые используют экстремальные пределы или граничные точки. Например, функция, которая принимает (1 на основе) месяцев года, должна быть проверена с помощью 0, 1, 12 и 13, так что вы знаете, что допустимые-недопустимые переходы находятся в нужном месте. Это переутомление, чтобы использовать 2..11 для этих тестов.

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

ответил Toby Speight 4 Maypm17 2017, 12:31:11
3
  

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

Это понимание неверно.

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

В этом смысле единица не обязательно является «методом в классе». Мне нравится определение единицы Роем Ошеровым в Искусство модульного тестирования :

  

Единица - это все производственный код, который по той же причине может меняться.

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


  

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

Он прав, но по-другому, чем он думает.

Из вашего вопроса я понимаю, что вы - «выделенный тестер» в этом проекте.

Большое недоразумение состоит в том, что он ожидает, что вы напишете единичные тесты (в отличие от «теста с использованием модульной системы тестирования»). Написание ynit-тестов - это ответственность разработчиков , а не тестеров (в идеальном мире, я знаю ...). С другой стороны, вы отметили этот вопрос с помощью TDD, что подразумевает именно это.

Ваша задача в качестве тестера - написать (или выполнить вручную) тестирование модулей и /или приложений. И такие тесты должны в основном проверять, чтобы все блоки работали плавно. Это означает, что вам нужно выбрать тестовые примеры, чтобы каждый элемент выполнялся хотя бы один раз . И эта проверка - это пробежки. Фактический результат менее важен, поскольку он может быть изменен с учетом будущих требований.

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

Пункт здесь:

Нам нужно знать эту разницу между «модульными тестами» и «тестом, автоматизированным с использованием модульной системы тестирования».


  

Для меня 100% -ное охват тестирования - это высокая цель, но даже если мы достигнем 50%, мы бы знали, что 100% из 50% покрыто.

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

Вам не нужно 100% охвата кода.

Но вам нужно 100% покрытие поведения. (Да, покрытие кода и покрытие поведения как-то коррелируют, но для этого они не идентичны.)

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


Заключение

Несколько тестов лучше, чем никаких тестов. Без сомнения!

Но нет такой вещи, как слишком много модульных тестов.

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

ответил Timothy Truckle 5 Mayam17 2017, 11:56:30
1

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

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

ответил mrog 4 Mayam17 2017, 00:00:00
1

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

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

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

ответил wrschneider 4 Maypm17 2017, 18:53:28
0

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

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

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

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

ответил mmathis 4 Maypm17 2017, 18:12:33

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132