china online pharmacies sale brand levitra for sale how to get viagra natural viagra cheap levitra canada canadian cialis fast cialis daily online cialis 5mg levitra next day cialis order buy cialis usa canadian viagra cheap cialis from india once daily cialis sales canadian propecia discounts buying real viagra online cialis without a prescription canada pharm get propecia delivery cialis canada cialis prescription discounts cheap viagra sale do you need a prescription for viagra cheap canadapharm gereric cialis buy levitra canadian pharmacy canadian cheap viagra pills meds cheap viagra in canada fast cialiscanada how much is levitra buy viagra no prescription levitra for sale certified how do i get viagra without a prescription canadian viagra buy cialis discounts how much is propecia usat do you need a prescription to get viagra generic daily cialis levitra generic propecia fast canadian pharmacies no prescription buying viagra in canada canadian pharmacy no prescription canadian+viagra discounts generic cialis no rx online cialis canada pharmacy online certified cost cialis daily can i buy propecia without a prescription canadian pharmacy levitra canadapharmacyonline certified buy levitra without prescription canadian canadian generic viagra online cialic canada get viagra canadian non prescription viagra online acomplia usa cialis daily cost 1 online pharmacy how to get viagra without a prescription discounts how fast does viagra work buying viagra non prescription ultracet canada cialis delivery how much does levitra cost cilias canada buy propecia online no prescription canadian pharmacy fof cialis cialis from canada cheap med store canada cialis no prescription canada canadian levitra suppliers buy viagra fast generic viagra online fast cialis by mail cheap viagra no prescription buy illegal viagra canada levitra 5mg delivery cialis usa canadian cilias buy generic cialis canadian cialis generic sale cheap propecia without perscription delivery canada pharmacy cialis generic viagra canadian discount viagra delivery can i buy viagra online usat cialis without prescription levitra without a prescription generic cialis daily canadian med store sale cialis cialis online india one a day cialis canadian india online pharmacy buy levitra online once daily cialis cost how much does propecia cost mexico pharmacy abilify discounts canadian pharmacy viagra scam sale india viagra online no 1 online viagra cost of cialis daily cialis online cheapest getting viagra without prescription fast generic levitra master card canada viagra no prescription delivery cialis daily female viagra get prescription to propecia acomplia without prescription sale buy viagra usa acomplia for sale online cialis online no rx fast buy acomplia online without prescription cheap soft cialis sale indian viagra online how much is cialis generic viagra canada canadian cialis no prescription cheapest pills on the net best price on generic propecia buy canadian pharmacy cialis fast dicount levitra cialis canadian pharmacy delivery online pharmacies china cheap canada pharmacy indian viagra usat how much does cialis cost cialis discounts best viagra online delivery buy viagra online no prescription canadian buy propecia without prescription cialis without perscribtion buy canada pharmacy cialis online get viagra without prescription delivery buy levitra without a prescription canadian needs a prescription in us buying acomplia buy levitra 20mg daily cialis cost cheapest viagra without prescription cheap buy propecia online levitra online canada cialis 100mg cialis 1 a day canadian pharmacies canadian online canadian sales one a day cealis mexico pharmacy usat buying viagra from canada online cialis daily generic levitra daily use delivery cialis 5mg price certified daily levitra cheap viagra canada cialis sales do you need prescription for viagra discounts cheap viagra for sale cialis order cialis daily price delivery canada viagra generic cialis cheap buy viagra without prescription sale cialisis online cialis in a day delivery cheap viagra from canada sales cheap cialis usat canadian pharmacy viagra usat discount levitra canada pharmacy brand cialis cheap cialis on sale cialis substitute canadian canada propecia canadian rx cost for daily cialis canadian pharmacies lavitra can i buy viagra without prescription brand cialis buy gernic viagra certified generic propecia india inexpensive viagra discounts levitra cost discounts cialis price sale buy cialis online buy viagra online canadian 20mg levitra generic levitra online canadian pharmacy cheap can you get viagra without a prescription discounts accomplia no prescription discounts levitra sales cialisis on line sale cost of cialis canadian levitra canada pharmacy one a day cielas delivery levitra without prescription discounts buy real viagra buy generic cialis from india discounts how much does propecia cost fast cheap canadian fast online rx discounts canadan viagra delivery canadians buy viagra from usa genericpropecia buy levitra price canadian pharm acomplia does it work cialis canada online pharmacy cialis 50mg buy 5mg propecia certified cialis online sales canadian levitra fast delivery buy buy real viagra online discount cialis canada based pharmacy without a prescription cialis pills cialis online certified cost of propecia in canada cost of propecia sale canadian pharm viagra buy acomplia no prescription acomplia no prescription bay propecia cialis canada online canadian pharmacy viagra buy no prescription buy cialis one day discounts cialis in canada levitra online canadian levitra 50mg viagra buy acomplia without prescription buy acomplia buy propecia no prescription certified generic for cialis online cialis generic canadian daily cialis sale no prescription online pharmacy usat generic levitra canada online pharmacy canadian get cialis online generic levitra usa cheap buy cialis without prescription buy canadian viagra professional cheap online levitra sales sale canadianpharmacy buy how do i get viagra sale canadian pharmacy fast delivery day cialis gel viagra discounts levitra daily fast cialis 20mg cialis on the spot usat cheapest viagra cheap how do you get viagra do you need a prescription for viagra in canada canadian pharm meds canadian pharmacy scam usat cialis soft tabs fast can i buy viagra without a prescription sale canada viagra how much does cialis cost canadian can i buy viagra without prescription online canadian ciali s sale canadian pharmacy online certified levitra 100mg online female viagra pills cialis for sale acomplia online buy medstore discounts buy viagra online in canada cialis cost discount propecia daily cialis online cialis canadian online get viagra without a prescription cost of daily cialis sale buy propecia without a prescription delivery acomplia without a prescription certified canadian drug store propecia canadian can you get viagra without prescription how to get viagra without prescription cialis suppliers how to get real viagra buy viagra online without prescription discounts cialis one a day

