Как стать автором
Обновить

Комментарии 50

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

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

Юнит-тесты у нас есть — и всегда можно найти, что еще бы потестировать. Тут почти нет пределов — возможно, это специфика области (движок базы данных)

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

Бывает код, который можно рефакторить-рефакторить, да не вырефакторить. В таких случаях нужен профессионал, который сам многому научит, который придет и перепишет «как надо».

А студента, у которого опыта с пару курсовых, так «человеком» не сделать, нужно хорошие примеры: код, тесты, процесс, обучение.
Я даже не знаю, кто из вас сейчас сильнее прав. Я сейчас работаю на проекте, который изначально был… Мм… Фаршем. Простые примеры:
— ни одного теста. То есть, вообще. Совсем. Тупо нет и всё.
— код пестрит чем-то типа String Rabota = «1»; это такое, до сих пор не очень понял
— коммиты почти всегда идут без комментов
— иногда натыкаешься на 200 строк закомментированого кода и не понятно что это, зачем и почему оставлено
— ну и так далее. Все подробности описывать не буду.
Опыта у меня довольно мало, чуть больше, чем пол года. Но на предыдущем месте работы всё было «по феншую»: начиная от багтракера, которого тут тупо нет, заканчивая строгим Code Policy.
В общем, к чему это я… начинал изучение этого самого проекта я именно с тестов, в смысле, что сам писал. Помимо того, что начал хоть немного врубаться, как это устроено, нашел кучку багов, которые тут же фиксил. Но для того, чтобы привести тут всё в порядок — надо просто всё переписывать. С нуля.
Суть подхода и опыт работы в таком процессе у вас есть, это хорошо. Теперь вам надо двигаться в сторону работы с legacode, его модернизации, тестирование и рефакторинг. Процесс не легкий. Иногда кажется что все выкинуть и написать с нуля будет лучшем решением, но это не так.
| legacode
А можно чуть подробнее? Куда копать, что почитать, и т.д. Впервые слышу подобный термин :(
Сори, может ввел немного в заблуждение. Я имел в виду legacy code. Обычно так называют код который достался в наследство :) И имеет ряд недостатков:
— Например такой код тяжело модернизировать.
— Такой код тяжело поддается тестированию.
— Код работает но чтобы понять почему, уходить много времени
— и так далее.
Думаю начать нужна с этой книги Michael Feather «Working Effectively with Legacy Code». Далее великий интернет вам поможет :)
Ну вообще написание тестов по готовому коду немного противоречит TDD, тесты же должны писаться до разработки функционала. Но соглашусь с тем, что это в идеале, т.к. очень часто приходится дописывать неучтенные тесты, или даже внедрять тестирование на уже запущенных проектах.

А по-поводу новых сотрудников мы работали несколько иначе. Приходил человек, мы его подсаживали в пару к другому разработчику, как результат человек за 2 — 3 недели въезжал в проект, а также быстрее вливался в коллектив =)
Ну, по этому поводу тоже много кто писал. XP, парное программирование )) У нас на старой работе ребята как-то чуть не подрались, сидя за одним компом )) До сих пор вспоминаем :)
И пускай кто-то скажет, что парное программирование не экстремально :)
А у нас на работе все программируют в пара, и это отлично работает. Это как доказательство того, что XP — это хорошо. А тот факт, что у вас это один раз не получилось, вовсе не доказывает, что XP — это плохо.
Да ладно Вам) Я же не сказал, что это хорошо или плохо. Просто случай из жизни рассказал. Вообще, если я правильно помню, то TDD — это один из подходов XP («Extreme programming explained», Kent Beck).
Кстати да! Хорошие способы, но вопрос «с чего же начать?» так и не раскрыт, на мой взгляд. Помню себя в те мучительные времена становления вопросов ТДД в голове и могу только посоветовать начинающим курить эту классику до тех пор, пока в голове не «щелкнет». Лично мне только она помогла.
Начинать полезно с интерфейсов )

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

