51 votes

Rails : Comment écrire des tests pour un module ruby ?

J'aimerais savoir comment écrire des tests unitaires pour un module qui est mélangé à quelques classes, mais je ne sais pas vraiment comment m'y prendre :

  1. Dois-je tester les méthodes d'instance en écrivant des tests dans l'un des fichiers de test d'une classe qui les inclut (ce qui ne semble pas correct) ou est-il possible de conserver les tests des méthodes incluses dans un fichier séparé spécifique au module ?

  2. La même question se pose pour les méthodes de la classe.

  3. Dois-je avoir un fichier de test distinct pour chacune des classes du module, comme le font les modèles rails normaux, ou doivent-ils être placés dans le fichier de test général du module, s'il existe ?

59voto

cwninja Points 5041

IMHO, vous devriez faire une couverture de test fonctionnel qui couvrira toutes les utilisations du module, et ensuite le tester de manière isolée dans un test unitaire :

setup do
  @object = Object.new
  @object.extend(Greeter)
end

should "greet person" do
  @object.stubs(:format).returns("Hello {{NAME}}")
  assert_equal "Hello World", @object.greet("World")
end

should "greet person in pirate" do
  @object.stubs(:format).returns("Avast {{NAME}} lad!")
  assert_equal "Avast Jim lad!", @object.greet("Jim")
end

Si vos tests unitaires sont bons, vous devriez être en mesure de tester la fonctionnalité dans les modules auxquels elle est intégrée.

Ou

Écrivez une aide de test, qui affirme le comportement correct, puis utilisez-la contre chaque classe dans laquelle elle est mélangée. L'utilisation serait la suivante :

setup do
  @object = FooClass.new
end

should_act_as_greeter

Si vos tests unitaires sont bons, il peut s'agir d'un simple test fumigène du comportement attendu, vérifiant que les bons délégués sont appelés, etc.

0 votes

Lorsque vous dites "couverture de test fonctionnelle", je suppose que vous vous référez à la fonctionnalité que les modèles acquièrent et non aux tests du contrôleur stockés test/fonctionnel ? Merci pour votre réponse. J'aime l'idée de tester le module de manière isolée et d'écrire une aide que les autres classes peuvent appeler pour utiliser ce module.

1 votes

Par fonctionnel, j'entends de l'extérieur vers l'intérieur. Il s'agit généralement d'un test de contrôleur, mais pas toujours. Dans tous les cas, la couverture fonctionnelle doit toucher (ou au moins effleurer) toutes les zones du système. Si vos tests unitaires sont solides, alors les tests fonctionnels sont souvent suffisants pour vous couvrir. <br />Écrire trop de tests de bas niveau peut être un mauvais investissement. Si cela ne va jamais échouer seul, alors est-ce que cela attrape des bogues ? Est-ce que le "temps de débogage probable économisé" * "probabilité d'un bug" > "temps pour écrire le test" ? Ignorez cela si un bug peut tuer des gens ou votre entreprise. </rant>

0 votes

Non. Les tests de contrôleur sont (presque) toujours de mauvaises idées (les histoires de Cucumber font mieux la même chose), et ils ne sont pas pertinents pour le problème en question de toute façon. Il suffit de faire des tests unitaires comme dans le premier exemple de code.

14voto

Julik Points 3226

Utiliser des classes en ligne (je ne fais pas d'utilisation fantaisiste de flexmock ou de stubba/mocha juste pour montrer le point).

def test_should_callout_to_foo
   m = Class.new do
     include ModuleUnderTest
     def foo
        3
     end
   end.new
   assert_equal 6, m.foo_multiplied_by_two
 end

N'importe quelle bibliothèque d'imitation/stubbing devrait vous offrir une façon plus propre de le faire. Vous pouvez également utiliser des structs :

 instance = Struct.new(:foo).new
 class<<instance
     include ModuleUnderTest
 end
 instance.foo = 4

Si j'ai un module qui est utilisé dans de nombreux endroits, j'ai un test unitaire pour lui qui fait exactement cela (glisser un objet de test sous les méthodes du module et tester si les méthodes du module fonctionnent correctement sur cet objet).

6voto

Ce que j'aime faire, c'est créer une nouvelle classe hôte et y intégrer le module, comme ceci :

describe MyModule do
  let(:host_class) { Class.new { include MyModule } }
  let(:instance) { host_class.new }

  describe '#instance_method' do
    it 'does something' do
      expect(instance.instance_method).to do_something
    end
  end
end

4voto

rockskull Points 798

Sur minitest Comme chaque test est explicitement une classe, vous pouvez simplement inclure le module dans le test et tester les méthodes :

class MyModuleTest < Minitest::Test
   include MyModule

   def my_module_method_test
     # Assert my method works
   end
end

4voto

Dave Sims Points 2982

J'essaie de faire en sorte que mes tests se concentrent uniquement sur le contrat pour cette classe/module particulière. Si j'ai prouvé le comportement du module dans une classe de test pour ce module (généralement en incluant ce module dans une classe de test déclarée dans la spécification de ce module), je ne reproduirai pas ce test pour une classe de production qui utilise ce module. Mais s'il y a un comportement supplémentaire que je veux tester pour la classe de production, ou des problèmes d'intégration, j'écrirai des tests pour la classe de production.

Par exemple, j'ai un module appelé AttributeValidator qui effectue des validations légères, similaires à celles de l'outil ActiveRecord . J'écris des tests pour le comportement du module dans la spécification du module :

before(:each) do
  @attribute_validator = TestAttributeValidator.new
end

describe "after set callbacks" do
  it "should be invoked when an attribute is set" do
    def @attribute_validator.after_set_attribute_one; end
    @attribute_validator.should_receive(:after_set_attribute_one).once
    @attribute_validator.attribute_one = "asdf"
  end
end

class TestAttributeValidator 
    include AttributeValidator
    validating_str_accessor [:attribute_one, /\d{2,5}/]      
end

Maintenant, dans une classe de production qui inclut le module, je ne vais pas réaffirmer que les rappels sont effectués, mais je peux affirmer que la classe incluse a une certaine validation définie avec une certaine expression régulière, quelque chose de particulier à cette classe, mais qui ne reproduit pas les tests que j'ai écrits pour le module. Dans la spécification de la classe de production, je veux garantir que des validations particulières sont définies, mais pas que les validations fonctionnent en général. Il s'agit d'une sorte de test d'intégration, mais qui ne répète pas les mêmes assertions que celles que j'ai faites pour le module :

describe "ProductionClass validation" do
  it "should return true if the attribute is valid" do
    @production_class.attribute = @valid_attribute 
    @production_class.is_valid?.should be_true
  end
  it "should return false if the attribute is invalid" do
    @production_class.attribute = @invalid_attribute
    @production_class.is valid?.should be_false
  end
end

Il y a une certaine duplication ici (comme la plupart des tests d'intégration), mais les tests prouvent deux choses différentes pour moi. Un ensemble de tests prouve le comportement général du module, l'autre prouve les problèmes particuliers d'implémentation d'une classe de production qui utilise ce module. Grâce à ces tests, je sais que le module validera les attributs et effectuera des rappels, et je sais que ma classe de production possède un ensemble spécifique de validations pour des critères spécifiques propres à la classe de production.

J'espère que cela vous aidera.

0 votes

Merci pour cette réponse complète avec des exemples.

0 votes

L'inconvénient de cette approche est qu'elle crée en fait une classe qui peut entrer en collision avec d'autres tests. Voir les réponses les mieux notées pour les approches qui ne laissent pas d'effets secondaires.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X