Cucumber [1][2], est un outil de BDD, ou Behaviour Driven Development (Développement guidé par le comportement). L´idée du BDD est de compléter le TDD (Test Driven Development) avec quelque chose qui rend plus de services. Non seulement on fait des tests, mais en plus, on le fait main dans la main avec le client.
J´ai personnellement eu quelques difficultés à m´y mettre, ne comprenant pas trop comment intégrer ce processus dans ma façon de travailler. Cependant, suite à une expérience récente, j'étais à la recherche d´une façon de modifier mon modus operandi afin d'éviter de perdre du temps et que mon activité soit plus rentable (comprendre « me fasse vivre »).
Cucumber est relativement connu, il est écrit en Ruby, marche avec RubyOnRails, a déjà plusieurs plugins (pour Nagios, par exemple, [4]) et un certain nombre d´outils pour Rails (Factory Girl [5], Machinist [6], ...). Bref, il semblait être un bon candidat pour résoudre mon problème.
Nous allons donc voir dans cet article comment faire une petite application Ruby (pas Ruby On Rails) en utilisant Cucumber. Nous couvrirons aussi rapidement ce qu´il faut faire pour intégrer Cucumber dans une application déjà existante.
J´ai aussi eu du mal à trouver une documentation claire sur le sujet. Je me suis souvent trouvé devant des instructions incomplètes. J´espère donc que cet article comblera ce problème, au moins pour les francophones.
Pour les allergiques à Ruby, il paraît que ça marche très bien avec d´autres langages comme Python, PHP, Java, ... Je dis « il paraît » parce que je ne l´ai pas vérifié personnellement. Consultez le site de Cucumber pour plus d´informations.
1. BDD ?
Dan North, un des auteurs de The Rspec book, décrit le BDD de la façon suivante :
1. Assez est assez : faire ce qu´il faut de préparation, analyse, ..., mais pas plus.
2. Livrer quelque chose qui a de la valeur : si vous n'êtes pas en train de faire quelque chose qui a de la valeur ou qui permettra d´en rajouter, alors arrêtez tout de suite.
3. Tout est comportement : que ce soit au niveau du code, de l´application ou au-delà, nous pouvons utiliser la même façon de penser et les mêmes constructions linguistiques pour décrire le comportement à tout niveau de granularité.
C´est un concept que j´apprécie particulièrement car j´ai récemment eu l´occasion de découvrir de façon douloureuse que j´avais codé trop et pas dans la bonne direction pour un projet. Que de temps perdu !
De façon plus large, le BDD fait partie des méthodes dites « Agile », de la seconde génération. Les méthodes Agile visent à améliorer le processus de création de logiciels en refondant les grandes lignes de celui-ci. Cela avait été résumé dans le Agile manifesto et notamment les « 4 valeurs » [10] :
- Davantage l´interaction avec les personnes que les processus et les outils.
- Davantage un produit opérationnel qu´une documentation pléthorique.
- Davantage la collaboration avec le client que la négociation de contrat.
- Davantage la réactivité face au changement que le suivi d´un plan.
Un exemple concret de l´utilisation de Cucumber est le suivant : élaborer dés le départ du projet la liste des fonctionnalités et leurs descriptions avec le client. Imaginez vous 2 minutes dans une salle de réunion comme nous les connaissons tous. Avec votre équipe, et le ou les clients en face. L´idée est de pousser le client à définir le plus possible ses besoins (avec votre aide) et de se servir de ce processus pour éclairer des zones d´ombres et comprendre ses besoins.
Pour les anglophones, une petite vidéo [3] illustre tout à fait ce processus dans les premières minutes. En assénant la question « pourquoi ? » un nombre suffisant de fois, le client détaillera le processus lié à une fonctionnalité demandée, et ce jusqu'à décrire son besoin initial (réduire les coûts, etc.).
Un exemple cité est le suivant (C : client, D : développeur) :
- C : « Il nous faut une possibilité d´imprimer » ;
- D : « Pourquoi ? » ;
- C : « Parce qu´il nous faut pouvoir avoir les données sur une feuille » ;
- D : « Pourquoi ? » ;
- C : « Parce qu´on s´en sert pour rentrer les données dans ce poste et celui ci. »
Le lecteur malin comprendra vite que l´impression est donc remplaçable par un transfert de données entre les postes. (Sauvant ainsi un arbre et ses enfants du tronçonnage).
2. Concombre + yaourt
Passons aux choses concrètes et plongeons dans les hostilités. Pour commencer, il nous faut installer Cucumber et Rspec.
> sudo gem install cucumber
> sudo gem install rspec
Pffiou, après cet effort surhumain, créons l´arborescence qui va bien :
- some/where/
|
`-- lib/
`-- features/
| `-- step_definitions/
| `-- support/
| `-- env.rb
`-- Rakefile # optionnel
`-- script.rb # un .rb
- Le répertoire lib contiendra vos modules et classes.
- Le répertoire features contiendra tout ce qui concerne Cucumber. A sa racine se trouveront des .feature qui décriront le test, la feature voulue.
- Dans le répertoire step_definitions se trouvera le code des tests.
- Le répertoire support contient, lui, un fichier env.rb qui servira à initialiser l’environnement de test.
- Le Rakefile, lui, est optionnel, mais nous verrons quoi y mettre pour pouvoir utiliser Rake pour lancer les tests.
- script.rb sera un simple script Ruby. Nous partirons de l´idée que c’est notre application.
Il nous faut ensuite spécifier quelques prérequis pour Cucumber dans son fichier d´environnement :
# features/support/env.rb
require 'spec/expectations'
Cette bibliothèque correspond à des « attentes », méthodes qui nous serons utiles un peu plus loin et qui sont incluses dans la gem rspec installée précédemment.
2.1 Fonctionnement
Cucumber utilise deux fichiers pour fonctionner :
- une fonctionnalité, ou feature ;
- une liste d'étapes, ou steps.
Le premier permet de décrire en langage commun (pas celui des elfes donc) la fonctionnalité voulue à travers un scénario. On parle parfois d’«histoire ». Il utilise un langage particulier : Gherkin, qui est lisible par le commun des mortels.
Le second permet de faire un pont entre le précédent et le code réel.
2.2 Fonctionnalité
Notre exemple concernera des renards. Nous voulons avoir des renards. Avec du bacon. Pourquoi ? Parce que c´est sympa un renard.
Une fonctionnalité est stockée dans un fichier dont le nom se termine par l´extension « .feature ». Ils sont stockés dans le répertoire features/ et utilisent une syntaxe particulière :
# features/fox.feature
Feature: TITRE
DESCRIPTION
Scenario: TITRE 2
Given ...
And ...
When ...
Then ...
And ...
Feature vous permet de définir le début de la fonctionnalité, et son titre, suivi d´une brève description. Puis Scenario permet de donner un titre au scénario décrit. Ensuite vient une description en anglais en utilisant les mots-clés Given, When et Then.
- Given (« Soit ») : permet d´indiquer quelque chose qui est connu comme vrai dans le cadre de ce scénario : une « vérité générale ». Ce ne doit pas être une pré-condition, il faut que ce soit quelque chose qui fournit un contexte au scénario.
- When (« Quand ») : décrit l'événement qui a lieu dans le scénario (« Quand je donne un nom », « Quand je prends le train », ...). On préférera donc n´avoir qu’un événement par scénario.
- Then (« Alors ») : décrit les conséquences de l'événement, la situation que l´on doit trouver à la suite de celui-ci.
Nous pouvons aussi utiliser le And pour spécifier une condition supplémentaire, point supplémentaire, ou une conséquence supplémentaire de l'événement. Que les non anglophones se rassurent : Cucumber permet aussi de décrire les fonctionnalités en anglais, passez au paragraphe « Molière serait content » en fin de cette section, pour avoir les clés pour suivre l´article.
Rédigeons donc une fonctionnalité : nous voulons prendre un renard et pouvoir lui donner un nom.
# features/fox.feature
Feature: Give a name to a fox
In order to find our fox we want to give him a name
Scenario: Give a name
Given a fox
And a "name"
When I set the name to the fox
Then the fox should have a name
Nous venons de décrire un processus relativement simple qui correspond à ce que l´on veut faire. C´est parfaitement compréhensible, en anglais ou en français, par des personnes ayant ou non des connaissances techniques.
Une fois une fonctionnalité écrite, on peut utiliser la commande cucumber en lui passant le fichier .feature en paramètre :
> cucumber features/fox.feature
Feature: Give a name to a fox
In order to find our fox we want to give him a name
Scenario: Give a name # features/fox.feature:4
Given a fox # features/fox.feature:5
And a "name" # features/fox.feature:6
When I set the name to the fox # features/fox.feature:7
Then the fox should have a name # features/fox.feature:8
1 scenario (1 undefined)
4 steps (4 undefined)
0m0.009s
You can implement step definitions for undefined steps with these snippets:
Given /^a fox$/ do
pending # express the regexp above with the code you wish you had
end
Given /^a "([^\"]*)"$/ do |arg1|
pending # express the regexp above with the code you wish you had
end
When /^I set the name to the fox$/ do
pending # express the regexp above with the code you wish you had
end
Then /^the fox should have a name$/ do
pending # express the regexp above with the code you wish you had
end
Nous récupérons donc : une sortie directe de notre scénario, avec toutes les lignes en jaune. Le statut du scénario (undefined), et le statut des 4 steps (undefined). Le tout suivi de snippets, que nous pourrions utiliser dans un « step definitions ».
2.3 Définitions des étapes
Nous passons donc à l'étape suivante : écrire le code qui va réaliser ces tests : les step definitions, ou « définitions d'étapes ». Nous pouvons donc copier/coller le code précédemment proposé par Cucumber dans le fichier features/step_definitions/fox_step.rb :
# features/step_definitions/fox_step.rb
Given /^a fox$/ do
pending # express the regexp above with the code you wish you had
end
Given /^a "([^\"]*)"$/ do |arg1|
pending # express the regexp above with the code you wish you had
end
When /^I set the name to the fox$/ do
pending # express the regexp above with the code you wish you had
end
Then /^the fox should have a name$/ do
pending # express the regexp above with the code you wish you had
end
Le nom du fichier n’est évidemment pas anodin puisqu’il correspond au basename du fichier fox.feature auquel on a ajouté le suffixe _step.rb. C´est ainsi que Cucumber fait le lien entre les deux. C´est un fichier Ruby avec une syntaxe un peu particulière : nous remarquons de belles regexp et le mot-clé pending, qui permet à Cucumber de voir que le step n´est pas encore écrit. Nous allons donc essayer d'écrire ces steps, en commençant par le premier (logique, non ?) :
Given /^a fox$/ do
@fox = Fox.new
end
Cette étape correspond à « Soit un renard ». Il nous faut donc un renard. Nous créons donc un objet, instance de la classe Fox (renard). Puis nous exécutons à nouveau le scénario :
> cucumber features/fox.feature
Feature: Give a name to a fox
In order to find our fox we want to give him a name
Scenario: Give a name # features/fox.feature:4
Given a fox # features/step_definitions/fox_step.rb:1
uninitialized constant Fox (NameError)
./features/step_definitions/fox_step.rb:2:in `/^a fox$/'
features/fox.feature:5:in `Given a fox'
And a "name" # features/step_definitions/fox_step.rb:5
When I set the name to the fox # features/step_definitions/fox_step.rb:9
Then the fox should have a name # features/step_definitions/fox_step.rb:13
Failing Scenarios:
cucumber features/fox.feature:4 # Scenario: Give a name
1 scenario (1 failed)
4 steps (1 failed, 3 skipped)
0m0.009s
Les lignes jaunes ont disparu, et nous avons désormais du rouge et du bleu. Le scénario est déclaré comme « failed » (« échoué »), 3 étapes sont déclarées comme « ignorées » et une étape est déclarée comme « échouée ».
Le problème est que Cucumber ne trouve pas la constante Fox. C´est logique, puisque nous n´avons pas encore défini de classe Fox. Continuons à rédiger les étapes :
Given /^a "([^\"]*)"$/ do |name|
@name = name
end
When /^I set the name to the fox$/ do
@fox.name = @name
end
Then /^the fox should have a name$/ do
@fox.name.should_not == nil
end
Rien de bien sorcier, mais le premier step est particulièrement intéressant, puisque nous utilisons un groupe dans l´expression régulière pour récupérer une variable et la passer au code.
A nouveau, nous ne faisons rien de compliqué : nous ne faisons qu'écrire en Ruby ce qui est décrit en anglais : - « Soit un nom » : en extrayant name via la regexp, nous l´assignons à la variable @name. – « Quand je donne un nom au renard » : nous donnons le nom au renard en assignant l´attribut name (nom) de l´objet @fox (renard), la valeur de la variable @name. – « Alors le renard devrait avoir un nom » : nous testons alors que @fox.name existe bel et bien. Plus exactement, nous vérifions qu´il n´est pas égal à nil. Notons au passage l’utilisation de la méthode should et de la lisibilité que cela donne au code.
Si nous réexécutons les tests, rien n’a changé. C’est tout à fait normal. Nous avons décrit la fonctionnalité voulue, nous avons écrit les tests pour vérifier si cette fonctionnalité est implémentée, mais nous n´avons pas encore implémenté celle-ci. Il nous faut donc passer à cette étape-là.
2.4 Du code dudju, du code !
Créons donc un fichier lib/fox.rb
class Fox
end
En exécutant Cucumber à nouveau, « paf », nous obtenons toujours la même erreur. Cette fois-ci, le problème se situe dans le fait que Cucumber ne sait pas où aller chercher nos classes et nous envoie donc balader. Ce problème n´existe pas dans le cas de l´utilisation de Cucumber avec Rails, mais existe bel et bien dans le cas qui nous intéresse présentement.
Pour résoudre ce problème, il nous faut éditer le fichier features/support/env.rb et lui ajouter un path de chargement en la personne de notre répertoire lib/ qui contient notre classe. Enfin, nous devons ajouter un petit require pour inclure notre classe :
# features/support/env.rb
# cucumber own requires
require 'spec/expectations'
# your app requires
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
require 'lib/fox'
Exécutons à nouveau cucumber :
> cucumber features/fox.feature
Feature: Give a name to a fox
In order to find our fox we want to give him a name
Scenario: Give a name # features/fox.feature:4
Given a fox # features/step_definitions/fox_step.rb:1
And a "name" # features/step_definitions/fox_step.rb:5
When I set the name to the fox # features/step_definitions/fox_step.rb:9
undefined method `name=' for #<Fox:0x101520768> (NoMethodError)
./features/step_definitions/fox_step.rb:10:in `/^I set the name to the fox$/'
features/fox.feature:7:in `When I set the name to the fox'
Then the fox should have a name # features/step_definitions/fox_step.rb:13
Failing Scenarios:
cucumber features/fox.feature:4 # Scenario: Give a name
1 scenario (1 failed)
4 steps (1 failed, 1 skipped, 2 passed)
0m0.023s
Cette fois-ci, deux steps sont passés (passed), un a échoué (et continue de cracher ses tripes sur la plage), et le dernier a été ignoré.
Le step qui a échoué nous indique qu´il voudrait bien une méthode name= pour l´objet de classe Fox. Nous allons donc écrire cette méthode :
class Fox
def name=(name)
@name = name
end
end
#
# executons cucumber
> cucumber features/fox.feature
...
Then the fox should have a name # features/step_definitions/fox_step.rb:13
undefined method `name' for #<Fox:0x101520510 @name="name"> (NoMethodError)
./features/step_definitions/fox_step.rb:14:in `/^the fox should have a name$/'
features/fox.feature:8:in `Then the fox should have a name'
Failing Scenarios:
cucumber features/fox.feature:4 # Scenario: Give a name
1 scenario (1 failed)
4 steps (1 failed, 3 passed)
0m0.012s
Une étape de plus de passée, mais il en reste encore une, et il manque pour cela une méthode name. Ajoutons donc celle-ci à notre classe et repassons un coup de cucumber :
def name
return @name
end
#
# executons cucumber
> cucumber features/fox.feature
...
1 scenario (1 passed)
4 steps (4 passed)
0m0.010s
Si c´est tout vert, c´est que c´est bon.
Le but de base est atteint : nous avons écrit une fonctionnalité, des étapes, et le code qu´il fallait pour que celles-ci passent au vert. Cependant, on peut probablement faire mieux et l'étape qui suit est donc celle de la refactorisation (comme en maths, oui).
2.5 Refactor
Le code n'étant pas très « rubiesque », faisons un peu mieux en remplaçant les deux méthodes par un simple attr_accessor :
class Fox
attr_accessor :name
end
#
# executons cucumber
> cucumber features/fox.feature
...
1 scenario (1 passed)
4 steps (4 passed)
0m0.015s
Nous venons de terminer une itération classique de BDD : écriture des scénarios, des étapes, du code, et refactorisation.
2.6 Un peu de poivre
Juste pour s´amuser, nous pourrions changer le scénario afin de voir un peu plus ce qui est faisable :
Feature: Give a name to a fox
In order to find our fox we want to give him a name
Scenario: Give a name
Given a fox
And a name : "bob"
When I set the name to the fox
Then the fox should be called "bob"
Nous avons ajouté ici le nom "bob", il serait en effet intéressant de passer un vrai nom dans le scénario et de tester l´objet avec. Nous devons changer les étapes en conséquence :
Given /^a fox$/ do
@fox = Fox.new
end
Given /^a name : "([^\"]*)"$/ do |name|
@name = name
end
When /^I set the name to the fox$/ do
@fox.name = @name
end
Then /^the fox should be called "([^\"]*)"$/ do |name|
@fox.name.should == name
end
Nous remarquons l´utilisation d´un groupe à nouveau. Si nous exécutons Cucumber à nouveau, nous devrions obtenir du vert tout vert.
2.7 Bonus track
Pour simplifier notre vie, nous pouvons utiliser Rake pour lancer Cucumber. Un Rakefile pour faire cela peut se présenter comme suit :
task :cucumber do
$:.unshift(File.dirname(__FILE__) + '../lib')
begin
require 'cucumber/rake/task'
Cucumber::Rake::Task.new(:features)
rescue LoadError
puts "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
end
Cucumber::Rake::Task.new do |t|
t.cucumber_opts = %w{--format pretty}
end
end
Pour tester tous les scénarios, il nous suffira alors de taper :
> rake cucumber
Nous pouvons aussi simplement utiliser la commande cucumber à la racine de notre projet.
2.8 Molière serait content
Puisque Cucumber se veut lisible par le client, il supporte plusieurs langues pour écrire les features et les steps. On peut donc très bien utiliser du français, ou même du LOLCAT (si si) :
# features/renard.feature
# language: fr
Fonctionnalité: TITRE
DESCRIPTION
Scénario: TITRE 2
Soit ...
Et ...
Lorsque ...
Alors ...
Et ...
Vous pouvez utiliser la commande cucumber --i18n help pour avoir la liste des langues supportées et cucumber --i18n fr pour connaître la correspondance exacte des mots-clés en français.
Voici une version française de ce que nous venons de faire en anglais :
# renard.feature
# language: fr
Fonctionnalité: Donner un nom au renard
Afin de retrouver notre renard nous voulons lui donner un nom
Scénario: Donner un nom
Soit un renard
Et un nom : "bob"
Lorsque je donne le nom au renard
Alors le renard devrait s'appeller "bob"
>
# renard_step.rb
# language: fr
Soit %r{^un renard$} do
@fox = Fox.new
end
Soit %r{^un nom : "([^\"]*)"$} do |nom|
@name = nom
end
Lorsque %r{^je donne le nom au renard$} do
@fox.name = @name
end
Alors %r{^le renard devrait s'appeller "([^\"]*)"$} do |nom|
@fox.name.should == nom
end
La syntaxe des regexp est ici différente, mais seulement par goût personnel, et revient exactement à la même chose. Si nous lançons la tâche rake :
> rake cucumber
...
Feature: Give a name to a fox
In order to find our fox we want to give him a name
Scenario: Give a name # features/fox.feature:4
...
# language: fr
Fonctionnalité: Donner un nom au renard
Afin de retrouver notre renard nous voulons lui donner un nom
Scénario: Donner un nom # features/renard.feature:5
...
2 scenarios (2 passed)
8 steps (8 passed)
0m0.005s
Molière serait donc content de voir sa langue si bien utilisée.
2.9 Pour finir
Nous avons donc vu un rapide aperçu de Cucumber, comment l’utiliser et comment baser un processus de développement dessus. En somme, il s´agit de suivre les étapes suivantes :
1. Décrire l´application en termes de features.
2. Ecrire les étapes de chaque feature.
3. Lancer Cucumber et voir les tests échouer.
4. Ecrire le code de façon à ce qu’une feature passe au vert.
5. Lancer Cucumber.
6. Si des steps ne sont pas verts, retourner en 4.
7. Refactoriser le code.
Comme le souligne un article (7, en anglais), Cucumber ne remplace pas non plus des tests unitaires. Et cet article recommande d’utiliser le processus suivant :
- Décrire l´application en termes de features.
- Ecrire les étapes de chaque feature.
- Lancer Cucumber et voir les tests échouer.
- Ecrire les tests unitaires, les voir échouer, écrire le code nécessaire à leur passage.
- Ecrire les tests fonctionnels, les voir échouer, écrire le code nécessaire à leur passage.
- Lancer Cucumber, si certains steps ne sont toujours pas en vert, retourner en 4.
- Refactoriser le code.
A vous de voir.
3. Concombre + sauce toute prête
Intégrer Cucumber à une application existante est relativement simple, bien qu’il faille tomber sur les bonnes informations.
3.1 Créer l´arborescence
Il nous suffit de créer l´arborescence vue précédemment :
- some/where/
|
`-- features/
| `-- step_definitions/
| `-- support/
| `-- env.rb
Attention, cela n´est que pour une application Ruby classique, pas une application RubyOnRails, pour celle-ci, il y a des choses particulières à mettre en œuvre. Nous ne les abordons pas dans cet article, hélas, mais elles sont décrites dans plusieurs documentations.
3.2 Rake
Pour plus de confort, je vous conseille d´intégrer la tâche rake précédemment décrite dans votre Rakefile.
3.3 Env
Le fichier env.rb devra contenir le contenu suivant :
# features/support/env.rb
require 'spec/expectations'
# changer LIB par le dossier contenant vos classes et modules
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../LIB')
## requires
Complétez cette base avec les requires correspondant à vos classes.
Conclusion
Nous avons donc couvert un terrain relativement large de façon très rapide, et Cucumber offre bien d´autres possibilités :
- organiser les features par thèmes dans des sous-dossiers ;
- utiliser des tags ;
- utiliser des tableaux dans les scénarios ;
- intégrer des outils comme Shoulda, Machinist, Factory Girl ... ;
- ...
Bref, il y a encore de quoi lire et apprendre. Je vous conseille donc de vous diriger vers le site de Cucumber et de creuser la question, lisez les docs, les blogs, etc.
Personnellement, j´ai beaucoup aimé l’idée globale et le fait que cela peut nous permette de réduire la quantité de code à écrire (et donc à maintenir). Un problème de taille à l´adoption d´un tel outil (ou d’un tel processus de développement) peut être notre incapacité à nous remettre en question (je suis passé par là), nous devons nous demander si notre processus est vraiment le bon ou pas, ou s’il peut être amélioré. C’est là le but des méthodes Agile (à mon humble avis), et j´ai appris la leçon d´une façon un peu moche. Donc : toujours rester ouvert à la nouveauté, au changement.
Comme certains lutins le soulignaient récemment, Ruby semble prendre de plus en plus pied dans le milieu de l´administration système, comme Perl a pu le faire par le passé. Il est vrai que, puisqu’il est largement inspiré de Perl, mais avec un aspect objet beaucoup plus -ahem- moderne (?), il a beaucoup pour plaire.
J´espère que ça vous sera utile.
Liens
[1] Cucumber @ GitHub : http://github.com/aslakhellesoy/cucumber
[2] Cucumber : http://cukes.info/
[3] Vidéo de présentation : http://mwrc2009.confreaks.com/14-mar-2009-15-00-bdd-with-cucumber-ben-mabey.html
[4] Cucumber Nagios : http://auxesis.github.com/cucumber-nagios/
[5] Factory Girl : http://github.com/thoughtbot/factory_girl
[6] Machinist : http://github.com/notahat/machinist
[7] « Cucumber déchire mais ne remplace pas les tests unitaires » : http://www.pathf.com/blogs/2009/06/cucumber-rocks-but-its-not-a-replacement-for-unit-tests/
[8] 15 Cucumber Tips : http://www.engineyard.com/blog/2009/15-expert-tips-for-using-cucumber/ (orienté Rails)
[9] Code d´exemple de l´article : http://github.com/mcansky/Article-Cucumis-sativus (sous licence MIT).
[10] Agile Manifesto sur Wikipédia : http://fr.wikipedia.org/wiki/Manifeste_agile