Intégration continue Ruby on Rails

28. avril 2007 Non classé

Bienvenue sur cette nouvelle page de mon blog. Elle va me permettre de partager les bonnes pratiques d’intégration continue que j’ai pu expériencer en Java, en les intégrant au framework Ruby on Rails.

Tout d’abord, pour ceux qui ne connaissent pas l’intégration continue, nous allons définir ce que c’est : l’intégration continue est le fait de construire en continu un projet de manière automatisée, tout en pouvant effectuer des tâches annexes, comme exécuter des tests unitaires, des revues de qualité de code, etc… Les plateformes d’intégration continue les plus connues et utilisées sont Apache Continuum et CruiseControl. Les constructions de projets avec ces outils fonctionnent avec Maven, ou Ant par exemple.

L’intérêt de cette page est donc d’obtenir une plateforme complète d’intégration continue fonctionnelle pour Ruby on Rails. Ce framework est encore jeune, mais pourquoi ne pourrait-il pas en profiter, alors que tous les outils sont disponibles ?

1 - Commencer par la base : le serveur d’intégration continue CruiseControl.rb

Ce serveur est même plus que ça, c’est aussi une application Rails à part entière. Vous ne serez donc pas perdus pour savoir comment le personnaliser.

Pour commencer, téléchargez la dernière version sur le site web de l’éditeur.

Maintenant que vous avez CruiseControl de dézippé dans un répertoire, vous devez savoir deux choses sur votre projet Rails :

  • Vous devez le publier sur un référentiel Subversion (seul ce type de référentiel est géré)
  • Pour que votre projet puisse être construit correctement, vous devez avoir configuré vos bases de données et créé au moins un fichier de migration Rails

Une fois que ces requis sont remplis, ajoutez votre projet Rails dans CruiseControl. Positionnez-vous à la racine du répertoire de CruiseControl, et tapez :

./cruise add [votre_nom_de_projet] --url [URL acces svn] [[--username [nom user svn] --password [password user svn] ]]

Une fois que vous avez fait cela, vous pouvez démarrer CruiseControl :

./cruise start

Ouvrez votre navigateur à l’URL http://localhost:3333 : votre projet est en train d’être construit pour la première fois. Si la construction n’aboutit pas, allez en voir le détail pour effectuer les corrections nécessaires.

Avant de finir, il est important de voir que votre construction de projet peut être personnalisée : il suffit de modifier le fichier projects/[votre projet]/cruise_config.rb afin de l’adapter à vos besoins

