Подтвердить что ты не робот

Каковы плюсы и минусы автоматических тестов Unit Tests и автоматизированных тестов интеграции?

Недавно мы добавляли автоматические тесты к существующим Java-приложениям.

Что у нас

Большинство этих тестов являются интеграционными тестами, которые могут охватывать стек вызовов, таких как: -

  • HTTP-сообщение в сервлет
  • Сервлет проверяет запрос и вызывает бизнес-уровень
  • Бизнес-уровень делает кучу вещей с помощью hibernate и т.д. и обновляет некоторые таблицы базы данных.
  • Сервлет создает некоторый XML, запускает его через XSLT для создания ответа HTML.

Затем мы проверяем, что сервлет ответил правильным XML и что в базе данных существуют правильные строки (наш экземпляр Oracle для разработки). Эти строки затем удаляются.

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

Эти тесты выполняются как часть наших ночных (или adhoc) сборников.

Вопрос

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

Какие проблемы мы можем столкнуться с этим подходом?

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

4b9b3361

Ответ 1

Вы спрашиваете плюсы и минусы двух разных вещей (каковы плюсы и минусы верховой езды лошади против езды на мотоцикле?)

Конечно, оба являются "автоматическими испытаниями" (~ верховая езда), но это не означает, что они являются альтернативными (вы не катаетесь на лошади на сотни миль, и вы не катаетесь на мотоцикле в закрытом - транспортные мутные места)


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

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

Не имеет смысла проводить ручные тесты единиц.

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

