Разработка через тестирование

Примите отказ с помощью разработки через тестирование с использованием RSpec в Rails

Практическое руководство, которое поможет вам разобраться в образе мышления разработки, ориентированной на тестирование (отказ), с использованием RSpec в Ruby on Rails.

Что такое разработка через тестирование, TDD

Короче говоря, разработка тест-драйва или TDD - это методология разработки, при которой тесты пишутся до кода реализации. Тесты пишутся неудачно, и разработчики постараются их пройти, написав необходимый код. Этот процесс часто называют циклом «Красный, зеленый, рефакторинг».

«Написание тестов - пустая трата времени»

Именно об этом я подумал, когда впервые научился писать тесты. Когда я начинал, я был внештатным разработчиком. Мне не хватало денег, и последнее, что мне нужно, - это больше работать, чтобы добиться цели. Написание тестов для меня было не только трудной для понимания концепцией, мне требовалось вдвое больше усилий, чтобы что-то сделать. Итак, почему люди вообще пишут тесты?

Только когда я начал создавать большие приложения с командой инженеров, я наконец понял суть и важность написания тестов.

«Почему» важно

Саймон Синек в своей книге «Начни с того, почему» лучше всего объясняет это с помощью «Золотого круга». Должен быть фактор притяжения, который дает нам достаточно оснований для принятия новой привычки или процесса. Вот 3 основные причины, почему:

1. Рефакторинг с уверенностью

Давайте на секунду забудем о программировании и представим, что вы подрядчик, которому поручено внести изменения в существующее здание. Вы удалили столб, и следующее, что вы знаете, здание полностью рушится на ваших глазах. Если бы вы знали, что столб был одной из основных опорных конструкций, от которых зависят другие, ваше здание все равно бы стояло.

Код имеет зависимости точно так же, как опорные столбы в здании. Структуры полагаются друг на друга, чтобы ваше приложение работало. Как разработчики, нам необходимо постоянно реорганизовывать наш код. Тестирование основных частей нашего приложения служит защитой от критических изменений, которые могут быть фатальными.

2. Четкий код

Легко заблудиться в сорняках, если вы по колено в создании объекта. Без четкого плана выполнения мы часто упускаем сценарии или контексты, которые необходимо выполнить при создании функции.

Написание тестов в первую очередь дает нам возможность сделать шаг назад и «обдумать» все возможные сценарии с ожидаемыми входными данными и результатами, прежде чем писать какой-либо код. Он служит своего рода «планом», который помогает поэтапно направлять разработку функции, обеспечивая выполнение всех бизнес-целей / задач, даже новых, которые вы обнаружите в процессе написания.

3. Самодокументирование

Не знаю, как вы, но я не люблю держать вещи в голове надолго. Я забываю вещи, а другие не могут читать мои мысли. Тесты служат справочником, чтобы помочь вам и вашим товарищам по команде понять, в определенной степени, почему функция написана так, как они есть. Комментарии могут доходить только до этого момента, если вам не нравится писать роман рядом с определениями ваших методов.

Хватит причин, по которым стоит писать тесты. Давайте сразу перейдем к основным концепциям того, как вам следует относиться к тестам.

Речь идет об установлении ожиданий

Я уверен, что вы слышали о термине assertion, когда речь идет о тестах. В Википедии, касающейся разработки программного обеспечения, утверждение определяется как:

предикат (булевозначная функция в пространстве состояний, обычно выражаемый как логическое предложение с использованием переменных программы), связанный с точкой в программа, которая всегда должна оцениваться как истина в этот момент выполнения кода.

Не знаю, как вы, но это меня чертовски смущает, когда я впервые это читаю. Вот лучший вариант:

Думайте об утверждениях как об ожиданиях.

Мы многого ожидаем от жизни. Я ожидаю, что мой торт получится идеальным, если я буду следовать инструкциям по выпечке. Я рассчитываю похудеть, ЕСЛИ буду заниматься спортом. Я ожидаю, что мои дети перестанут кричать, ЕСЛИ я дам им мороженое. Это естественный мыслительный процесс, когда мы ожидаем определенных результатов от нашего приложения при соблюдении определенных критериев. Однако, в отличие от реальных ожиданий, вы полностью контролируете ожидаемый результат в своем приложении. Если он предназначен для вывода чего-либо, вы можете сделать это.