Это хорошо работает для обособенной логики. Если же нужно протестить Web, EJB и т.д., то используем мощь соответствующего framework'а. Как правило, сейчас даже в мануалах есть разделы «как это тестировать».
Здоровски! :) Читается просто на одном дыхании. Действительно, самое сложное — это начать писать тесты. И по моему мнению, Вы отлично показали как это можно сделать. Спасибо!
Толково расписано, понятным языком.
Расписано, безусловно, толково, но это не TDD. Как было верно указано в начале статьи, тест должен не тестировать, а описывать поведение программы. Т.е. тест должен придумываться задолго до коммита или вопроса начальника. Написали тест — написали код — подтвердили прохождение теста — закоммитили.
Сами тесты же должны браться из ТЗ, спеки или переговоров с заказчиком. Вы же, в конце концов, знаете, что должна делать ваша программа — ну так и описывайте. Лично для себя я выбрал метод «от общего к частному». Т.е. сначала я описываю общий функционал отдельного модуля/страницы, затем, по выполнении общих тестов, описываю мелкие детали и частные случаи. Если эти тесты валятся — правлю код. Это очень удобно, т.к. не требует изначального утомительного описывания каждого чиха программы.
хорошая статья, еще раз убедился принял правильное решение о написании юнит тестов в своей программе
Уже, по-моему, 3-я статья за последние пару недель о юнит-тестах, где путается тёплое с мягким: заголовок про юнит-тесты, обязательное упоминание TDD, а затем и функциональные-приёмочные тесты до кучи (при этом ни слова о подходе самого TDD).
> Каждое утро на собрании начальник спрашивает тебя: «Что ты вчера делал?».
> — Багу исправлял.
Автор целый день исправлял багу в одном методе! Налицо эффективность TDD! Это не просто язвительное замечание. Производительность людей, зацикленных на TDD, реально такая. И ничего сложней валидатора они как правило не пишут. Вы хоть примеры-то для разнообразия поинтересней придумайте, а то одни валидаторы…

> — Слушай, я тут твой код дебажу-дебажу, а всё никак не могу понять, как это работает?
Так надо было по-русски прямо в коде и написать:

/** Энкодит букву по алгоритму:…
Например если на вход подать А, то получится Б */
public String encodeLetter( String letter )

Да-да, прямо в коде, не надо бояться! И ошибки не будет, и лишний тест не надо писать, и коллега сразу сообразит что к чему. Это называется контрактом. А в Вашем случае прекрасно сработает и это:
public String encodeLetter( String letter ) { return «Б» }
И будет соответствовать «спецификации».

> assertTrue(validateEmail(«tina.turner@music.info»)); // 4 letters now allowed!
Где спецификация самого метода? С каких хренов 4 буквы not allowed? Где это написано? При чем тут тесты? А 5 буквами как дела обстоят? А с доменом-поддоменом? Рановато +1 в карму…
Откуда столько ненависти?
Да, это нормально исправлять целый день один метод, даже если вы не пишете юнит-тесты, просто методы и их сложность бывают разные. В статье простой и подходящий пример — валидация.
Нет, не нужно в коде писать по-русски, это же код а не художественный роман, пишите на английском хотя бы.
Откуда столько ненависти?
Да, это нормально исправлять целый день один метод, даже если вы не пишете юнит-тесты, просто методы и их сложность бывают разные. В статье простой и подходящий пример — валидация.
Нет, не нужно в коде писать по-русски, это же код а не художественный роман, пишите на английском хотя бы.
Откуда столько ненависти?
Да, это нормально исправлять целый день один метод, даже если вы не пишете юнит-тесты, просто методы и их сложность бывают разные. В статье простой и подходящий пример — валидация.
Нет, не нужно в коде писать по-русски, это же код а не художественный роман, пишите на английском хотя бы.
Да, я ошибся формой для ответа, подлежу расстрелу
Между прочим, такие ошибки зачастую мемы порождают)
а у меня не получается писать юнит-тесты:
1. Их обычно пишут на public методы, а у меня public метод только один для класса. Все остальные private
2. Подготовить данные для тестирования бизнес-логики настоящая проблема, потому что надо сгенерировать сложнейшие связи в базе данных, которые зависимы друг от друга.
3. Написать скрипт длиной в несколько сотен строк, генерирующий данные, мне просто знаний sql не хватит.
4. Объяснять спецу по базам данных что мне надо, а потом еще 20 раз попросить исправить что-то в скрипте — займет 90% от выделенного мне на задачу времени.
5. Иметь каждому разработчику отдельную базу и синхронизировать у нас не принято, да и не удобном опять же по причине синхронизации и написания полотен sql скриптов.

