1 00:00:00,000 --> 00:00:04,037 Итак, начнем с этого чрезвычайно практичного подхода к тестированию. 2 00:00:04,037 --> 00:00:08,064 Тестирование, я должен признаться, где-то всего лишь три или четыре года назад 3 00:00:08,064 --> 00:00:12,096 я принял как религию. И то, что я имею в виду, отчасти так и было: мне говорили, что это 4 00:00:12,096 --> 00:00:16,092 важно, я верил, что это важно, я пытался делать это, но именно этот подход действительно изменил 5 00:00:16,092 --> 00:00:21,035 мою жизнь. И я надеюсь, изменит и вашу тоже. 6 00:00:21,035 --> 00:00:25,047 И нет, я не о выборах, это ни в коем случае. Итак, давайте поговорим о 7 00:00:25,047 --> 00:00:29,021 юнит-тестах. В книге есть небольшая вводная о различных видах тестирования. 8 00:00:29,021 --> 00:00:33,010 Мы сначала заострим внимание на юнит-тестах и немного на функциональных тестах, 9 00:00:33,010 --> 00:00:36,089 и как и со многими другими темами на этих лекциях, есть удобный акроним, который поможет нам запомнить 10 00:00:36,089 --> 00:00:40,064 каким должны быть хорошие юнит-тесты. Итак, для начала - они должны быть быстрыми - 11 00:00:40,064 --> 00:00:44,020 т.е. не должны выполняться слишком долго; они должны быть независимыми. 12 00:00:44,020 --> 00:00:47,077 Это означает, что не должно иметь значения, какой тест надо запускать первым, какой - вторым 13 00:00:47,077 --> 00:00:51,042 порядок не важен. Они не должны иметь зависимостей друг от друга. 14 00:00:51,042 --> 00:00:54,093 Они должны быть воспроизводимыми - т.е. если тест находит ошибку, он должен находить ее каждый раз. 15 00:00:54,093 --> 00:01:01,047 В некоторых случаях этого легко добиться, в других - довольно непросто. 16 00:01:01,047 --> 00:01:05,018 Самоконтроль. Вот что означало тестирование не так давно для многих компаний: 17 00:01:05,018 --> 00:01:08,065 Программное обеспечение перебрасывали через стену в отдел контроля качества, а там сотрудники отдела 18 00:01:08,065 --> 00:01:12,044 вручную понажимают кнопочки в программе, что-то поделают в ней и - "О, оно работает, оно работает" 19 00:01:12,044 --> 00:01:16,010 Но мы ведь не хотим больше этого, правда? Тест сам должен знать 20 00:01:16,010 --> 00:01:19,094 прошел ли он или сломался. И чтобы принять это решение не должно требоваться человеческое вмешательство. 21 00:01:19,094 --> 00:01:23,059 И, наконец, своевременность. Это означает, что тесты должны быть написаны практически одновременно 22 00:01:23,059 --> 00:01:27,011 с программным кодом. Если код меняется, тесты также также должны быть изменены. 23 00:01:27,011 --> 00:01:30,090 А на самом деле, мы собираемся делать это еще агрессивнее. Бы будем писать тесты в первую очередь, еще до того как будет написан код. 24 00:01:30,090 --> 00:01:34,046 То есть настолько своевременно, насколько вы можете. 25 00:01:34,046 --> 00:01:38,022 Итак что все это значит? Зачем нам нужны быстрые тесты? 26 00:01:38,022 --> 00:01:42,022 Затем, что мы можем запускать какое-то подмножество тестов все время. 27 00:01:42,022 --> 00:01:46,007 И если у нас тысячи и тысячи юнит-тестов, что не является необычным даже для проектов средних размеров, 28 00:01:46,007 --> 00:01:49,087 это может занять, как вы догадываетесь, минуту или две, чтобы выполнить набор тестов и это будет нас тормозить. 29 00:01:49,087 --> 00:01:53,043 Все что нам нужно - возможность быстро запускать только те тесты, которые относятся к конкретному куску кода, 30 00:01:53,043 --> 00:01:57,038 над которым мы работаем таким образом, чтобы не сбиваться с ритма. 31 00:01:57,038 --> 00:02:01,033 Независимость нужна по той же причине, чтобы мы могли запустить любое подмножество тестов 32 00:02:01,033 --> 00:02:05,027 и в любом порядке, в каком захотим. 33 00:02:05,027 --> 00:02:09,011 Поэтому, плохо, когда есть множество тестов, которые можно запускать 34 00:02:09,011 --> 00:02:13,023 только тогда, когда перед ними запускали какие-либо другие тесты. 35 00:02:13,023 --> 00:02:17,001 Воспроизводимость, как вы снова догадываетесь, означает что запустив тест N раз мы получим одинаковые результаты. 36 00:02:17,001 --> 00:02:20,089 Если мы хотим локализовать ошибку и включить возможность автоматической отладки, воспроизводимость важна. 37 00:02:20,089 --> 00:02:25,007 Самоконтроль: как я уже говорил, человек не должен проверять результат. 38 00:02:25,007 --> 00:02:29,019 Это означает, что мы можем иметь тесты, выполняющиеся в фоне все время 39 00:02:29,019 --> 00:02:33,022 и когда бы мы не внесли изменения, которые ломают что-то в 25 милях в другом участке кода, 40 00:02:33,022 --> 00:02:37,015 какой-нибудь тест обнаружит это и привлечет наше внимание. 41 00:02:37,015 --> 00:02:40,093 И, наконец, своевременность. Как я и говорил, мы собираемся использовать разработку через тестирование, 42 00:02:40,093 --> 00:02:45,062 в которой тесты пишутся перед тем, как будет написан программный код. 43 00:02:45,062 --> 00:02:49,096 Мы будем использовать RSpec, который я рассматриваю как предметно-ориентированный язык для написания тестов. 44 00:02:49,096 --> 00:02:53,086 Для тех, кто не знаком с подобными языками, скажу, что в основе это вроде небольшого языка программирования 45 00:02:53,086 --> 00:02:58,008 который умеет делать небольшое количество вещей в рамках одной предметной области. Т.е. это не язык общего назначения. 46 00:02:58,024 --> 00:03:02,057 На самом деле мы уже видели примеры подобного языка. Миграции являются разновидностью DSL. 47 00:03:02,057 --> 00:03:06,041 Это небольшое подмножество операторов, чья единственная работа - описывать изменения в схему базы данных.. 48 00:03:06,041 --> 00:03:10,057 Т.о., миграции оказались DSL, который встроен в Ruby, т.е. 49 00:03:10,057 --> 00:03:14,074 миграции являются всего лишь кодом на Ruby, но стилизованные под задачи, которые они выполняют. 50 00:03:14,074 --> 00:03:19,016 На самом деле мы увидим, что RSpec - похожий пример. Итак, мы будем называть все подобное внутренним DSL. 51 00:03:19,016 --> 00:03:23,047 Он реализован внутри другого языка. Регулярные выражения - тоже внутренний DSL. 52 00:03:23,047 --> 00:03:27,088 Он как подмножество действий, которые мы можем выполнять в регулярных выражениях. 53 00:03:27,088 --> 00:03:32,055 Другой пример - внешний или автономный DSL - это SQL. SQL-запросы к базам данных. 54 00:03:32,055 --> 00:03:36,047 Это отдельный язык, и те, кто работали с другими фреймворками 55 00:03:36,047 --> 00:03:40,039 перед тем, как перейти к Rails, обычно заканчивали тем, что писали SQL-запросы 56 00:03:40,039 --> 00:03:44,056 и затем передавали кому-то, да? Так вот это очень яркий пример работы с разными языками. 57 00:03:44,056 --> 00:03:48,047 Итак, в RSpec, каждый тест называется спекой - от "спецификация" (specification). 58 00:03:48,047 --> 00:03:52,041 Как ни странно, они размещаются в каталоге, называемом "spec" - потому что нам нравится все делать просто. 59 00:03:52,041 --> 00:03:56,076 В Rails есть генератор, "rspec:install", который создает структуру подкаталогов. 60 00:03:56,076 --> 00:04:01,066 Все это есть в книге и в последующих демонстрациях, которые мы покажем сегодня, мы подразумеваем, 61 00:04:01,066 --> 00:04:05,090 что мы уже выполнили эти подготовительные шаги. 62 00:04:05,090 --> 00:04:10,086 Итак, с чего все начинается? Подкаталоги каталога spec организованы так, чтобы отражать структуру 63 00:04:10,086 --> 00:04:14,094 нашего приложения. Так вот: в app/models у вас лежат ваши модели, 64 00:04:14,094 --> 00:04:19,061 а в spec/models у вас лежат spec-файл для каждой модели. Ничего удивительного. 65 00:04:19,061 --> 00:04:24,016 Подобным же образом располагаются спеки для контроллеров. А как насчет представлений (view)? 66 00:04:24,016 --> 00:04:28,060 Вообще, мы не будем делать спеки для представлений. Сделать их можно, но будет это несколько 67 00:04:28,060 --> 00:04:33,021 кривовато - много из того, что мы хотим проверить в представлении, на самом деле 68 00:04:33,021 --> 00:04:37,094 можно проверить в контроллере, и мы увидим это в сегодняшнем примере. 69 00:04:37,094 --> 00:04:42,084 К тому же, мы решили, что наш подход для веб-приложений, с которыми напрямую взаимодействуют пользователи 70 00:04:42,084 --> 00:04:46,073 - через создание пользовательских историй, которые описывают те части приложения, с которыми взаимодействует заказчик. 71 00:04:46,073 --> 00:04:50,058 Таким образом, то, что является частью представления - что должно быть видимым в представлении 72 00:04:50,058 --> 00:04:54,019 и то, что может быть нажато и т.д., мы для всего этого будем использовать Cucumber. 73 00:04:54,019 --> 00:04:58,009 И в дальнейшем мы так и будем поступать. 74 00:04:58,023 --> 00:05:02,003 Таким образом в основном мы будем уделять внимание RSpec с точки зрения 75 00:05:02,003 --> 00:05:05,060 написания спецификаций для наших моделей и контроллеров. 76 00:05:05,060 --> 00:05:08,095 Итак, давайте начнем с примера новой гипотетической фичи для RottenPotatoes, 77 00:05:08,095 --> 00:05:13,047 при помощи которой мы можем добавлять фильмы, используя данные из TMDb. 78 00:05:13,047 --> 00:05:17,098 TMDb - реальный сайт. Он вроде IMDb, но только не коммерческий, что-то вроде open-source проекта. 79 00:05:17,098 --> 00:05:22,028 Идея в том, что у них есть вся информация о фильмах, и если мы хотим 80 00:05:22,028 --> 00:05:26,058 добавить фильм в RottenPotatoes, то почему бы нам просто не скачать нужную информацию оттуда? 81 00:05:26,058 --> 00:05:31,039 На деле, когда мы обсуждали пользовательские истории, в одной из них был шаг, в котором говорится 82 00:05:31,039 --> 00:05:36,011 "Я заполняю ключевые слова для поиска, Я хочу найти фильм "Начало", 83 00:05:36,011 --> 00:05:40,059 и когда я нажимаю кнопку, мне предлагают "Искать на TMDb", не так ли? 84 00:05:40,059 --> 00:05:45,019 Таким образом, предполагается, что должна быть кнопка, по нажатию которой наше приложение обратится к TMDb 85 00:05:45,019 --> 00:05:49,033 и проверит, есть ли там "Начало", и если да, возьмет информацию о нем оттуда. 86 00:05:49,033 --> 00:05:53,020 Вопрос в том, что мы должны сделать. Какой код нужно написать 87 00:05:53,020 --> 00:05:57,003 и какой вид тестирования нужно применить. 88 00:05:57,003 --> 00:06:01,016 И прежде, чем мы займемся этим, помните наш разговор о "Кулинарии Rails", 89 00:06:01,016 --> 00:06:05,019 рецептах, как готовить при помощи Rails? Вспомните, что когда мы добавляем какую-либо новую фичу 90 00:06:05,019 --> 00:06:08,088 это означает, что нам нужен новый маршрут (route), новый метод контроллера. Также нам может понадобиться, 91 00:06:08,088 --> 00:06:13,001 а может и нет, новое представление. Это зависит от того, может ли эта фича использовать существующее представление 92 00:06:13,001 --> 00:06:17,000 или мы должны сделать новое. Эти шаги мы должны выполнять всегда. 93 00:06:17,000 --> 00:06:20,095 Итак, давайте займемся всем этим, но будем делать по одному шагу за раз. 94 00:06:20,095 --> 00:06:25,012 Ладно, так. Это идея, которую мы будем видеть многократно. Я всего лишь приучаю вас к фразе 95 00:06:25,012 --> 00:06:29,060 "Код, который вы бы хотели иметь." Это очень сильная мысль, как только вы к ней привыкнете. 96 00:06:29,060 --> 00:06:33,098 Она странно звучит, когда слышите ее первый раз, но она действительно очень сильная. 97 00:06:33,098 --> 00:06:38,028 Итак, мы спрашиваем себя: "Хорошо, когда пользователь нажимает на эту кнопку "Искать на TMDb", мы знаем, что 98 00:06:38,028 --> 00:06:42,004 где-то должен быть метод контроллера, который получит все то, что было отправлено через форму. 99 00:06:42,004 --> 00:06:45,076 Так что должен делать этот метод? Что должен делать метод контроллера, когда получает форму поиска? 100 00:06:45,076 --> 00:06:49,030 Если бы нас спросили об этом, и если бы мы написали, что он должен делать на разговорном языке 101 00:06:49,030 --> 00:06:52,061 наш ответ звучал бы как "Ладно, смотри, он должен вызывать метод (который мы еще не написали) 102 00:06:52,061 --> 00:06:56,033 который сходит на TMDb и поищет там фильм". Разумно. 103 00:06:56,033 --> 00:07:00,009 "Если он там найдет фильм, он должен сформировать некоторое представление, чтобы показать результат поиска 104 00:07:00,023 --> 00:07:03,094 (и снова, мы пока еще не создали это представление, но логически, это как раз то, что мы собираемся 105 00:07:03,094 --> 00:07:07,039 написать)." Сегодня на третий шаг нам времени не хватит, но для неуспешного исхода сценарий таков: 106 00:07:07,039 --> 00:07:11,048 "Если фильм не найден, метод должен перенаправить на главную страницу RottenPotatoes и сообщить 107 00:07:11,048 --> 00:07:15,086 "Ничего не найдено". И если если вы просмотрите пример в книге, 108 00:07:15,086 --> 00:07:20,003 мы на самом деле вариант неуспешного исхода проработали в главе, посвященной BDD. 109 00:07:20,003 --> 00:07:24,014 Итак, у нас есть эти два момента. Сосредоточимся на №1 и №2 - это то, что метод контроллера должен делать, 110 00:07:24,014 --> 00:07:28,025 и вот как мы будем это описывать. Где моя мышь? Итак, поехали. 111 00:07:28,025 --> 00:07:32,075 Рассмотрим, как мы опишем все эти требования при помощи RSpec. Итак... 112 00:07:32,075 --> 00:07:38,096 Ничего особенного здесь ни происходит, видите. Все достаточно просто, не так ли? 113 00:07:38,096 --> 00:07:43,081 И это корректный код RSpec. SpecHelper - это просто файл, который RSpec создает как часть шага установки. 114 00:07:43,081 --> 00:07:48,083 Он просто выполняет кое-какие вещи, гарантирующие, что все необходимое подгружено. 115 00:07:48,083 --> 00:07:52,089 И собираемся начать, спросив "Что должен проверять этот тест?" 116 00:07:52,089 --> 00:07:57,037 Или, мы собираемся описать поведение MoviesController. 117 00:07:57,037 --> 00:08:01,059 У контроллера есть много вариантов поведения, но нас волнует одно конкретное, а именно 118 00:08:01,059 --> 00:08:05,065 поведение во время поиска на TMDb. Итак, мы можем представить, для каждого поведения в контроллере 119 00:08:05,065 --> 00:08:09,050 как растет наша спека, мы будем добавлять больше описательных блоков внутри этого внешнего 120 00:08:09,050 --> 00:08:13,049 "describe MoviesController", и так далее вкладывать, вкладывать, вкладывать. 121 00:08:13,049 --> 00:08:17,009 Все, что я здесь сделал - это переписал те три требования, о которых мы говорили. 122 00:08:17,009 --> 00:08:21,093 Итак, "it" - это на самом деле вызов метода в RSpec. Он принимает аргумент, который является 123 00:08:21,093 --> 00:08:25,085 строкой, описывающий, что должно произойти. И как мы увидим, 124 00:08:25,085 --> 00:08:29,079 он принимает также второй аргумент, который является процедурой, которая и выполняет тест. 125 00:08:29,079 --> 00:08:33,065 Но пока, все что мы сделали - лишь сделали перевод. Мы обдумали три требования 126 00:08:33,065 --> 00:08:37,004 которые должен удовлетворять контроллер, и мы написали эти три требования в RSpec. 127 00:08:37,004 --> 00:08:41,000 Этого достаточно, чтобы выполнить запуск, и у нас есть скринкаст, который я рекомендую вам посмотреть. 128 00:08:41,000 --> 00:08:44,086 Он соответствует главе книги и в нем как раз все это делается. 129 00:08:45,000 --> 00:08:48,086 Все, что он делает - запускает три теста, которые ничего не делают. Поэтом вывод RSpec желтый, да? 130 00:08:48,086 --> 00:08:52,058 Желтый означает "еще не реализовано", точно как и в Cucumber.