Написание тестов - это запись ожиданий. RSpec дает полную ясность в своем собственном DSL:

expect(page).to have_text(‘Hello World’)

Я ожидаю, что где-то на странице будет текст «Hello World». Все, что мне нужно сделать сейчас, это написать код, который будет печатать текст «Hello World» на странице, когда он будет отрисован. Это простая концепция.

RSpec с Ruby on Rails

RSpec - это хорошо известная среда тестирования, написанная на Ruby, которую я обычно использую для тестирования своего приложения rails. Чтобы использовать его, вам нужно будет включить гем rspec-rails в свой Gemfile. Ниже кратко описано, как настроить приложение Rails:

# Spin up a new rails app and make sure to create the db and schema                       rails new example-app -T -d postgresql
# Add and install the following gems to your Gemfile                       group :development, :test do
  ...                         
  gem 'rspec-rails'
  gem 'capybara'
  gem 'selenium-webdriver'
end
# Run Rspec generator
rails g rspec:install
# Require the following at the top of rails_helper.rb file
require 'rspec/rails'
require 'capybara/rspec'
# Create your spec files under the respective spec folders
spec/controllers/posts_controller.rb
spec/system/user_create_post.rb
# Run your spec files and watch them fail
rspec spec/system/user_create_post.rb

Гем rspec-rails по существу определяет 10 различных типов спецификаций для тестирования различных частей типичного приложения Rails. Вот иллюстрация основных типов, которая поможет вам представить их в перспективе:

Характеристики или характеристики системы

Давайте разберемся. Тесты функций, системные тесты, интеграционные тесты, приемочные тесты, сквозные тесты - все они одинаковы. Его цель - помочь вам протестировать ваше приложение с точки зрения реального пользователя (т.е. перейти на страницу, заполнить текстовое поле, щелкнуть кнопку и т. Д.). Моделируя взаимодействие реального пользователя, тестируются все ваши маршруты, представления, контроллеры и модели.

Начиная с Rails 5.1+, с введением SystemTestCase, теперь рекомендуется писать спецификации системы вместо спецификаций функций.

Запросить спецификации

README Rails Rspec объясняет это лучше всего: спецификации запроса предназначены для тестирования вашего приложения с точки зрения клиента машины . Они начинаются с HTTP-запроса и заканчиваются HTTP-ответом, поэтому они быстрее, чем спецификации функций, но не проверяют пользовательский интерфейс или JavaScript вашего приложения.

Он идеально подходит для тестирования запросов и ответов API, которые я использую в большинстве случаев.

Характеристики контроллера

Спецификация контроллера - это не что иное, как юнит-тесты для контроллеров. Он позволяет моделировать HTTP-запросы и указывать ожидаемые результаты, такие как:

  • должен отображать шаблон
  • должен перенаправить на другую страницу
  • некоторая переменная экземпляра назначается для использования в представлении
  • куки устанавливаются и отправляются обратно в ответ

Одна из самых распространенных ошибок, которые я нахожу в спецификациях контроллеров, - это чрезмерные ожидания результатов. Ожидание создания записи в спецификации контроллера может быть приемлемым, если ваш код делает именно это. Что неприемлемо, так это когда вы начинаете писать ожидания для почтовых программ, уведомлений и даже компонентов просмотра (включая render_views в спецификации вашего контроллера). Обычно это показатель раздутого контроллера, который требует рефакторинга.

Модель / Технические характеристики устройства

Спецификация модели - это просто модульные тесты для моделей. В типичном приложении Rails это позволяет вам описывать ожидания:

  • Связи с другими моделями (например, has_many, own_to)
  • Такие проверки, как уникальность, присутствие и т. Д.
  • Входные данные и результаты методов, определяющих поведение вашей модели.

Характеристики маршрутизации

