2009-11-03 14 views
5

Ich bin auf der Suche nach einer Möglichkeit, meine Shoulda + FactoryGirl Tests zu beschleunigen.Shoulda + FactoryGirl: Kann ich meine Tests schneller machen?

Das Modell, das ich versuche zu testen (StudentExam) hat Assoziationen zu anderen Modellen. Diese verknüpften Objekte müssen existieren, bevor ich eine StudentExam erstellen kann. Aus diesem Grund werden sie in setup erstellt.

Eines unserer Modelle (School) benötigt jedoch viel Zeit für die Erstellung. Da setup vor jeder should Anweisung aufgerufen wird, nimmt der gesamte Testfall Äonen auszuführen - es ist eine neue schafft @school, @student, @topic und @exam für jeden sollte Anweisung ausgeführt.

Ich suche nach einer Möglichkeit, diese Objekte einmal und nur einmal zu erstellen. Gibt es so etwas wie eine startup für before_all Methode, die es mir erlauben würde, Datensätze zu erstellen, die den Rest des Testfalls beibehalten?

Grundsätzlich bin ich auf der Suche nach etwas genau wie RSpec before(:all). Ich bin nicht besorgt über das Problem der Abhängigkeiten, da diese Tests niemals diese teuren Objekte modifizieren werden.

Hier ist ein Beispiel für einen Testfall. Wir entschuldigen uns für den langen Code (die ich angelegt habe auch eine 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 

Antwort

2

Wenn das Problem diese Datensätze nur einmal erstellt, können Sie eine Klassenvariable verwenden. Es ist kein sauberer Ansatz, aber zumindest sollte es funktionieren.

# 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 

EDIT: die Super-ugly Abhilfe Um dies zu beheben, die Auswertung mit einer Instanzmethode verschieben.

# 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

Ich mag diesen Ansatz am besten, aber es scheint nicht richtig zu funktionieren. '@@ school = Factory (: school)' löst einen Validierungsfehler aus, dass 'name' bereits vergeben ist (it' validates_uniqueness_of'). Ich habe versucht mit '@@ Schule || = Fabrik (: Schule)' und es funktioniert, wenn die Test-Datenbank sauber ist. Also habe ich mit der super-hässlichen '@@ school || = School.first || Schluss gemacht Factory (: school) ' –

+0

Um die super-hässliche Problemumgehung zu beheben, verschieben Sie die Auswertung mit einer Instanzmethode. (siehe meine Bearbeitung) –

0

http://m.onkey.org/2009/9/20/make-your-shoulda-tests-faster-with-fast_context ist ein ausgezeichneter Beitrag darüber, wie Ihr shoulda/factory-Mädchen Tests schneller zu machen, ein Juwel namens fast_context. Lass es mich wissen, wenn es nicht das ist, was du brauchst.

+0

Ich sah fast_context, aber ich denke nicht, dass es überhaupt hilft. Ich kann sehen, dass es bei jedem Test immer noch den '@ School'-Rekord erstellt. Die Kommentare zu diesem Beitrag haben mich auch inspiriert, so etwas zu versuchen, aber es hat nicht funktioniert: http://gist.github.com/221668 –

+0

funktioniert @school || = Fabrik (: Schule) funktioniert? –

0

Es gibt ein Plugin namens fast_context (github link), das Anweisungen in einen einzigen Kontext zusammenfasst und die Tests beschleunigt.

Die andere Sache, die ich verwendet habe, um meine Tests zu beschleunigen, ist Vorbelegen der Fixture-Daten. FactoryGirl ist langsam, da jedes Mal, wenn der Setup-Block ausgeführt wird, diese Datensätze erstellt werden.

Ich habe ein Plugin namens Fixie geschrieben, das ActiveRecord verwendet, um die Testdatenbank vorab zu füllen, sodass die für Ihre Tests benötigten Datensätze bereits erstellt wurden. Sie können Fixie zusammen mit FactoryGirl verwenden, wenn Sie auch zur Laufzeit neue Datensätze erstellen möchten.

+0

(Siehe meinen obigen Kommentar zu: fast_context). Ich möchte die Testdatenbank nicht vorbelegen - deshalb verwende ich FactoryGirl (im Gegensatz zu den Fixtures). Schreiben Sie Ihre Tests gegen eine vorbereitete Datenmenge ist ziemlich brüchig. Ich bevorzuge es, die Testdaten innerhalb des Testfalls zu erstellen (ich will einfach nicht, dass jede einzelne Behauptung im Grunde neu erstellt wird). Ich werde weiter nach einer Möglichkeit suchen, Zeug genau einmal pro Testfall initialisieren zu lassen. –

+0

Ich stimme nicht zu, aber jedem sein eigenes. Wenn das Objekt teuer zu erstellen ist (vor allem die API-Aufrufe), warum nicht ein paar oder alle dieser Sachen ausstoßen? –

2

Welche Art von Tests versuchen Sie zu schreiben? Wenn Sie wirklich sicherstellen möchten, dass alle diese Objekte ordnungsgemäß koordiniert werden, schreiben Sie einen Integrationstest und Geschwindigkeit ist nicht Ihre Hauptsorge. Wenn Sie jedoch versuchen, das Modell zu testen, können Sie bessere Ergebnisse erzielen, wenn Sie aggressiv stubben. Wenn Sie z. B. überprüfen möchten, ob eine Prüfung den Namen ihrer Schulvereinigung verwendet, wenn Sie examen.location aufrufen (oder wie immer Sie sie nennen), benötigen Sie kein ganzes Schulobjekt.

Sie müssen nur sicherstellen, dass die Prüfung die richtige Methode in der Schule nennt. Um zu testen, dass, könnten Sie so etwas wie die folgenden tun (mit Test :: Unit und Mokka, weil das ist, was mit ich kenne):

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 

Grundsätzlich, wenn Sie Ihre Unit-Tests beschleunigen müssen, weil Objekte zu teuer zu konstruieren, du bist nicht wirklich Unit-Tests. Alle oben genannten Testfälle haben das Gefühl, dass sie auf dem Unit-Test-Level sein sollten, also stub stub stub!

+0

Vielleicht liegt es daran, dass ich mit der Praxis nicht allzu vertraut bin, aber aus irgendeinem Grund bin ich nicht scharf auf die Idee, Stubs zu verwenden. Ich möchte diese Tests gegen tatsächliche Modellinstanzen ausführen. Außerdem stellen einige der Tests später sicher, dass ein Student nicht mehr als "n" Mal eine Prüfung geschrieben hat, was Datenbankabfragen erfordert - sind diese Situationen, mit denen Stubs gut umgehen können? –

+0

Grundsätzlich denke ich, was ich zu kommunizieren versuchte, ist, dass verschiedene Tests verschiedenen Zwecken dienen. Wenn Sie einen Komponententest schreiben, testen Sie eine einzelne "Einheit" Code - Ihr Modell selbst. In diesem Fall ist alles, was Sie interessieren, dass es auf der Interface-Ebene gut spielt, also sollten Sie davon ausgehen, dass das andere Objekt nette Daten zurückgibt (zum Beispiel durch Mocking) und sicherstellen, dass sich Ihr Modell in einer perfekten Welt verhält. Wenn Sie tatsächlich mehrere "Einheiten" Ihres Systems testen möchten (mehrere Klassen gleichzeitig), sollten Sie einen langsameren Integrationstest durchführen, bei dem tatsächlich alle Objekte erstellt werden. – Kyle

Verwandte Themen