Hej,
mam 3 klasy które mają identyczną logikę dotyczącą uploadu, przechowywania i zarządzania zdjęciami (klasa has_many photos). Więc wrzucam tą logikę do modułu i go inkluduję w każdej klasie. I teraz jak to przetestować? Mam kilka opcji:
Stworzyć w testach klasę, która includuje moduł. Tylko, że dużo w tej klasie będę musiał mockować/dodawać , łącznie z relacjami(ponieważ korzystam z wielu metod active recorda), więc takie lipne…
Powielam testy w każdej klasie, mniej lipne, ale w utrzymaniu kiepskie
Rozbudowane shared examples, wyglądające mniej wiecej tak:
# w spec/models/shared/photoable_shared_examples.rb
module PhotoableSharedExamples
shared_examples_for 'photoable '
it 'should has photos' do
@object.should have(1).photos
end
end
end
# w spec/spec_helper.rb
RSpec.configure do |config|
config.include PhotoableSharedExamples
end
[code=ruby]# w spec/models/car_spec.rb
describe Car
context ‘photoable’ do
before(:each) do @object = Factory.create(:car_with_one_photo)
end
it_should_behave_like 'photoable'
end
end[/code]
Kiedyś powyższy przykład mi się podobał, a teraz mam co do niego mocno mieszane uczucia…
Moim zdaniem najlepsza jest opcja pierwsza, czyli stworzenie w testach minimalnej klasy, z którą działa mixin, i która odpowiada zgodnie z potrzebami testów.
Co do potencjalnie dużej liczby mocków i stubów w tym scenariuszu:
można rozważyć, czy moduł nie bierze na siebie zbyt dużej odpowiedzialności (i czy przez to nie jest zbyt związany z AR)
można rozważyć, czy nie lepiej zrobić z tego pełnoprawną klasę narzędziową, możliwie niezależną od AR
można rozważyć pragmatyczne stworzenie tabeli pod testową klasą, aby uniknąć masowego mockowania AR
To w dużej części zależy od Twojego kodu, ale zamiast modułu możesz mieć “zwykłą” klasę, która zarządza np uploadem, zapisem zdjęć itd, a podczas inicjalizacji dostaje w parametrze obiekt AR na którym wywołuje typowe metody (create, save itd) podczas wykonywania pozostałych akcji.
Wtedy w testach możesz normalnie użyć takich rzeczy jak
obj = mock_model(MojModel)
i stubować/mockować tylko część rzeczy.
Mniej więcej tak rozwiązałem podobny problem (nie chodziło o uploady, ale o komunikację z backendem napisanym w Python’ie).
slawosz: dużo zależy od twojego kodu, ja jeżeli chcę testować w pełni dany moduł, w dodatku korzysta on mocno z AR to robię mniej więcej tak:
[code]class DummyTestTable < ActiveRecord::Base
def self.create_table
# I tutaj albo
ActiveRecord::Migration.create_table(:dummy_test_tables) do |t|
end
# albo
ActiveRecord::Base.connection.execute << SQL
CREATE TABLE dummy_test_tables AS SELECT
t.id as id, random() as float_column, ceil(random() * 100) as integer_column – some other fields here
FROM generate_series(1, 10000) as t(id)
SQL
# Co daje odrazu tabelkę wypełnioną dużą ilością danych pseudolosowych (postgresql specific)
end
def self.drop_table; ActiveRecord::Migration.drop_table(:…) end
include ModulDoTestowania
end
A tu spece. DummyTestTable.create_table w setup, DummyTestTable.drop_table w teardown[/code]
Minusem tego rozwiązania jest jego szybkość - a właściwie brak. Plusem jest całkowita niezależność pomiedzy testami, oraz całkowite wyizolowanie modułu z prostym uzupełnianiem metod wirtualnych (zamiast stubować, mockować, po prostu implementujesz je w klasie).