Pull to refresh

Comments 32

Мы, например, пишем тесты на кейсы, а не на функции — становится понятно как и зачем.
Одно не отменяет другое.
Даже для return «word» нужны тесты, т. к. сегодня это return «word», а завтра return net.get.word().
Я и не говорю, что отменяет. Я говорю что так проще писать, ибо есть четкая формулировка и, вследствие этого, большая мотивация.
исходя из опыта — мы не ломаем то, что уже работает. Ну вот как-то так повелось

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

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

Ну да. Так если с пользовательским участием, то мне и тест не нужен, я просто запущу парсинг с --verbose --fake. А хотелось бы, чтобы запустил тест и ответ был в виде ok/fail.
И ни разу не было, что переделывание одной части кода вызывало проблемы в другой? Скажем функция get_word используется в двух местах, но в одном крайне редко, а в другом постоянно, и вот решили изменить её там, где постоянно, но про второе место вообще забыли, лишь через полгода какой-то разгневанный пользователь прислал баг, что раньше было одно слово, а теперь оно изменилось на другое. Причем он будет утверждать, что это после последнего обновления, хотя их уже сто прошло с тех пор.

Вы не поняли. Вы пишите 7 строк кода теста, запускаете тестовый фреймворк, и он открывает браузер, вводит адрес, запускает парсинг и проверяет реальный результат с ожидаемым, после чего пишет ok/fail От вас требуется написать 7 строк, подготовить эталонные результаты и не забывать запускать тест.В конце-концов, можно даже код не писать и специально результаты не готовить. Реализовали функцию, сняли снэпшот с системы, включили запись действий пользователя, ручками ввели что обычно пользователь вводит, запустили, дождались результата, зафиксировали запись, сняли второй снэпшот. В следующий раз просто системе подается первый сныпшот, воспроизводится запись, и сравнивается её состояние со вторым снэпшотом.
Ну было, конечно. Именно из-за этого я и написал заметку. Хочу тестировать, но не получается.
Да, видимо не понимаю. Не представляю, как это сделать в 7 строк.
Видимо, надо начинать писать тесты. Тогда может быть пойму.
Главное различать различные виды тестирования и применять для них соответствующие фреймворки и другие инструменты. Многие разочаровываются из-за того, например, что пытаются фреймворком для юнит-тестирования провести функциональный тест. Там, действительно, несколькими строками не обойдешься.
Если ошибка один раз появилась, то мы просто исправляем функцию, зачем нам писать тест для этой ошибки? Её больше не будет!

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

Но нет смысла писать маленькие тесты для каждой функции. Они итак работают хорошо.

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

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

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

Да, конечно, всё так. Но я проверяю разными способами, в отладчике, в общем цикле работы, и т.д. Можно написать по 20 тестов для каждой функции, но жалко время, если они дальше не особо-то и нужны.
необоснованно обобщаете свой опыт на всю веб разработку

Ни в коем случае. Я говорю исключительно про нашу рабочую ситуацию. У нас нет огромных проектов. Видимо поэтому есть недопонимание.
Имхо, в большинстве случаев, когда говорят о тестировании кода, говорят о юнит-тестировании. Например, в той статье habrahabr.ru/company/infopulse/blog/177581/, откуда вы, похоже, взяли первый пример про helloWorld.
Есть юнит-тесты, а есть функциональные тесты. Не надо их путать. Юнит-тест тестирует не ситуацию, которую можно обработать внутри функции (bmp внутри jpg), юнит-тесты тестируют сами функции — передаём параметр и сравниваем результат с ожидаемым. Если у вас такие функции, которые нужно проверять десятками тестов, то, возможно, эти функции перегружены, и стоит разбить такие функции на несколько отдельных попроще, с 1-2 параметрами. Их и тестировать будет проще.
Юнит-тесты предназначены для тестирования конкретного участка программы — не взаимодействия нескольких юнитов, а одного юнита — функции/метода, как мельчайшего элемента программы, не путать с модулями (например, в Паскале unit — модуль программы, отдельный файл со множеством классов/функций).
Тестируют взаимодействие разных подсистем программы друг с другом функциональные тесты, а они пишутся совсем по другому, чем юнит-тесты.
Отдельно если говорить про TDD — там у тестов появляются ещё несколько важнейших назначений, помимо собственно проверки работоспособности кода.
Я имел ввиду, что если ошибка была в том, что мы скачиваем изображение, его расширение jpeg, мы считаем, что это jpeg, а оно bmp внутри. То для этого случая можно написать тест. Но зачем, если в функции мы уже всё равно проверяем по содержимому?

Ну как «зачем»? Чтобы проверить в интерфейсе пользователя, который загружает изображение, что всё работает как надо.
Если при загрузке bmp вместо jpg у вас должно показываться предупреждение пользователю: пишем функциональный тест, который это проверяет.

А то, что у вас сейчас проверяется внутри функции — это можно протестировать уже юнит-тестами.

