2009-11-03 8 views
5

Szukam sposobu na przyspieszenie moich testów Shoulda + FactoryGirl.Shoulda + FactoryGirl: Czy mogę przyspieszyć testy?

Model, który próbuję przetestować (StudentExam) ma skojarzenia z innymi modelami. Te powiązane obiekty muszą istnieć przed utworzeniem StudentExam. Z tego powodu są one tworzone w setup.

Jednak utworzenie jednego z naszych modeli (School) zajmuje dużo czasu. Ponieważ setup jest wywoływana przed każdą instrukcją should, cały test ma wykonać eony - tworzy nową instrukcję, której należy wykonać.

Poszukuję sposobu utworzenia tych obiektów po raz i tylko raz. Czy istnieje coś takiego, jak metoda startup dla before_all, która pozwoliłaby mi tworzyć rekordy, które będą się utrzymywać w pozostałej części sprawy testowej?

Zasadniczo szukam czegoś dokładnie takiego, jak RSPec's before(:all). Nie przejmuję się kwestią zależności, ponieważ testy te nigdy nie zmodyfikują tych drogich obiektów.

Oto przykładowy przypadek testowy. Przepraszam za długi kodu (Ja również stworzył gist):

# A StudentExam represents an Exam taken by a Student. 
# It records the start/stop time, room number, etc. 
class StudentExamTest < ActiveSupport::TestCase 

    should_belong_to :student 
    should_belong_to :exam 

    setup do 
    # These objects need to be created before we can create a StudentExam. Tests will NOT modify these objects. 
    # @school is a very time-expensive model to create (associations, external API calls, etc). 
    # We need a way to create the @school *ONCE* -- there's no need to recreate it for every single test. 
    @school = Factory(:school) 
    @student = Factory(:student, :school => @school) 
    @topic = Factory(:topic, :school => @school) 
    @exam = Factory(:exam, :topic => @topic) 
    end 

    context "A StudentExam" do 

    setup do 
     @student_exam = Factory(:student_exam, :exam => @exam, :student => @student, :room_number => "WB 302") 
    end 

    should "take place at 'Some School'" do 
     assert_equal @student_exam, 'Some School' 
    end 

    should "be in_progress? when created" do 
     assert @student_exam.in_progress? 
    end 

    should "not be in_progress? when finish! is called" do 
     @student_exam.finish! 
     assert [email protected]_exam.in_progress 
    end 

    end 

end 

Odpowiedz

2

Jeśli problem polega na tworzeniu tych rekordów tylko raz, można użyć zmiennej klasy. To nie jest czyste podejście, ale przynajmniej powinno działać.

# A StudentExam represents an Exam taken by a Student. 
# It records the start/stop time, room number, etc. 
class StudentExamTest < ActiveSupport::TestCase 

    should_belong_to :student 
    should_belong_to :exam 

    # These objects need to be created before we can create a StudentExam. Tests will NOT modify these objects. 
    # @school is a very time-expensive model to create (associations, external API calls, etc). 
    # We need a way to create the @school *ONCE* -- there's no need to recreate it for every single test. 
    @@school = Factory(:school) 
    @@student = Factory(:student, :school => @@school) 
    @@topic = Factory(:topic, :school => @@school) 
    @@exam = Factory(:exam, :topic => @@topic) 


    context "A StudentExam" do 

    setup do 
     @student_exam = Factory(:student_exam, :exam => @@exam, :student => @@student, :room_number => "WB 302") 
    end 

    should "take place at 'Some School'" do 
     assert_equal @student_exam, 'Some School' 
    end 

    should "be in_progress? when created" do 
     assert @student_exam.in_progress? 
    end 

    should "not be in_progress? when finish! is called" do 
     @@student_exam.finish! 
     assert [email protected]_exam.in_progress 
    end 

    end 

end 

EDYCJA: Aby naprawić super-brzydkie obejście odłożyć ocenę za pomocą metody instancji.

# A StudentExam represents an Exam taken by a Student. 
# It records the start/stop time, room number, etc. 
class StudentExamTest < ActiveSupport::TestCase 

    ... 

    private 

    def school 
     @@school ||= Factory(:school) 
    end 

    # use school instead of @@school 
    def student 
     @@school ||= Factory(:student, :school => school) 
    end 

end 
+0

Najbardziej podoba mi się to podejście, ale wygląda na to, że nie działa prawidłowo. '@@ school = Factory (: school)' podnosi błąd sprawdzania poprawności, nazwa 'name' jest już zajęta (" validates_uniqueness_of "). Próbowałem używać '@@ school || = Factory (: school)' i będzie działać, jeśli testowa baza danych jest czysta. Więc skończyłem z super-brzydką '@@ school || = School.first || Factory (: school) ' –

+0

Aby naprawić super-brzydkie obejście, odłóż ocenę za pomocą metody instancji. (zobacz moją edycję) –

