2009-11-03 10 views
5

Je suis à la recherche d'un moyen d'accélérer mes tests + FactoryGirl.Shoulda + FactoryGirl: Puis-je faire mes tests plus rapidement?

Le modèle que j'essaie de tester (StudentExam) a des associations avec d'autres modèles. Ces objets associés doivent exister avant que je puisse créer un StudentExam. Pour cette raison, ils sont créés en setup.

Cependant, un de nos modèles (School) prend beaucoup de temps à créer. Parce que setup est appelée avant chaque instruction should, le test complet prend des éons à exécuter - il crée un nouveau @school, @student, @topic et @exam pour chaque instruction should exécutée.

Je cherche un moyen de créer ces objets une fois et seulement une fois. Y a-t-il quelque chose comme une méthode startup pour before_all qui me permettrait de créer des enregistrements qui persisteront dans le reste du cas de test?

Fondamentalement, je suis à la recherche de quelque chose exactement comme before(:all) de RSpec. Je ne suis pas concerné par le problème des dépendances car ces tests ne modifieront jamais ces objets coûteux.

Voici un exemple de cas de test. Toutes mes excuses pour le long code (j'ai aussi créé un 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 

Répondre

2

Si le problème ne crée ces enregistrements qu'une seule fois, vous pouvez utiliser une variable de classe. Ce n'est pas une approche propre mais au moins cela devrait fonctionner. EDIT: Pour corriger la super-solution de contournement, repoussez l'évaluation avec une méthode d'instance.

# 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

J'aime mieux cette approche, mais elle ne semble pas fonctionner correctement. '@@ school = Factory (: school)' soulève une erreur de validation, que 'name' est déjà pris (il' validates_uniqueness_of'). J'ai essayé d'utiliser '@@ school || = Factory (: school)' et cela fonctionnera si la base de données de test est propre. Donc j'ai fini avec le super-laid '@@ school || = School.first || Factory (: school) ' –

+0

Pour résoudre la solution super-laid retarder l'évaluation avec une méthode d'instance. (Voir mon édition) –

0

http://m.onkey.org/2009/9/20/make-your-shoulda-tests-faster-with-fast_context est un excellent article sur la façon de faire vos Shoulda/essais en usine fille plus rapide, en utilisant un petit bijou appelé fast_context. Faites-moi savoir si ce n'est pas ce dont vous avez besoin.

+0

J'ai vu fast_context, mais je ne pense pas que cela aide du tout. Je peux voir qu'il est toujours en train de créer l'enregistrement '@ school' pour chaque test. Les commentaires sur ce post m'ont également inspiré pour essayer quelque chose comme ça, mais ça n'a pas marché: http://gist.github.com/221668 –

+0

@school || = Factory (: school) work? –

0

Il existe un plugin appelé fast_context (github link) qui combine les instructions should dans un contexte unique, accélérant les tests.

L'autre chose que j'utilise pour accélérer mes tests est de pré-remplir les données de l'appareil. FactoryGirl est lent car il crée ces enregistrements chaque fois que le bloc d'installation s'exécute.

J'ai écrit un plugin appelé Fixie qui utilise ActiveRecord pour pré-remplir la base de données de test, ainsi les enregistrements dont vous avez besoin pour vos tests sont déjà créés. Vous pouvez utiliser Fixie avec FactoryGirl si vous souhaitez également créer de nouveaux enregistrements lors de l'exécution.

+0

(Voir mon commentaire ci-dessus re: fast_context). Je ne veux pas préremplir la base de données de test - c'est pourquoi j'utilise FactoryGirl en premier lieu (par opposition aux appareils). Écrire vos tests sur un ensemble de données pré-rempli est assez fragile. Je préfère créer les données de test dans le scénario de test (je ne veux simplement pas qu'il soit recréé à chaque affirmation, fondamentalement). Je vais continuer à chercher un moyen d'initialiser des éléments exactement une fois par test. –

+0

Je ne suis pas d'accord, mais à chacun son propre. Si l'objet est cher à créer (en particulier les appels d'API), pourquoi ne pas en supprimer une partie ou la totalité? –

2

Quel genre de tests essayez-vous d'écrire? Si vous voulez vraiment vous assurer que tous ces objets se coordonnent correctement, vous écrivez un test d'intégration et la vitesse n'est pas votre principale préoccupation. Cependant, si vous essayez de tester le modèle à l'unité, vous pouvez obtenir de meilleurs résultats en écrasant agressivement. Par exemple, si vous essayez de vérifier qu'un examen utilise le nom de son association scolaire lorsque vous appelez exam.location (ou ce que vous appelez), vous n'avez pas besoin d'un objet scolaire complet.Vous avez juste besoin de vous assurer que l'examen appelle la bonne méthode à l'école. Pour tester cela, vous pouvez faire quelque chose comme ce qui suit (en utilisant Test :: Unit et Mocha parce que ce que je suis au courant):

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 

En gros, si vous avez besoin pour accélérer vos tests unitaires parce que les objets sont trop cher à construire, vous n'êtes pas vraiment un test unitaire. Tous les cas de test ci-dessus se sentent comme ils devraient être au niveau de test unitaire, donc talon tronqué!

+0

Peut-être que c'est parce que je ne suis pas trop familier avec la pratique, mais pour une raison quelconque, je ne suis pas enthousiaste à l'idée d'utiliser des talons. Je voudrais exécuter ces tests sur des instances de modèle réelles. En outre, certains tests plus tard s'assurer qu'un étudiant n'a pas écrit un examen plus de «n» fois, ce qui nécessitera des requêtes de base de données - sont-ils des cas de ces situations peuvent gérer joliment? –

+0

Fondamentalement, je pense que ce que j'essayais de communiquer, c'est que différents tests servent des objectifs différents. Si vous écrivez un test unitaire, vous testez une seule "unité" de code - votre modèle lui-même. Dans ce cas, tout ce qui vous intéresse est qu'il joue bien au niveau de l'interface, vous devez donc supposer que l'autre objet renvoie des données sympas (en se moquant, par exemple) et s'assurer que votre modèle se comporte dans un monde parfait. Lorsque vous voulez réellement tester plusieurs "unités" de votre système (plusieurs classes à la fois), vous devriez avoir un test d'intégration plus lent qui crée réellement tous les objets. – Kyle

Questions connexes