Rails, SRP и юнит-тесты

преамбула

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

Но я сталкиваюсь с проблемами. Я подписался и смотрел множество экранных передач "Уничтожить все программное обеспечение", и в теории мне нравится то, что он проповедует, но у меня возникают проблемы с тем, чтобы это работало на практике.

Проблема под рукой

Я знаю, что основной функцией моего приложения будет поиск профилей в зависимости от местоположения. Поэтому я пишу простую функцию Cucumber для этого (намеренно оставляя на некоторое время маршруты / контроллеры и т. Д., Чтобы упростить задачу под рукой).

search.feature:

Feature: Search for profiles

  Scenario: By zipcode
    When I search for 90210
    Then I will see profiles near 90210

search_steps.rb:

When /^I search for 90210$/ do
  @profiles = ProfileSearch.new.near_location(90210).all
end

Then /^I will see profiles near 90210$/ do
  pending # express the regexp above with the code you wish you had
end

Нет проблем, теперь о спецификациях:

profile_search_spec.rb:

require_relative '../../app/services/profile_search'

describe ProfileSearch do  
  it "finds nearby profiles"
  it "does not find far away profiles"
end

profile_search.rb:

class ProfileSearch
end

Я использовал класс ProfileSearch по нескольким причинам.

  1. Похоже, что правильно сделать так, чтобы как можно больше бизнес-логики выходило за пределы ActiveRecord (принцип единой ответственности).
  2. PORO делают для более быстрых тестов (без загрузки Rails).
  3. Я планирую использовать ElasticSearch или Solr в ближайшем будущем и хотел бы, чтобы интерфейс оставался прежним.

Я не совсем уверен, что делать дальше. ProfileSearch очевидно, зависит от Profile модель, и я совершенно уверен, что это будет ActiveRecord.

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

Или я должен использовать заглушки / издевательства для всех звонков Profile в моем ProfileSearch spec и убедитесь, что правильные методы вызываются? Это также кажется неправильным, потому что тогда я бы не стал тестировать поведение, и мне пришлось бы переписывать тест при переключении на Solr или ElasticSearch, даже если ожидалось бы то же поведение.

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

Или... f * ck это и бросить все в модели ActiveRecord:\

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

Чтобы ты делал?

1 ответ

Сначала вам нужно выбрать подход к тестированию:

  • Огурец предназначен для тестирования BDD (снаружи). Сценарии Cucumber предназначены для тренировки всего вашего стека (веб-страницы, контроллеров, моделей, базы данных - или любого другого стека), а не для использования в качестве модульных тестов, как вы, кажется, делаете выше. Так что, если первое, что вы пишете, это сценарий с огурцом, вам нужно вызвать все ваши слои, чтобы этот первый сценарий прошел. Это то, что я делаю, будь то со спецификациями Cucumber (мой любимый) или RSpec. После того, как вы это сделаете, вы сможете выполнить рефакторинг в соответствии с любыми критериями проектирования, и сможете протестировать подробные требования с помощью модульных тестов.

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

В любом случае, ваша цель ProfileSearch класс, который возвращает Profile экземпляры, но не обещают, что они являются объектами ActiveRecord или чем-то еще, специфичным для персистентности. То, как вы обрабатываете эти классы в тестах, зависит от типа теста:

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

  • В модульных тестах классов, которые используют ProfileSearchВы можете заглушить его и вернуть PORO для скорости. (Если вы используете rspec-rails, require 'spec_helper' и не rails_helper в этих спецификациях, чтобы избежать загрузки Rails, когда вы запускаете их сами.)

  • Самый простой способ сделать юнит-тесты ProfileSearch Сам проход, вероятно, просто сделать Profile модель ActiveRecord и иметь ProfileSearch использование ProfileActiveRecord и возвращать реальные экземпляры Profile, Эти спецификации должны будут require 'rails_helper' тоже.

Другие вопросы по тегам