Fixtures

witam - którkie pytanie , czy używając fixtures i przykładowo:

ActiveRecord::Fixtures.create_fixtures(Rails.root + ‘spec/fixtures’, [‘programs’])

mogę coś pokonfigurować / użyć innego polecenia aby przy tworzeniu np. rekordów klasy Program nie kasowało się istniejące tylko dodawały/ updateowały istniejące?

Pozdrawiam

Możesz rzucić okiem na to jak jest zaimplementowany task rake db:fixtures:load
Być może jest tam wołana jakaś metoda, która czyści bazę lub metoda, która pozwala na ustawienie flagi “nie czyść bazy”

…ale moim skromnym zdaniem jeśli musisz tak kombinować, to na 90% robisz coś źle.
Do generowania dużej ilości rekordów możesz użyć np. factory_girl
Swoją drogą, w dzisiejszych czasach każdy unika stosowania fixturek i raczej używa się factory_girl (lub podobnych rozwiązań)

Nie każdy i w niektórych projektach używanie factory girl może być dużo bardziej uciążliwe niż fixtures.

Protip, ekstrapolowanie swoich własnych preferencji na wszystkich naokoło jest kiepskim pomysłem. Oraz gwarantuje wypowiedź fałszywą.

factory_girl skończyło się na kill’em all.

z factory_girl się miło zaczyna, ale w miarę rośnięcia aplikacji liczba problemów zwiększa się często w tempie geometrycznym.

Protip, ekstrapolowanie swoich własnych preferencji na wszystkich naokoło jest kiepskim pomysłem. Oraz gwarantuje wypowiedź fałszywą.[/quote]
Podkreślam, że użyłem sformułowania raczej używa.
Przez ostatnie 4-5 lat pracy w rails nie widziałem chyba ani jednego projektu z fixtures, a pracowałem w 2 firmach, każda po około 10 osób, stąd wyciągnąłem te śmiałe wnioski.

Mimo tego dzięki za protipa :wink:

[quote=filiptepper]factory_girl skończyło się na kill’em all.

z factory_girl się miło zaczyna, ale w miarę rośnięcia aplikacji liczba problemów zwiększa się często w tempie geometrycznym.[/quote]
Jestem ciekaw jakie konkretnie problemy miałeś z factory_girl?
W jakich przypadkach zwykłe fixturki są lepsze niż szeroko pojęte fabryczki?

[quote=lucassus]Jestem ciekaw jakie konkretnie problemy miałeś z factory_girl?
W jakich przypadkach zwykłe fixturki są lepsze niż szeroko pojęte fabryczki?[/quote]
+1 :wink:

A jak miałoby wyglądać użycie FactoryGirla zamiast fixtures w celu zaktualizowania bazy z istniejącymi rekordami , poprzez dodanie kilku rekordów / zaktualizowanie kilku istniejących. Jak dotąd używałem FactoryGirla jedynie do testów i nie widziałem nigdzie przykładów innego zastosowania FactoryGirla. Oczywiście , że sobie o tym pogooglam, ale skoro już wywiązała się dyskusja może coś ciekawego jeszcze tu się pojawi ;]

Nie lepiej po prostu użyć do tego istniejących modeli?
Ewentualnie jakieś większe porcję danych możesz wczytywać z zewnętrznych plików yml.

Nom na chwilę obecną zaczytuję z plików .yml przy pomocy fixtures . W zasadzie to wczesniej robilem
unless record = Record.find(blabla)
Record.create (blablala)
else
record.update_attributes(blalbla)
end
natknalem się gdzieś na fixtures i pomyslalem ze da się nimi to zastąpić

No nic, czas googlac i testować ;]

[quote=lucassus][quote=filiptepper]factory_girl skończyło się na kill’em all.

z factory_girl się miło zaczyna, ale w miarę rośnięcia aplikacji liczba problemów zwiększa się często w tempie geometrycznym.[/quote]
Jestem ciekaw jakie konkretnie problemy miałeś z factory_girl?
W jakich przypadkach zwykłe fixturki są lepsze niż szeroko pojęte fabryczki?[/quote]
Wszystko zależy od tego z jakim projektem masz do czynienia i jakie masz podejscie do testowania. FactoryGirl się sprawdza dobrze jeżeli masz relatywnie mało modeli, modele nie są za bardzo połączone asocjacjami i żeby obtestować różne części aplikacji nie potrzebujesz tworzyć dużej liczby rekordów. Wtedy w większości testów możesz tworzyć pojedyńcze rekordy i wszystko ładnie działa.

