Насколько глубоки ваши юнит-тесты?

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

Мой вопрос в том, на каком уровне детализации вы пишете вам модульные тесты?

.. и есть ли тест слишком много?

Ответ 1

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

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

Ответ 2

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

  • Исправлена ​​ошибка:
  • Ошибка не появится снова.

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

Ответ 3

Все должно быть сделано так просто, как возможно, но не проще. - А. Эйнштейн

Одно из самых непонятных вещей о TDD - это первое слово в нем. Контрольная работа. Вот почему пришел BDD. Потому что люди действительно не понимали, что первый D был важным, а именно Driven. Мы все склонны немного думать о тестировании, и немного о том, как управлять дизайном. И я предполагаю, что это неопределенный ответ на ваш вопрос, но вам, вероятно, следует подумать о том, как управлять своим кодом, а не тем, что вы на самом деле тестируете; это инструмент Coverage, который может вам помочь. Дизайн - довольно большая и более проблематичная проблема.

Ответ 4

Тем, кто предлагает тестирование "все": понимают, что для "полного тестирования" метод типа int square(int x) требует около 4 миллиардов тестовых случаев на обычных языках и типичных средах.

На самом деле это еще хуже: метод void setX(int newX) также обязан не изменять значения каких-либо других членов, кроме x - проверяете ли вы, что obj.y, obj.z и т.д. все остаются неизменными после вызова obj.setX(42);?

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

Часть проблемы с пропуском простых тестов теперь заключается в том, что в будущем рефакторинг может сделать это простое свойство очень сложным с большим количеством логики. Я думаю, что лучшая идея заключается в том, что вы можете использовать тесты для проверки требований к модулю. Если при передаче X вы должны получить Y обратно, то это то, что вы хотите проверить. Затем, когда вы позже измените код, вы можете проверить, что X дает вам Y, и вы можете добавить тест для A, который дает вам B, когда это требование будет добавлено позже.

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

Ответ 8

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

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

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

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

Ответ 9

Test Driven Development означает, что вы прекращаете кодирование, когда проходят все ваши тесты.

Если у вас нет теста на свойство, то зачем его реализовать? Если вы не тестируете/не определяете ожидаемое поведение в случае "незаконного" назначения, что должно делать свойство?

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

Чтобы упростить это тестирование, я создал простой NUnit TestFixture, который предоставляет точки расширения для установки/получения значения и принимает списки допустимых и недопустимых значений и имеет один тест, чтобы проверить, работает ли свойство правильно. Тестирование одного свойства может выглядеть так:

[TestFixture]
public class Test_MyObject_SomeProperty : PropertyTest<int>
{

    private MyObject obj = null;

    public override void SetUp() { obj = new MyObject(); }
    public override void TearDown() { obj = null; }

    public override int Get() { return obj.SomeProperty; }
    public override Set(int value) { obj.SomeProperty = value; }

    public override IEnumerable<int> SomeValidValues() { return new List() { 1,3,5,7 }; }
    public override IEnumerable<int> SomeInvalidValues() { return new List() { 2,4,6 }; }

}

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

P.S.: Вероятно, PropertyTest также должен иметь способ проверить, что свойства другие на объекте не изменились. Вернитесь к чертежной доске.

Ответ 10

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

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

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

Ответ 11

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

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

Ответ 12

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

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

Ответ 13

Я думаю, вы должны проверить все в своем "ядре" вашей бизнес-логики. Getter ans Setter тоже, потому что они могут принимать отрицательное значение или нулевое значение, которое вы, возможно, не хотите принимать. Если у вас есть время (всегда зависит от вашего босса), хорошо протестировать другую бизнес-логику и весь контроллер, который вызывает этот объект (вы медленно переходите от unit test к тесту интеграции).

Ответ 14

Я не тестирую простые методы setter/getter, которые не имеют побочных эффектов. Но я unit test каждый другой общедоступный метод. Я пытаюсь создать тесты для всех граничных условий в своих альгортимах и проверить охват моих модульных тестов.

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

Вы не сказали, почему архитектура вы тоже кодируете. Но для Java я использую Maven 2, JUnit, DbUnit, Cobertura, и EasyMock.

Ответ 15

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

Когда вам нужно проверить, действительно ли ваш тривиальный получатель возвращает правильное значение, это связано с тем, что вы можете смешивать имя получателя и имя переменной-члена. Введите 'attr_reader: name' of ruby, и этого больше не может быть. Просто невозможно в java.

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

Ответ 16

Проверьте исходный код, который вас беспокоит.

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

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

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

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

Ответ 17

Этот ответ больше подходит для определения того, сколько модульных тестов нужно использовать для данного метода, который, как вы знаете, хотите unit test из-за его важности/важности. Используя метод Базового Path Testing McCabe, вы можете сделать следующее, чтобы количественно получить более уверенную уверенность в кодовом покрытии, чем простое "покрытие оператора" или "охват веток":

  • Определите значение Cyclomatic Complexity вашего метода, который вы хотите использовать unit test (например, Visual Studio 2010 Ultimate может рассчитать это для вас с помощью инструментов статического анализа, в противном случае вы можете вычислить его вручную методом flowgraph - http://users.csc.calpoly.edu/~jdalbey/206/Lectures/BasisPathTutorial/index.html)
  • Перечислите базовый набор независимых путей, которые протекают через ваш метод - см. ссылку выше для примера flowgraph
  • Подготовьте модульные тесты для каждого независимого базового пути, определенного на шаге 2