Je viens de publier un article sur le blog d’O2Sources, sur le thème suivant : « Ecrire du code testable« .
En effet, vous pouvez avoir toute la bonne volonté du monde à vouloir écrire des tests dans votre code, si celui-ci n’est pas écrit de manière à vous permettre de créer des tests de manière aisée, vous n’arriverez à rien.

Cet article donne donc une liste de 10 conseils pour écrire du code testable.

  1. Ne pas mélanger le constructeur avec le code logique
  2. Demandez les choses. N’allez pas les chercher vous même
  3. Ne faites pas tout le travail dans le constructeur
  4. Evitez les singletons et variables globales
  5. Evitez les méthodes statiques
  6. Favoriser les compositions aux héritages
  7. Favoriser le polymorphisme aux conditions
  8. Mélanger des objets de valeur et des objets de service
  9. Ne mélangez pas tout
  10. Pensez deux fois, n’agissez qu’une

Je vous invite donc à lire cet article « Celui qui voulait rendre son code testable« .

Bon en fait si. Mais en lisant cet article, vous verrez que ce qu’il a dit aux journalistes dans l’avion l’emmenant en Afrique n’est absolument pas ce que les journalistes nous font croire.
Par ailleurs cet article n’est absolument pas dans la ligne éditoriale de ce blog. Mais j’ai décidé de faire une exception.

Tout d’abord, afin que cela soit clair tout de suite. Je suis chrétien catholique pratiquant. J’habite dans un foyer de jeunes, avec 5 collocataires dans le presbytère d’une église; je participe à l’organisation d’une prière hebdomadaire à Villeurbanne; je fais partie du « staff » d’un bar associatif en dessous de l’une des églises de Villeurbanne, la sCène. Et je serai le responsable photographie pour Lyon lors du pélerinage national en Terre Sainte cet été.

Maintenant voyons un petit peu ce que le pape a dit aux journalistes dans cet avion qui les emmenait en Afrique.

Je pense que l’entité la plus efficace, la plus présente sur le front de la lutte contre le
sida est justement l’Eglise catholique, avec ses mouvements, avec ses réalités diverses.
Je pense à la communauté de Sant’Egidio qui fait tellement, de manière visible et aussi invisible, pour la lutte contre le sida, je pense aux Camilliens, à toutes les sœurs qui sont au service des malades…
Je dirais que l’on ne peut vaincre ce problème du sida uniquement avec des slogans publicitaires.
S’il n’y a pas l’âme, si les Africains ne s’aident pas, on ne peut résoudre ce fléau en distribuant des préservatifs : au contraire, cela risque d’augmenter le problème.
On ne peut trouver la solution que dans un double engagement : le premier, une humanisation de la sexualité, c’est-à-dire un renouveau spirituel et humain qui implique une nouvelle façon de se comporter l’un envers l’autre, et le second, une amitié vraie, surtout envers ceux qui souffrent, la disponibilité à être avec les malades, au prix aussi de sacrifices et de renoncements personnels.
Ce sont ces facteurs qui aident et qui portent des progrès visibles.
Autrement dit, notre double effort pour renouveler l’homme intérieurement, donner une force spirituelle et humaine pour un comportement juste à l’égard de son propre corps et de celui de l’autre, et notre capacité à souffrir, à rester présent dans les situations d’épreuve avec les malades.
Il me semble que c’est la réponse juste, l’Eglise agit ainsi et offre par là même une contribution très grande et très importante. Remercions tous ceux qui le font.

Source : salle de presse du Saint Siège; Traduction La Croix.

La mise en gras est de moi. C’est la seule partie qui a été retenue par les médias.
Comme vous le constatez maintenant, une fois remis dans son contexte, les mots du pape prennent un tout autre sens.
Les médias n’ont même pas fait l’effort de couper en gardant une phrase entière et dans le contexte de ce qu’a dit le pape. Ils en sortent juste un bout.

Je comprends que faire dire ce genre de choses au pape est plus vendeur. Mais la, c’est de la désinformation.
Et je ne sais pas qui est le journaliste français qui était présent dans cet avion et qui n’a retenu que ce bout de phrase. Mais ça s’approche de la faute professionnelle la.

Je laisse les commentaires ouverts. Mais si vous trollez, ne vous étonnez pas de voir votre message supprimé.