Если вы реструктурируете более агрессивно, меняя методы существования или поведения, то да, вам нужно написать новый unit test для каждого нового метода и, возможно, выбросить старый. Но писать Unit Test, особенно если вы напишете его перед самим кодом, поможет прояснить дизайн (т.е. , что должен делать метод, а , что он должен " t), не путаясь деталями реализации (т.е. , как метод должен делать то, что ему нужно делать).


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

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


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

В вашем случае я предпочел бы использовать подход "средний уровень":

  • less Integration Tests, которые проверяют только те разделы, которые вы собираетесь реорганизовать. Если вы реорганизуете все это, вы можете использовать текущие тесты интеграции, но если вы только рефакторинг -say- генерация XML, не имеет смысла требовать наличия базы данных, поэтому я бы написал простой и маленький тест интеграции XML.
  • куча Unit Tests для нового кода, который вы собираетесь писать. Как я уже писал выше, Unit Tests будет готов, как только вы "возитесь с чем-либо между ними", убедившись, что ваш "беспорядок" куда-то идет.

Фактически, ваш тест интеграции будет только убедиться, что ваш "беспорядок" не работает (потому что вначале это не сработает, правда?), но это не даст вам никаких подсказок в

  • почему он не работает.
  • Если ваша отладка "беспорядка" действительно исправляет что-то
  • Если ваша отладка "беспорядка" ломает что-то еще

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

Ответ 2

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


(далее...)

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

long pow2(int p); // returns 2^p for 0 <= p <= 30

Ваш тест и ваши спецификации выглядят по сути одинаково (это пример псевдо-xUnit для иллюстрации):

assertEqual(1073741824,pow2(30);
assertEqual(1, pow2(0));
assertException(domainError, pow2(-1));
assertException(domainError, pow2(31));

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

Если вы измените реализацию так, чтобы, скажем, она возвращала 16 бит (помните, что sizeof(long) гарантируется быть не меньше, чем sizeof(short)), тогда эти тесты быстро сработают. Тест уровня интеграции, вероятно, должен завершиться неудачно, но не обязательно, и это так же вероятно, как не сбой где-то далеко ниже по течению от pow2(28).

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

Ответ 3

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

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

2) Второе значение модульных тестов состоит в том, что если они правильно написаны, они очень быстрые. Если я внес изменения в класс в вашем проекте, могу ли я запустить все соответствующие тесты, чтобы увидеть, что я что-то сломал? Как узнать, какие тесты нужно запустить? И сколько времени они возьмут? Я могу гарантировать, что он будет длиннее, чем хорошо написанные модульные тесты. Вы должны иметь возможность запускать все ваши модульные тесты максимум за пару минут.

Ответ 4

Всего несколько примеров из личного опыта:

Тесты устройств:

  • (+) Продолжает тестирование рядом с соответствующим кодом.
  • (+) Относительно легко проверить все пути кода
  • (+) Легко видеть, что кто-то непреднамеренно изменяет поведение метода.
  • (-) Намного сложнее писать для компонентов пользовательского интерфейса, чем для не GUI

Интеграционные тесты:

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

В идеале оба необходимы.

<сильные > Примеры:

  • Unit test: убедитесь, что индекс вводa >= 0 и < длина массива. Что происходит, когда внешние границы? Должен ли метод генерировать исключение или возвращать null?

  • Тест интеграции: что пользователь видит, когда вводится отрицательное значение запаса?

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

Самая лучшая часть нашего тестирования модулей - это то, что разработчики переходят из кода code- > test- > think to think- > test- > . Если разработчик должен сначала написать тест, [s], он, как правило, больше думает о том, что может пойти не так.

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

Ответ 5

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

Дизайн, основанный на проверке, используемый в качестве средства для создания лучшего разработчика, имеет свои достоинства, но для этого он не требуется. Существует много хорошего программиста, который никогда не писал unit test. Лучшая причина для модульных тестов - это сила, которую они дают вам при рефакторинге, особенно когда многие люди меняют источник одновременно. Пятнистые ошибки на checkin также являются огромным временем сбережения для проекта (подумайте о том, чтобы перейти к модели CI и построить на checkin вместо ночного). Поэтому, если вы пишете unit test, либо до, либо после того, как вы написали код, который он тестирует, в этот момент вы уверены, о новом коде, который вы написали. Именно это может произойти с этим кодом позже, чем unit test гарантирует против - и это может быть значительным. Модульные тесты могут остановить ошибки до того, как они достигнут QA, тем самым ускоряя ваши проекты.

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

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

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

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

Ответ 6

То, что отличает тесты Unit и Integration, - это количество частей, необходимых для запуска теста.

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

Интеграция тестирует поведение теста и инфраструктуру. Единичные тесты обычно проверяют поведение.

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

Итак, почему unit test?

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

Итак, для этого вам понадобится unit test (функции).

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

вызов от HTTP-клиента проверка сервлета вызов сервлета на бизнес-уровень проверка бизнес-уровня чтение базы данных (спящий режим) преобразование данных бизнес-слоем запись базы данных (спящий режим) преобразование данных → XML преобразование XSLT → HTML передача HTML → клиент

Чтобы ваши интеграционные тесты работали, вам нужно, чтобы ВСЕ эти процессы работали правильно. Для unit test проверки сервлета вам нужно только одно. Проверка сервлета (которая может быть независимой от всего остального). Проблема в одном слое упрощается для отслеживания.

Вам нужны как модульные тесты, так и интеграционные тесты.

Ответ 7

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

Интеграционные тесты будут включать в себя другие компоненты вашего приложения и стороннего программного обеспечения (например, базы данных Oracle dev или тесты Selenium для webapp). Эти тесты могут быть очень быстрыми и выполняться как часть непрерывной сборки, но поскольку они вводят дополнительные зависимости, они также рискуют вводить новые ошибки, которые вызывают проблемы для вашего кода, но не вызваны вашим кодом. Предпочтительно, интеграционные тесты также включают то, где вы вводите реальные/записанные данные и утверждаете, что стек приложений в целом ведет себя так, как ожидалось, с учетом этих входов.

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

Ответ 8

Джоэл Спольский написал очень интересную статью об модульном тестировании (это был диалог между Джоэлем и другим парнем).

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

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

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

Как я использую модульные тесты: мне не нравится TDD, поэтому я сначала пишу код, затем тестирую его (используя консоль или браузер), чтобы быть уверенным, что этот код работает незаметно. И только после этого я добавляю "хитрые" тесты - 50% из них терпят неудачу после первого тестирования.

Он работает, и это не занимает много времени.

Ответ 9

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

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

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

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

Ответ 10

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

Ответ 11

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

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

Ответ 12

В нашем проекте есть 4 разных типа тестов:

  • Единичные тесты с издевательством при необходимости
  • Тесты DB, которые действуют аналогично блочным тестам, но затем коснитесь db и очистят
  • Наша логика раскрывается через REST, поэтому у нас есть тесты, которые делают HTTP
  • Тесты Webapp с использованием WatiN, которые фактически используют экземпляр IE и выполняют основные функции.

Мне нравятся модульные тесты. Они работают очень быстро (100-1000x быстрее, чем тесты №4). Они безопасны по типу, поэтому рефакторинг довольно прост (с хорошей IDE).

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

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

# 3 и особенно # 4 являются более проблематичными. Они требуют некоторого подмножества производственной среды на сервере сборки. Вы должны создавать, развертывать и запускать приложение. Вы должны иметь чистую БД каждый раз. Но, в конце концов, это окупается. Тесты Watin требуют постоянной работы, но вы также получаете постоянное тестирование. Мы запускаем тесты на каждом коммите, и очень легко увидеть, когда мы что-то сломаем.

Итак, вернемся к вашему вопросу. Единичные тесты быстрые (что очень важно, время сборки должно быть меньше, скажем, 10 минут), и их легко рефакторировать. Намного легче, чем переписывать целую игру, если ваш дизайн меняется. Если вы используете хороший редактор с хорошей командой поиска (например, IDEA или VS.NET + Resharper), вы всегда можете найти, где тестируется ваш код.

С помощью тестов REST/HTTP вы получаете хорошую проверку, что ваша система действительно работает. Но тесты выполняются медленно, поэтому на этом уровне трудно получить полную проверку. Я предполагаю, что ваши методы принимают несколько параметров или, возможно, XML-вход. Чтобы проверить каждый node в XML или каждом параметре, потребуется несколько десятков или сотен вызовов. Вы можете сделать это с помощью модульных тестов, но вы не можете сделать это с помощью вызовов REST, когда каждый из них может занимать большую часть секунды.

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