[Script Info] Title: [Events] Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text Dialogue: 0,0:00:00.00,0:00:04.04,Default,,0000,0000,0000,,Итак, начнем с этого чрезвычайно практичного подхода к тестированию. Dialogue: 0,0:00:04.04,0:00:08.06,Default,,0000,0000,0000,,Тестирование, я должен признаться, где-то всего лишь три или четыре года назад Dialogue: 0,0:00:08.06,0:00:12.10,Default,,0000,0000,0000,,я принял как религию. И то, что я имею в виду, отчасти так и было: мне говорили, что это Dialogue: 0,0:00:12.10,0:00:16.09,Default,,0000,0000,0000,,важно, я верил, что это важно, я пытался делать это, но именно этот подход действительно изменил Dialogue: 0,0:00:16.09,0:00:21.04,Default,,0000,0000,0000,,мою жизнь. И я надеюсь, изменит и вашу тоже. Dialogue: 0,0:00:21.04,0:00:25.05,Default,,0000,0000,0000,,И нет, я не о выборах, это ни в коем случае. Итак, давайте поговорим о Dialogue: 0,0:00:25.05,0:00:29.02,Default,,0000,0000,0000,,юнит-тестах. В книге есть небольшая вводная о различных видах тестирования. Dialogue: 0,0:00:29.02,0:00:33.01,Default,,0000,0000,0000,,Мы сначала заострим внимание на юнит-тестах и немного на функциональных тестах, Dialogue: 0,0:00:33.01,0:00:36.09,Default,,0000,0000,0000,,и как и со многими другими темами на этих лекциях, есть удобный акроним, который поможет нам запомнить Dialogue: 0,0:00:36.09,0:00:40.06,Default,,0000,0000,0000,,каким должны быть хорошие юнит-тесты. Итак, для начала - они должны быть быстрыми - Dialogue: 0,0:00:40.06,0:00:44.02,Default,,0000,0000,0000,,т.е. не должны выполняться слишком долго; они должны быть независимыми. Dialogue: 0,0:00:44.02,0:00:47.08,Default,,0000,0000,0000,,Это означает, что не должно иметь значения, какой тест надо запускать первым, какой - вторым Dialogue: 0,0:00:47.08,0:00:51.04,Default,,0000,0000,0000,,порядок не важен. Они не должны иметь зависимостей друг от друга. Dialogue: 0,0:00:51.04,0:00:54.09,Default,,0000,0000,0000,,Они должны быть воспроизводимыми - т.е. если тест находит ошибку, он должен находить ее каждый раз. Dialogue: 0,0:00:54.09,0:01:01.05,Default,,0000,0000,0000,,В некоторых случаях этого легко добиться, в других - довольно непросто. Dialogue: 0,0:01:01.05,0:01:05.02,Default,,0000,0000,0000,,Самоконтроль. Вот что означало тестирование не так давно для многих компаний: Dialogue: 0,0:01:05.02,0:01:08.06,Default,,0000,0000,0000,,Программное обеспечение перебрасывали через стену в отдел контроля качества, а там сотрудники отдела Dialogue: 0,0:01:08.06,0:01:12.04,Default,,0000,0000,0000,,вручную понажимают кнопочки в программе, что-то поделают в ней и - "О, оно работает, оно работает" Dialogue: 0,0:01:12.04,0:01:16.01,Default,,0000,0000,0000,,Но мы ведь не хотим больше этого, правда? Тест сам должен знать Dialogue: 0,0:01:16.01,0:01:19.09,Default,,0000,0000,0000,,прошел ли он или сломался. И чтобы принять это решение не должно требоваться человеческое вмешательство. Dialogue: 0,0:01:19.09,0:01:23.06,Default,,0000,0000,0000,,И, наконец, своевременность. Это означает, что тесты должны быть написаны практически одновременно Dialogue: 0,0:01:23.06,0:01:27.01,Default,,0000,0000,0000,,с программным кодом. Если код меняется, тесты также также должны быть изменены. Dialogue: 0,0:01:27.01,0:01:30.09,Default,,0000,0000,0000,,А на самом деле, мы собираемся делать это еще агрессивнее. Бы будем писать тесты в первую очередь, еще до того как будет написан код. Dialogue: 0,0:01:30.09,0:01:34.05,Default,,0000,0000,0000,,То есть настолько своевременно, насколько вы можете. Dialogue: 0,0:01:34.05,0:01:38.02,Default,,0000,0000,0000,,Итак что все это значит? Зачем нам нужны быстрые тесты? Dialogue: 0,0:01:38.02,0:01:42.02,Default,,0000,0000,0000,,Затем, что мы можем запускать какое-то подмножество тестов все время. Dialogue: 0,0:01:42.02,0:01:46.01,Default,,0000,0000,0000,,И если у нас тысячи и тысячи юнит-тестов, что не является необычным даже для проектов средних размеров, Dialogue: 0,0:01:46.01,0:01:49.09,Default,,0000,0000,0000,,это может занять, как вы догадываетесь, минуту или две, чтобы выполнить набор тестов и это будет нас тормозить. Dialogue: 0,0:01:49.09,0:01:53.04,Default,,0000,0000,0000,,Все что нам нужно - возможность быстро запускать только те тесты, которые относятся к конкретному куску кода, Dialogue: 0,0:01:53.04,0:01:57.04,Default,,0000,0000,0000,,над которым мы работаем таким образом, чтобы не сбиваться с ритма. Dialogue: 0,0:01:57.04,0:02:01.03,Default,,0000,0000,0000,,Независимость нужна по той же причине, чтобы мы могли запустить любое подмножество тестов Dialogue: 0,0:02:01.03,0:02:05.03,Default,,0000,0000,0000,,и в любом порядке, в каком захотим. Dialogue: 0,0:02:05.03,0:02:09.01,Default,,0000,0000,0000,,Поэтому, плохо, когда есть множество тестов, которые можно запускать Dialogue: 0,0:02:09.01,0:02:13.02,Default,,0000,0000,0000,,только тогда, когда перед ними запускали какие-либо другие тесты. Dialogue: 0,0:02:13.02,0:02:17.00,Default,,0000,0000,0000,,Воспроизводимость, как вы снова догадываетесь, означает что запустив тест N раз мы получим одинаковые результаты. Dialogue: 0,0:02:17.00,0:02:20.09,Default,,0000,0000,0000,,Если мы хотим локализовать ошибку и включить возможность автоматической отладки, воспроизводимость важна. Dialogue: 0,0:02:20.09,0:02:25.01,Default,,0000,0000,0000,,Самоконтроль: как я уже говорил, человек не должен проверять результат. Dialogue: 0,0:02:25.01,0:02:29.02,Default,,0000,0000,0000,,Это означает, что мы можем иметь тесты, выполняющиеся в фоне все время Dialogue: 0,0:02:29.02,0:02:33.02,Default,,0000,0000,0000,,и когда бы мы не внесли изменения, которые ломают что-то в 25 милях в другом участке кода, Dialogue: 0,0:02:33.02,0:02:37.02,Default,,0000,0000,0000,,какой-нибудь тест обнаружит это и привлечет наше внимание. Dialogue: 0,0:02:37.02,0:02:40.09,Default,,0000,0000,0000,,И, наконец, своевременность. Как я и говорил, мы собираемся использовать разработку через тестирование, Dialogue: 0,0:02:40.09,0:02:45.06,Default,,0000,0000,0000,,в которой тесты пишутся перед тем, как будет написан программный код. Dialogue: 0,0:02:45.06,0:02:49.10,Default,,0000,0000,0000,,Мы будем использовать RSpec, который я рассматриваю как предметно-ориентированный язык для написания тестов. Dialogue: 0,0:02:49.10,0:02:53.09,Default,,0000,0000,0000,,Для тех, кто не знаком с подобными языками, скажу, что в основе это вроде небольшого языка программирования Dialogue: 0,0:02:53.09,0:02:58.01,Default,,0000,0000,0000,,который умеет делать небольшое количество вещей в рамках одной предметной области. Т.е. это не язык общего назначения. Dialogue: 0,0:02:58.02,0:03:02.06,Default,,0000,0000,0000,,На самом деле мы уже видели примеры подобного языка. Миграции являются разновидностью DSL. Dialogue: 0,0:03:02.06,0:03:06.04,Default,,0000,0000,0000,,Это небольшое подмножество операторов, чья единственная работа - описывать изменения в схему базы данных.. Dialogue: 0,0:03:06.04,0:03:10.06,Default,,0000,0000,0000,,Т.о., миграции оказались DSL, который встроен в Ruby, т.е. Dialogue: 0,0:03:10.06,0:03:14.07,Default,,0000,0000,0000,,миграции являются всего лишь кодом на Ruby, но стилизованные под задачи, которые они выполняют. Dialogue: 0,0:03:14.07,0:03:19.02,Default,,0000,0000,0000,,На самом деле мы увидим, что RSpec - похожий пример. Итак, мы будем называть все подобное внутренним DSL. Dialogue: 0,0:03:19.02,0:03:23.05,Default,,0000,0000,0000,,Он реализован внутри другого языка. Регулярные выражения - тоже внутренний DSL. Dialogue: 0,0:03:23.05,0:03:27.09,Default,,0000,0000,0000,,Он как подмножество действий, которые мы можем выполнять в регулярных выражениях. Dialogue: 0,0:03:27.09,0:03:32.06,Default,,0000,0000,0000,,Другой пример - внешний или автономный DSL - это SQL. SQL-запросы к базам данных. Dialogue: 0,0:03:32.06,0:03:36.05,Default,,0000,0000,0000,,Это отдельный язык, и те, кто работали с другими фреймворками Dialogue: 0,0:03:36.05,0:03:40.04,Default,,0000,0000,0000,,перед тем, как перейти к Rails, обычно заканчивали тем, что писали SQL-запросы Dialogue: 0,0:03:40.04,0:03:44.06,Default,,0000,0000,0000,,и затем передавали кому-то, да? Так вот это очень яркий пример работы с разными языками. Dialogue: 0,0:03:44.06,0:03:48.05,Default,,0000,0000,0000,,Итак, в RSpec, каждый тест называется спекой - от "спецификация" (specification). Dialogue: 0,0:03:48.05,0:03:52.04,Default,,0000,0000,0000,,Как ни странно, они размещаются в каталоге, называемом "spec" - потому что нам нравится все делать просто. Dialogue: 0,0:03:52.04,0:03:56.08,Default,,0000,0000,0000,,В Rails есть генератор, "rspec:install", который создает структуру подкаталогов. Dialogue: 0,0:03:56.08,0:04:01.07,Default,,0000,0000,0000,,Все это есть в книге и в последующих демонстрациях, которые мы покажем сегодня, мы подразумеваем, Dialogue: 0,0:04:01.07,0:04:05.09,Default,,0000,0000,0000,,что мы уже выполнили эти подготовительные шаги. Dialogue: 0,0:04:05.09,0:04:10.09,Default,,0000,0000,0000,,Итак, с чего все начинается? Подкаталоги каталога spec организованы так, чтобы отражать структуру Dialogue: 0,0:04:10.09,0:04:14.09,Default,,0000,0000,0000,,нашего приложения. Так вот: в app/models у вас лежат ваши модели, Dialogue: 0,0:04:14.09,0:04:19.06,Default,,0000,0000,0000,,а в spec/models у вас лежат spec-файл для каждой модели. Ничего удивительного. Dialogue: 0,0:04:19.06,0:04:24.02,Default,,0000,0000,0000,,Подобным же образом располагаются спеки для контроллеров. А как насчет представлений (view)? Dialogue: 0,0:04:24.02,0:04:28.06,Default,,0000,0000,0000,,Вообще, мы не будем делать спеки для представлений. Сделать их можно, но будет это несколько Dialogue: 0,0:04:28.06,0:04:33.02,Default,,0000,0000,0000,,кривовато - много из того, что мы хотим проверить в представлении, на самом деле Dialogue: 0,0:04:33.02,0:04:37.09,Default,,0000,0000,0000,,можно проверить в контроллере, и мы увидим это в сегодняшнем примере. Dialogue: 0,0:04:37.09,0:04:42.08,Default,,0000,0000,0000,,К тому же, мы решили, что наш подход для веб-приложений, с которыми напрямую взаимодействуют пользователи Dialogue: 0,0:04:42.08,0:04:46.07,Default,,0000,0000,0000,,- через создание пользовательских историй, которые описывают те части приложения, с которыми взаимодействует заказчик. Dialogue: 0,0:04:46.07,0:04:50.06,Default,,0000,0000,0000,,Таким образом, то, что является частью представления - что должно быть видимым в представлении Dialogue: 0,0:04:50.06,0:04:54.02,Default,,0000,0000,0000,,и то, что может быть нажато и т.д., мы для всего этого будем использовать Cucumber. Dialogue: 0,0:04:54.02,0:04:58.01,Default,,0000,0000,0000,,И в дальнейшем мы так и будем поступать. Dialogue: 0,0:04:58.02,0:05:02.00,Default,,0000,0000,0000,,Таким образом в основном мы будем уделять внимание RSpec с точки зрения Dialogue: 0,0:05:02.00,0:05:05.06,Default,,0000,0000,0000,,написания спецификаций для наших моделей и контроллеров. Dialogue: 0,0:05:05.06,0:05:08.10,Default,,0000,0000,0000,,Итак, давайте начнем с примера новой гипотетической фичи для RottenPotatoes, Dialogue: 0,0:05:08.10,0:05:13.05,Default,,0000,0000,0000,,при помощи которой мы можем добавлять фильмы, используя данные из TMDb. Dialogue: 0,0:05:13.05,0:05:17.10,Default,,0000,0000,0000,,TMDb - реальный сайт. Он вроде IMDb, но только не коммерческий, что-то вроде open-source проекта. Dialogue: 0,0:05:17.10,0:05:22.03,Default,,0000,0000,0000,,Идея в том, что у них есть вся информация о фильмах, и если мы хотим Dialogue: 0,0:05:22.03,0:05:26.06,Default,,0000,0000,0000,,добавить фильм в RottenPotatoes, то почему бы нам просто не скачать нужную информацию оттуда? Dialogue: 0,0:05:26.06,0:05:31.04,Default,,0000,0000,0000,,На деле, когда мы обсуждали пользовательские истории, в одной из них был шаг, в котором говорится Dialogue: 0,0:05:31.04,0:05:36.01,Default,,0000,0000,0000,,"Я заполняю ключевые слова для поиска, Я хочу найти фильм "Начало", Dialogue: 0,0:05:36.01,0:05:40.06,Default,,0000,0000,0000,,и когда я нажимаю кнопку, мне предлагают "Искать на TMDb", не так ли? Dialogue: 0,0:05:40.06,0:05:45.02,Default,,0000,0000,0000,,Таким образом, предполагается, что должна быть кнопка, по нажатию которой наше приложение обратится к TMDb Dialogue: 0,0:05:45.02,0:05:49.03,Default,,0000,0000,0000,,и проверит, есть ли там "Начало", и если да, возьмет информацию о нем оттуда. Dialogue: 0,0:05:49.03,0:05:53.02,Default,,0000,0000,0000,,Вопрос в том, что мы должны сделать. Какой код нужно написать Dialogue: 0,0:05:53.02,0:05:57.00,Default,,0000,0000,0000,,и какой вид тестирования нужно применить. Dialogue: 0,0:05:57.00,0:06:01.02,Default,,0000,0000,0000,,И прежде, чем мы займемся этим, помните наш разговор о "Кулинарии Rails", Dialogue: 0,0:06:01.02,0:06:05.02,Default,,0000,0000,0000,,рецептах, как готовить при помощи Rails? Вспомните, что когда мы добавляем какую-либо новую фичу Dialogue: 0,0:06:05.02,0:06:08.09,Default,,0000,0000,0000,,это означает, что нам нужен новый маршрут (route), новый метод контроллера. Также нам может понадобиться, Dialogue: 0,0:06:08.09,0:06:13.00,Default,,0000,0000,0000,,а может и нет, новое представление. Это зависит от того, может ли эта фича использовать существующее представление Dialogue: 0,0:06:13.00,0:06:17.00,Default,,0000,0000,0000,,или мы должны сделать новое. Эти шаги мы должны выполнять всегда. Dialogue: 0,0:06:17.00,0:06:20.10,Default,,0000,0000,0000,,Итак, давайте займемся всем этим, но будем делать по одному шагу за раз. Dialogue: 0,0:06:20.10,0:06:25.01,Default,,0000,0000,0000,,Ладно, так. Это идея, которую мы будем видеть многократно. Я всего лишь приучаю вас к фразе Dialogue: 0,0:06:25.01,0:06:29.06,Default,,0000,0000,0000,,"Код, который вы бы хотели иметь." Это очень сильная мысль, как только вы к ней привыкнете. Dialogue: 0,0:06:29.06,0:06:33.10,Default,,0000,0000,0000,,Она странно звучит, когда слышите ее первый раз, но она действительно очень сильная. Dialogue: 0,0:06:33.10,0:06:38.03,Default,,0000,0000,0000,,Итак, мы спрашиваем себя: "Хорошо, когда пользователь нажимает на эту кнопку "Искать на TMDb", мы знаем, что Dialogue: 0,0:06:38.03,0:06:42.00,Default,,0000,0000,0000,,где-то должен быть метод контроллера, который получит все то, что было отправлено через форму. Dialogue: 0,0:06:42.00,0:06:45.08,Default,,0000,0000,0000,,Так что должен делать этот метод? Что должен делать метод контроллера, когда получает форму поиска? Dialogue: 0,0:06:45.08,0:06:49.03,Default,,0000,0000,0000,,Если бы нас спросили об этом, и если бы мы написали, что он должен делать на разговорном языке Dialogue: 0,0:06:49.03,0:06:52.06,Default,,0000,0000,0000,,наш ответ звучал бы как "Ладно, смотри, он должен вызывать метод (который мы еще не написали) Dialogue: 0,0:06:52.06,0:06:56.03,Default,,0000,0000,0000,,который сходит на TMDb и поищет там фильм". Разумно. Dialogue: 0,0:06:56.03,0:07:00.01,Default,,0000,0000,0000,,"Если он там найдет фильм, он должен сформировать некоторое представление, чтобы показать результат поиска Dialogue: 0,0:07:00.02,0:07:03.09,Default,,0000,0000,0000,,(и снова, мы пока еще не создали это представление, но логически, это как раз то, что мы собираемся Dialogue: 0,0:07:03.09,0:07:07.04,Default,,0000,0000,0000,,написать)." Сегодня на третий шаг нам времени не хватит, но для неуспешного исхода сценарий таков: Dialogue: 0,0:07:07.04,0:07:11.05,Default,,0000,0000,0000,,"Если фильм не найден, метод должен перенаправить на главную страницу RottenPotatoes и сообщить Dialogue: 0,0:07:11.05,0:07:15.09,Default,,0000,0000,0000,,"Ничего не найдено". И если если вы просмотрите пример в книге, Dialogue: 0,0:07:15.09,0:07:20.00,Default,,0000,0000,0000,,мы на самом деле вариант неуспешного исхода проработали в главе, посвященной BDD. Dialogue: 0,0:07:20.00,0:07:24.01,Default,,0000,0000,0000,,Итак, у нас есть эти два момента. Сосредоточимся на №1 и №2 - это то, что метод контроллера должен делать, Dialogue: 0,0:07:24.01,0:07:28.02,Default,,0000,0000,0000,,и вот как мы будем это описывать. Где моя мышь? Итак, поехали. Dialogue: 0,0:07:28.02,0:07:32.08,Default,,0000,0000,0000,,Рассмотрим, как мы опишем все эти требования при помощи RSpec. Итак... Dialogue: 0,0:07:32.08,0:07:38.10,Default,,0000,0000,0000,,Ничего особенного здесь ни происходит, видите. Все достаточно просто, не так ли? Dialogue: 0,0:07:38.10,0:07:43.08,Default,,0000,0000,0000,,И это корректный код RSpec. SpecHelper - это просто файл, который RSpec создает как часть шага установки. Dialogue: 0,0:07:43.08,0:07:48.08,Default,,0000,0000,0000,,Он просто выполняет кое-какие вещи, гарантирующие, что все необходимое подгружено. Dialogue: 0,0:07:48.08,0:07:52.09,Default,,0000,0000,0000,,И собираемся начать, спросив "Что должен проверять этот тест?" Dialogue: 0,0:07:52.09,0:07:57.04,Default,,0000,0000,0000,,Или, мы собираемся описать поведение MoviesController. Dialogue: 0,0:07:57.04,0:08:01.06,Default,,0000,0000,0000,,У контроллера есть много вариантов поведения, но нас волнует одно конкретное, а именно Dialogue: 0,0:08:01.06,0:08:05.06,Default,,0000,0000,0000,,поведение во время поиска на TMDb. Итак, мы можем представить, для каждого поведения в контроллере Dialogue: 0,0:08:05.06,0:08:09.05,Default,,0000,0000,0000,,как растет наша спека, мы будем добавлять больше описательных блоков внутри этого внешнего Dialogue: 0,0:08:09.05,0:08:13.05,Default,,0000,0000,0000,,"describe MoviesController", и так далее вкладывать, вкладывать, вкладывать. Dialogue: 0,0:08:13.05,0:08:17.01,Default,,0000,0000,0000,,Все, что я здесь сделал - это переписал те три требования, о которых мы говорили. Dialogue: 0,0:08:17.01,0:08:21.09,Default,,0000,0000,0000,,Итак, "it" - это на самом деле вызов метода в RSpec. Он принимает аргумент, который является Dialogue: 0,0:08:21.09,0:08:25.08,Default,,0000,0000,0000,,строкой, описывающий, что должно произойти. И как мы увидим, Dialogue: 0,0:08:25.08,0:08:29.08,Default,,0000,0000,0000,,он принимает также второй аргумент, который является процедурой, которая и выполняет тест. Dialogue: 0,0:08:29.08,0:08:33.06,Default,,0000,0000,0000,,Но пока, все что мы сделали - лишь сделали перевод. Мы обдумали три требования Dialogue: 0,0:08:33.06,0:08:37.00,Default,,0000,0000,0000,,которые должен удовлетворять контроллер, и мы написали эти три требования в RSpec. Dialogue: 0,0:08:37.00,0:08:41.00,Default,,0000,0000,0000,,Этого достаточно, чтобы выполнить запуск, и у нас есть скринкаст, который я рекомендую вам посмотреть. Dialogue: 0,0:08:41.00,0:08:44.09,Default,,0000,0000,0000,,Он соответствует главе книги и в нем как раз все это делается. Dialogue: 0,0:08:45.00,0:08:48.09,Default,,0000,0000,0000,,Все, что он делает - запускает три теста, которые ничего не делают. Поэтом вывод RSpec желтый, да? Dialogue: 0,0:08:48.09,0:08:52.06,Default,,0000,0000,0000,,Желтый означает "еще не реализовано", точно как и в Cucumber.