[Edit : apparemment avoir uniquement le vrai texte ne suffit pas à certains. Heureusement Eolas fait un commentaire beaucoup plus détaillé et qui va dans le même sens que le mien]

J’ai déjà parlé plusieurs fois ici de tests unitaires. En Ruby ou en PHP.
Cependant toute application qui se respecte aura également une couche navigateur, permettant d’ajouter de l’ajax par exemple.
Ces fonctionnalités, tout comme le code serveur, sont testables.

Voyons un petit peu comment cela fonctionne avec QUnit, le framework de tests unitaires intégré avec JQuery.

Pour cela, je vais reprendre mon exemple d’il y a quelques mois ou je proposais de signaler si un pseudonyme est déjà utilisé lors d’une inscription, et de tester cela.
Cependant aujourd’hui, j’ai fait la chose en PHP, afin d’épargner la création d’un projet RoR. On a au final, dans cet exemple, besoin que de deux fichiers :

check_user.php, qui vérifie si notre pseudonyme est disponible ou non. Et retourne ‘OK’ si il l’est et ‘NOT’ sinon.
On ne s’embête pas ici. Voici ce que j’ai dans mon document :

<?php if ($_GET['u'] == 'oh') echo 'OK';
else echo 'NOT'; ?>

Puis dans notre index.php, nous allons placer le champ de texte du nom d’utilisateur.

<html>
    <head>
        <title></title>
        <script src="http://www.google.com/jsapi"></script>
        <script type="text/javascript>
            google.load("jquery", "1.3.2");
        </script>
        <script type="text/javascript" src="code.js"></script>
    </head>
    <body>
        <input type="text" id="username" name="username" />
        <div id="availability"></div>
    </body>
</html>

Et nous appellons notre document javascript qui fera un appel ajax à check_user.php.

$(document).ready(function() {
    $('#username').blur(function () {
        username = $('#username').val();
        if (username == '') {
            $('#availability').html('');
            return false;
        }

        $.ajax({
            url: 'check_user.php?u='+username,
            success: function(data) {
                    if (data == 'OK') {
                        $('#availability').html('Disponible');
                        $('#availability').css('color', 'green');
                    } else {
                        $('#availability').html('Non disponible');
                        $('#availability').css('color', 'red');
                    }
            },
            error: function(data) {
                $('#availability').html('');
            }
        });
    })
});

Voila ! Maintenant, nous avons notre test de validité de l’utilisateur de fonctionnel. Pour l’instant du moins car pour être sur qu’il le reste, nous allons ajouter des tests.
Commençons par implémenter QUnit dans notre index.php.

<html>
    <head>
        <title></title>

        <script src="http://www.google.com/jsapi"></script>
        <script type="text/javascript">
            google.load("jquery", "1.3.2");
        </script>
        <script type="text/javascript" src="code.js"></script>
    </head>
    <body>

        <input type="text" id="username" name="username" />
        <div id="availability"></div>

        <link rel="stylesheet" href="http://github.com/jquery/qunit/raw/master/qunit/qunit.css" type="text/css" media="screen" /
	<script type="text/javascript" src="http://github.com/jquery/qunit/raw/master/qunit/qunit.js"></script>
        <script type="text/javascript" src="tests.js"></script>
        <h2 id="qunit-banner">Qunit Testing</h2>
	<h2 id="qunit-userAgent"></h2>
	<ol id="qunit-tests"></ol>
    </body>
</html>

Vous pouvez constater que nous ajoutons divers tags html en fin de page. Ceux-ci seront automatiquement remplacés par l’interface de visualisation des tests.

Et enfin dans le fichier tests.js inclus précédemment, nous plaçons tous nos tests.

$.ajaxSetup({
  async: false
});

module("username")

test("availability div shouldn't have any value by default", function() {
    equals($('#availability').html(), '', "availability div doesn't have any value by default");
});

test("should allow inexistant username", function() {
    $('#username').val('oh');
    $('#username').blur();

    equals($('#availability').html(), 'Disponible', "the username is available");
    equals($('#availability').css('color'), 'green', "the username is available");
});

test("should not allow existant username", function() {
    $('#username').val('doh');
    $('#username').blur();

    equals($('#availability').html(), 'Non disponible', "the username is not available");
    equals($('#availability').css('color'), 'red', "the username is not available");
});

