playlist Ajout de tests dans une application ruby on rails

Publié il y a 27 jours dans la série : Rails
Nicolas Cavigneaux
Votre formateur
Nicolas Cavigneaux

A la recherche d'un langage polyvalent, j'ai fait la découverte de Ruby en 2003. J'ai donc très vite commencé à utiliser Ruby au quotidien pour des tâches diverses et variées (scripting, applications lourdes…).

Courant 2004, une vague de fraicheur est apparue avec l'arrivée de Ruby on Rails qui m'a de suite conquis. J'ai donc décidé de participer activement à la communauté (forums, patches, librairies, …). En 2010, je fais la rencontre de Martin Catty et retrouve dans sa vision la rigueur et les bonnes pratiques que j'aime mettre en place, le déclic a donc été immédiat.

Synbioz met en place des solutions robustes sur la base d'outils modernes et funs, je veux faire partie de l'aventure.

Catégories : Développement

Cette épisode se concentre sur l'écriture de tests automatisés dans une application Rails pour s'assurer qu'elle fonctionne comme attendu.
Afficher le transcript complet de la vidéo

Bienvenue dans cette vidéo consacrée à Ruby on Rails. 


Aujourd'hui nous allons modifier les tests créés automatiquement par les générateurs et en ajouter de nouveaux. Ces tests automatisés que nous allons écrire en Ruby vont nous permettre de nous assurer que notre application reste dans un état cohérent, qu'elle est valide d'un point de vue métier, que les différentes pages sont accessibles, etc.


Il est nécessaire d'écrire des tests si vous voulez pouvoir faire évoluer votre application sereinement et c'est encore plus vrai si vous travaillez en équipe.


En ayant un jeu de tests qui vous garanti que l'application fonctionne comme attendu, vous pourrez facilement essayer d'en améliorer des portions sans avoir peur de tout casser. Les tests vont aussi vous permettre de vous assurer que les nouvelles fonctionnalités que vous développez se comportent bien comme attendu.


Corriger les tests existants


Rails met à notre disposition une tâche Rake qui nous permet de lancer l'ensemble des tests.


Ces tests se trouvent dans le répertoire test/ qui contient lui même plusieurs sous-répertoires :

  • controllers qui contient les fichiers dédiés aux tests fonctionnels des contrôleurs. On y teste par exemple le statut de la réponse rendue par une action, si une redirection a eu lieu ou encore si un message donné apparaît dans la vue générée
  • models qui contient les fichiers dédiés aux tests de vos modèles. C'est ici qu'on va tester la logique métier de notre application, comme par exemple s'assurer qu'un utilisateur sans email n'est pas valide ou que la méthode full_name de ce même modèle retourne bien le prénom suivi du nom
  • integration contient quant à lui des tests qui ont pour but de tester les interactions entre différentes parties de l'application. Ce ne sont pas des tests isolés sur un contrôleur ou un modèle mais plutôt des tests qui vont s'assurer que les workflows importants de l'application fonctionnent comme attendus. On vérifiera par exemple si un utilisateur peut aller sur une page produit, l'ajouter au panier, accéder à son panier puis le valider.
  • finalement system est destiné aux test end-to-end, ces tests sont réalisés en conditions réelles, les actions sont jouées dans un navigateur comme si une personne testait par elle même. Ce sont donc les tests les plus proches de l'utilisation en condition réelle mais ce sont aussi les plus lents et de loin.
  • en marge on a le répertoire fixtures dont le but est de permettre de stocker des données qui seront automatiquement injectées en base de donnée avant chaque test ce qui permet d'avoir des éléments sur lesquels se reposer lorsque vous écrivez vos tests.

On peut maintenant lancer les tests :

$ bin/rake test


Le résultat de ces tests nous dit que nous avons joué 7 tests avec 7 assertions que deux d'entre eux ont échoués et qu'un est en erreur.


C'est normal, ce sont des tests auto-générés et nous avons changé la logique métier dans l'épisode précédent pour ajouter des validations. Ça aurait même été inquiétant que tous les tests passent sans qu'on ai rien changé.


Le test ProductsControllerTest#test_should_get_index: nous dit que l'asset pipeline dont le rôle est de gérer les fichiers statiques ne trouve pas de fichier se nommant "MyString". On voit que ce test a lieu dans le fichier test/controllers/products_controller_test.rb à la ligne 9 et que selon toute vraisemblance ça plante dans le fichier app/views/products/index.html.erb ligne 22.


En y regardant, c'est à cet endroit qu'on charge l'image qui correspond au produit. Nous n'avons effectivement pas de fichier s'appelant "MyString", on va donc simplement remplacer cette chaîne dans le fichier de fixture test/fixtures/products.yml pour utiliser le nom d'un fichier qui est effectivement présent.


On change les deux occurences pour y mettre un nom de fichier valide. Chez moi ça sera "tshirt.jpg" et "foo.jpg".


Si on relance les tests :

$ bin/rake test


On voit que le test qui était en erreur a disparu, parfait !


On peut maintenant passer à la correction du test suivant ProductsControllerTest#test_should_create_product qui lui est en échec. Il n'est donc pas cassé mais simplement, l'application ne remplit plus les conditions nécessaires pour que ce test soit valide.


On nous dit que le nombre de produits n'a pas changé comme attendu, on aurait dû en avoir 3 mais il n'y en a que 2 en base.


Si on regarde le fichier de test test/controllers/products_controller_test.rb ligne 19


on voit que le test vérifie qu'il y a bien un différence sur le nombre total de produits après avoir fait un post sur notre URL de création de produit. À ce post on lui passe les paramètres qu'on peut voir dans l'attribut params.