В своё время я долго не мог въехать в принципиальную разницу: юнит-тесты — это тесты разработчиков. Эти тесты подтверждают: поведение кода не изменилось, все тестируемые функции-методы работают именно так, как и работали (это утверждение может быть неверным, если при написании теста вы прошляпили что-нибудь. К примеру — не добавили проверку деления на ноль в методе divide(a, b) ).

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

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

Почувствуйте разницу:
1. проверка того, что пользователь может зайти на страницу логина, ввести работающие логин и пароль, и после этого он залогинится в систему.
2. проверка того, что если методу Auth::login($user, $pass) отдать работающие логин и пароль, в сессии появятся соответствущие данные.

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

Опять же — для функционального тестирования тоже есть инструменты, которые позволяют описать последовательность действий и ожидаемый результат. А уже инструменты тестирования сами разберуться с запуском браузера и запрограммированными в тесте действиями.

Опять же — экономия телодвижений и времени.

Но всё это требует «въезжания» в своеобразную философию тестирования. Более продвинутый коллега, которого я в своё время расспрашивал о плюсах и минусах юнит-тестов, сразу предупредил: «В среднем на переосмысление своей методики работы и переход к TDD уходит около года. Большая часть времени уходит на понимание того, что тебе это действительно нужно. И несомненно есть задачи, при которых следование методике TDD не оправдывает себя. Но чтобы это утверждать, ты должен владеть TDD и понимать, в каких случаях тебе это не нужно».

Как-то так. :)
Хорошее и понятное разделение видов тестов.

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

Например, частый случай для нас: мы написали часть приложения, клиент посмотрел его и понял, что ему надо не это. Он даже не спорит, что надо доплатить за переделку, но просит сделать по-быстрее, т.к. у него уже есть бизнес план и т.д. И мы переделываем эту часть. Причем сильно. Если было бы написано скажем 50 тестов, то 45 из них стали бы не нужны. Не просто красными, а именно не нужны. Но надо написать ещё 60 тестов на новую функциональность, которые опять окажутся не нужны по той же причине.

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

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

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

Если у вас «ядро» системы не меняется никогда, а вся работа идёт только на уровне бизнес-логики, которая вполне может сразу после написания отправится в корзину — да, вероятно вам тесты не нужны. :)

Но если вы можете выделить код, который живёт достаточно долго, с которым вы регулярно работаете, и в котором в ходе жизненного цикла происходят изменения — тесты могут весьма облегчить задачу. А если вы работаете над «продуктом» как единицей программного обеспечения, то тут они будут весьма и весьма полезны.
Из текста совершенно очевидно, что они не пишут тестов в принципе… Они лишь только хотят писать тесты.
UFO just landed and posted this here
UFO just landed and posted this here
Но нет смысла писать маленькие тесты для каждой функции. Они итак работают хорошо.

В динамическом языке много смысла, потому что очень часто ломается то, что работает.
По опыту, приходится покрывать ЮНИТ-тестами только две вещи:
* Шаблоны C++. Некоторые компиляторы пропускают некомпилируемый шаблон, если он не используется.
* Низкоуровневые библиотечные функции. Просто потому, что используются везде и любой отказ расползётся на всю программу.

И второй тип автотестов, которые мы используем,— «дымовые». Разрабатываются небольшими (все вместе выполняются меньше минуты), каждый из них покрывает небольшой кусок функциональности. Если тест не проходит, смотрим вручную, что творится: может, перед нами новый правильный ответ, а может — надо где-то править.
Покажите, если не сложно, как у Вас все устроено? Т.е. как тесты к проекту зацеплены? как они запускаются? Есть ли они в CI?
Поздравляю, вы открываете для себя интеграционное тестирование.
А еще узнаете простую истину: качество стоит для вас — времени, а для заказчика — денег.
К чему весь этот пост?
Тестирование — это часть разработки. Закладывайте её сразу в цену проекта (стоимости работы программиста), а не как отдельную услугу.
Если ошибка один раз появилась, то мы просто исправляем функцию, зачем нам писать тест для этой ошибки? Её больше не будет!


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

Частично с автором согласен, но с такими ограничениями:
1) толковые коментарии классам/функциям убирают 90% потенциальных ошибок при будущем рефакторинге, особенно если эти комменты обновляются каждый раз при изменении функции, особенно если пишут в этой функции, почему сделано именно так, а не так, как кажется логичнее. Мне кажется, что по соотношению простота, скорость, эффективность и цена, — это самый оптимальный способ!
2) Тесты не очень нужны, если есть нормальная дока, в которой описано что делает каждый класс и функция. К сожалению, очень часто доки либо вообще нет, либо она не обновляется, либо она написана с потолка, либо описывает общие требования, а не реализацию. Поэтому иногда приходиться понимать логику работы функции анализируя ее название/использование/код, а часто все 3 параметра вообще не коррелируют.

