Maîtriser les fixtures dans les tests de Ruby on Rails
Dans un article précédent, j’ai vanté les mérites du test driven development avec Ruby on Rails. Étant donné que très peu de gens utilisent les outils de test de Rails et que ces derniers reposent (comme tout dans ce framework) sur un ensemble de conventions à prendre en main, j’avais promis de revenir plus largement sur ce point.
Il me semble qu’il est intéressant de commencer par les fixtures, car ce sont les données de bases des tests et surtout, parce que l’utilisation qui en est faite est souvent très basique, alors que les fixtures offrent des possibilités avancées qui facilitent grandement les tests.
Je vais d’abord revenir sur les bases des fixtures pour ceux qui découvrent les outils de test, et nous verrons ensuite ces sucreries.
- Maîtriser les fixtures
- Maîtriser les tests unitaires
- Maîtriser les tests fonctionnels
- Maîtriser les tests d’intégration
Au commencement, il y avait data
Eh oui l’informatique, c’est le traitement automatique de l’information. On pourrait décrire l’ensemble des entités informatiques sous l’expression : data + process. Les tests de Rails ne font pas défaut : il faut d’abord leur fournir des données à manipuler.
Si vous avez scrupuleusement suivi les tutoriels d’initiation à Rails, vous avez pris l’habitude de créer trois bases de données : development, production et test. Ce n’est pas anodin. Cela veut dire que chaque environnement possède son propre ensemble de données.
Vous l’aurez deviné, les outils de test utilisent la base de donnée test, qui ne recoupe pas les données des autres environnements (ce qui évite de laisser la base de donnée dans un état indéfini après avoir lancé les tests). Comment introduire des données dans cette base, alors? C’est le travail des fixtures.
Et son nom était YML
Il existe trois formats pour les fixtures, le plus courant étant YAML (ou YML). Son avantage est d’être supporté en builtin par Ruby. Si vous utilisez Ruby, vous l’avez sûrement déjà rencontré car il est abondamment utilisé pour serialiser des objets. C’est un peu l’équivalent de JSON pour le Javascript (bien que ce ne soit pas exclusif à js et que ce dernier pourrait utiliser d’autres formats). Et même sans être un rubyiste, vous vous en servez pour configurer la connexion à la base de donnée dans Rails, dans le fichier config/database.yml.
Son usage est relativement intuitif. On donne un nom à chaque fixture, et ses attributs sont placés en dessous, en étant indentés (attention : seulement des espaces, pas de tabulations!). Le nom est tout à fait arbitraire et pourra être utilisé dans les tests (et dans les fixtures eux-mêmes).
Chaque fichier de fixture correspond à un modèle. Ainsi, tous les éléments dans test/fixtures/comments.yml seront transformés en objets Comment, tels que définis dans app/models/comment.rb.
spam: id: 1 user_id: 1 created_at: "2008-10-12 18:50:10" content: "http://inpythonwetrust.com" status: 2
Cet exemple présuppose que vous avez par ailleurs un modèle User et un fixture dans users.yml avec un id = 1, par exemple :
troller: id: 1 name: "persiffleur" mail: "nid2viper@gmail.com" created_at: "2008-10-12 18:50:10"
Les fixtures sont aussi simples à utiliser que ça : il suffit de donner un nom et de reprendre les champs SQL de la table à laquelle correspond le modèle. C’est sympa, mais on est en droit de s’attendre à un peu plus de magie de la part de Rails.
Et il dit : Que la lumière soit!
C’est bien-heureusement le cas. Pour commencer, il est inutile de préciser les champs created_at et updated_at. Rails leur donne automatiquement la valeur de Time.now.
Si vous désirez préciser une valeur autre que Time.now, Rails vous facilite la tâche en permettant d’utiliser erb dans les fixtures. Vous pouvez donc utiliser tous les helpers accessibles depuis vos vues, par exemple :
created_at: <%= 2.days.ago.to_s :db %>
Ensuite (et c’est mon feature préféré), vous pouvez également ne pas spécifier l’id. Un id est alors automatiquement généré par Rails à partir du nom du fixture. Ce qui permet d’écrire quelque chose comme ça:
# users.yml troller: name: "persiffleur" mail: "nid2viper@gmail.com"
et
# comments.yml spam: user: troller content: "http://inpythonwetrust.com" status: 2
Cette possibilité doit commencer à vous faire entrevoir l’intérêt en terme de Test Driven Development. À noter que les guillemets ne sont pas nécessaire pour marquer le début et la fin des strings, mais je préfère personnellement les utiliser quand même pour distinguer les strings des identifiers.
Enfin, le fait de pouvoir utiliser erb signifie également la possibilité d’introduire de la logique dans vos fixtures. Ne faites pas ces gros yeux, YAML ne définit pas de standard MVC :] . Cela a un intérêt majeur si vous voulez voir comment votre application tient le choc avec 5000 utilisateurs.
<% 5000.times do |i| %> user_<%= i -%>: name: "user_<%= i -%>" mail: "user_<%= i -%>@gmail.com" <% end %>
Le premier jour, il écrivit les fixtures
Personnellement, je commence toujours un projet par… inkscape. Je fais une map des différentes entités dont je pense avoir besoin dans l’immédiat, je dessine leur relation et je leur donne les quelques attributs qui me passent par la tête. Le dessin vectoriel est juste parfait pour ça.
L’étape suivante, pour tout développeur Rails, est de créer les modèles (juste la création même, par le biais des générateurs). Puis, ils écrivent généralement les migrations.
Du point de vue du test driven development, c’est une mauvaise idée. Les fixtures, dans leur usage avancé, permettent une écriture bien plus naturelle des schémas. Prenons l’exemple suivant de migration :
class CreateComments < ActiveRecord::Migration
def self.up
create_table :comments do |t|
t.column 'article_id', :integer, :null => false
t.column 'user_id', :integer, :default => 0
t.column 'content', :text, :null => false
t.timestamps
end
end
def self.down
drop_table :comments
end
end
Cela est déjà très concret et implique un blocage réflexif sur l’implémentation de la base de donnée. En écrivant d’abord les fixtures, on peut se permettre d’écrire simplement ce qu’on attends du modèle en terme de structure :
ruvanche_comment: article: ruby user: ruvanche content: "301"
Cela permet de se faire une idée rapidement des attributs dont on a réellement besoin et de voir comment les données sont reliées entre elles, nous faisant vite remarquer si une relation de notre map est défaillante (par exemple, un has_many qui devrait plutôt devenir un many_to_many, ou un attribut qui devrait plutôt devenir un modèle). On regagne donc le temps perdu à modifier/reset les migrations et réintroduire des données (ou à écrire des migrations de fix).
Une fois que ces données abstraites mais en action ont été écrites, on peut penser à écrire les migrations, qui sont des informations concrètes mais de réserve (dans le sens où on ne les pensera en action que quand on écrira les modèles, si on omet les tests). Puis, quasiment simultanément, les relations dans les modèles, c’est-à-dire les has_many, belongs_to et autres.
Les fixtures qu’on aura écrit permettront de tester rapidement s’il y a des problèmes dans le schémas de la base de donnée ou dans le jeu des relations. Pas même besoin d’écrire des tests, le simple test_truth qui aura été généré automatiquement dans les tests de vos modèles vous suffiront à mettre ce réseau de relations à l’épreuve. Lancez simplement :
rake test
Et vous pourrez fixer les bases de vos modèles et vos migrations avant même d’avoir commencé à écrire du code effectif, et surtout avant d’avoir chargé des données.
Pas besoin d’imaginer non plus cinq fixtures différents en différents états lors de la première écriture des fixtures. Il s’agit juste de se représenter à quoi peut ressemble une instance d’un modèle. Les états particuliers viendront d’eux-même lorsqu’on écrira effectivement les tests.






14 octobre, 2008 à 1:30
Maîtriser les fixtures dans les tests de Ruby on Rails…
Premier billet d’une série sur les outils de test de Rails. Les fixtures en profondeur….