Достаточно ли использовать приемочные и интеграционные тесты вместо юнит-тестов?

62

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

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

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

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

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

редактировать

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

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

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

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

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

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

Все, что я хотел бы знать на данный момент, - если новый дизайн не нарушает каких-либо существующих требований. Мне не нужно было менять ЛЮБОЙ мой приемочный тест. Мне пришлось провести рефакторинг или удалить почти КАЖДЫЕ юнит-тесты, что было огромной кучей работы.

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

Заключение

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

  • Прежде всего, я пишу все тесты, прежде чем реализовать что-либо, как всегда делал.
  • Для требований я пишу сначала несколько приемочных тестов, которые тестируют всю программу. Затем я пишу несколько интеграционных тестов для компонентов, где мне нужно реализовать требование. Если есть компонент, который тесно взаимодействует с другим компонентом для реализации этого требования, я бы также написал несколько интеграционных тестов, в которых оба компонента тестируются вместе. И последнее, но не менее важное, если мне нужно написать алгоритм или любой другой класс с высокой перестановкой - например, сериализатор - я бы написал модульные тесты для этих конкретных классов. Все остальные классы не тестируются, а проходят какие-либо юнит-тесты.
  • Для ошибок этот процесс может быть упрощен. Обычно ошибка вызвана одним или двумя компонентами. В этом случае я бы написал один интеграционный тест для компонентов, который тестирует ошибку. Если бы это касалось алгоритма, я бы написал только тестовый модуль. Если не легко обнаружить компонент, где возникает ошибка, я бы написал приемочный тест, чтобы найти ошибку - это должно быть исключением.
Иггдрасиль
источник
Эти вопросы, кажется, больше решают проблему того, зачем вообще писать тесты. Я хочу обсудить, может ли написание функциональных тестов вместо модульных тестов быть лучшим подходом.
Иггдрасиль
за мое чтение, ответы на дубликате вопроса, в первую очередь о том, когда не являющиеся тестах -Unit больше смысла
комар
Эта первая ссылка сама по себе является дубликатом. Думаю, что вы имеете в виду: programmers.stackexchange.com/questions/66480/…
Робби Ди
Ответы по ссылке от Робби Ди - это даже больше о том, зачем вообще тестировать.
Иггдрасиль

Ответы:

37

Это сравнивает апельсины и яблоки.

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

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

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

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

Приемочные испытания:

Приемочный тест должен напрямую соответствовать бизнес-сценарию. Он может быть огромным («сделки представлены правильно») или крошечным («фильтр успешно фильтрует список») - это не имеет значения; важно то, что он должен быть явно привязан к конкретному требованию пользователя. Я хотел бы сосредоточиться на них для разработки на основе тестов, потому что это означает, что у нас есть хорошее справочное руководство по тестам для пользовательских историй для dev и qa для проверки.

Модульные тесты:

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

Поведенческие тесты:

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

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

