WEBVTT 00:00:00.000 --> 00:00:04.037 Итак, начнем с этого чрезвычайно практичного подхода к тестированию. 00:00:04.037 --> 00:00:08.064 Тестирование, я должен признаться, где-то всего лишь три или четыре года назад 00:00:08.064 --> 00:00:12.096 я принял как религию. И то, что я имею в виду, отчасти так и было: мне говорили, что это 00:00:12.096 --> 00:00:16.092 важно, я верил, что это важно, я пытался делать это, но именно этот подход действительно изменил 00:00:16.092 --> 00:00:21.035 мою жизнь. И я надеюсь, изменит и вашу тоже. 00:00:21.035 --> 00:00:25.047 И нет, я не о выборах, это ни в коем случае. Итак, давайте поговорим о 00:00:25.047 --> 00:00:29.021 юнит-тестах. В книге есть небольшая вводная о различных видах тестирования. 00:00:29.021 --> 00:00:33.010 Мы сначала заострим внимание на юнит-тестах и немного на функциональных тестах, 00:00:33.010 --> 00:00:36.089 и как и со многими другими темами на этих лекциях, есть удобный акроним, который поможет нам запомнить 00:00:36.089 --> 00:00:40.064 каким должны быть хорошие юнит-тесты. Итак, для начала - они должны быть быстрыми - 00:00:40.064 --> 00:00:44.020 т.е. не должны выполняться слишком долго; они должны быть независимыми. 00:00:44.020 --> 00:00:47.077 Это означает, что не должно иметь значения, какой тест надо запускать первым, какой - вторым 00:00:47.077 --> 00:00:51.042 порядок не важен. Они не должны иметь зависимостей друг от друга. 00:00:51.042 --> 00:00:54.093 Они должны быть воспроизводимыми - т.е. если тест находит ошибку, он должен находить ее каждый раз. 00:00:54.093 --> 00:01:01.047 В некоторых случаях этого легко добиться, в других - довольно непросто. 00:01:01.047 --> 00:01:05.018 Самоконтроль. Вот что означало тестирование не так давно для многих компаний: 00:01:05.018 --> 00:01:08.065 Программное обеспечение перебрасывали через стену в отдел контроля качества, а там сотрудники отдела 00:01:08.065 --> 00:01:12.044 вручную понажимают кнопочки в программе, что-то поделают в ней и - "О, оно работает, оно работает" 00:01:12.044 --> 00:01:16.010 Но мы ведь не хотим больше этого, правда? Тест сам должен знать 00:01:16.010 --> 00:01:19.094 прошел ли он или сломался. И чтобы принять это решение не должно требоваться человеческое вмешательство. 00:01:19.094 --> 00:01:23.059 И, наконец, своевременность. Это означает, что тесты должны быть написаны практически одновременно 00:01:23.059 --> 00:01:27.011 с программным кодом. Если код меняется, тесты также также должны быть изменены. 00:01:27.011 --> 00:01:30.090 А на самом деле, мы собираемся делать это еще агрессивнее. Бы будем писать тесты в первую очередь, еще до того как будет написан код. 00:01:30.090 --> 00:01:34.046 То есть настолько своевременно, насколько вы можете. 00:01:34.046 --> 00:01:38.022 Итак что все это значит? Зачем нам нужны быстрые тесты? 00:01:38.022 --> 00:01:42.022 Затем, что мы можем запускать какое-то подмножество тестов все время. 00:01:42.022 --> 00:01:46.007 И если у нас тысячи и тысячи юнит-тестов, что не является необычным даже для проектов средних размеров, 00:01:46.007 --> 00:01:49.087 это может занять, как вы догадываетесь, минуту или две, чтобы выполнить набор тестов и это будет нас тормозить. 00:01:49.087 --> 00:01:53.043 Все что нам нужно - возможность быстро запускать только те тесты, которые относятся к конкретному куску кода, 00:01:53.043 --> 00:01:57.038 над которым мы работаем таким образом, чтобы не сбиваться с ритма. 00:01:57.038 --> 00:02:01.033 Независимость нужна по той же причине, чтобы мы могли запустить любое подмножество тестов 00:02:01.033 --> 00:02:05.027 и в любом порядке, в каком захотим. 00:02:05.027 --> 00:02:09.011 Поэтому, плохо, когда есть множество тестов, которые можно запускать 00:02:09.011 --> 00:02:13.023 только тогда, когда перед ними запускали какие-либо другие тесты. 00:02:13.023 --> 00:02:17.001 Воспроизводимость, как вы снова догадываетесь, означает что запустив тест N раз мы получим одинаковые результаты. 00:02:17.001 --> 00:02:20.089 Если мы хотим локализовать ошибку и включить возможность автоматической отладки, воспроизводимость важна. 00:02:20.089 --> 00:02:25.007 Самоконтроль: как я уже говорил, человек не должен проверять результат. 00:02:25.007 --> 00:02:29.019 Это означает, что мы можем иметь тесты, выполняющиеся в фоне все время 00:02:29.019 --> 00:02:33.022 и когда бы мы не внесли изменения, которые ломают что-то в 25 милях в другом участке кода, 00:02:33.022 --> 00:02:37.015 какой-нибудь тест обнаружит это и привлечет наше внимание. 00:02:37.015 --> 00:02:40.093 И, наконец, своевременность. Как я и говорил, мы собираемся использовать разработку через тестирование, 00:02:40.093 --> 00:02:45.062 в которой тесты пишутся перед тем, как будет написан программный код. 00:02:45.062 --> 00:02:49.096 Мы будем использовать RSpec, который я рассматриваю как предметно-ориентированный язык для написания тестов. 00:02:49.096 --> 00:02:53.086 Для тех, кто не знаком с подобными языками, скажу, что в основе это вроде небольшого языка программирования 00:02:53.086 --> 00:02:58.008 который умеет делать небольшое количество вещей в рамках одной предметной области. Т.е. это не язык общего назначения. 00:02:58.024 --> 00:03:02.057 На самом деле мы уже видели примеры подобного языка. Миграции являются разновидностью DSL. 00:03:02.057 --> 00:03:06.041 Это небольшое подмножество операторов, чья единственная работа - описывать изменения в схему базы данных.. 00:03:06.041 --> 00:03:10.057 Т.о., миграции оказались DSL, который встроен в Ruby, т.е. 00:03:10.057 --> 00:03:14.074 миграции являются всего лишь кодом на Ruby, но стилизованные под задачи, которые они выполняют. 00:03:14.074 --> 00:03:19.016 На самом деле мы увидим, что RSpec - похожий пример. Итак, мы будем называть все подобное внутренним DSL. 00:03:19.016 --> 00:03:23.047 Он реализован внутри другого языка. Регулярные выражения - тоже внутренний DSL. 00:03:23.047 --> 00:03:27.088 Он как подмножество действий, которые мы можем выполнять в регулярных выражениях. 00:03:27.088 --> 00:03:32.055 Другой пример - внешний или автономный DSL - это SQL. SQL-запросы к базам данных. 00:03:32.055 --> 00:03:36.047 Это отдельный язык, и те, кто работали с другими фреймворками 00:03:36.047 --> 00:03:40.039 перед тем, как перейти к Rails, обычно заканчивали тем, что писали SQL-запросы 00:03:40.039 --> 00:03:44.056 и затем передавали кому-то, да? Так вот это очень яркий пример работы с разными языками. 00:03:44.056 --> 00:03:48.047 Итак, в RSpec, каждый тест называется спекой - от "спецификация" (specification). 00:03:48.047 --> 00:03:52.041 Как ни странно, они размещаются в каталоге, называемом "spec" - потому что нам нравится все делать просто. 00:03:52.041 --> 00:03:56.076 В Rails есть генератор, "rspec:install", который создает структуру подкаталогов. 00:03:56.076 --> 00:04:01.066 Все это есть в книге и в последующих демонстрациях, которые мы покажем сегодня, мы подразумеваем, 00:04:01.066 --> 00:04:05.090 что мы уже выполнили эти подготовительные шаги. 00:04:05.090 --> 00:04:10.086 Итак, с чего все начинается? Подкаталоги каталога spec организованы так, чтобы отражать структуру 00:04:10.086 --> 00:04:14.094 нашего приложения. Так вот: в app/models у вас лежат ваши модели, 00:04:14.094 --> 00:04:19.061 а в spec/models у вас лежат spec-файл для каждой модели. Ничего удивительного. 00:04:19.061 --> 00:04:24.016 Подобным же образом располагаются спеки для контроллеров. А как насчет представлений (view)? 00:04:24.016 --> 00:04:28.060 Вообще, мы не будем делать спеки для представлений. Сделать их можно, но будет это несколько 00:04:28.060 --> 00:04:33.021 кривовато - много из того, что мы хотим проверить в представлении, на самом деле 00:04:33.021 --> 00:04:37.094 можно проверить в контроллере, и мы увидим это в сегодняшнем примере. 00:04:37.094 --> 00:04:42.084 К тому же, мы решили, что наш подход для веб-приложений, с которыми напрямую взаимодействуют пользователи 00:04:42.084 --> 00:04:46.073 - через создание пользовательских историй, которые описывают те части приложения, с которыми взаимодействует заказчик. 00:04:46.073 --> 00:04:50.058 Таким образом, то, что является частью представления - что должно быть видимым в представлении 00:04:50.058 --> 00:04:54.019 и то, что может быть нажато и т.д., мы для всего этого будем использовать Cucumber. 00:04:54.019 --> 00:04:58.009 И в дальнейшем мы так и будем поступать. 00:04:58.023 --> 00:05:02.003 Таким образом в основном мы будем уделять внимание RSpec с точки зрения 00:05:02.003 --> 00:05:05.060 написания спецификаций для наших моделей и контроллеров. 00:05:05.060 --> 00:05:08.095 Итак, давайте начнем с примера новой гипотетической фичи для RottenPotatoes, 00:05:08.095 --> 00:05:13.047 при помощи которой мы можем добавлять фильмы, используя данные из TMDb. 00:05:13.047 --> 00:05:17.098 TMDb - реальный сайт. Он вроде IMDb, но только не коммерческий, что-то вроде open-source проекта. 00:05:17.098 --> 00:05:22.028 Идея в том, что у них есть вся информация о фильмах, и если мы хотим 00:05:22.028 --> 00:05:26.058 добавить фильм в RottenPotatoes, то почему бы нам просто не скачать нужную информацию оттуда? 00:05:26.058 --> 00:05:31.039 На деле, когда мы обсуждали пользовательские истории, в одной из них был шаг, в котором говорится 00:05:31.039 --> 00:05:36.011 "Я заполняю ключевые слова для поиска, Я хочу найти фильм "Начало", 00:05:36.011 --> 00:05:40.059 и когда я нажимаю кнопку, мне предлагают "Искать на TMDb", не так ли? 00:05:40.059 --> 00:05:45.019 Таким образом, предполагается, что должна быть кнопка, по нажатию которой наше приложение обратится к TMDb 00:05:45.019 --> 00:05:49.033 и проверит, есть ли там "Начало", и если да, возьмет информацию о нем оттуда. 00:05:49.033 --> 00:05:53.020 Вопрос в том, что мы должны сделать. Какой код нужно написать 00:05:53.020 --> 00:05:57.003 и какой вид тестирования нужно применить. 00:05:57.003 --> 00:06:01.016 И прежде, чем мы займемся этим, помните наш разговор о "Кулинарии Rails", 00:06:01.016 --> 00:06:05.019 рецептах, как готовить при помощи Rails? Вспомните, что когда мы добавляем какую-либо новую фичу 00:06:05.019 --> 00:06:08.088 это означает, что нам нужен новый маршрут (route), новый метод контроллера. Также нам может понадобиться, 00:06:08.088 --> 00:06:13.001 а может и нет, новое представление. Это зависит от того, может ли эта фича использовать существующее представление 00:06:13.001 --> 00:06:17.000 или мы должны сделать новое. Эти шаги мы должны выполнять всегда. 00:06:17.000 --> 00:06:20.095 Итак, давайте займемся всем этим, но будем делать по одному шагу за раз. 00:06:20.095 --> 00:06:25.012 Ладно, так. Это идея, которую мы будем видеть многократно. Я всего лишь приучаю вас к фразе 00:06:25.012 --> 00:06:29.060 "Код, который вы бы хотели иметь." Это очень сильная мысль, как только вы к ней привыкнете. 00:06:29.060 --> 00:06:33.098 Она странно звучит, когда слышите ее первый раз, но она действительно очень сильная. 00:06:33.098 --> 00:06:38.028 Итак, мы спрашиваем себя: "Хорошо, когда пользователь нажимает на эту кнопку "Искать на TMDb", мы знаем, что 00:06:38.028 --> 00:06:42.004 где-то должен быть метод контроллера, который получит все то, что было отправлено через форму. 00:06:42.004 --> 00:06:45.076 Так что должен делать этот метод? Что должен делать метод контроллера, когда получает форму поиска? 00:06:45.076 --> 00:06:49.030 Если бы нас спросили об этом, и если бы мы написали, что он должен делать на разговорном языке 00:06:49.030 --> 00:06:52.061 наш ответ звучал бы как "Ладно, смотри, он должен вызывать метод (который мы еще не написали) 00:06:52.061 --> 00:06:56.033 который сходит на TMDb и поищет там фильм". Разумно. 00:06:56.033 --> 00:07:00.009 "Если он там найдет фильм, он должен сформировать некоторое представление, чтобы показать результат поиска 00:07:00.023 --> 00:07:03.094 (и снова, мы пока еще не создали это представление, но логически, это как раз то, что мы собираемся 00:07:03.094 --> 00:07:07.039 написать)." Сегодня на третий шаг нам времени не хватит, но для неуспешного исхода сценарий таков: 00:07:07.039 --> 00:07:11.048 "Если фильм не найден, метод должен перенаправить на главную страницу RottenPotatoes и сообщить 00:07:11.048 --> 00:07:15.086 "Ничего не найдено". И если если вы просмотрите пример в книге, 00:07:15.086 --> 00:07:20.003 мы на самом деле вариант неуспешного исхода проработали в главе, посвященной BDD. 00:07:20.003 --> 00:07:24.014 Итак, у нас есть эти два момента. Сосредоточимся на №1 и №2 - это то, что метод контроллера должен делать, 00:07:24.014 --> 00:07:28.025 и вот как мы будем это описывать. Где моя мышь? Итак, поехали. 00:07:28.025 --> 00:07:32.075 Рассмотрим, как мы опишем все эти требования при помощи RSpec. Итак... 00:07:32.075 --> 00:07:38.096 Ничего особенного здесь ни происходит, видите. Все достаточно просто, не так ли? 00:07:38.096 --> 00:07:43.081 И это корректный код RSpec. SpecHelper - это просто файл, который RSpec создает как часть шага установки. 00:07:43.081 --> 00:07:48.083 Он просто выполняет кое-какие вещи, гарантирующие, что все необходимое подгружено. 00:07:48.083 --> 00:07:52.089 И собираемся начать, спросив "Что должен проверять этот тест?" 00:07:52.089 --> 00:07:57.037 Или, мы собираемся описать поведение MoviesController. 00:07:57.037 --> 00:08:01.059 У контроллера есть много вариантов поведения, но нас волнует одно конкретное, а именно 00:08:01.059 --> 00:08:05.065 поведение во время поиска на TMDb. Итак, мы можем представить, для каждого поведения в контроллере 00:08:05.065 --> 00:08:09.050 как растет наша спека, мы будем добавлять больше описательных блоков внутри этого внешнего 00:08:09.050 --> 00:08:13.049 "describe MoviesController", и так далее вкладывать, вкладывать, вкладывать. 00:08:13.049 --> 00:08:17.009 Все, что я здесь сделал - это переписал те три требования, о которых мы говорили. 00:08:17.009 --> 00:08:21.093 Итак, "it" - это на самом деле вызов метода в RSpec. Он принимает аргумент, который является 00:08:21.093 --> 00:08:25.085 строкой, описывающий, что должно произойти. И как мы увидим, 00:08:25.085 --> 00:08:29.079 он принимает также второй аргумент, который является процедурой, которая и выполняет тест. 00:08:29.079 --> 00:08:33.065 Но пока, все что мы сделали - лишь сделали перевод. Мы обдумали три требования 00:08:33.065 --> 00:08:37.004 которые должен удовлетворять контроллер, и мы написали эти три требования в RSpec. 00:08:37.004 --> 00:08:41.000 Этого достаточно, чтобы выполнить запуск, и у нас есть скринкаст, который я рекомендую вам посмотреть. 00:08:41.000 --> 00:08:44.086 Он соответствует главе книги и в нем как раз все это делается. 00:08:45.000 --> 00:08:48.086 Все, что он делает - запускает три теста, которые ничего не делают. Поэтом вывод RSpec желтый, да? 00:08:48.086 --> 00:08:52.058 Желтый означает "еще не реализовано", точно как и в Cucumber.