В своей практике из-за отсутсвия этих двух «пожеланий» иногда менял функцию (ибо казалось, что она работает неправильно/медленно/криво), а потом приходилось откатывать изменения (слава git revert !), ибо где-то в глубинах процесса/SDK/еще-где-то есть костыль, из-за которого в функции так же был костыль. Если бы хоть один предыдущий программист утрудился написать коммент, почему он здесь вставил его — я бы не наступал на его же грабли. А проявлялись баги потом через несколько дней/недель и было сложно понять, почему и когда это перестало работать (слава git checkout -> git revert )
Вот в этом случае автоматический тест на «эту ошибку» не помешал бы тоже — он бы мне, вероятно, подсказал, что что-то не так стало в функции!

3) на тесты можно забить, если у вас есть большая QA комада с вполне отлаженными и проверенными тест-кейсами на пару часов! Но это только на огромных бизнеспроектах — они готовы платить за живых QA даже дороже, чем за авто-тесты.

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

Вот именно. Для меня основная проблема ненаписания тестов — это скорость модификации кода. Зачем мне тесты, которые будут постоянно либо валиться, либо говорить, что что-то изменилось — ведь я как раз и хотел внести эти изменения.
Замораживать свой «живой» проект тестами желания нет, да и клиенты не поймут — им нужны постоянные изменения/улучшения/доработки.
Основное назначение тестов как раз фиксация функциональности. Но вообще, тесты после кода пишутся обычно из-под палки, как раз из-за психологических причин типа «говорить, что что-то изменилось — ведь я как раз и хотел внести эти изменения». Нужно сначала изменить тест или написать новый, описав новую функциональность, увидеть что он валится, а потом внести такие изменения в код, чтобы он перестал валиться. Чисто психологически проще, не «сейчас напишу код и тест сломается», а «сейчас напишу код и тест починится». Другими словами попробуйте TDD. Тест должен описывать ЧТО предполагаемый кусок коды должен делать, а код — КАК это делать.
Я вот совершенно не могу себя отнести к фанатам TDD, но модульные тесты и регрессионное тестирование на уровне модулей использую постоянно. На более низком уровне мне больше помогает строгая (и никогда, даже в релизе не отключаемая) проверка контрактов и формальная проверка правильности для самых критичных участков кода. Как результат — большой проект (многопоточный server-side с обработкой кучи каналов звука в реальном масштабе времени в режиме 24/7), в котором за 2 года разработки с кучей промежуточных версий было обнаружено ровно 2 бага, которые просочились на сторону клиента.

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

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

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

И заказчик тут вообще не причем. Поддерживать актуальность тестов — это всё равно что поддерживать работоспособность приложения. Как вы можете быть уверены что приложение функционирует как положено, если вы это никак не проверяли? Проверяли? А как? Покажите код…

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

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

Всё что вы описали всё тестируется. Просто надо знать инструменты тестирования (Mock'и, stub'ы, spec'и unit-test'ы) и уметь писать тесты. Все ваши проблемы от незнания инструментария…
Тут уже много всего было сказано, но я еще добавлю.
На мой взгляд, неправильно воспринимать юнит и интегральные тесты как средство проверки работоспособности программы.
Процедура тестирования — она, действительно, предназначена для того, чтобы проверить, что программа более менее работоспособна. Тесты пишутся для того, чтобы в любой момент можно было проверить работоспособность программы. А это, в свою очередь, необходимо для того, чтобы быть готовым к внесению изменений в программу.
Понимание этой цепочки следствий приводит к следующему:
Если вы пишете программу, развитие и изменение которой не планируется, то разработка тестов — это просто трата времени. Тестирование можно провести и вручную (оно все равно происходит).
Но если вы используете гибкий подход к разработке, всегда готовы к появлению новых и изменению существующих требований, то тесты — это просто суровая необходимость. Без них просто невозможно обеспечить необходимую скорость внесения изменений.
исходя из опыта — мы не ломаем то, что уже работает. Ну вот как-то так повелось


Вы счастливый человек.

Разработкой нашей системы (видеонаблюдение) мы занимаемся уже более 10 лет, постоянно развивая ее и адаптируя под новые требования (определяемые реалиями рынка — меняется оборудование, меняется даже архитектура систем). Волей неволей приходится вносить изменения в уже работавший и проверенный код. Это неизбежно приводит к ситуации, когда потенциально возможно возникновение ошибок «там, где не ждали» (даже если сам автор кода меняется свой код через пару лет).

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

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

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

Мне кажется не всем нужны тесты. Понимание когда нужны тесты придёт само, если ты конечно не совсем «дундук». :)

ЗЫ всё это не отменяет тестирование путём прокликивания, и прочей верификации во время самой разработки.

Это работает, если Вы начали писать проект «с нуля», завершили, сдали и выбросили. Если же Ваша система (как в нашем случае) живет годами, то увы и ах.
Естественно. Даже так если система живёт годами но по отдельности её компоненты делаются с нуля, завершаются и про них забывают и они слабо связаны. В вашем случае тесты будут крайне полезны.
Sign up to leave a comment.

Articles