2 - Les tests et l’intégration continue

Dans ce chapitre, nous allons nous attaquer au vaste sujet des tests dans le développement, au sens du framework Ruby On Rails. Nous allons voir également que l’exécution des tests est déjà préintégrée à l’intégration continue sans que l’on ait à faire quoi que ce soit, car ils sont compris dans les tâches de construction de base (via la commande ‘rake test’ par exemple).

2.1) Les tests unitaires

Les tests unitaires font partie intégrante du framework Ruby On Rails, car ils sont intimement liés au modèles. Par exemple, imaginons que nous voulons générer un modèle pour la gestion d’utilisateurs. Nous faisons alors :

./script/generate model user

En voici la sortie :

      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/user.rb
      create  test/unit/user_test.rb
      create  test/fixtures/users.yml
      exists  db/migrate
      create  db/migrate/001_create_users.rb

Prenons en considération les fichiers importants : les fichiers test/unit/user_test.rb et test/fixtures/users.yml. Respectivement, ce sont un fichier de tests unitaires, et un fichier d’alimentation de données au format YAML qui sera chargé avant le lancement des tests unitaires.

Un autre fichier est facultatif mais intéressant : le fichier de migration de base de données (db/migrate/001_create_users.rb). Editons le pour créer la table users :

class CreateUser < ActiveRecord::Migration
  def self.up
    # On crée la table users
      create_table :users, :force => false do |t|
        t.column :login, :string, :limit => 20
        t.column :nom, :string, :limit => 255
        t.column :prenom, :string, :limit => 255
        t.column :email, :string, :limit => 255
        t.column :password, :string, :limit => 32
      end
  end
 
  def self.down
    # On détruit la table users
    drop_table :users
  end
end

Pour créer la table en base de données, nous devons lancer la tâche de migration :

rake db:migrate

Maintenant, alimentons nos données de tests en éditant le fichier test/fixtures/users.yml. Attention, pour le passage des cas de tests, si vous utilisez ce style de fichiers, les données seront chargées dans votre base de données. Autant le faire alors dans l’environnement de test Rails.

vdu:
  id: 1
  login: collaborateurvdu
  nom: VDU
  prenom: Collaborateur
  email: cvdu@rubyonrails.org
  password: 1234567890ABCDEFGHIJKLMNOPQRSTUV
other:
  id: 2
  login: ayafly
  nom: FLY
  prenom: Abdel Yves Akhim
  email: abdelyvesakhimfly@rubyonrails.org
  password: 1234567890ABCDEFGHIJKLMNOPQRSTUV

Voilà ! Nous sommes maintenant armés pour réaliser des cas de tests unitaires efficaces. Editons le fichier test/unit/user_test.rb :

require File.dirname(__FILE__) + '/../test_helper'
 
class UserTest < Test::Unit::TestCase
  # Utilisation des données d'alimentation
  fixtures :users
 
  # Modeste cas de test
  def test_existence
    user = User.find_by_login users(:vdu).login
    assert_not_nil user
  end
 
  # Test de création/suppression
  def test_create_retrieve_delete
    params = {:login => 'test', :nom => 'TEST', :prenom => 'Test', :email => 'test@test.com', :password => 'test'}
    user = User.create params
    user.save
    user_get = User.find_by_email 'test@test.com'
    assert_not_nil user_get
    user_get.destroy
    deleted_user_get = User.find_by_email 'test@test.com'
    assert_nil deleted_user_get
  end
end

Lancez maintenant les commandes suivantes pour exécuter vos tests unitaires :

export RAILS_ENV=test
rake test:units

Bien entendu cette présentation des tests unitaires est assez succinte; vous pourrez trouver un excellent guide complet sur le site de Ruby On Rails : A guide to testing the Rails

2.2) Les tests de contrôleurs

Les contrôleurs étant les points d’accès à vos applications Rails, il est plus que conseillé de les tester. On peut imaginer par exemple dans un cas idéal réaliser un cas de test pour chaque scénario d’exécution d’une fonctionnalité d’un système. Bon OK, souvent, vu le temps de création de tests auquel on a droit, on en fait pour la plupart le strict minimum (–note aigrie d’informaticien, très rare–).