Dans ce test généré par défaut, on essaie de générer un nouveau produit sur la base d'un produit existant stocké dans la variable @product. Cette variable a été initialisée dans le callback setup qui va chercher le produit nommé one dans le fichier de fixtures products, celui-là même qu'on a modifié juste avant.


Ça ne peut donc pas fonctionner puisqu'on a ajouté une validation qui dit que le nom d'un produit doit être unique.


On va donc corriger ce test et pour ce faire, on va simplement modifier les paramètres passés à la requête pour lui en fournir d'autres.

post products_url, params: { product: {
        description: "something",
        image_url: "foo.jpg",
        price: 12.5,
        title: "my new product"
        } 
      }


On sauvegarde et on relance les tests :

$ bin/rake test


Et voilà ce test est réparé, passons au dernier. ProductsControllerTest#test_should_update_product ne passe plus, une redirection est attendue mais à la place on a un 200. Voyons ce test de plus près.


Le test essaie simplement d'appeler l'action update en passant en paramètre les infos inchangées du produit. Ça devrait fonctionner alors pourquoi a t'on cette erreur qui nous rend à nouveau la page d'édition plutôt que de rediriger vers la liste de produits ?


Une fois encore c'est dû à nos ajouts de validations. Il faut savoir que pour des raisons de performances, les fixtures sont insérées telle quelle en base de donnée avec une simple requête SQL, les données qu'on y trouve ne passe donc pas par le cycle de validation des modèles. On peut donc tout à fait insérer des données invalides en base. C'est notre cas ici, si on ouvre à nouveau notre fichier de fixtures, on voit que les deux produits on le même nom. Ce n'est pas cohérent avec nos validations. Quand on tente de mettre à jour le produit, les validations sont jouées et la sauvegarde est refusée parce qu'un autre produit porte déjà le même nom.

On va donc corriger ce test en modifiant nos fixtures pour qu'elles soient valides.

puis on joue à nouveau nos test :

$ bin/rake test

Et maintenant tous nos tests passent ! On a un jeu de données ainsi que des tests qui sont cohérents avec notre logique métier.

Ajouts de test dans le modèle

Pour être complet, il faut modifier les tests du modèle Product pour s'assurer qu'il remplie bien les conditions de notre logique métier. On ouvre donc le fichier test/models/product_test.rb.

Pas étonnant que nos tests modèle passent puisqu'il n'y en a aucun. À nous d'en ajouter.


Commençons par vérifier qu'un produit qui n'a pas ses attributs obligatoires est bien en erreur :

test "product attributes must not be empty" do
  product = Product.new
  assert product.invalid?
  assert product.errors[:title].any?
  assert product.errors[:description].any?
  assert product.errors[:price].any?
  assert product.errors[:image_url].any?
end


On lance nos test, mais cette fois avec une autre commande rake test:models qui ne va jouer que les tests relatifs aux modèles. C'est tout ce qu'on a changé donc autant gagner du temps en ne jouant que ce qui nous intéresse.


Notre test passe, on a bien un test joué qui contient 5 assertions.


Continuons en testant nos autres validations. Dans le précédent épisode, nous avions mis en place une validation qui s'assure que le prix doit être supérieur ou égal à 1. Testons ça :

test "product price must be greater than or equal to 1" do
  product = Product.new(
    title: "Some product",
    description: "some desc",
    price: 0,
    image_url: "abc.jpg"
  )

  assert product.invalid?
  assert_equal ["must be greater than or equal to 1"], product.errors[:price]

  product.price = 1
  assert product.valid?
end


On peut à nouveaux lancer nos tests de modèle :

$ bin/rake test


qui passent sans souci. On a bien 2 tests et 8 assertions.


On peut passer à notre test suivant dans lequel on va vérifier que notre titre est bien unique et ne tient pas compte de la casse :

test "product title must be unique regardless of the case" do
  product = products(:one).dup
  assert product.invalid?
  assert_equal ["has already been taken"], product.errors[:title]

  product.title.upcase!
  assert product.invalid?
  assert_equal ["has already been taken"], product.errors[:title]

  product.title = "changed"
  assert product.valid?
end


On lance nos tests et ça passe. Pour finir, on passe à la validation suivante, à savoir le fait que l'URL d'une image soit au bon format :

test "product image_url must end with .jpg, .gif or .png regardless of the case" do
  product = products(:one)
  valids = %w(foo.jpg foo.gif foo.png foo.JPG foo.GIF foo.PNG http://foo.bar/baz/some.jpg)
  invalids = %w(foo.xls foo.gif.exe)

  invalids.each do |url|
    product.image_url = url
    assert product.invalid?
    assert_equal ["doit être au format jpg, png ou gif"], product.errors[:image_url]
  end

  valids.each do |url|
    product.image_url = url
    assert product.valid?
  end
end


On peut lancer nos tests de modèles :

$ bin/rake test


Et tout est ok. On est arrivé au bout de l'écriture des tests qui valident que notre application fonctionne comme attendu.


Idéalement il vaux mieux écrire les tests avant de développer votre fonctionnalité. Ça permet de s'assurer que le test est probant, il ne devra pas passer puisque la fonctionnalité n'existe pas. Si le test passe sans développement c'est le signe qu'il n'est pas écrit correctement. Écrire vos tests en amont vous permet aussi de mieux réfléchir à ce dont vous avez besoin et comment vous allez le découper.


Quoi qu'il en soit, que ce soit avant ou après avoir développé la fonctionnalité, il est important d'avoir des tests pour pouvoir développer sereinement sur le long terme.


Dans le prochain épisode nous mettrons en place la première page destinée au public et verrons comment utiliser le cache.


À bientôt !

4/7 dans la sérieRails