Лирическое отступление
Приходит время, когда просто сидеть на работе и как робот выполнять таски по трекеру становится скучно и не интересно. И в этот момент просыпается необходимость разнообразить свою профессиональную жизнь. Самое первое, что можно придумать - это вести блог. Так что прошу не сильно закидывать шишками, т.к. это мой первый подобный пост.
Lets go!
Задача
Сколько людей - столько и мнений. Наверное сейчас очень сложно придумать такую задачу, чтобы она была уникальной. Поэтому появилось устоявшееся выражение - писать велосипед. Вот таким велосипедом я сейчас и займусь (я даже специально не гуглил на эту тему, чтобы заранее себя не расстраивать). А проблема вот какая:
Последнее время очень часто возникает необходимость сливать базу данных с продакшена и накатывать ее на девелопменте, т.к. править различные штуки удобнее локально. Для этого приходится делать достаточно много шагов: зайти по ssh и сделать дамп базы, зайти через Filezilla и скачать этот дамп, затем накатить его на dev базу. И тут я задался вопросом ведь яжпрограммист или кто?!
Поэтому я решил написать на ruby утилиту, которая поможет мне автоматизировать этот процесс.
Я понимаю, что это велосипед, но ведь он свой :-)
Цель
С самого начала я взял листок бумаги и накидал кусок кода, который, возможно, должен получится в конце. Можно сказать своеобразный DSL, при помощи которого я смог бы описывать шаги, которые я делал вручную. Получилось что-то такое:
1 2 3 4 5 6 |
|
Сразу хочу отметить тот факт, что конечный результат может и даже, скорее всего, будет отличаться от кода выше.
Будем пробовать TDD
Есть многие вещи, которые ты не поймешь зачем они нужны, пока ты сам до этого не дойдешь, даже если какой-нибудь гуру будет тебе говорить, что это классно. Такая история и с TDD. Сама идея писать код для проверки кода звучит дико, не правда ли? Но не нужно смотреть на TDD только через призму, того что нужно писать дополнительный код, ведь программисты и так ленивые. Это целая методология со своими условными ‘правилами’ и ее главная цель уменьшить вашу головную боль. Но не буду заострять внимание на этом. Просто будте готовы писать тесты!
Ну-с начнем
Самый сложный момент. С чего начать, когда есть только идея? Существуют два варианта движения:
- Inside-out
- Outside-in
Выбор зависит индивидуально от программиста (смотря как у кого работает голова), а также от того, какую информацию содержит ваша первоначальная идея (листинг 1).
Давайте еще раз посмотрим, что у нас есть - это пример АПИ, которое должна предоставлять библиотека или, другими словами, ее outside часть. Для нас очевиден путь (по крайней мере я выбрал этот вариант и возможно к концу написания библиотеки пойму, что он был неудобный) outside-in.
Первые шаги
С самого начала хочу определить структуру каталога, чтобы была возможность ссылаться на нее, а также вам для наглядности:
1 2 3 |
|
Я думаю тут вопросов не должно возникнуть)
В первую очередь давайте настроем наше test environment. Надеюсь вы заметили, что я ни слова не сказал насчет какого-нибудь фреймворка? Да, вы правильно поняли - это будет проект на чистом ruby. Поэтому любые настройки придется делать вручную. Но это даже к лучшему! Так вот, если взять rails, то там уже из коробки идет настроенный unit testing, но я всегда сразу устанавливал Rspec, и файл с настройками назывался test_helper или spec_helper (для rspec). Давайте и у нас создадим такой файл, в котором будут содержаться общие настройки для всех наших тестов:
1 2 3 4 5 |
|
Что мы здесь сделали?
- Подключили фреймворк для unit тестирования - Minitest, который идет в ruby stdlib
- Загрузили все файлы из папки lib
И вот теперь, наконец-то, можно приступать к написанию кода! А если точнее - теста. Вот тут начинается еще одна интересная вещь - а что тестировать, если ничего нет?
Небольшое отступление. Сейчас мне в голову пришла мысль и я хочу немного изменить и дополнить нашу первоначальную идею:
1 2 3 4 5 6 7 |
|
И теперь стало немного понятнее с чего нам начать. Как я уже говорил выше я выбрал путь outside-in, т.е. мы будем двигаться в сторону уточнения внутренней реализации. Поэтому начнем с теста внешнего АПИ. Да, я не опечатался - начнем с ТЕСТА)
1 2 3 4 5 6 7 |
|
Сейчас давайте разберем листинг 4:
- Мы заинклудили наш test_helper, поэтому в данном контексте доступен Minitest
- Написали тест, который проверяет правильно работы метода
define_runner
(здесь лучше сказать, что мы проверяем не правильность, а то, какой результат ожидаем после вызова данного метода). Все, что мы можем сейчас проверить - методdefine_runner
возвращает новый экземпляр классаDbFetcher::Runner
.
А давайте запустим наш тест и посмотрим результат? (подсказка: для
запуска теста набирайте ruby test/lib/db_fetcher_test.rb
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Упс… Но все правильно! Ведь кода у нас еще нет! Сейчас мы должны написать минимум кода для того, чтобы удовлетворить данный тест.
1 2 3 4 5 6 7 |
|
Так-с… Попытка следующая:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Вот черт! Совсем забыл про Runner
. Давайте определим и его!
1 2 3 4 |
|
А вот теперь скрестили пальцы и-и-и-и:
1 2 3 4 5 6 7 8 9 |
|
Отлично! 1 assertions
значит, что наш тест прошел! Фууух.
Итерационность
Главное слово в этом процессе - итерационность. После того, как вы добились чтобы тест прошел, можете писать тест дальше. Затем вы опять пишете код, удовлетворяющий новые тесты, но и не сломал предыдущие! И этот процесс повторяется снова и снова. Самой главной величиной этого процесса является размер вот этого шага или количество логики, заложенной в тесте, после написания которого вы приступаете к написанию кода. Здесь нет никаких пожеланий и вступает в силу индивидуальность разработчика. Каждый выбирает этот шаг так, чтобы было комфортно: если напишите много тестов - зациклитесь на написании кода, а также есть вероятность сделать тесты неактуальными, т.к. при их написании не учли каких-либо архитектурных моментов; если напишите мало - то придется слишком часто переключать ваш контекст между кодом и тестами, что тоже не гуд, т.к. опять же можно что-то упустить.
Продолжим?
Спешу вас разочаровать(а может и обрадовать) - я не буду полностью описывать весь процесс разработки данной библиотеки, т.к. это будет слишком много неинтересного текста и чередующихся листингов тесты-код-тесты-код-тесты… и т.д. Поэтому я предлагаю мне дописать библиотеку и затем я выложу исходники на github, и затем кину сюда ссылку, хорошо?) Ну и отлично!
Спасибо за внимание!
UPDATE. Как и обещал, выкладываю ссылку на репозитарий db_fetcher. Библиотека еще будет дорабатываться, но базовые концепции уже реализованы и протестированы.