web-dev-qa-db-fra.com

Rails: Comment écrivez-vous des tests pour un Ruby?

J'aimerais savoir comment écrire des tests d'unité pour un module mélangé dans quelques classes mais ne savez pas tout à fait comment y aller:

  1. Est-ce que je teste les méthodes d'instance en écrivant des tests dans l'un des fichiers de test pour une classe qui les inclut (ne semble pas la droite) ou pouvez-vous conserver des tests pour les méthodes incluses dans un fichier séparé spécifique au module?

  2. La même question s'applique aux méthodes de classe.

  3. Dois-je avoir un fichier de test séparé pour chacune des classes du module comme Normal Rails Models DO ou vivent-ils dans le fichier de test de module général, si cela existe?

49
tsdbrown

IMHO, vous devriez faire une couverture de test fonctionnelle qui couvrira toutes les utilisations du module, puis le tester isolément dans un test d'unité:

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 d'unité sont bons, vous devriez pouvoir simplement fumer de la fonctionnalité dans les modules qu'il est mélangé.

Ou…

Écrivez un assistant de test, qui affirme le comportement correct, puis utilisez-le contre chaque classe qu'il est mélangé. L'utilisation serait la suivante:

setup do
  @object = FooClass.new
end

should_act_as_greeter

Si vos tests de l'unité sont bons, cela peut être un simple test de fumée du comportement attendu, la vérification des bons délégués s'appelle etc.

57
cwninja

Utilisez des classes intégrées (je ne fais pas d'utilisation de Fancy FlexMock ou de Stubba/Moka 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

Toute bibliothèque moquante/étanche à l'extérieur devrait vous donner un moyen plus propre de le faire. Vous pouvez également utiliser des structures:

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

Si j'ai un module utilisé dans de nombreux endroits, j'ai un test unitaire pour celui-ci qui ne suffit que (faites glisser un objet de test sous les méthodes de module et testez si les méthodes de module fonctionnent correctement sur cet objet).

13
Julik

Dans minitest Comme chaque test est explicitement une classe, vous pouvez simplement inclure le module au test et testez les méthodes:

class MyModuleTest < Minitest::Test
   include MyModule

   def my_module_method_test
     # Assert my method works
   end
end
4
lcguida

J'essaie de garder mes tests ne se concentrer que sur le contrat pour ce module particulier. 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 dupliquerai pas ce test pour une classe de production utilisant ce module. Mais s'il y a un comportement supplémentaire que je veux tester pour la classe de production ou les préoccupations d'intégration, je vais écrire des tests pour la classe de production.

Par exemple, j'ai un module appelé AttributeValidator _ qui effectue des validations légères, un peu similaire à 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 comprenant le module, je ne revendiquerai pas 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 pas reproduire les tests que j'ai écrites pour le module. Dans la spécification de la classe de production, je tiens à garantir que des validations particulières sont définies, mais pas que les validations fonctionnent en général. Ceci est une sorte de test d'intégration, mais celui qui ne répète pas les mêmes affirmations 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 quelques doubles emplois ici (car la plupart des tests d'intégration auront), 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 des problèmes de mise en œuvre particuliers d'une classe de production utilisant ce module. À partir de ces tests, je sais que le module validera les attributs et effectuera des rappels, et je sais que ma classe de production dispose d'un ensemble spécifique de validations pour des critères spécifiques propres à la classe de production.

J'espère que cela pourra aider.

4
Dave Sims

Je vais généralement tester le module autant d'isolement que possible, testant essentiellement les méthodes, avec juste suffisamment de code, des moqueurs et des talons pour le faire fonctionner.

J'aurais alors probablement également des tests pour les classes que les modules sont inclus. Je ne peux pas tester chaque classe, mais il suffirait de tester suffisamment les classes pour obtenir une bonne couverture et avoir un aperçu de tout problème surviennent. Ces tests n'ont pas besoin de tester explicitement le module, mais testeraient certainement une utilisation dans des scénarios particuliers.

Chaque ensemble de tests aurait son propre fichier.

3
Toby Hede

Ce que j'aime faire est de créer une nouvelle classe d'hôte et de mélanger le module, quelque chose 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
2
Marnen Laibow-Koser