Майкл
источник
В вашем определении я предполагаю, что я имею в виду писать больше тестов поведения (?), Чем модульных тестов. Модульный тест для меня - это тест, который тестирует один класс со всеми макетированными зависимостями. Есть случаи, когда модульные тесты наиболее полезны. Например, когда я пишу сложный алгоритм. Тогда у меня есть много примеров с ожидаемым выводом алгоритма. Я хочу проверить это на уровне модулей, потому что это на самом деле быстрее, чем тест поведения. Я не вижу смысла тестировать класс на уровне модуля, у которого только одна рука заполнена путями через класс, которые можно легко проверить с помощью теста поведения.
Иггдрасиль
14
Лично я считаю, что приемочные тесты являются наиболее важными, поведенческие тесты важны при тестировании таких вещей, как коммуникация, надежность и случаи ошибок, а модульные тесты важны при тестировании небольших сложных функций (хороший пример
Michael
Я не так тверд с вашей терминологией. Мы программируем программный пакет. Там я отвечаю за графический редактор. Мои тесты тестировали редактор с помощью мошеннических сервисов из остальной части пакета и с мошенническим интерфейсом Что это за тест?
Иггдрасиль
1
Зависит от того, что вы тестируете - тестируете ли вы бизнес-функции (приемочные тесты)? Вы тестируете интеграцию (интеграционные тесты)? Вы тестируете, что происходит, когда вы нажимаете кнопку (поведенческие тесты)? Вы тестируете алгоритмы (юнит-тесты)?
Майкл
4
«Мне не нравится сосредотачиваться на подходах к учебникам - скорее, сосредотачивайтесь на том, что дает ценность вашим тестам» О, так верно! Первый вопрос, который нужно всегда задавать: «Какую проблему я решаю, делая это?». И разные проекты могут иметь разные проблемы для решения!
Лоран Бурго-Рой
40

TL; DR: Пока это отвечает вашим потребностям, да.

Я занимаюсь разработкой Acceptance Test Driven Development (ATDD) уже много лет. Это может быть очень успешным. Есть несколько вещей, о которых нужно знать.

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

Теперь преимущества

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

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

dietbuddha
источник
8
Отличный момент. Слишком легко стать немного космическим кадетом в тестировании и написать сотни случаев, чтобы получить теплое свечение удовлетворения, когда оно «проходит» с летающими цветами. Проще говоря: если ваше ПО не выполняет то, что ему нужно с точки зрения ПОЛЬЗОВАТЕЛЯ, вы провалили первый и самый важный тест.
Робби Ди
Хорошая точка для точного определения конкретной проблемы. Если у меня есть огромное требование, я пишу приемочные тесты, которые тестируют всю систему, а затем пишу тесты, которые тестируют подзадачи определенных компонентов системы, чтобы выполнить требование. С этим я могу точно определить компонент, в котором лежит дефект.
Иггдрасиль
2
«Модульные тесты помогают применять МОК»?!? Я уверен, что вы имели в виду DI там, а не IoC, но в любом случае, зачем кому-то хотеть принудительно применять DI? Лично я считаю, что на практике DI приводит к не объектам (программирование в стиле процедур).
Rogério
Можете ли вы дать свой вклад в (лучший вариант IMO) проведения ОБА интеграции и модульного тестирования против аргумента только проведения интеграционного тестирования? Ваш ответ здесь хороший, но, похоже, эти вещи оформлены как взаимоисключающие, что, как я полагаю, не так.
starmandeluxe
@starmandeluxe Они действительно не являются взаимоисключающими. Скорее, это вопрос ценности, которую вы хотите получить от тестирования. Я бы проводил модульное тестирование в любом месте, где стоимость превышала стоимость разработки / поддержки написания модульных тестов. ех. Я бы определенно проверил составную процентную функцию в финансовом приложении.
dietbuddha
18

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

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

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

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

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

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

РЕДАКТИРОВАТЬ: Иногда дизайн ваших компонентов может быть в порядке, но дизайн ваших модульных тестов может вызвать проблемы . Простой пример: вы хотите проверить метод «MyMethod» класса X и написать

    var x= new X();
    Assert.AreEqual("expected value 1" x.MyMethod("value 1"));
    Assert.AreEqual("expected value 2" x.MyMethod("value 2"));
    // ...
    Assert.AreEqual("expected value 500" x.MyMethod("value 500"));

(предположим, что значения имеют какое-то значение).

Предположим далее, что в рабочем коде есть только один вызов X.MyMethod. Теперь, для нового требования, метод «MyMethod» нуждается в дополнительном параметре (например, что-то вроде context), который нельзя пропустить. Без модульных тестов нужно было бы реорганизовать вызывающий код в одном месте. С юнит-тестами нужно провести рефакторинг 500 мест.

Но причина здесь не в самих модульных тестах, а в том, что один и тот же вызов X.MyMethod повторяется снова и снова, не строго следуя принципу «Не повторяйся (СУХОЙ)». здесь нужно поместить тестовые данные и связанные с ними ожидаемые значения в список и выполнить вызовы «MyMethod» в цикле (или, если инструмент тестирования поддерживает так называемые «тесты накопителя данных», использовать эту функцию). количество мест, которые нужно изменить в модульных тестах, когда сигнатура метода меняется на 1 (вместо 500).

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

Док Браун
источник
«Это означает, что когда компоненты уже спроектированы хорошо»: я согласен с вами, но как компоненты могут быть уже спроектированы, если вы пишете тесты до написания кода, а код - это дизайн? По крайней мере, так я понял TDD.
Джорджио
2
@ Джорджио: на самом деле, это не имеет значения, если вы пишете тесты сначала или позже. Проектирование означает принятие решений об ответственности компонента, об общедоступном интерфейсе, о зависимостях (прямых или внедренных), о поведении во время выполнения или времени компиляции, об изменчивости, об именах, о потоке данных, потоке управления, слоях и т. Д. Хорошо Дизайн также означает отложить некоторые решения до самого последнего возможного момента времени. Модульное тестирование может показать вам косвенно, если ваш дизайн был в порядке: если вы захотите провести рефакторинг многих из них впоследствии, когда требования изменятся, это, вероятно, не так.
Док Браун
@ Джорджио: пример может прояснить: скажем, у вас есть компонент X с методом «MyMethod» и 2 параметрами. Используя TDD, вы пишете X x= new X(); AssertTrue(x.MyMethod(12,"abc"))перед тем, как реализовать метод. Используя предварительный дизайн, вы можете class X{ public bool MyMethod(int p, string q){/*...*/}}сначала написать , а потом написать тесты. В обоих случаях вы приняли одно и то же дизайнерское решение. Если решение было хорошим или плохим, TDD не сообщит вам.
Док Браун
1
Я согласен с вами: я немного скептически отношусь, когда вижу, что TDD применяется вслепую, предполагая, что он автоматически создаст хороший дизайн. Кроме того, иногда TDD мешает, если дизайн еще не ясен: я вынужден проверить детали, прежде чем у меня будет обзор того, что я делаю. Так что, если я правильно понимаю, мы согласны. Я думаю, что (1) модульное тестирование помогает проверить проект, но проектирование - это отдельная деятельность, и (2) TDD не всегда является лучшим решением, потому что вам нужно организовать свои идеи перед тем, как начинать писать тесты, а TDD может замедлить работу. это.
Джорджио
1
Вкратце, модульные тесты могут показать недостатки во внутренней конструкции компонента. Интерфейс, предварительные и последующие условия должны быть известны заранее, иначе вы не сможете создать модульный тест. Поэтому проект компонента, что делает компонент, должен быть выполнен до написания модульного теста. Как это происходит - дизайн нижнего уровня, детальный дизайн или внутренний дизайн или как вы хотите это назвать - может иметь место после написания модульного теста.
Maarten Bodewes
9

Да, конечно.

Учти это:

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

Смотрите общую разницу ....

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

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

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

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

gbjbaanb
источник
7
Не совсем верно ... В интеграционных тестах можно иметь два компонента, которые оба содержат ошибки, но их ошибки устраняются в интеграционном тесте. Это не имеет большого значения, пока конечный пользователь не использует его таким образом, чтобы использовался только один из этих компонентов ...
Майкл Шоу
1
Покрытие кода! = Проверено - кроме ошибок, устраняющих друг друга, как насчет сценариев, о которых вы никогда не задумывались? Интеграционное тестирование отлично подходит для удачного тестирования пути, но я редко вижу адекватное интеграционное тестирование, когда дела идут плохо.
Майкл
4
@Ptolemy Я думаю, что редкость двух глючных компонентов, взаимно уничтожающих друг друга, намного ниже, чем 2 рабочих компонента, мешающих друг другу.
gbjbaanb
2
@ Майкл, тогда ты просто не приложил достаточных усилий к тестированию, я сказал, что хорошее интеграционное тестирование сложнее, поскольку тесты должны быть гораздо более подробными. Вы можете предоставить неверные данные в интеграционном тесте так же легко, как в модульном тесте. Интеграционное тестирование! = Счастливый путь. Речь идет об использовании как можно большего количества кода, поэтому существуют инструменты покрытия кода, которые показывают, сколько кода было выполнено.
gbjbaanb
1
@Michael Когда я правильно использую такие инструменты, как Cucumber или SpecFlow, я могу создавать интеграционные тесты, которые также тестируют исключения и экстремальные ситуации так же быстро, как и модульные тесты. Но я согласен, что если у одного класса слишком много перестановок, я предпочитаю написать модульный тест для этого класса. Но это будет реже, чем наличие классов с несколькими путями.
Иггдрасиль
2

Я думаю, что это ужасная идея.

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

Нет, вы обычно должны писать больше юнит-тестов, если у вас нет нечетного приложения с 90% пользовательским интерфейсом или чего-то еще, что неудобно для юнит-теста. Боль, с которой вы сталкиваетесь, связана не с юнит-тестами, а с разработкой тестов. Как правило, вы должны тратить только 1/3 своего времени на большинство написания тестов. В конце концов, они здесь, чтобы служить вам, а не наоборот.

Telastyn
источник
2
Основная претензия, которую я слышу от TDD, заключается в том, что она нарушает естественный процесс разработки и обеспечивает защитное программирование с самого начала. Если программисту уже не хватает времени, он, вероятно, захочет просто вырезать код и отшлифовать его позже. Конечно, соблюдение произвольного срока с ошибочным кодом является ложной экономией.
Робби Ди
2
Действительно, тем более, что «отполируй позже», кажется, никогда не происходит - каждый мой обзор, когда разработчик подталкивает «нужно выйти, мы сделаем это позже», когда мы все знаем, этого не произойдет - технический долг = обанкротившиеся застройщики на мой взгляд.
Майкл
3
Ответ имеет смысл для меня, не знаю, почему у него так много минусов. Процитируем Майка Кона: «Модульное тестирование должно быть основой стратегии автоматизации надежного тестирования и, как таковое, представляет собой большую часть пирамиды. Автоматизированные модульные тесты замечательны, потому что они дают конкретные данные программисту - есть ошибка, и она включена строка 47" mountaingoatsoftware.com/blog/...
guillaume31
4
@ guillaume31 То, что один парень сказал однажды, что они хорошие, еще не значит, что они хорошие. По моему опыту, в модульных тестах ошибки НЕ обнаруживаются, потому что они были изменены в соответствии с новым требованием. Также большинство ошибок - ошибки интеграции. Поэтому я обнаружил большинство из них с помощью интеграционных тестов.
Иггдрасиль
2
@Yggdrasil Я также мог бы процитировать Мартина Фаулера: «Если вы получаете сбой в тесте высокого уровня, вы не только имеете ошибку в своем функциональном коде, но и отсутствующий модульный тест». martinfowler.com/bliki/TestPyramid.html В любом случае, если у вас работают только интеграционные тесты, тогда все в порядке. Мой опыт показывает, что, при необходимости, они медленнее, доставляют менее точные сообщения об ошибках и менее маневренны (более комбинаторны), чем модульные тесты. Кроме того, я склонен меньше ориентироваться на будущее, когда пишу интеграционные тесты - рассуждая о заранее (ошибочных?) Задуманных сценариях, а не о правильности объекта как такового.
guillaume31
2

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

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

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

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

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

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

Робби Ди
источник
1
Я пишу свои тесты заранее и пишу достаточно тестов, чтобы покрыть большинство ошибок. Что касается ошибок, которые появляются позже. Мне кажется, что требование изменилось или в игру вступило новое требование. Затем нужно изменить интеграционные / поведенческие тесты. Если тогда в старых требованиях будет показана ошибка, мой тест на это покажет. Что касается автоматизации. все мои тесты выполняются постоянно.
Иггдрасиль
Пример, о котором я думал в последнем абзаце, где, скажем, библиотека использовалась исключительно одним приложением, но тогда было бизнес-требование сделать эту библиотеку общего назначения. В этом случае может быть лучше провести хотя бы некоторое модульное тестирование, чем писать новые тесты интеграции / поведения для каждой системы, которую вы подключаете к библиотеке.
Робби Ди
2
Автоматизированное тестирование и модульное тестирование являются полностью ортогональными. Любой уважающий себя проект будет иметь автоматизированную интеграцию и функциональное тестирование. Конечно, вы не часто видите ручные модульные тесты, но они могут существовать (в основном, ручные модульные тесты - это утилита для тестирования некоторых специфических функций).
Ян Худек
Ну, действительно. Был процветающий рынок для автоматизированных сторонних инструментов, которые существуют за пределами сферы разработки в течение значительного времени.
Робби Ди
1

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

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

Конечно, юнит-тесты также становятся медленнее, но они все же более чем на порядок быстрее. Например, в моем предыдущем проекте (c ++, около 600 kLOC, 4000 модульных тестов и 200 интеграционных тестов) потребовалось около одной минуты, чтобы выполнить все, и более 15, чтобы выполнить интеграционные тесты. Для создания и выполнения модульных тестов для изменяемой детали в среднем потребуется менее 30 секунд. Когда вы можете сделать это так быстро, вы захотите делать это все время.

Просто чтобы прояснить: я не говорю не добавлять интеграционные и приемочные тесты, но похоже, что вы сделали TDD / BDD неправильно.

Модульные испытания также хороши для дизайна.

Да, проектирование с учетом тестируемости сделает проект лучше.

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

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

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


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

BЈовић
источник
1
Вы смотрели на мой пример? Это происходило все время. Дело в том, что, когда я реализую новую функцию, я изменяю / добавляю модульный тест, чтобы они тестировали новую функцию - поэтому ни один модульный тест не будет прерван. В большинстве случаев у меня есть побочные эффекты от изменений, которые не обнаруживаются в модульных тестах, - потому что среда имитируется. По моему опыту из-за этого ни один модульный тест никогда не говорил мне, что я сломал существующую функцию. Это всегда были интеграционные и приемочные тесты, которые показали мне мои ошибки.
Иггдрасиль
Что касается времени исполнения. С растущим применением у меня в основном растет количество изолированных компонентов. Если нет, я сделал что-то не так. Когда я реализую новую функцию, это в основном только в ограниченном количестве компонентов. Я пишу один или несколько приемочных тестов в рамках всего приложения - которые могут быть медленными. Кроме того, я пишу те же тесты с точки зрения компонентов - эти тесты бывают быстрыми, потому что компоненты обычно бывают быстрыми. Я могу выполнять тесты компонентов постоянно, потому что они достаточно быстрые.
Иггдрасиль
@Yggdrasil Как я уже сказал, юнит-тесты не все мощные, но они, как правило, первый уровень тестирования, так как они самые быстрые. Другие тесты также полезны и должны быть объединены.
BЈовић
1
То, что они работают быстрее, не означает, что их следует использовать только из-за этого или потому, что их часто пишут. Как я уже сказал, мои юнит-тесты не ломаются - поэтому они не имеют для меня никакой ценности.
Иггдрасиль
1
Вопрос был в том, какое значение я получаю от юнит-теста, когда они не ломаются? Зачем их писать, когда мне всегда нужно адаптировать их к новым требованиям? Единственное значение из них я вижу для алгоритмов и других классов с высокой перестановкой. Но это меньше, чем компоненты и приемочные испытания.
Иггдрасиль