Цель модульного тестирования и TDD: найти / минимизировать ошибки или улучшить дизайн? - PullRequest
6 голосов
/ 09 июля 2011

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

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

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

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

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

Заранее спасибо.

Ответы [ 7 ]

5 голосов
/ 10 июля 2011

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

Модульное тестирование должно проверять некоторый путь выполнения в очень маленьком модуле. Этот модуль обычно является публичным или внутренним методом, доступным для вашего объекта. Сам метод все еще может использовать много других защищенных или частных методов из того же экземпляра объекта. Вы можете иметь один метод и несколько модульных тестов для этого метода, чтобы проверить разные пути выполнения. (Под путем выполнения я имел в виду что-то, контролируемое if, switch и т. Д.). Написание модульных тестов таким образом подтвердит, что ваш код действительно выполняет то, что вы ожидаете. Это может быть особенно важно в некоторых угловых случаях, когда вы ожидаете выбросить исключение в некоторых редких сценариях и т. Д. Вы также можете проверить поведение метода, если передаете разные параметры - например, null вместо экземпляра объекта, отрицательное значение для целого числа, используемого для индексация и т. д. Это особенно полезно для публичного API.

Теперь предположим, что ваш протестированный метод также использует экземпляры других классов. Как с этим бороться? Стоит ли вам проверять свой единственный метод и верить, что класс работает? Что если класс еще не реализован? Что если в классе есть какая-то сложная логика внутри? Вы должны проверить эти пути выполнения также на вашем текущем методе? Есть два подхода для решения этой проблемы:

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

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

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

Как это согласуется с процессом разработки ПО? Худшая часть процесса разработки - это стабилизация и исправление ошибок, потому что эту часть очень трудно оценить. Чтобы оценить, сколько времени занимает исправление ошибки, вы должны знать, что является причиной ошибки. Но это расследование не может быть оценено. Вы можете исправить ошибку, которая займет один час, но вы потратите две недели на отладку приложения и поиск этой ошибки. При использовании хорошего покрытия кода, скорее всего, вы найдете такую ​​ошибку на ранних стадиях разработки.

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

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

4 голосов
/ 10 июля 2011

Это ложный выбор.«Найти / минимизировать ошибки» ИЛИ улучшить дизайн. В частности,

TDD (и в отличие от «просто» модульного тестирования) - это улучшение дизайна.* А когда ваш дизайн лучше, каковы последствия?

  • Ваш код легче читать
  • Ваш код легче понять
  • Ваш код легчедля проверки
  • Ваш код легче использовать повторно
  • Ваш код легче отлаживать
  • Ваш код содержит меньше ошибок в первую очередь

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

2 голосов
/ 10 июля 2011

Я думаю, что ответ на ваш вопрос: оба.

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

Например: UI.Когда вы начнете писать тесты, вы увидите, что эти Бого-Формы невозможно протестировать, поэтому вы разделяете логику за экранами для докладчика / контроллера и получаете MVP / MVC / что угодно.

ИмеяКонцепция модульного тестирования класса и зависимостей позволяет вам перейти к принципу единой ответственности.В каждом из принципов SOLID есть смысл.

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

2 голосов
/ 09 июля 2011

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

Я обычно проектирую API, используя комбинацию нормальных шаблонов проектирования (сверху вниз) и разработки, управляемой тестированием (TDD; снизу вверх), чтобы убедиться, что у меня естьнадежный API как с точки зрения передового опыта, так и с точки зрения фактического использования.Тесты сосредоточены как на основных сценариях использования API, так и на полноте API и его поведении, поэтому они являются основными тестами «черного ящика».Часто последовательность разработки:

  • основной API, основанный на шаблонах проектирования и "интуиции"
  • тесты TDD для основных вариантов использования в соответствии с высокоуровневой спецификацией для API- первичный, чтобы убедиться, что API является «естественным» и простым в использовании
  • конкретизировал API и поведение
  • все необходимые тестовые случаи для обеспечения полноты и правильного поведения

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

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

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

1 голос
/ 12 июля 2011

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

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

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

0 голосов
/ 10 июля 2011

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

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

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

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

0 голосов
/ 10 июля 2011

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

...