Dans cette section, nous n’allons pas tout voir, car les tests de contrôleurs peuvent aller très loin (tests de contruction d’URLs, de présence de tags HTML en réponse, d’uploads…). Nous allons uniquement couvrir les cas les plus courants, c’est à dire les accès directs (get) ou les tests sur formulaires (la plupart du temps, des post).

Commençons par présenter une classe de test de contrôleur vierge (généré par Rails lors de la création d’une HomeController). Selon la norme Rails, il se situe dans test/functional/home_controller_test.rb :

require File.dirname(__FILE__) + '/../test_helper'
require 'home_controller'
 
# Re-raise errors caught by the controller.
class HomeController; def rescue_action(e) raise e end; end
 
class HomeControllerTest < Test::Unit::TestCase
  def setup
    @controller = HomeController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end
 
  def test_truth
    # Mettre ici votre code de test
  end
end

Débutons le plus simplement possible : nous allons tester que le point d’entrée de l’application (contrôleur HomeController, action index) répond correctement. Renommons la méthode ‘test_truth’ en ‘test_index’, et ajoutons-y le code nécessaire :

def test_index
  get :index
  assert_response :success
  # Equivalent à assert_response 200 (voir http://manuals.rubyonrails.com/read/chapter/28#page234)
end

Rajoutons-y un peu de piment : vous avez une application qui affiche une fiche client, vous voulez tester le point d’entrée en lui passant un vrai identifiant de client, puis un faux :

require File.dirname(__FILE__) + '/../test_helper'
require 'customer_controller'
 
# Re-raise errors caught by the controller.
class CustomerController; def rescue_action(e) raise e end; end
 
class CustomerControllerTest < Test::Unit::TestCase
  def setup
    @controller = HomeController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end
 
  def test_detail
    get :detail, {'id' => "12"} # Client existant
    assert_response :success
    assert_not_nil flash[:customer]
    get :detail, {'id' => "XBR22"} # Client inexistant
    assert_response :redirect
    # Oui, nous testons ici la redirection, car vous gérez les exceptions,
    # et vous redirigez sur une page commune d'erreur au besoin :)
    # Nous pourrions tester aussi :
    assert_nil flash[:customer]
  end
end

Prémices d’industrialisation du processus :

Il est très contraignant de devoir écrire des tests pour tous les points d’accès directs d’un contrôleur, alors que l’on pourrait très bien faire :

require File.dirname(__FILE__) + '/../test_helper'
require 'home_controller'
 
# Re-raise errors caught by the controller.
class HomeController; def rescue_action(e) raise e end; end
 
class HomeControllerTest < Test::Unit::TestCase
  def setup
    @controller = HomeController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    @urls       = YAML::load(File.read("test/functional/urls.yml"))
  end
 
  def test_access_points
    @urls.each do |url|
      get url[0]
      assert_response url[1]
    end
  end
end

Dans la classe de test ci-dessus, on automatise l’exécution des cas de tests en externalisant les URLs à tester dans un fichier YAML. Voici le contenu de ce fichier :

---
-
  - index
  - 200
-
  - register
  - 200
-
  - forgot_password
  - 200

Ce fichier nous permet de récupérer un tableau à double dimension contenant le nom de l’action à tester, et le code HTTP de retour attendu. Comme vous pouvez le voir, c’est assez succint, cet automatisme pourrait largement être amélioré.

Attaquons-nous maintenant aux tests des formulaires de saisie : vous vous dites peut être que cela est compliqué; en fait, cela est presque aussi simple que ce que nous avons fait jusqu’à présent. Imaginons que nous avons un formulaire de connexion à une application de la forme :

<% form_tag :controller => "home", :action => "login" do %>
	<fieldset class="encadre">
    	<legend>Connexion à l'application</legend>
<table border="0"><tbody>
<tr>
<td class="label"><label>Login :</label></td>
<td><%= text_field :login, :login, :size => 10 %></td>
</tr>
<tr>
<td class="label"><label>Mot de passe :</label></td>
<td><%= password_field :login, :password, :size => 10 %></td>
</tr>
<tr>
<td colspan="2" align="center"><%= submit_tag "Connexion" %></td>
</tr>
</tbody></table>
</fieldset>
<% end %>