Вот и выходит, что тестировать не удобно юнит тестами, а учитывая, что мы используем не простые sql запросы, а fluent nhibernate, то создать объект внутри базы становится непосильной задачей, т.к. объект не будет записан в базу (ссылка на null в базе) до тех пор, пока мы создадим еще 100500 подобъектов

Писать тесты для валидации или работа со строками — это просто, а что делать со связями в БД не знаю.

А я очень хочу писать тесты, но как решить проблему не знаю.
В таких случаях используются фабрики и генераторы фейковых объектов для наполнения тестовой базы. Да, кстати, о тестовой базе. Она должна быть в любом случае.
В инструментах на PHP, к сожалению, не искушен, но можете посмотреть FFaker и Fabricator для Ruby, чтобы понять, о чем я говорю.
1. Их обычно пишут на public методы, а у меня public метод только один для класса. Все остальные private

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

Это значит много: во-1, вы скипнули юнит-тесты и пишете уже интеграционный тест. Это вследствие (неиспользования TBC и, как результат) того что объекты у вас тесно связаны, курите dependency invertion. В третьих, (а от этого и ваши 3й и 4й пункты) возможно нарушен принцип single responsibility — объект должен моделью создаваться (скажем одним вызовом addObject()), какой SQL при этом генерируется — его личное инкапсулированное дело. Вы же ведь не достаете данные напрямую из базы, через геттеры ведь достаете (нет?)
5. Иметь каждому разработчику отдельную базу и синхронизировать у нас не принято, да и не удобном опять же по причине синхронизации и написания полотен sql скриптов.
Ну а это уже совсем напрасно. Один рефакторит/валит базу остальные в это время курят? Разве сложно вынести имя базы в конфиг? Впрочем, я не знаю особенностей вашего приложения.
2DarthSim
я пишу для .net. Боюсь что сложность бизнеслогики не позволит использовать автогенераторы данных. граничные и фейковые значение мне не столь сложно проверить, как соответствие дат, денежных сумм, наличия/отсутсвия связей и т.д.

2denver
простите, но я понял (язык и терминологию не понял) что вы написали в п.2 Прошу вас, объясните подробнее.
Объект-то базы создается моделью. Только вот чтобы заполнить все поля объекта (банковское ПО у нас) и найти все связи таблиц (не просто состояния или типы, а до 30 различных логических связей и взаимодействий таблиц) можно потратить несколько дней на такие заготовки для 1 нового класса бизнес-логики по автоматизации чего либо.
т.е. не получается просто сделать 2-3 вызова paymentsRepository.AddObject(newPaymentFromCustomer)
и тем самым сгенерировать данные.
Поэтому честно сказать я не знаю, что с этим делать.

to ALL
Я правильно понял, что все заливают тестовые данные из файлов перед каждым запуском юнит теста? Ну тогда чем это лучше чем скрипты с запросами к БД.
По феншую, если для юнит-теста нужна база, то это либо тестирование модуля работы с базой (но не модели предметной области приложения), либо это не юнит-тест :)

Объект базы не должен создаваться моделью, по крайней мере захардкодено, объект или класс базы должен задаваться извне класса модели (в конструкторе, в сеттере, в конфиге, где-то ещё), так чтобы можно было перед сохранением модели изменить собственно хранилище (от параметров вроде имени БД/логина до класса этого хранилища, например заменить БД на memcached или вообще на фэйковый объект — mock)
НЛО прилетело и опубликовало эту надпись здесь
Про пункты №№2 и 3

1. Mock-objects
2. Yaml/XML/JSON — DB
3. Migrations

Посредством миграций тестовая юаза наполняется идеальными (и нет) данными из файликов. Для проверки кода, использующего внешние зависимости (RPC, SOAP, REST) зависимости перекрываются моками и проверяется, что именно они вызвались.
у нас была специальная тестовая база,
создавалась из дампа перед запуском каждого теста
в конце теста — уничтожалась
специальный скрипт для запуска тестов, который запускал тестовую БД, гонял тесты и дропал БД.

