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:
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?
La même question s'applique aux méthodes de classe.
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?
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é.
É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.
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).
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
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.
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.
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