Как вы юнит тест юнит тест?

Я смотрел веб-трансляции Роба Коннериса в приложении 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, так как мне до сих пор трудно принять его как полезное. Я тоже хочу, потому что я хочу оставаться прогрессивным, но это просто не имеет смысла для меня.

РЕДАКТИРОВАТЬ: пара людей постоянно упоминают, что тестирование помогает реализовать спецификации. По моему опыту, спецификация также была неправильной, чаще всего, но, возможно, я обречен работать в организации, где спецификации пишутся людьми, которые не должны писать спецификации.

17 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[РЕДАКТИРОВАТЬ]

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

Как проверить тест? Мутационное тестирование - это ценная техника, которую я лично использовал для получения удивительно хорошего эффекта. Прочитайте связанную статью для получения более подробной информации и ссылок на еще более академические ссылки, но в целом она "проверяет ваши тесты", изменяя ваш исходный код (например, изменяя "x += 1" на "x -= 1"), а затем повторный запуск ваших тестов, гарантирующий, что хотя бы один тест не пройден. Любые мутации, которые не вызывают сбой теста, помечаются для последующего изучения.

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

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

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

Assert.IsEqual(p.DiscountPrice,90);

Нет причин, по которым тест развивается в этом направлении - или я что-то упускаю в ваших рассуждениях. Когда цена равна 100, а скидка 20, цена со скидкой 80. Это как инвариант.

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


Красный - Зеленый - Рефакторинг - это запоминание сути процесса TDD.

Красный относится к красной полосе JUnit, если тесты не пройдены.

Зеленый - это цвет индикатора выполнения JUnit, когда все тесты пройдены.

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


Теперь рассмотрим вашу мысль о "3-4 слоях над кодом", это верно для традиционного (похожего на водопад) процесса, а не тогда, когда процесс разработки является гибким. А Agile - это мир, откуда приходит TDD; TDD является краеугольным камнем программирования eXtreme.

Agile - это прямое общение, а не брошенные через стену документы с требованиями.

Хотя я все за модульное тестирование, иногда я задаюсь вопросом, действительно ли такая форма разработки для тестирования действительно полезна...

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

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

...[I] В реальном процессе у вас есть 3-4 уровня над вашим кодом (бизнес-запрос, документ требований, документ архитектуры), где фактическое определенное бизнес-правило (цена со скидкой - цена - скидка) может быть неверно определено. Если это так, то ваш модульный тест ничего не значит для вас.

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

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

Кроме того, ваш модульный тест является еще одной точкой сбоя...

Успешный тест - это тест, который демонстрирует сбой в системе. Неудачный тест предупредит вас об ошибке в логике теста или в логике вашей системы. Цель ваших тестов - сломать ваш код или доказать, что один сценарий работает.

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

Что мы здесь получаем?

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

Говоря о Перьях, есть два замечательных ресурса, на которые следует обратить внимание:

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

[I] Если дисконтная цена неверна, команда тестирования все равно найдет проблему, как модульное тестирование спасло какую-либо работу?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Я понимаю вашу точку зрения, но она явно завышена.

Ваш аргумент в основном: тесты вводят неудачу. Поэтому тесты плохие / пустая трата времени.

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

TDD предполагает: больше тестов = меньше ошибок.

Тесты чаще выявляют точки отказа, чем вводят их.

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

Обычно это легче увидеть с помощью чего-то большего, чем тривиальный пример... и с тривиальными примерами, ну, если вы как-то испортите модульный тест, то человек, который его проверяет, поймает ошибку в тесте или ошибку в коде, или и то и другое. (Они пересматриваются, верно?) Как указывает tvanfosson, модульное тестирование - это только одна часть плана SQA.

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

Вы должны использовать правильную парадигму при написании тестов.

  1. Начните с первого написания ваших тестов.
  2. Убедитесь, что они не могут начать с.
  3. Заставь их пройти.
  4. Проверка кода перед проверкой кода (убедитесь, что тесты проверены.)

Вы не всегда можете быть уверены, но они улучшают общие тесты.

Еще больше автоматизации может помочь здесь! Да, написание юнит-тестов может быть очень трудоемким, поэтому воспользуйтесь некоторыми инструментами. Посмотрите на что-то вроде Pex от Microsoft, если вы используете.Net. Он автоматически создаст для вас наборы модульных тестов, изучив ваш код. Он придет с тестами, которые дают хорошее покрытие, пытаясь охватить все пути через ваш код.

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

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

Я немного подумал о том, как ответить на этот вопрос, и хотел бы провести параллель с научным методом. ИМО, вы могли бы перефразировать этот вопрос: "Как вы экспериментируете с экспериментом?"

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

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

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

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

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

Другие вопросы по тегам