недостаток: одновременно нельзя было запустить два теста.
Вот читаю, читаю я статьи на Хабре про TDD и BDD и никак вкурить не могу… Примеры приводятся достаточно элементарные, а вот что-то реально нужного никак не могу прочитать.
Давайте рассмотрим банальный подход по принципу BLL->DAL->DB. Предположим у нас GridView отображает данные возвращаемые одним из методов одного из классов неймспейса BLL. Каким образом писать эти тесты для элементарного получения DataTable из базы и тем что должно получится? Писать сравнение каждой строчки возвращаемой? Или мы интерфейс не тестируем? А что мы тогда тестируем? А как тестировать вставку данных? Опять же для проверки формы из 10 полей (пусть все обязательные) какого же размера тест надо написать для разнообразных комбинаций?
тестировать UI — очень непростая задача.
Тесты — это фактически проверка на соответствие требованиям. И если требования к классам легко описываются ассертами, то требования к UI формально описать очень сложно. Отсюда и сложность тестирования.

Работа с базой… модели работы с данными могут быть протестированы с использованием тестовых фикстур данных.
То есть тестирование UI слишком сложно — пропускаем… Получается что от TDD плясать уже не получается, как это красиво описано в куче статей, о том как от тестов пишем код…
А от тестовых данных в БД плясать… Ну не знаю. Это получается пиши данные — считывай и проверяй читая все строки и сравнивая все ячейки? Так что ли?
В каких проектах вообще применим TDD? Со сложной логикой? Без сложного UI? Без работы с внешними интерфейсами, включая БД? Потому что иначе размер тестов явно превысит сам код раз в 10.
Это даже взять простейший пример, когда тест пишется для функции имеющий 1 вход, 1 выход: требуется проверить и NULL и "" и маленькую строку и большую и ОЧЕНЬ большую и т.д. А берем пример с запросом к базе данных — а там уже с точки зрения выходных данных пусть даже всего 20 полей и 5 разных комбинаций строк — уже имеем дофига вариантов которые НАДО по логике TDD проверять — а ху-ху не хо-хо — пупок-то не развяжется проверять всякую маловероятную ерунду? Когда же сам код-то писать? Элементарный фильтр из 3 частей в каждой 3 чекбокса уже задолбаешься описывать тестами, да еще и само сравнение с возвращаемыми данными из БД…
А Ajax? А JavaScript с точки зрения того же UI? Боюсь даже представить тест сложносвязанной формы и сколько для этого придется кода писать…

По-моему, надо тщательнее проектировать и аккуратно писать код.
Нет никакого грида, аджаска и т.п., есть метода в API получить_данные_такие_то, вот его и надо тестировать в первую очередь, т.е. приложение должно быть спроектировано удобнотестируемым, с выделенным высокоуровневым API. А отрисовку в грид проверит тестер — уверен там если и будут, то очень мелкие ошибки.
Все варианты перебирать не надо, ибо не всегда и возможно, нужно основные, а если хочется побольше перебрать, то можно взять нечто типа LectroTest
В базу ничего писать не надо, нужно использовать Mock объекты
Ну вот и как ты будешь «данные_такие_то» проверять? Хоть в DataTable тебе их выдай хоть в чём.
«Все данные проверять не надо» — а как же идея-фикс про 100% покрытие кода тестами? А ВДРУГ мы-то и не догадываемся что тут основное?
В базу писать ничего не надо? А вдруг неправильно запросы написали? Что делать? Неужели руками каждый раз перетестировать?
Короче — что же все-таки дает-то такое тестирование? Тут не тестируй, тут упрости, тут вообще заглушки используй…
А вы кажется неправильно TDD представляете. Тест не после кода, а ДО него писать надо. Тогда и покрытие будет 100%, и вопросов не возникнет что тестировать (как что? что хочешь написать, то и проверь сначала).