Problem w tym, że aplikacje lubią się rozrastać i nagle z kilku modeli robi się kilkanaście albo kilkadziesiąt i okazuje się, że do części testów przydaje się trochę więcej danych (szczególnie jeżeli mówimy tu o wszelkiego rodzaju testach akceptacyjnych). I wtedy zaczyna się walka z factory girl. Widziałem (i w zasadzie pisałem :wink: ) aplikacje, w których trzeba było potworzyć sobie specjalne helpery, które tworzyły zestawy powiązanych danych (jakiś rekord rekord i różne jego asocjacje). W takim wypadku główna zaleta fabryk, czyli przejrzystość testu, przestaje działać, a dodatkowo testy zaczynają być przerażająco wolne, bo zamiast zrobić ROLLBACK po teście, trzeba ładować dane przed jego rozpoczęciem.

Dlatego w aplikacjach, w których stosuje się testy integracyjne i do dobrego przetestowania niektórych modeli potrzeba większej ilości danych, opłaca się moim zdaniem zrobić podstawowy zestaw danych używając fixtures, na którym można oprzeć większość testów. Do edge case’ów dalej można użyć factory girl. Ma to oczywiście swoje wady, ale w tym temacie każde rozwiązanie ma wady, więc jak to mówią “choose your poison”.

To jest dokładnie pytanie o rodzaj testów. Jeśli mówimy o testach jednostkowych, to oczywiście, możnaby się zapytać, po co te wszystkie fixtures, skoro można stworzyć factory, sprawdzić jedną prostą metodę i mieć z głowy.

Idąc jeszcze dalej można by zapytać, po co wtedy w ogóle factories, skoro testy unitowe tak naprawdę mogłyby (i z punktu widzenia teorii nawet powinny) nie odwoływać się bazy danych?

Widziałem już dużo potworków, gdzie chyba najgorszym z nich było posiadanie testów w cucamberze, gdzie na podstawie tablic przekazywanych w krokach scenariusza tworzyły się jakieś obiekty FactoryGirl.

Używanie fixtures ma jeszcze tę zaletę, że jak się dobrze to wszystko przemyśli, to można wrzucać je do bazy danych w developmencie (łącznie z obrazkami itp.). Dużym minusem z kolei jest konieczność pisania fixtures z palca, ale tego się nie robi często.

P.S. Przekonanie, że fabryki są dobre a fixtures są złe, i już, to jest jedna z rzeczy w środowisku, której ciągle nie potrafię zrozumieć i argument “bo wszyscy tak robią” jest mimo wszystko słaby ;).

@Adam, jeśli starasz się użyć fixtures do dodania rekordów do jakiejś bazy typu production czy development, to raczej robisz to źle.

