Podejście do testów modeli z użyciem bazy danych

Ostatnio zastanawiałem się nad testami jednostkowymi i użyciem w nich bazy danych. Z jednej strony zasada jest taka, że testy jednostkowe nie powinny dotykać bazy danych, a z drugiej strony wiele metod, które coś z bazy wyciągają ciężko przetestować bez odwołania się do niej.

Dopóki jest to prosty find, nie ma problemu. Możemy zmockować metodę find i sprawdzić czy otrzyma pożądane opcje. Nie jestem zwolennikiem mocków, w takich sytuacjach, ale powiedzmy, że jakbym chciał być purystą w kwestii testów jednostkowych, to tak bym zrobił. Problem powstaje wtedy, gdy trzeba wrzucić jakiegoś SQLa, wykonującego np. jakiegoś joina.

Jak podchodzicie do tego typu problemów?

Railsy się raczej nie trzymają tej zasady. Nie widzę w tym żadnego problemu poza tym, że z czasem testy są coraz wolniejsze ze względu na hity do bazy i z tego powodu czasem warto coś zmockować. Wydaje mi się, że purystyczne podejścia rzadko się sprawdzają a testowanie oprogramowania to zawsze jest szukanie kompormisów między tysiącem rzeczy. Albo masz testy bliższe temu co robi naprawdę aplikacja ale im bliżej temu tym dłuzej trwa testowanie i samo pisanie takich testów albo masz je jak najbardziej modularne, rozdzielone, łatwiejsze w utrzymaniu, szybsze ale po prostu w niektórych rzeczach oszukujesz jakiś kawałek kodu, że inny zrobił cos innego i trzeba być wtedy ostrożnym, żeby nie wyszło na to, że testujesz jakąś mockową abstrakcję a nie prawdziwy kod. Jak to w życiu: nie ma jednej, łatwej drogi dla wszystkich, która jest słuszna i należałoby nią podążać.

Wat? :smiley:
Gdzie taką bzdurę wyczytałeś?
RSpec promuje (ułatwia) testowanie kontrolerów (testy funkcjonalne) z użyciem mocków modeli, coby rozwalenie czegoś od strony modelu nie posypało testu kontrolera.

Ale mockowanie modeli w testach modeli? Bezsens (nawet na poziomie filozoficznym, no prośba), przecież w ten sposób to się połowy logiki nie przetestuje.

Pamiętam takie założenie z czasów, kiedy rządził RSpec, BDD, RSpecowe stuby i mocki, itp. Był to IMHO najsłabszy punkt RSpecowej filozofii (o ile w ogóle były jakieś niesłabe :wink: ). Stubowanie/mockowanie modeli AR jest koszmarem. Poza aspektem wydajnościowym nie widzę w tym żadnych zalet.

Kurde, czytałem dokumentację chyba tego samego RSpeca i pamiętam zachęcanie do mockowania w testach kontrolera (właśnie dla hermetyzacji testów), ale nie pamiętam podobnego zachęcania przy opisywaniu testów modeli :wink:

[quote]Wat?
Gdzie taką bzdurę wyczytałeś?[/quote]
Nie wiem gdzie, wiem, że z definicji testy jednostkowe powinny działać w całkowitej izolacji. Inna sprawa jest taka, że to często niepraktyczne, niepotrzebne i trudne do uzyskania.

Nie. Mockowanie odwołań do bazy danych w testach modeli. Nie chcę tutaj wchodzi w niepotrzebne dyskusje, bo nigdzie nie napisałem, że mi się podoba mockowanie w testach modeli (a właściwie napisałem coś całkowicie odwrotnego), ale wyobraź sobie:

it "should set something to true if model can't be saved" do @model.should_receive(:save).and_return(false) @model.run_some_method @model.something.should be_true end
Jak widać w powyższym specu zależy nam na tym, żeby model zachował się w konkretny sposób jeżeli nie może być zapisany. Można próbować ustawić pola w modelu tak, żeby się nie validował i tym samym, żeby nie mógł być zapisany, ale nie jest to praktyczne, bo jeżeli walidacje się zmienią, to test może nie przejść chociaż powinien. Inny prosty przykład, to odwołanie do innego modelu. Nie chcemy testować czy jak wykonay jakąś metodę na innym modelu, to zwróci ona pożądane obiekty, tylko co się stanie jak zwróci (albo i nie zwróci) te obiekty. Takie sytuacje zdarzają się stosunkowo często, więc bez generalizowania proszę :wink:

Wracając do meritum. Jak już napisałem chodzi mi najbardziej o różnego rodzaju findy, szczególnie połączone joinami z innymi modelami. W takich sytuacjach najchętniej zrobiłbym coś co z reguły robi się w testach integracyjnych, czyli użył database cleanera, żeby czyścić bazę danych po każdym teście. Tylko nie opłaca się czegoś takiego wrzucać do testu, w którym większość metod tego nie potrzebuje.

W każdym razie prosilbym olać tą część o mockowaniu z pierwszego posta, bo nie chciałem, żeby to się przerodziło w dyskusję o mockowaniu tylko o tym co zrobić z testami, które potrzebują absolutnie czystej bazy danych.

Ale przecież ActiveRecord::Base.find masz już rewelacyjnie pokryte testami w samych Railsach.

Co do czystej bazy danych – przecież jeśli dobrze operujesz kontekstami w RSpecu, to powinieneś mieć czysto wtedy, kiedy chcesz mieć czysto?

Pamiętam takie założenie z czasów, kiedy rządził RSpec, BDD, RSpecowe stuby i mocki, itp. Był to IMHO najsłabszy punkt RSpecowej filozofii (o ile w ogóle były jakieś niesłabe :wink: ). Stubowanie/mockowanie modeli AR jest koszmarem. Poza aspektem wydajnościowym nie widzę w tym żadnych zalet.[/quote]
Sorry za drobny offtop, ale skoro wspomniane przez Ciebie mechanizmy rządziły w jakichś tam czasach, to co stosuje się teraz? Ostatnio zainteresowałem się BDD, Cucumberem i Rspecem, a z Twojego postu wnioskuję, że zastępują je już inne technologie?

YHBT :wink:

Ale mi chodzi o coś takiego na przykład:

.all(:joins => "tutaj jakiś wywalony join na 3 tabele ;-)", :group => "jeszcze więcej SQL magic")

Dobra, rzeczywiście chyba niepotrzebnie biję pianę ;]

[code]decribe “finds” do
after :each do
# database cleaner
end

it “should find something with huge join, that only few people in the world can understand” do
# …
end
end[/code]

Chodziło mi bardziej o to, że jak ukazał się RSpec to wszyscy byli nim zajawieni. W międzyczasie powstało trochę innych narzędzi, filozofii, skrótów, itp. ;-). Wydaje mi się, że to założenie z mockowaniem/stubowaniem wynikało z tego, że RSpec na starcie udostępniał łatwy w użyciu mechanizm stubów - ludzie zaczęli go po prostu nadużywać.

I uzupełnienie do posta Kuby: sprawa jest prosta, jeśli przesadzisz z atomizacją i izolacją swoich testów, Twoje testy będą zmierzały do mierzenia poprawności (spójności) samego test suite’u, a nie testowanej aplikacji.

Mockowanie modeli jest fajne i koszerne w testach kontrolerów, bo tam najważniejsze jest przetestowanie przygotowania i podania danych dla widoków.
Mockowanie modeli w testach modeli oznacza testowanie mocka. Czyli jest z dupy, bo izoluje test od tego, czego ma on tak naprawdę dotyczyć.