Спецификация маршрутизации в основном позволяет вам ожидать / утверждать, что ваши HTTP-запросы будут перенаправлены на правильный контроллер и действие. Это может оказаться полезным, когда у вас есть настраиваемые маршруты, которые могут стать довольно сложными, если не будут управляться / тестироваться.

Посмотреть спецификации

Я почти никогда не пишу спецификации представлений, но для полноты картины решил включить их сюда. Как следует из названия, спецификации представления позволяют ожидать, что ваше представление / страница будет отображать определенный контент при визуализации без фактического вызова ваших контроллеров.

Внешний подход к тестированию приложения на Rails

Теперь, когда вы поняли, что такое TDD, и, надеюсь, получили достаточно оснований для внедрения тестирования в первую очередь в своем следующем проекте, следующий логический шаг - выяснить, как на самом деле протестировать ваше приложение Rails. Я не говорю о том, как писать тесты, я говорю о том, как научиться тестировать. Есть большая разница. Это все равно, что знать, как написать кучу английских слов, а не знать, как составить предложение с идеальной грамматикой.

Как показано на диаграмме выше, внешний вид - это распространенный подход, при котором сначала пишутся высокоуровневые тесты функций, описывающие, как функция должна работать. Работа над тем, чтобы оправдать эти ожидания высокого уровня, в свою очередь, приведет нас к тестам более низкого уровня, таким как тесты маршрутизации, контроллеров и моделей. Написание кода реализации для прохождения тестов на каждом уровне поможет нам в конечном итоге создать желаемую функцию.

Давайте рассмотрим несколько примеров из реальной жизни:

Представьте, что мы работаем над следующим классным приложением для блога, и вас просят создать функцию для публикации статей:

As a user, I can post a blog article so that I can share my thoughts with my friends

Прежде чем сразу перейти к написанию кода маршрутов, контроллера, модели и представления, мы пишем высокоуровневую систему или спецификацию функции, чтобы конкретизировать, как пользователь будет взаимодействовать с нашей функцией для создания сообщения в блоге.

# spec/system/create_post_spec.rb
require 'rails_helper'
describe "Create post", type: :system do
    it "enables me to create new post" do
        visit "/posts/new"
        fill_in  'Title', with: "First Blog Post"
        fill_in  'Body',  with: "Test driven development is awesome!"
        click_on 'Submit'
        expect(page).to have_text("First Blog Post"
    end
end

Запуск спецификации сейчас с rspec spec/system/create_post_spec.rb вызовет ошибку отсутствия маршрутов. Потрясающий! Теперь давайте добавим соответствующий маршрут в наш файл маршрутов.

Rails.application.routes.draw do
    resources :posts
end

Повторный запуск спецификации вызовет новую ошибку, указывающую на то, что нам не хватает класса контроллера. Для исправления этой ошибки вам просто потребуется создать недостающий контроллер.

Как вы уже можете сказать, повторение этого цикла приведет к новым сбоям, и по мере того, как вы будете работать над исправлением этих сбоев тестов, в конечном итоге вы в конечном итоге напишете необходимые маршруты, контроллер, модель и код представления для завершения своей функции.

Резюме

Как общество, мы воспринимаем неудачи как нечто негативное. С юных лет нас учат избегать неудач. С другой стороны, разработка, основанная на тестировании, переворачивает это представление и требует, чтобы мы принимали неудачи, постоянно писали тесты, которые терпят неудачу, и использовали их в качестве ориентира для создания нашей функции. Я считаю, что это прекрасный образ мышления и процесс, который можно использовать как в нашей работе, так и в жизни.

Как однажды написал Джефф Безос в письме акционерам:

Неудача является неотъемлемой частью изобретения. Это не обязательно. Мы понимаем это и верим в неудачу на ранней стадии и повторение, пока не получим все правильно.

Надеюсь, вы нашли эту статью полезной. В следующей части я поделюсь тактическими примерами того, как я использую let, contexts, теги примеров, Rspecs, различные помощники по ожиданиям и драгоценные камни, такие как webmocks, для создания заглушек и тестирования внешних запросов и многое другое. Дайте мне знать в разделе комментариев, если у вас есть какие-нибудь интересные темы, которые вы хотите, чтобы я затронул. До следующего раза, берегись!