test("should hide the message if username is empty", function() {
    $('#username').val('');
    $('#username').blur();

    equals($('#availability').html(), '', "the username is empty");
})

La méthode « module » permet de signaler que nous sommes dans la partie de vérification du pseudonyme de la page.
Chaque méthode test définit un test.

Quant au ajaxSetup, il nous permet de forcer les requêtes ajax à ne pas être asynchrone. Si elles l’étaient, nos tests seraient exécutés avant le retour de la requête ajax et échoueraient donc.

Si vous rechargez votre page, vous pouvez maintenant voir que nous testons convenablement (et avec succès) la vérification de la disponibilité de notre pseudonyme.

Pour finir, vous me direz rapidement que c’est bien gentil de tester ça. Mais que vous n’allez pas charger chacune des pages de votre site à chaque fois que vous changez une ligne de code.
La solution à cela, qui est cependant trop complexe pour tenir dans cet article, est d’utiliser Selenium, un outil permettant de tester le frontend d’une application web sur de multiples navigateurs.

Pour finir, vous pouvez voir toutes les portions de code données dans cet article avec mise en couleur du code.

Ruby est très développé et très bien document lorsqu’il s’agit de développer des applications web.
Mais parce qu’il n’y a pas que le web dans la vie (ah bon ???), il peut être utile, même dans le cas du développement web, de mettre en place des applications console.

Par console, j’entends sans aucune interface graphique. Celles-ci sont encore un autre sujet.

Du coup en faisant quelques recherches, j’ai découvert SimpleConsole, qui est justement un framework de développement d’applications console simples.
Et en plus il gère un (semblant) de MVC.

Votre application va donc se diviser en deux parties.
Supposons un fichier « myapp.rb », avec le code suivant :
#!/usr/bin/env ruby -w
require 'rubygems'
require 'simpleconsole'
require 'views/myapp'

class Controller < SimpleConsole::Controller
  params :string => {:n => :name, :w => :word}

  def default
    @name = params[:name]
    @word = params[:word]
  end
end

SimpleConsole::Application.run(ARGV, Controller, View)

Puis un fichier views/myapp.rb
class View < SimpleConsole::View
  def default
    puts "Your name is " + @name + "."
    puts "You wanted me to say the word " + @word + "."
  end
end

Si vous exécutez
ruby myapp.rb -name Damien -w Yay

La console vous retournera alors :

Your name is Damien.
You wanted me to say the word Yay.

Le code est tout d'abord interprété par le contrôleur pour ensuite être transmis à la vue, qui renvoie le contenu que vous désirez.
C'est du pseudo MVC et la chose serait largement améliorable.

Mais c'est un bon début pour une application console qui se doit d'être simple.
Et puis vu que l'application ne semble plus maintenue, il est possible que je la forke et que je fasse quelques commits dessus ;)

Après avoir vu comment utiliser Active Record sans Rails, voyons maintenant comment créer des migrations de nos bases de données sans rails également.
Tout d’abord, si vous ne savez pas ce qu’est une migration de base de données, je vous invite à vous documenter. Ici par exemple.

Maintenant allons y pour nos migrations en dehors de notre projet rails !
Pour exécuter celles-ci, nous allons utiliser Rake. Si vous en maitrisez les bases, c’est un peu mieux :)

Nous allons tout d’abord créer un dossier db/migrate dans lequel nous plaçerons tous nos fichiers de migrations.
Créons la première, dans 001_create_users.rb

class CreateUsers < ActiveRecord::Migration
    def self.up
        create_table :users do |t|
            t.column :name, :string, :null => false
            t.column :password, :string, :null => false
        end
    end
    def self.down
        drop_table :users
    end
end

Ici, nous versionnons notre document en 001. Rien ne vous empêche cependant d’avoir vos propres règles de versionning ici afin de gérer la chose de manière plus efficace.

Mettons maintenant en place le rakefile qui exécutera nos migrations.

require 'active_record'
require 'yaml'

task :default => :migrate

desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x"
task :migrate => :environment do
    ActiveRecord::Migrator.migrate('db/migrate', ENV["VERSION"] ? ENV["VERSION"].to_i : nil )
end

task :environment do
    ActiveRecord::Base.establish_connection(YAML::load(File.open('database.yml')))
    ActiveRecord::Base.logger = Logger.new(File.open('database.log', 'a'))
end