А в базу писать ничего не надо, надо тестировать только методы класса, их внешнее проявление, а не внутреннюю кухню (потому что сегодня там MySQL, а завтра Mongo). Если запрос там внутри «неправильно» написан, а тесты зеленые, то ничего, как говорится, и не трогай :)
Пардон. Если запрос там внутри «неправильно» написан, а тесты зеленые, и все новые тесты (в попытке повалить ожидаемое поведение) выходят также зелеными, то ничего, как говорится, и не трогай :)
Так и буду проверять — если при заданных условиях вернулся ожидаемый набор данных, то ок, более конкретно можно сказать для более конкретного примера
100% покрытие это идеал, но мы в реальном мире, тесты очень ценны даже если не покрывают 100%, а покрытие со временем улучшится до достаточного уровня, начать нужно с того что кажется основным
Не надо путать модульные тесты и интеграционные тесты, модульные проверяют только модуль в изолированном окружении, а интеграционные работу модулей вместе, в интеграционных можно и в базу писать тестовые данные
Даёт знание что модуль с высокой вероятностью работает по спецификации, и что особенно важно — знание того, что наше очередное изменение не ухудшило работу модуля
Тестировать надо прежде всего BLL (бизнес лоджик лэйер — я правильно понял?). DAL (дата абстракшн лэйер?) и DB уже как бы считаются идеальными и в тестировании не нуждаются (их тестировал их разработчик) — вы априори считаете, что если передали DAL запрос на сохранение объекта, то он сохранится, на загрузку — загрузится.

По идее в BLL у вас запросов быть не должно вообще на этапе разработки, если и появятся, то на этапе оптимизации. Мок объекты это не заглушки, а «проверялки» того, что ваш объект правильно вызывает другие объекты. Допустим у вас есть объект User, который в методе Save вызывает метод Save объекта Storage, передавая ему параметры login и password_hash, которые задаются в конструкторе (хэш считается внутри). В тесте вы создаёте объект User, передав в конструкторе строковые константы логин и пароль. Подставляете вместо Storage объект StorageMock, который конфигурируете так, что он ожидает вызова метода Save c логином и хэшем пароля и вызываете метод Save объекта User. Выполняете тест. Если метод Save объекта StorageMock не будет вызван — тест не пройдёт. Если будет вызван, то параметры не будут равны ожидаемым (например, пароль не захэширован будет) — тест не пройдёт. Он пройдёт только если метод будет вызван с ожидаемыми параметрами, а занчит когда вы вызовите настоящий Storage, эти параметры будут переданы ему.

Вы подставляете вместо Storage объект StorageMock, который предварительно конфигурир
«Все данные проверять не надо» — а как же идея-фикс про 100% покрытие кода тестами? А ВДРУГ мы-то и не догадываемся что тут основное?

Для этого есть регрессивные тесты. Обнаружили баг, поняли что что-то забыли, тогда и создаём тест на этот случай. А изначально описываются лишь основные варианты.
В каких проектах вообще применим TDD? Со сложной логикой? Без сложного UI? Без работы с внешними интерфейсами, включая БД? Потому что иначе размер тестов явно превысит сам код раз в 10.

Это если тестировать всё приложение как «черный ящик». Но юнит-тестируют не всё сразу, а по отдельности, равно как и пишут. Комбинаторику (интеграцию) после этого тестировать можно, но необязательно (нужно для контроля качества, а не для кодинга с TDD).

Ну и да, TDD применим в любых проектах. И чем сложнее проект, тем более необходим. Да, время занимает, но порядка 50%, зато фактически разгружает QA. К тому же необязательно покрывать совсем всё. Можно например только «ядро» и хрупкие участки. Ну а UI покрывать имхо стоит уже в самую последнюю очередь (поскольку меняется чаще всего).
А может кто-то написать действительно толковую статью о TDD?
Я вот решил использовать TDD, но пока что никак не могу переделать мозги под написание тестов. Открываю файл для тестов и вступаю в ступор…
С чего начать? Как называть? Что в конце-концов тестить? Ведь можно углубляться до бесконечности в требуемый функционал!
Кент Бек. Экстремальное программирование: разработка через тестирование. — «Питер», 2003

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

Начинать проще всего, имхо, если говорить о вебе и MVC, с модели, тестировать именно бизнес-логику, а не как она сохраняется в БД. Идеально для этого подходит паттерн DataMapper, с ActiveRecord похуже, имхо. Самое сложное по началу выбрать нужный шаг тестирования, когда начинал писать первый проект, используя TDD, то тестировал абсолютно всё, включая среду выполнения, транслятор, сторонние библиотеки (Doctrine в частности :) ).

Действительно, может, вам лучше хорошую книжку почитать?

Или может, это поможет habrahabr.ru/blogs/tdd/112851/?
Мой рабочий цикл: эксперимент — спецификация — тест — реализация — верификация.

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

Публикации