Nie wiem co starasz się zrobić, ale jeśli chcesz przenieść bazę pomiędzy dwoma systemami (również mysql -> postgres) to polecam taps server (https://github.com/ricardochimal/taps).

Jeśli możesz zapisać dane w YAMLu czy CSV, to wczytanie tego pliku i stworzenie rekordów na bazie kolumn wydaje się dość trywialne, i pewnie będzie łatwiejsze niż próba dostosowania fixtures (które robią dużo rzeczy których nie potrzebujesz: ustalanie statycznych ID rekordów, czyszczenie bazy itd.).

W sprawie wojenki fixtures / factories: Ja używam obu. Fixtures, kiedy muszę przetestować kompleksowy scenariusz w teście integracyjnym. Factories, gdy scenariusz do przetestowania jest stosunkowo prosty i muszę stworzyć dosłownie kilka rekordów. Na dłuższą metę, jeśli masz przed każdym testem tworzyć kilkaset rekordów to albo kod testu będzie popaprany, albo będzie to trwało w nieskończoność, albo i jedno i drugie. Fixtures głupie nie są, a przede wszystkim są szybkie.

Żeby się nie zachęcić do stosowania zbyt porąbanych factories, zamiast factory girl mam ponapisywane helpery typu:

def user_attributes(custom_attributes = {})
  { name: 'john', email: 'john.doe@example.com' }.merge custom_attributes
end

# i w teście
User.create! user_attributes(email: 'foo@bar.com')

Jeśli to nie wystarcza, to scenariusz jest zbyt skomplikowany na factories i lepiej użyć fixtures.

Doszedłem własnie do momentu w projekcie gdzie zdecydowanie musze zastanowić się nad tym jak napisałem testy i jak najlepiej je zoptymalizować.
Jednym z elementów które najbardziej mnie zastanawiają jest sytuacja w której duża część testów unitowych operuje na tych samych danych (komentarze, że powinienem przejrzec to jak są napisane testy i architekture projektu poproszę odłożyć na bok bo zdaje sobię sprawę z tego jaki mam burdel w projekcie, ale co tydzien zmieniają mi się główne założenia ktore mają być zaimplementowane na wczoraj także musze z tym żyć :< ) .

W spec_helperze więc odpaliłem sobie kilka helperów ponazywanych seed_roles, seed_programs bla bla bla w których tworzę odpowiednie rekordy. Jest to oczywiście strasznie wolne, ponieważ zapisują mi się one do bazy przed każdym testem.
Czas to zmienić (i nigdy nie wracać do takiego rozwiązania;d) . To czy kolejne rozwiązanie zostanie w spec_helperze z jakimis ifami, czy w odpowiednich testach będę to odpalać to już poboczna kwestia, moje pytanie jest takie: czy jest sposób dodania rekordów które nie będą czyszczone po kazdym tescie tylko np po ostatnim tescie ? Aby odpalic te seedy raz przed wszystkimi testami i po ostatnim tescie je usunąć ? Jeżeli nie to jak rozumiem z:

[quote=sarniak]Idąc jeszcze dalej można by zapytać, po co wtedy w ogóle factories, skoro testy unitowe tak naprawdę mogłyby (i z punktu widzenia teorii nawet powinny) nie odwoływać się bazy danych?
.[/quote]
pozostaje mi zmockowanie i stubowanie wszystkiego co się da?

W zasadzie opisując mój problem dochodzę do wniosku , że ten post powinien się znaleźć w zakładzie patologii kodu .

Testy modeli ActiveRecord to NIE SĄ testy jednostkowe. Dlatego w Rails4 zmienia się ich nazwa na po prostu test/models.

Ups ;] Czyli jeżeli w testach modelowych odnoszę się do bazy danych to nie będę się smażyć w piekle? Bądź co bądź pytanie z postu powyżej dalej aktualne ;]

Smażyć się piekle będziesz jeśli w ogóle nie będziesz pisał testów :wink:

Jeśli chodzi o testy do modeli, to w swoich projektach generalnie staram się trzymać pewnych zasad:

  • jeśli coś da się przetestować bez uderzenia po bazie, to testuję to bez uderzenia po bazie, klasycznym przykładem będzie metoda full_name, która skleja dwa pola first_name, last_name
  • jeśli jakaś metoda w modelu robi zbyt dużo, np. uaktualnia wiele rekordów na raz lub dokonuje zapisu do różnych tabel, to wydzielam ją z danego modelu do osobnej klasy, i dalej…
  • jeśli w tej osobnej klasie da się coś zastubować, to stubuje
  • jeśli setup takiego stuba dużo zajmuje lub jest nieczytelny to po prostu olewam te kilka milisekund, które mógłbym potencjalnie zyskać

Inna sprawa: ze stubowaniem niestety jest tak, że powinnyśmy go stosować raczej do stabilnego API.
Jeśli np. często w kodzie coś zmieniamy lub refaktoryzujemy, to duża ilość stubów szybko stanie się bardzo upierdliwa.

Wracając do modeli. Nie wyobrażam sobie testowania scope modeli bez uderzania po bazie.
Czy ktoś z was ma na to jakąś metodę?

W testach jednostkowych powinieneś testować zachowanie kodu w izolacji. Jak doskonale wiesz, klasyczne modele ActiveRecord od razu mapują swoje atrybuty na kolumny, dziedzicząc po ActiveRecord::Base dołączają bardzo dużo funkcjonalności związanej z zapisem/odczytem z bazy. Nie jest łatwo testować je, w większości przypadków, nie dotykając bazy danych bądź innych klas. Modele po prostu robią za dużo, co niestety jest problemem w dużych aplikacjach.

Ogólnie lepsze takie testy niż żadne, ale jest kilka haczyków:

  • testy działają wolno (bo dotykają bazy czy też innych klas)
  • testy tego typu stają się trudne w utrzymaniu (gdyż musisz troszczyć się o strukturę bazy danych, fixtures itd.)
  • testujesz zarówno swoją logikę biznesową jak i warstwę dostępu do bazy, czasami trudno to mentalnie rozgraniczyć, ale po cholere masz testować ActiveRecord skoro tenderlove napisał już w cholerę do tego testów? :wink:

Nic z powyższych nie jest problemem w przypadku małych aplikacji. Wszystko staje się potwornie trudne gdy masz 40 tabel w bazie danych.