La tâche « migrate » effectue notre migration.
La tâche « environment » définit la connexion à la base de données et la manière de logger la chose. Vous pouvez bien évidemment le faire évoluer afin de gérer les migrations dans vos environnements de développement et de production.

Enfin il ne reste plus qu’à exécuter la migration en exécutant, en console

rake

La création est correctement loggée et la base créée :)
Si par la suite, vous créez un document 002_create_premissions.rb avec la définition d’une table permissions alors celle-ci sera créée de la même manière.
Et comme migrate gère le versionning, si vous exécutez une seconde fois vos migrations, les premières ne seront pas réexécutées.

Si vous désirez cependant les réexécuter tout de même, il vous suffit de vider la base schema_migrations.
Et de supprimer les bases déjà créées par vos migrations évidemment. Sans quoi vous aurez une belle erreur de table déjà existante.


Si vous avez déjà utilisé Active Record. Et vu que vous lisez ce blog et cet article, je suppose que c’est le cas, même si vous n’en savez peut-être rien.

Dans le cas ou votre mémoire vous jouerai des tours, voici un petit rappel. Active Record est une librairie utilisée dans Ruby on Rails et permettant aux applications de manipuler de manière aisée les bases de données.

C’est compatible avec MySQL, SQLite et PostgreSQL (il est cependant tout à fait possible de développer son propre adaptateur).

Mais Rails est un framework pour applications web. Et le web ne se résume pas au développement web.

Du coup nous allons voir comment implémenter et utiliser Active Record dans votre application console.

Tout d’abord, vous devez installer la librairie. Si vous avez déjà installé Rails sur votre machine, vous êtes prêt à démarrer. Dans le cas contraire, tapez la commande suivante en console :

gem install activerecord

Cela téléchargera la dernière version stable de la librairie et l’installera sur la machine.

Maintenant commençons à nous amuser un peu en développant :)

Créez un nouveau document que vous appellerez (par exemple) main.rb

Tout d’abord, vous devez appeller les librairies rubygems et activerecord.

require 'rubygems'
require 'active_record'

Avec ces lignes en haut de votre document, vous avez maintenant accès à toutes les méthodes fournies par Active Record.

Du coup, autant les utiliser.

ActiveRecord::Base.establish_connection(
    :adapter => 'mysql',
    :host => 'localhost',
    :user => 'root',
    :password => 'root',
    :database => 'test'
)

Ceci initialisera la librairie.

Cependant, cela ne vous connectera pas à la base de données. Vous ne le serez que lorsque vous ferez votre première requête.

Maintenant, créons un modèle.

Je vous invite à ne pas le créer dans le document, pour des raisons de propreté de l’application. Créons donc un document nommé « user.rb » dans le même répertoire que votre main.rb.

Placez le code suivant dans votre nouveau document :

class User < ActiveRecord::Base
end

Et dans votre main.rb, appellez le :

require 'user'

Vous avez maintenant accès, dans votre main.rb, à l’objet User, qui hérite de ActiveRecord::Base et vous fournit donc toutes les méthodes habituellement utilisées dans les modèles Ruby on Rails. Vous n’êtes cependant pas dans une application Rails.

Du coup récupérons tous nos utilisateurs.

p User.find(:all)

Si votre table « users » existe dans la base de données « test », ceci affichera dans votre console un bump du hash de tous vos utilisateurs.

Vous pouvez donc maintenant faire tout ce que vous faites habituellement dans vos modèles Ruby on Rails. En passant par les associations entre modèles, en passant par les validations. Et même les migrations (ceci fera cependant l’objet d’un second article).

Mais attendez ce n’est pas tout. Jusqu’à maintenant, nous avons défini nos paramètres de configuration directement dans l’application. Et c’est très très mal. Du coup nous allons ajouter un document database.yml externe :

adapter: mysql
host: localhost
username: root
password: root
database: refcrawler_dev

Et pour charger ces paramètres de connexion dans notre document :

require 'yaml'
dbconfig = YAML::load(File.open('config/database.yml'))
ActiveRecord::Base.establish_connection(dbconfig)

Je l’attendais depuis le début de la semaine, il vient enfin d’arriver.
Je viens en effet de changer de boitier :)

Du « petit » 350D je passe donc à la gamme au dessus, mais toujours chez Canon parce que le rouge ça rox avec un 50D.

 
Fork me on GitHub