0

http://m.onkey.org/2009/9/20/make-your-shoulda-tests-faster-with-fast_context jest doskonałym post na temat jak zrobić powinieneś/fabrycznie dziewczyna testy szybciej, używając gem nazwie fast_context. Daj mi znać, jeśli nie to, czego potrzebujesz.

+0

Widziałem fast_context, ale nie sądzę, że to pomaga w ogóle. Widzę, że wciąż tworzy zapis "@ school" dla każdego testu. Komentarze na temat tego wpisu również zainspirowały mnie do wypróbowania czegoś takiego, ale to nie zadziałało: http://gist.github.com/221668 –

+0

czy @school || = Fabryka (: szkoła) działa? –

0

Istnieje wtyczka o nazwie fast_context (github link), która łączy oświadczenia w jednym kontekście, co przyspiesza testy.

Inną rzeczą, której używam do przyspieszenia testów, jest wstępne zapełnienie danych urządzenia. FactoryGirl działa wolno, ponieważ tworzy te rekordy za każdym razem, gdy uruchamia się blok konfiguracji.

Napisałem wtyczkę o nazwie Fixie, która używa ActiveRecord do wstępnego wypełnienia testowej bazy danych, więc rekordy potrzebne do testów zostały już utworzone. Możesz użyć Fixie wraz z FactoryGirl, jeśli chcesz tworzyć nowe rekordy w czasie wykonywania.

+0

(Zobacz mój komentarz powyżej re: fast_context). Nie chcę wstępnie wypełniać testowej bazy danych - dlatego właśnie używam FactoryGirl (w przeciwieństwie do urządzeń). Zapisywanie testów na wstępnie przygotowanym zbiorze danych jest dość kruche. Wolę tworzyć dane testowe wewnątrz sprawy testowej (po prostu nie chcę, aby było ponownie tworzone każde pojedyncze stwierdzenie). Będę szukał sposobu na zainicjowanie rzeczy dokładnie raz na każdy przypadek testowy. –

+0

Nie zgadzam się, ale z każdym jego własnym. Jeśli obiekt jest drogi do stworzenia (szczególnie wywołań API), dlaczego nie odgryza niektórych lub wszystkich z tych rzeczy? –

2

Jakiego rodzaju testy próbujesz napisać? Jeśli rzeczywiście chcesz się upewnić, że wszystkie te obiekty są odpowiednio koordynowane, piszesz test integracji, a prędkość nie jest dla Ciebie najważniejsza. Jeśli jednak próbujesz jednostkowego przetestować model, możesz uzyskać lepsze wyniki, agresywnie obciążając.

Na przykład, jeśli próbujesz sprawdzić, czy egzamin używa nazwy stowarzyszenia szkolnego podczas wywołania exam.location (lub jak tam go nazywasz), nie potrzebujesz całego obiektu szkolnego.Musisz tylko upewnić się, że egzamin wywołuje właściwą metodę w szkole. Aby przetestować, że można zrobić coś jak następujące (przy użyciu testu :: Jednostka i Mocha bo to co ja znam):

test "exam gets location from school name" do 
    school = stub_everything 
    school.expects(:name).returns(:a_school_name) 
    exam = Factory(:exam, :school => school) 

    assert_equal :a_school_name, exam.location 
end 

Zasadniczo, jeśli chcesz przyspieszyć testy jednostkowe, ponieważ obiekty są zbyt drogie w budowie, tak naprawdę nie testujesz jednostki. Wszystkie powyższe przypadki testowe mają wrażenie, że powinny znajdować się na poziomie testu jednostkowego, a więc stub stub stub!

+0

Może dlatego, że nie jestem zbyt zaznajomiony z praktyką, ale z jakiegoś powodu nie mam ochoty używać kodów pośredniczących. Chciałbym uruchomić te testy względem rzeczywistych instancji modelu. Ponadto, niektóre testy później sprawiają, że student nie napisał egzaminu więcej niż "n" razy, co będzie wymagać zapytań do bazy danych - czy te stany sytuacji mogą ładnie sobie poradzić? –

+0

W gruncie rzeczy myślę, że starałem się przekazać, że różne testy służą różnym celom. Jeśli piszesz test jednostkowy, testujesz pojedynczą "jednostkę" kodu - sam twój model. W tym przypadku wszystko, na czym nam zależy, to to, że ładnie gra na poziomie interfejsu, więc powinieneś założyć, że drugi obiekt zwraca dobre dane (na przykład kpiąc) i upewnić się, że twój model zachowuje się w idealnym świecie. Kiedy rzeczywiście chcesz przetestować wiele "jednostek" swojego systemu (kilka klas jednocześnie), powinieneś mieć wolniejszy test integracji, który faktycznie tworzy wszystkie obiekty. – Kyle