Pour tester ce formulaire, rajoutons une nouvelle méthode de test à notre classe de test du HomeController. Le comportement de la connexion est : l’utilisateur connecté est positionné en session, et on le redirige vers son tableau de bord :

def test_login
  # Ci-dessous, les clé utilisées dans le pseudo-fichier YAML
  # correspondent à celles du formulaire de saisie
  post :login, YAML.load(<<-END.gsub(/^s*|/, "")).symbolize_keys
  |---
  |login:
  |  login: collaborateurvdu
  |  password: test
  END
  assert_response :redirect
  assert_redirected_to :controller => 'home', :action => 'board'
  assert_not_nil session["user_session"]
end

Enfin, pour lancer vos tests :

export RAILS_ENV=test
rake test:functionals

Vous voici maintenant bien armés pour tester vos contrôleurs Rails. Si vous souhaitez aller plus loin, vous pouvez lire la page correspondante du fameux guide Rails de tests.

2.3) TU et TNR avec Selenium on Rails

Maintenant que nous avons une base solide en termes de tests unitaires pour Rails, nous allons étudier ici un outil puissant servant à automatiser des campagnes de tests unitaires mais aussi et surtout de tests de non-régression, Selenium. A la base, Selenium est un outil fait pour toutes les applications de type Internet. Utilisateurs de Rails, sachez que vous avez été choyés, car les créateurs de Selenium vous ont créé un plugin qui permet d’intégrer Selenium sans effort à n’importe quel projet Rails. Encore mieux, il est possible de créer des campagnes de tests avec Selenium IDE (plugin pour Firefox), et d’exporter ces campagnes en tests Ruby. L’automatisation ici atteint son comble.

Un seul petit bémol à tout ce mécanisme. En étudiant ce plugin, j’ai pu observer un petit problème : si on intègre la tache Rake de lancement des tests Selenium à CruiseControl, les campagnes de tests sont exécutées, mais à la fin, Firefox ne se ferme pas et ne rend pas la main au thread Ruby, donc l’ensemble du build CruiseControl échoue (testé uniquement sous Linux, néanmoins). C’est la seule limite de ce système, il vaut mieux lancer ces campagnes de tests à la main et en observer les résultats.

Voyons maintenant comment installer et utiliser ce plugin. Pour commencer, lancer la commande :

ruby script/plugin install http://svn.openqa.org/svn/selenium-on-rails/selenium-on-rails

Si vous êtes sous Windows, il faut installer un gem supplémentaire :

gem install win32-open3

Pour vérifier que tout s’est bien déroulé, vous devez tester le plugin :

cd vendor/plugins/selenium-on-rails/
rake

Si tout se passe correctement, vous pouvez continuer, sinon je vous recommande chaudement d’aller consulter la page Selenium on Rails.

Créons un cas simple, par exemple pour se logger à une application Rails. Lancez Selenium IDE, puis tapez dans le navigateur l’URL de votre application. Par exemple un test qui consiste à se connecter puis se déconnecter de l’application peut donner comme résultat :





Quand vous avez fini votre test, dans Selenium IDE, faites : Fichier -> Exporter le Test sous… -> Ruby - Selenium RC. Enregistrez le fichier avec l’extension .rsel dans votre répertoire test/selenium/.

La dernière étape consiste à enlever les choses inutiles dans notre fichier de test. Prenons par exemple :

require "selenium"
require "test/unit"
 
class login2 < Test::Unit::TestCase
  def setup
    @verification_errors = []
    if $selenium
      @selenium = $selenium
    else
      @selenium = Selenium::SeleneseInterpreter.new("localhost", 4444, "*firefox", "http://localhost:4444", 10000);
      @selenium.start
    end
    @selenium.set_context("test_login2", "info")
  end
 
  def teardown
    @selenium.stop unless $selenium
    assert_equal [], @verification_errors
  end
 
  def test_login2
    @selenium.open "http://localhost:3000/"
    @selenium.type "login_login", "xxxxxxx"
    @selenium.type "login_password", "xxxxxxxx"
    @selenium.click "link=Connexion"
    @selenium.wait_for_page_to_load "30000"
    assert_equal "", @selenium.get_title
    @selenium.click "link=Déconnexion"
    @selenium.wait_for_page_to_load "30000"
    assert_equal "", @selenium.get_title
  end
end

Le fichier final ne devra contenir que :

