-
Итак, начнем с этого чрезвычайно практичного подхода к тестированию.
-
Тестирование, я должен признаться, где-то всего лишь три или четыре года назад
-
я принял как религию. И то, что я имею в виду, отчасти так и было: мне говорили, что это
-
важно, я верил, что это важно, я пытался делать это, но именно этот подход действительно изменил
-
мою жизнь. И я надеюсь, изменит и вашу тоже.
-
И нет, я не о выборах, это ни в коем случае. Итак, давайте поговорим о
-
юнит-тестах. В книге есть небольшая вводная о различных видах тестирования.
-
Мы сначала заострим внимание на юнит-тестах и немного на функциональных тестах,
-
и как и со многими другими темами на этих лекциях, есть удобный акроним, который поможет нам запомнить
-
каким должны быть хорошие юнит-тесты. Итак, для начала - они должны быть быстрыми -
-
т.е. не должны выполняться слишком долго; они должны быть независимыми.
-
Это означает, что не должно иметь значения, какой тест надо запускать первым, какой - вторым
-
порядок не важен. Они не должны иметь зависимостей друг от друга.
-
Они должны быть воспроизводимыми - т.е. если тест находит ошибку, он должен находить ее каждый раз.
-
В некоторых случаях этого легко добиться, в других - довольно непросто.
-
Самоконтроль. Вот что означало тестирование не так давно для многих компаний:
-
Программное обеспечение перебрасывали через стену в отдел контроля качества, а там сотрудники отдела
-
вручную понажимают кнопочки в программе, что-то поделают в ней и - "О, оно работает, оно работает"
-
Но мы ведь не хотим больше этого, правда? Тест сам должен знать
-
прошел ли он или сломался. И чтобы принять это решение не должно требоваться человеческое вмешательство.
-
И, наконец, своевременность. Это означает, что тесты должны быть написаны практически одновременно
-
с программным кодом. Если код меняется, тесты также также должны быть изменены.
-
А на самом деле, мы собираемся делать это еще агрессивнее. Бы будем писать тесты в первую очередь, еще до того как будет написан код.
-
То есть настолько своевременно, насколько вы можете.
-
Итак что все это значит? Зачем нам нужны быстрые тесты?
-
Затем, что мы можем запускать какое-то подмножество тестов все время.
-
И если у нас тысячи и тысячи юнит-тестов, что не является необычным даже для проектов средних размеров,
-
это может занять, как вы догадываетесь, минуту или две, чтобы выполнить набор тестов и это будет нас тормозить.
-
Все что нам нужно - возможность быстро запускать только те тесты, которые относятся к конкретному куску кода,
-
над которым мы работаем таким образом, чтобы не сбиваться с ритма.
-
Независимость нужна по той же причине, чтобы мы могли запустить любое подмножество тестов
-
и в любом порядке, в каком захотим.
-
Поэтому, плохо, когда есть множество тестов, которые можно запускать
-
только тогда, когда перед ними запускали какие-либо другие тесты.
-
Воспроизводимость, как вы снова догадываетесь, означает что запустив тест N раз мы получим одинаковые результаты.
-
Если мы хотим локализовать ошибку и включить возможность автоматической отладки, воспроизводимость важна.
-
Самоконтроль: как я уже говорил, человек не должен проверять результат.
-
Это означает, что мы можем иметь тесты, выполняющиеся в фоне все время
-
и когда бы мы не внесли изменения, которые ломают что-то в 25 милях в другом участке кода,
-
какой-нибудь тест обнаружит это и привлечет наше внимание.
-
И, наконец, своевременность. Как я и говорил, мы собираемся использовать разработку через тестирование,
-
в которой тесты пишутся перед тем, как будет написан программный код.
-
Мы будем использовать RSpec, который я рассматриваю как предметно-ориентированный язык для написания тестов.
-
Для тех, кто не знаком с подобными языками, скажу, что в основе это вроде небольшого языка программирования
-
который умеет делать небольшое количество вещей в рамках одной предметной области. Т.е. это не язык общего назначения.
-
На самом деле мы уже видели примеры подобного языка. Миграции являются разновидностью DSL.
-
Это небольшое подмножество операторов, чья единственная работа - описывать изменения в схему базы данных..
-
Т.о., миграции оказались DSL, который встроен в Ruby, т.е.
-
миграции являются всего лишь кодом на Ruby, но стилизованные под задачи, которые они выполняют.
-
На самом деле мы увидим, что RSpec - похожий пример. Итак, мы будем называть все подобное внутренним DSL.
-
Он реализован внутри другого языка. Регулярные выражения - тоже внутренний DSL.
-
Он как подмножество действий, которые мы можем выполнять в регулярных выражениях.
-
Другой пример - внешний или автономный DSL - это SQL. SQL-запросы к базам данных.
-
Это отдельный язык, и те, кто работали с другими фреймворками
-
перед тем, как перейти к Rails, обычно заканчивали тем, что писали SQL-запросы
-
и затем передавали кому-то, да? Так вот это очень яркий пример работы с разными языками.
-
Итак, в RSpec, каждый тест называется спекой - от "спецификация" (specification).
-
Как ни странно, они размещаются в каталоге, называемом "spec" - потому что нам нравится все делать просто.
-
В Rails есть генератор, "rspec:install", который создает структуру подкаталогов.
-
Все это есть в книге и в последующих демонстрациях, которые мы покажем сегодня, мы подразумеваем,
-
что мы уже выполнили эти подготовительные шаги.
-
Итак, с чего все начинается? Подкаталоги каталога spec организованы так, чтобы отражать структуру
-
нашего приложения. Так вот: в app/models у вас лежат ваши модели,
-
а в spec/models у вас лежат spec-файл для каждой модели. Ничего удивительного.
-
Подобным же образом располагаются спеки для контроллеров. А как насчет представлений (view)?
-
Вообще, мы не будем делать спеки для представлений. Сделать их можно, но будет это несколько
-
кривовато - много из того, что мы хотим проверить в представлении, на самом деле
-
можно проверить в контроллере, и мы увидим это в сегодняшнем примере.
-
К тому же, мы решили, что наш подход для веб-приложений, с которыми напрямую взаимодействуют пользователи
-
- через создание пользовательских историй, которые описывают те части приложения, с которыми взаимодействует заказчик.
-
Таким образом, то, что является частью представления - что должно быть видимым в представлении
-
и то, что может быть нажато и т.д., мы для всего этого будем использовать Cucumber.
-
И в дальнейшем мы так и будем поступать.
-
Таким образом в основном мы будем уделять внимание RSpec с точки зрения
-
написания спецификаций для наших моделей и контроллеров.
-
Итак, давайте начнем с примера новой гипотетической фичи для RottenPotatoes,
-
при помощи которой мы можем добавлять фильмы, используя данные из TMDb.
-
TMDb - реальный сайт. Он вроде IMDb, но только не коммерческий, что-то вроде open-source проекта.
-
Идея в том, что у них есть вся информация о фильмах, и если мы хотим
-
добавить фильм в RottenPotatoes, то почему бы нам просто не скачать нужную информацию оттуда?
-
На деле, когда мы обсуждали пользовательские истории, в одной из них был шаг, в котором говорится
-
"Я заполняю ключевые слова для поиска, Я хочу найти фильм "Начало",
-
и когда я нажимаю кнопку, мне предлагают "Искать на TMDb", не так ли?
-
Таким образом, предполагается, что должна быть кнопка, по нажатию которой наше приложение обратится к TMDb
-
и проверит, есть ли там "Начало", и если да, возьмет информацию о нем оттуда.
-
Вопрос в том, что мы должны сделать. Какой код нужно написать
-
и какой вид тестирования нужно применить.
-
И прежде, чем мы займемся этим, помните наш разговор о "Кулинарии Rails",
-
рецептах, как готовить при помощи Rails? Вспомните, что когда мы добавляем какую-либо новую фичу
-
это означает, что нам нужен новый маршрут (route), новый метод контроллера. Также нам может понадобиться,
-
а может и нет, новое представление. Это зависит от того, может ли эта фича использовать существующее представление
-
или мы должны сделать новое. Эти шаги мы должны выполнять всегда.
-
Итак, давайте займемся всем этим, но будем делать по одному шагу за раз.
-
Ладно, так. Это идея, которую мы будем видеть многократно. Я всего лишь приучаю вас к фразе
-
"Код, который вы бы хотели иметь." Это очень сильная мысль, как только вы к ней привыкнете.
-
Она странно звучит, когда слышите ее первый раз, но она действительно очень сильная.
-
Итак, мы спрашиваем себя: "Хорошо, когда пользователь нажимает на эту кнопку "Искать на TMDb", мы знаем, что
-
где-то должен быть метод контроллера, который получит все то, что было отправлено через форму.
-
Так что должен делать этот метод? Что должен делать метод контроллера, когда получает форму поиска?
-
Если бы нас спросили об этом, и если бы мы написали, что он должен делать на разговорном языке
-
наш ответ звучал бы как "Ладно, смотри, он должен вызывать метод (который мы еще не написали)
-
который сходит на TMDb и поищет там фильм". Разумно.
-
"Если он там найдет фильм, он должен сформировать некоторое представление, чтобы показать результат поиска
-
(и снова, мы пока еще не создали это представление, но логически, это как раз то, что мы собираемся
-
написать)." Сегодня на третий шаг нам времени не хватит, но для неуспешного исхода сценарий таков:
-
"Если фильм не найден, метод должен перенаправить на главную страницу RottenPotatoes и сообщить
-
"Ничего не найдено". И если если вы просмотрите пример в книге,
-
мы на самом деле вариант неуспешного исхода проработали в главе, посвященной BDD.
-
Итак, у нас есть эти два момента. Сосредоточимся на №1 и №2 - это то, что метод контроллера должен делать,
-
и вот как мы будем это описывать. Где моя мышь? Итак, поехали.
-
Рассмотрим, как мы опишем все эти требования при помощи RSpec. Итак...
-
Ничего особенного здесь ни происходит, видите. Все достаточно просто, не так ли?
-
И это корректный код RSpec. SpecHelper - это просто файл, который RSpec создает как часть шага установки.
-
Он просто выполняет кое-какие вещи, гарантирующие, что все необходимое подгружено.
-
И собираемся начать, спросив "Что должен проверять этот тест?"
-
Или, мы собираемся описать поведение MoviesController.
-
У контроллера есть много вариантов поведения, но нас волнует одно конкретное, а именно
-
поведение во время поиска на TMDb. Итак, мы можем представить, для каждого поведения в контроллере
-
как растет наша спека, мы будем добавлять больше описательных блоков внутри этого внешнего
-
"describe MoviesController", и так далее вкладывать, вкладывать, вкладывать.
-
Все, что я здесь сделал - это переписал те три требования, о которых мы говорили.
-
Итак, "it" - это на самом деле вызов метода в RSpec. Он принимает аргумент, который является
-
строкой, описывающий, что должно произойти. И как мы увидим,
-
он принимает также второй аргумент, который является процедурой, которая и выполняет тест.
-
Но пока, все что мы сделали - лишь сделали перевод. Мы обдумали три требования
-
которые должен удовлетворять контроллер, и мы написали эти три требования в RSpec.
-
Этого достаточно, чтобы выполнить запуск, и у нас есть скринкаст, который я рекомендую вам посмотреть.
-
Он соответствует главе книги и в нем как раз все это делается.
-
Все, что он делает - запускает три теста, которые ничего не делают. Поэтом вывод RSpec желтый, да?
-
Желтый означает "еще не реализовано", точно как и в Cucumber.