Я смотрел веб-трансляции Роба Коннериса в приложении MVCStoreFront и заметил, что он тестировал даже самые обыденные вещи, например:
public Decimal DiscountPrice
{
get
{
return this.Price - this.Discount;
}
}
Был бы такой тест:
[TestMethod]
public void Test_DiscountPrice
{
Product p = new Product();
p.Price = 100;
p.Discount = 20;
Assert.IsEqual(p.DiscountPrice,80);
}
Хотя я полностью сторонник модульного тестирования, я иногда задаюсь вопросом, действительно ли полезна эта форма первой разработки теста, например, в реальном процессе у вас есть 3-4 уровня над кодом (бизнес-запрос, документ требований, документ архитектуры) , где фактическое определенное бизнес-правило (Цена со скидкой - Цена - Скидка) могло быть определено неверно.
Если это так, то ваш модульный тест ничего для вас не значит.
Кроме того, ваш модульный тест - еще одна точка отказа:
[TestMethod]
public void Test_DiscountPrice
{
Product p = new Product();
p.Price = 100;
p.Discount = 20;
Assert.IsEqual(p.DiscountPrice,90);
}
Теперь тест ошибочен. Очевидно, что в простом тесте это не проблема, но предположим, что мы тестировали сложное бизнес-правило. Что мы здесь получаем?
Перенесемся на два года в жизнь приложения, когда разработчики сопровождения поддерживают его. Теперь бизнес меняет свое правило, и тест снова ломается, затем какой-то новичок-разработчик исправляет тест неправильно ... теперь у нас есть еще одна точка отказа.
Все, что я вижу, это больше возможных точек отказа, без реальной выгодной отдачи, если цена скидки неправильная, команда тестирования все равно найдет проблему, как модульное тестирование сохранило любую работу?
Что мне здесь не хватает? Пожалуйста, научите меня любить TDD, так как я пока не могу принять его как полезный. Я тоже хочу, потому что хочу оставаться прогрессивным, но для меня это просто не имеет смысла.
РЕДАКТИРОВАТЬ: пара людей постоянно упоминает, что тестирование помогает обеспечить соблюдение спецификации. По моему опыту, спецификации тоже чаще всего были неправильными, но, возможно, я обречен работать в организации, где спецификации написаны людьми, которым не следует писать спецификации.
источник
Ответы:
Во-первых, тестирование похоже на безопасность - вы никогда не можете быть уверены на 100%, что у вас есть это, но каждый уровень добавляет больше уверенности и основу для более легкого решения оставшихся проблем.
Во-вторых, вы можете разбить тесты на подпрограммы, которые затем можно будет протестировать. Когда у вас есть 20 подобных тестов, создание (протестированной) подпрограммы означает, что ваш основной тест - это 20 простых вызовов подпрограммы, что с большей вероятностью будет правильным.
В-третьих, некоторые утверждают, что TDD решает эту проблему. То есть, если вы просто напишете 20 тестов, и они пройдут, вы не совсем уверены, что они на самом деле что-то тестируют. Но если каждый тест, который вы написали изначально, не удался , а затем вы его исправили, вы гораздо более уверены, что он действительно тестирует ваш код. ИМХО, на это уходит больше времени, чем оно того стоит, но это процесс, который пытается решить вашу проблему.
источник
Неправильный тест вряд ли сломает ваш производственный код. По крайней мере, не хуже, чем вообще не пройти тест. Так что это не «точка отказа»: тесты не обязательно должны быть правильными, чтобы продукт действительно работал. Возможно, они должны быть правильными, прежде чем они будут признаны работающими, но процесс исправления любых неисправных тестов не подвергает опасности ваш код реализации.
Вы можете думать о тестах, даже о таких тривиальных, как о втором мнении относительно того, что должен делать код. Одно мнение - это тест, другое - реализация. Если они не согласны, то вы знаете, что у вас есть проблема, и присматриваетесь.
Это также полезно, если кто-то в будущем захочет реализовать тот же интерфейс с нуля. Им не нужно читать первую реализацию, чтобы знать, что означает скидка, а тесты действуют как недвусмысленная резервная копия любого письменного описания интерфейса, которое у вас может быть.
Тем не менее, вы теряете время. Если есть другие тесты, которые вы можете написать, сэкономив время, пропуская эти тривиальные тесты, возможно, они будут более ценными. На самом деле это зависит от вашей тестовой установки и характера приложения. Если для приложения важна Скидка, то вы все равно обнаружите ошибки в этом методе при функциональном тестировании. Все, что делает модульное тестирование, позволяет вам поймать их в момент тестирования этого модуля, когда местоположение ошибки станет очевидным, вместо того, чтобы ждать, пока приложение не будет интегрировано вместе, и местоположение ошибки может быть менее очевидным.
Кстати, лично я бы не стал использовать 100 в качестве цены в тестовом примере (точнее, если бы я это сделал, я бы добавил еще один тест с другой ценой). Причина в том, что кто-то в будущем может подумать, что Скидка должна быть процентной. Одна из целей таких тривиальных тестов - убедиться, что ошибки при чтении спецификации исправлены.
[Относительно редактирования: я думаю, что неверная спецификация неизбежно станет точкой отказа. Если вы не знаете, что приложение должно делать, скорее всего, оно этого не сделает. Но написание тестов, отражающих спецификацию, не усугубляет эту проблему, а просто не решает ее. Таким образом, вы не добавляете новые точки отказа, вы просто представляете существующие ошибки в коде, а не в
пустойдокументации.]источник
Модульное тестирование на самом деле не должно экономить работу, оно должно помогать вам находить и предотвращать ошибки. Это больше работы, но это правильная работа. Он думает о вашем коде на самом низком уровне детализации и пишет тестовые примеры, доказывающие, что он работает в ожидаемых условиях для заданного набора входных данных. Он изолирует переменные, поэтому вы можете сэкономить время , заглянув в нужное место, когда ошибка обнаруживается. Он сохраняет этот набор тестов, чтобы вы могли использовать их снова и снова, когда вам нужно будет внести изменения в будущем.
Я лично считаю, что большинство методологий не намного удаляются от разработки культового программного обеспечения , включая TDD, но вам не обязательно придерживаться строгой TDD, чтобы воспользоваться преимуществами модульного тестирования. Сохраните хорошие детали и выбросьте те, которые приносят мало пользы.
Наконец, ответ на ваш главный вопрос « Как вы проводите модульное тестирование модульного теста? » Заключается в том, что вам не нужно этого делать. Каждый модульный тест должен быть до безумия простым. Вызовите метод с конкретным входом и сравните его с ожидаемым результатом. Если спецификация метода изменится, вы можете ожидать, что некоторые модульные тесты для этого метода также должны измениться. Это одна из причин, по которой вы проводите модульное тестирование на таком низком уровне детализации, поэтому необходимо изменить только некоторые модульные тесты. Если вы обнаружите, что тесты для множества различных методов меняются при одном изменении требования, возможно, вы не проводите тестирование на достаточно высоком уровне детализации.
источник
Модульные тесты существуют для того, чтобы ваши модули (методы) выполняли то, что вы ожидаете. Написание теста сначала заставляет вас подумать о том, чего вы ожидаете, прежде чем писать код. Подумать, прежде чем действовать - всегда хорошая идея.
Модульные тесты должны отражать бизнес-правила. Конечно, в коде могут быть ошибки, но сначала написание теста позволяет вам написать его с точки зрения бизнес-правила до того, как будет написан какой-либо код. Я думаю, что написание теста после этого с большей вероятностью приведет к описанной вами ошибке, потому что вы знаете, как код реализует ее, и испытываете соблазн просто убедиться, что реализация правильная, а не то, что намерение правильное.
Кроме того, модульные тесты - это только одна форма - и самая низшая - из тестов, которые вам следует писать. Также должны быть написаны интеграционные и приемочные тесты, последние по возможности заказчиком, чтобы убедиться, что система работает так, как ожидается. Если вы обнаружите ошибки во время этого тестирования, вернитесь и напишите модульные тесты (которые не работают), чтобы проверить изменение функциональности, чтобы оно работало правильно, а затем измените свой код, чтобы тест прошел. Теперь у вас есть регрессионные тесты, фиксирующие исправления ваших ошибок.
[РЕДАКТИРОВАТЬ]
Еще одна вещь, которую я обнаружил при выполнении TDD. По умолчанию это почти заставляет хороший дизайн. Это связано с тем, что конструкции с сильной связью практически невозможно тестировать изолированно. Использование TDD не займет много времени, чтобы понять, что использование интерфейсов, инверсии управления и внедрения зависимостей - все шаблоны, которые улучшат ваш дизайн и уменьшат взаимосвязь, - действительно важны для тестируемого кода.
источник
Как проверить тест ? Мутационное тестирование - ценный метод, который я лично использовал с удивительно хорошим эффектом. Прочтите связанную статью для получения более подробной информации и ссылок на еще более академические ссылки, но в целом он «проверяет ваши тесты», изменяя исходный код (например, изменяя «x + = 1» на «x - = 1»), а затем повторный запуск ваших тестов, гарантируя, что хотя бы один тест не пройден. Любые мутации, не вызывающие сбоев теста, помечаются для дальнейшего исследования.
Вы будете удивлены, узнав, как вы можете получить 100% покрытие строк и веток с помощью набора тестов, которые выглядят исчерпывающими, и в то же время вы можете фундаментально изменить или даже закомментировать строку в своем источнике без каких-либо жалоб со стороны тестов. Часто это сводится к тому, что тестирование не с правильными входными данными, чтобы охватить все граничные случаи, иногда это более тонко, но во всех случаях я был впечатлен тем, как много из этого получилось.
источник
При применении разработки через тестирование (TDD) каждый начинает с провала теста. Этот шаг, который может показаться ненужным, на самом деле здесь, чтобы убедиться, что модульный тест что-то тестирует. В самом деле, если тест никогда не терпит неудачу, он не приносит никакой пользы и, что еще хуже, приводит к ложной уверенности, поскольку вы будете полагаться на положительный результат, который ничего не доказывает.
При строгом соблюдении этого процесса все «юниты» защищены сеткой безопасности, которую создают юнит-тесты, даже самые обычные.
Нет причин, по которым тест развивается в этом направлении - или я что-то упускаю из ваших рассуждений. Когда цена равна 100, а скидка 20, цена со скидкой равна 80. Это похоже на инвариант.
Теперь представьте, что ваше программное обеспечение должно поддерживать другой вид скидки, основанной на процентах; возможно, в зависимости от приобретенного объема ваш метод Product :: DiscountPrice () может стать более сложным. И возможно, что внесение этих изменений нарушит простое правило скидки, которое у нас было изначально. Затем вы увидите значение этого теста, который немедленно обнаружит регресс.
Красный - Зеленый - Рефакторинг - это напоминание о сути процесса TDD.
Красный относится к красной полосе JUnit, когда тест не проходит.
Зеленый - это цвет индикатора выполнения JUnit, когда все тесты пройдены.
Выполните рефакторинг в зеленом состоянии: удалите дублирование, улучшите читаемость.
Теперь, чтобы обратиться к вашей точке зрения о «3-4 уровнях над кодом», это верно для традиционного (похожего на водопад) процесса, а не для гибкого процесса разработки. А гибкость - это мир, из которого исходит TDD; TDD - краеугольный камень экстремального программирования .
Agile - это прямая коммуникация, а не бесконечные документы с требованиями.
источник
Такие небольшие, тривиальные тесты могут стать «канарейкой в угольной шахте» для вашей кодовой базы, предупреждая об опасности, пока не стало слишком поздно. Тривиальные тесты полезно держать под рукой, потому что они помогают вам правильно взаимодействовать.
Например, подумайте о тривиальном тесте, который проводится для проверки того, как использовать API, с которым вы не знакомы. Если этот тест имеет какое-либо отношение к тому, что вы делаете в коде, который использует API «по-настоящему», полезно сохранить этот тест. Когда API выпускает новую версию, и вам нужно выполнить обновление. Теперь у вас есть свои предположения о том, как вы ожидаете, что API будет вести себя, записанные в исполняемом формате, который вы можете использовать для выявления регрессий.
Если вы годами программировали без написания тестов, возможно, вам не сразу станет очевидным, что в этом есть какая-то ценность. Но если вы считаете, что лучший способ работы - это «выпускать раньше, выпускать часто» или «гибкость» в том смысле, что вам нужна возможность быстрого / непрерывного развертывания, то ваш тест определенно что-то значит. Единственный способ сделать это - узаконить каждое изменение, которое вы вносите в код, с помощью теста. Независимо от того, насколько небольшой тест, если у вас есть зеленый набор тестов, вы теоретически можете его развернуть. См. Также «непрерывное производство» и «бессрочное бета-тестирование».
Вам также не обязательно проходить тестирование, чтобы иметь такой образ мышления, но, как правило, это наиболее эффективный способ достичь желаемого. Когда вы выполняете TDD, вы замыкаетесь в небольшом двух-трехминутном цикле Red Green Refactor. Ни в коем случае вы не можете остановиться и уйти, и в ваших руках будет полный беспорядок, на отладку и сборку которого уйдет час.
Успешный тест - это тест, демонстрирующий сбой в системе. Неудачный тест предупредит вас об ошибке в логике теста или в логике вашей системы. Цель ваших тестов - взломать ваш код или доказать, что один сценарий работает.
Если вы пишете тесты после кода, вы рискуете написать «плохой» тест, потому что для того, чтобы убедиться, что ваш тест действительно работает, вам нужно увидеть, что он сломан и работает. Когда вы пишете тесты после кода, это означает, что вам нужно «вскрыть ловушку» и ввести ошибку в код, чтобы увидеть, что тест не прошел. Большинство разработчиков не только обеспокоены этим, но и утверждают, что это пустая трата времени.
В таком поступке определенно есть преимущества. Майкл Фезерс определяет «устаревший код» как «непроверенный код». Применяя этот подход, вы узакониваете каждое изменение, которое вносите в свою кодовую базу. Это более строго, чем отсутствие тестов, но когда дело доходит до поддержки большой базы кода, это окупается.
Говоря о Feathers, есть два замечательных ресурса, которые вы должны изучить по этому поводу:
Оба они объясняют, как использовать эти типы практик и дисциплин в проектах, которые не являются «гринфилдом». Они предоставляют методы написания тестов для тесно связанных компонентов, жестко привязанных зависимостей и вещей, над которыми вы не обязательно можете контролировать. Все дело в поиске "швов" и их проверке.
Подобные привычки похожи на вложение. Возврат не происходит немедленно; они накапливаются со временем. Альтернатива отсутствию тестирования - это, по сути, взять на себя ответственность за невозможность уловить регрессии, ввести код, не опасаясь ошибок интеграции, или принять дизайнерские решения. Прелесть в том, что вы узакониваете каждое изменение, внесенное в вашу кодовую базу.
Я смотрю на это как на профессиональную ответственность. Это идеал, к которому нужно стремиться. Но это очень сложно и утомительно. Если вам это небезразлично и вы чувствуете, что не должны создавать непроверенный код, вы сможете найти в себе силы и научиться хорошим привычкам тестирования. Одна вещь, которую я сейчас много делаю (как и другие), - это ограничиваю себя часом, чтобы написать код вообще без каких-либо тестов, а затем иметь дисциплину, чтобы выбросить его. Это может показаться расточительным, но на самом деле это не так. Не то чтобы это упражнение стоило компании материальных средств. Это помогло мне понять проблему и понять, как писать код таким образом, чтобы он был более качественным и тестируемым.
В конечном итоге я бы посоветовал вам, если у вас действительно нет желания преуспевать в этом, не делайте этого вообще. Плохие тесты, которые не обслуживаются, плохо работают и т. Д., Могут быть хуже, чем отсутствие тестов. Трудно научиться самостоятельно, и вам, вероятно, это не понравится, но будет практически невозможно научиться, если у вас нет желания делать это или вы не видите в этом достаточно ценности, чтобы оправдывают затраты времени.
Клавиатура разработчика - это место, где резина встречается с дорогой. Если спецификация неверна, и вы не поднимаете этот флаг, то весьма вероятно, что вас обвинят в этом. Или, по крайней мере, ваш код будет. Трудно придерживаться дисциплины и строгости при тестировании. Это совсем не просто. Это требует практики, много обучения и много ошибок. Но в конце концов это окупается. В быстро развивающемся, быстро меняющемся проекте это единственный способ спать по ночам, даже если это замедляет вас.
Еще одна вещь, о которой следует подумать, заключается в том, что методы, которые в основном аналогичны тестированию, доказали свою эффективность в прошлом: «чистая комната» и «проектирование по контракту» имеют тенденцию создавать одни и те же типы конструкций «метакода», которые тесты делают, и применяют их в разных точках. Ни один из этих методов не является серебряной пулей, и строгость в конечном итоге обойдется вам в объеме функций, которые вы можете предоставить с точки зрения времени выхода на рынок. Но дело не в этом. Речь идет о возможности поддерживать то, что вы делаете. И это очень важно для большинства проектов.
источник
Модульное тестирование очень похоже на ведение бухгалтерского учета по двойной записи. Вы формулируете одно и то же (бизнес-правило) двумя совершенно разными способами (как запрограммированные правила в вашем производственном коде и как простые репрезентативные примеры в ваших тестах). Очень маловероятно, что вы совершите одну и ту же ошибку в обоих, поэтому, если они оба согласны друг с другом, маловероятно, что вы ошиблись.
Как тестирование будет стоить затраченных усилий? По моему опыту, как минимум четырьмя способами, по крайней мере, при разработке через тестирование:
источник
Большинство модульных тестов, тестовых предположений. В этом случае цена со скидкой должна равняться цене за вычетом скидки. Если ваши предположения неверны, я уверен, что ваш код также неверен. А если вы допустите глупую ошибку, тест не пройдет, и вы ее исправите.
Если правила изменятся, тест не удастся, и это хорошо. Так что в этом случае вам также придется изменить тест.
Как правило, если тест не проходит сразу (и вы не используете предварительный дизайн теста), либо тест, либо код неверны (или и то, и другое, если у вас плохой день). Вы руководствуетесь здравым смыслом (и, возможно, спецификациями), чтобы исправить ошибочный код и повторно запустить тест.
Как сказал Джейсон, тестирование - это безопасность. И да, иногда из-за ошибочных тестов вводят лишнюю работу. Но в большинстве случаев они очень экономят время. (И у вас есть прекрасная возможность наказать парня, который срывает тест (речь идет о резиновой курице)).
источник
Проверяйте все, что можете. Даже тривиальные ошибки, такие как забывание перевести метры в футы, могут иметь очень дорогие побочные эффекты. Напишите тест, напишите код для его проверки, получите его, и двигайтесь дальше. Кто знает, в какой-то момент в будущем кто-то может изменить код скидки. Тест может обнаружить проблему.
источник
Я считаю, что модульные тесты и производственный код имеют симбиотические отношения. Проще говоря: одно проверяет другое. И оба тестируют разработчика.
источник
Помните, что стоимость исправления дефектов увеличивается (экспоненциально) по мере того, как дефекты переживают цикл разработки. Да, группа тестирования может выявить дефект, но (обычно) потребуется больше работы, чтобы изолировать и исправить дефект с этого момента, чем если бы модульный тест не удался, и будет легче ввести другие дефекты при его исправлении, если вы нет модульных тестов для запуска.
Обычно это легче увидеть на чем-то большем, чем на тривиальном примере ... и на тривиальных примерах, ну, если вы каким-то образом испортите модульный тест, человек, просматривающий его, поймает ошибку в тесте или ошибку в коде, или и то и другое. (Они пересматриваются, не так ли?) Как указывает Тванфоссон , модульное тестирование - это лишь часть плана SQA.
В некотором смысле модульные тесты - это страховка. Они не гарантируют, что вы обнаружите все дефекты, и иногда может показаться, что вы тратите на них много ресурсов, но когда они обнаружат дефекты, которые вы можете исправить, вы потратите намного меньше. чем если бы у вас вообще не было тестов и вам приходилось исправлять все дефекты в дальнейшем.
источник
Я понимаю вашу точку зрения, но она явно преувеличена.
Ваш аргумент в основном таков: тесты приводят к неудачам. Поэтому тесты плохие / пустая трата времени.
Хотя в некоторых случаях это может быть правдой, вряд ли большинство.
TDD предполагает: больше тестов = меньше отказов.
Тесты скорее выявляют точки отказа, чем знакомят с ними.
источник
Здесь может помочь еще большая автоматизация! Да, написание модульных тестов может потребовать много работы, поэтому воспользуйтесь некоторыми инструментами, которые помогут вам. Взгляните на что-то вроде Pex от Microsoft, если вы используете .Net. Он автоматически создает для вас наборы модульных тестов, исследуя ваш код. Он предложит тесты, которые дадут хорошее покрытие, пытаясь охватить все пути через ваш код.
Конечно, просто взглянув на ваш код, он не может узнать, что вы на самом деле пытались сделать, поэтому он не знает, правильно это или нет. Но он сгенерирует для вас интересные тестовые примеры, а затем вы сможете их изучить и посмотреть, ведет ли он себя так, как вы ожидаете.
Если вы затем пойдете дальше и напишете параметризованные модульные тесты (на самом деле вы можете думать об этом как о контрактах), он сгенерирует из них конкретные тестовые примеры, и на этот раз он сможет узнать, что-то не так, потому что ваши утверждения в ваших тестах потерпят неудачу.
источник
Я немного подумал о том, как правильно ответить на этот вопрос, и хотел бы провести параллель с научным методом. ИМО, вы могли бы перефразировать этот вопрос: «Как вы проводите эксперимент?»
Эксперименты подтверждают эмпирические предположения (гипотезы) о физической вселенной. Модульные тесты проверяют предположения о состоянии или поведении вызываемого кода. Мы можем говорить о достоверности эксперимента, но это потому, что мы знаем из множества других экспериментов, что что-то не подходит. Он не имеет как конвергентный справедливость и эмпирические данные . Мы не создать новый эксперимент , чтобы проверить или проверить обоснованность эксперимента , но мы можем разработать совершенно новый эксперимент .
Так как эксперименты , мы не будем описывать действительность модульного тестирования на основе проходит ли он или нет тест блок сам. Наряду с другими модульными тестами, он описывает наши предположения о тестируемой системе. Также, как и в случае с экспериментами, мы стараемся максимально упростить то, что мы тестируем. «Как можно проще, но не проще».
В отличие от экспериментов , у нас есть хитрость в рукаве, чтобы убедиться, что наши тесты действительны, кроме конвергентной достоверности. Мы можем грамотно ввести ошибку, которая, как мы знаем, должна быть обнаружена тестом, и посмотреть, действительно ли тест завершился неудачно. (Если бы мы только могли сделать это в реальном мире, мы бы гораздо меньше зависели от этой конвергентной валидности!) Более эффективный способ сделать это - посмотреть, как ваш тест завершится неудачно, прежде чем реализовывать его (красный шаг в Red, Green, Refactor ).
источник
При написании тестов нужно использовать правильную парадигму.
Не всегда можно быть уверенным, но они улучшают общие тесты.
источник
Даже если вы не протестируете свой код, он обязательно будет протестирован вашими пользователями в производственной среде. Пользователи очень изобретательны, пытаясь разрушить ваш софт и находить даже некритические ошибки.
Исправление ошибок в производственной среде намного дороже, чем решение проблем на этапе разработки. Как побочный эффект, вы потеряете доход из-за оттока клиентов. Вы можете рассчитывать на 11 потерянных или не приобретенных клиентов на 1 рассерженного покупателя.
источник