open "http://localhost:3000/"
type "login_login", "xxxxxxx"
type "login_password", "xxxxxxxx"
click "link=Connexion"
wait_for_page_to_load "30000"
assert_title "votre titre de test"
click "link=Déconnexion"
wait_for_page_to_load "30000"
assert_title "votre titre de test"

Il faut donc réadapter le fichier quelque peu. Pour plus d’informations sur la syntaxe des commandes, vous pouvez consulter SeleniumOnRails::TestBuilder.

Vous n’avez plus qu’à tester ! Lançez votre serveur :

ruby script/server -e test

Puis pointez votre navigateur sur l’URL : http://localhost:3000/selenium. Voilà, plus qu’à lancer vos tests !

Pour finir, si vous voulez lancer vos tests automatiquement, vous pouvez le faire. Tout d’abord, modifiez le fichier vendor/plugins/selenium-on-rails/config.yml à votre convenance. Ensuite, lancez les tests en tapant :

rake test:acceptance

Votre serveur doit être démarré pour que cela fonctionne.

3 - Intégration continue et qualité du code

3-1) Couverture des tests avec rcov

On ne le dit jamais assez, une bonne qualité d’application commence par la qualité de ses tests, mais aussi et surtout par l’exhaustivité de ceux-ci. C’est dans ce contexte qu’intervient rcov : il permet de lancer vos tests et d’en extraire les statistiques de couverture. Un exemple typique d’utilisation est par les tests de contrôleurs (car plus facile de lancer toutes les fonctionnalités de l’application, en en testant tous les points d’entrée. Attention, ce serait une erreur de ne se fier qu’à cela, il est vivement conseillé de ne pas négliger les tests unitaires (tests des modèles).

Voyons comment mettre en oeuvre rcov. Tout d’abord, il faut l’installer. Fort heureusement, il est disponible sous forme de gem standard. La dernière version en date (08/10/2007) est la 0.8.2 :

gem install rcov


La prochaine chose à faire est de désigner les classes de test que rcov va devoir lancer. Dans ce but, nous allons créer un fichier nommé rcov_test_all.rb que nous allons placer dans le répertoire test/. Voici le contenu de ce fichier :

require 'test/unit'
require 'test/unit/user_test'
require 'test/functional/user_controller_test'
require 'test/test_helper'


Dans l’exemple précédent, on indique à rcov de lancer les tests unitaires et les tests de contrôleurs sur le module user. Voici comment lancer rcov :

export RAILS_ENV=test
rcov --rails --exclude rcov,rubyforge test/rcov_test_all.rb


L’option –rails permet de ne parser que les répertoires/sources intéressants pour une application Rails. L’option –exclude permet d’exclure du calcul des statistiques des plugins/gems que vous utiliserez dans vos applications (il se peut que vous ayez donc à allonger la liste).

Plus qu’à aller voir le fichier coverage/index.html. Alors, maintenant, quel est votre score ? :)

3-2) Couverture de la documentation avec dcov

dcov est une application qui permet d’analyser la couverture de vos commentaires rdoc. C’est un outil très utile lorsque vous devez produire du code de qualité dans un projet.

Les rapports produits sont d’une simplicité incroyable : le nom de la classe apparaît en rouge si la couverture totale d’une classe est mauvaise. Si la couverture est différente selon les méthodes d’une classe, alors la granularité s’adapte, et le rapport est plus détaillé (certaines méthodes apparaissent en rouge, d’autres non). Le rapport tient sur une seule page HTML, donc le tout est super simple.

Pour l’installer, faire :

sudo gem install dcov

Pour lancer une analyse de couverture, faire :

dcov -p <pattern>

Exemple : dcov -p app/**/*.rb va analyser tous les fichiers ruby des répertoires sous app/.

Attention, il existe dans la version courante de dcov (la version 0.2.2) un bug qui empêche la création du rapport. A la ligne 188 du fichier …/gems/dcov-0.2.2/lib/dcov/analyzer.rb, vous trouverez ceci :

output_file = File.open("#{@options[:path]}/coverage.html", "w")

Vous pouvez corriger le problème en changeant la ligne avec par exemple :

output_file = File.open("./coverage.html", "w")

Alors, et maintenant ? Quel est votre pourcentage de couverture de documentation ?