Testowanie wspólnej logiki zawartej w module

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:

  1. 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…
  2. Powielam testy w każdej klasie, mniej lipne, ale w utrzymaniu kiepskie
  3. 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…

Może ktoś ma jakiś pomysł?

Świetne pytanie!

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

możesz napisac coś więcej o takim rozwiązaniu?

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).

(mam nadzieję, że wiadomo o co chodzi ;-))

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).