Jak przetestować problem w Railsach

104

Biorąc pod uwagę, że mam Personableproblem z moją aplikacją Rails 4, która ma full_namemetodę, jak bym to przetestował używając RSpec?

dotyczy / personable.rb

module Personable
  extend ActiveSupport::Concern

  def full_name
    "#{first_name} #{last_name}"
  end
end
Kyle Decot
źródło
Jakiego frameworka testowego używasz? Pamiętaj też, że Personable to zwykły moduł Ruby. Przetestuj go tak, jakbyś testował każdy inny mixin.
Lee Jarvis
Nie ActiveSupport::Concernzostał wyjęty z Railsów? Myślałem, że to minęło chwilę temu.
Russell
@LeeJarvis Używam Rspec razem z FactoryGirl
Kyle Decot
4
@Russell Zgadzam się. To powiedziawszy, nie pomogę nikomu w ich pytaniach tylko dlatego, że podążali za Railsowym sposobem robienia czegoś, z czym się nie zgadzałem. Zresztą to jakby uciec od tematu tego pytania :-)
Lee Jarvis

Odpowiedzi:

176

Metoda, którą znalazłeś, z pewnością zadziała, aby przetestować trochę funkcjonalności, ale wydaje się dość krucha - twoja fikcyjna klasa (właściwie tylko Structw twoim rozwiązaniu) może zachowywać się jak prawdziwa klasa, ale nie musi include. Dodatkowo, jeśli próbujesz przetestować obawy dotyczące modelu, nie będziesz mógł wykonywać takich czynności, jak testowanie poprawności obiektów lub wywoływanie wywołań zwrotnych ActiveRecord, chyba że odpowiednio skonfigurujesz bazę danych (ponieważ twoja fikcyjna klasa nie będzie miała kopii zapasowej tabeli bazy danych to). Co więcej, będziesz chciał nie tylko przetestować problem, ale także przetestować jego zachowanie w specyfikacji modelu.

Dlaczego więc nie upiec dwóch pieczeni na jednym ogniu? Używając wspólnych przykładowych grup RSpec , możesz sprawdzić swoje obawy w odniesieniu do klas, które ich używają (np. Modeli) i przetestować je wszędzie tam, gdzie są używane. I wystarczy tylko raz napisać testy, a następnie po prostu uwzględnić je w specyfikacji dowolnego modelu, która wykorzystuje Twoje obawy. W twoim przypadku może to wyglądać mniej więcej tak:

# app/models/concerns/personable.rb
module Personable
  extend ActiveSupport::Concern

  def full_name
    "#{first_name} #{last_name}"
  end
end

# spec/concerns/personable_spec.rb
require 'spec_helper'

shared_examples_for "personable" do
  let(:model) { described_class } # the class that includes the concern

  it "has a full name" do
    person = FactoryBot.build(model.to_s.underscore.to_sym, first_name: "Stewart", last_name: "Home")
    expect(person.full_name).to eq("Stewart Home")
  end
end

# spec/models/master_spec.rb
require 'spec_helper'
require Rails.root.join "spec/concerns/personable_spec.rb"

describe Master do
  it_behaves_like "personable"
end

# spec/models/apprentice_spec.rb
require 'spec_helper'

describe Apprentice do
  it_behaves_like "personable"
end

Zalety tego podejścia stają się jeszcze bardziej oczywiste, gdy zaczynasz robić rzeczy w swoim problemie, takie jak wywoływanie wywołań zwrotnych AR, gdzie nic mniej niż obiekt AR po prostu nie zadziała.

Josh Leitzel
źródło
2
Jedną z wad jest to, że zwolni parallel_tests. Myślę, że lepiej będzie mieć osobne testy zamiast używać shared_examples_fori it_behaves_like.
Artem Kalinchuk
6
@ArtemKalinchuk Nie jestem pewien, czy to prawda, na github.com/grosser/parallel_tests/issues/168 parallel_tests są oparte na plikach, więc udostępnione przykłady nie powinny spowalniać. Twierdziłbym również, że odpowiednio pogrupowane wspólne zachowania mają przewagę nad szybkością testowania.
Aaron K,
8
Pamiętaj, aby uwzględnić concernskatalog w swoim spec_helper.rb github.com/rspec/rspec-core/issues/407#issuecomment-1409871
Ziggy
1
Nie mogłem znaleźć nic o umieszczeniu katalogu problemów w tym linku. Czy mógłbyś wyjaśnić, jak to się robi? Nie mogę uzyskać testu RSpec, aby rozpoznać moduł w jednym z moich problemów.
Jake Smith
4
Nie dodawaj _specdo nazwy pliku, która zawiera shared_examples_for (w tym przypadku personable_spec.rb), w przeciwnym razie otrzymasz mylące ostrzeżenie - github.com/rspec/rspec-core/issues/828 .
Lalu
63

W odpowiedzi na otrzymane komentarze, oto co ostatecznie zrobiłem (jeśli ktoś ma ulepszenia, prosimy o ich opublikowanie) :

spec / problems / personable_spec.rb

require 'spec_helper'

describe Personable do
  let(:test_class) { Struct.new(:first_name, :last_name) { include Personable } }
  let(:personable) { test_class.new("Stewart", "Home") }

  it "has a full_name" do
    expect(personable.full_name).to eq("#{personable.first_name} #{personable.last_name}")
  end
end
Kyle Decot
źródło
1
Tak, spowoduje to przerwanie innych testów, jeśli zdarzy im się przetestować prawdziwą klasę o nazwie Person. Zmienię, żeby naprawić.
Russell
To nie działa. Daje mi błąd:undefined method 'full_name' for #<struct first_name="Stewart", last_name="Home">
Kyle Decot
Spróbuj dołączyć Personable, zamiast go rozszerzać. Zaktualizuję odpowiedź.
Russell
Teraz działa świetnie. Dziękuję za wskazanie mi właściwego kierunku i pomoc w refaktoryzacji @Russell
Kyle Decot
Działa świetnie i ładnie wygląda
Edward
7

Innym pomysłem jest użycie klejnotu with_model do testowania takich rzeczy. Chciałem sam przetestować problem i widziałem, jak robi to klejnot pg_search . Wydaje się to o wiele lepsze niż testowanie na poszczególnych modelach, ponieważ mogą się one zmienić i fajnie jest zdefiniować rzeczy, których będziesz potrzebować w swojej specyfikacji.

lobati
źródło