Dans toute application Rails, vous avez une tâche « rake » assez sympa : stats. Celle-ci vous retourne le nombre de lignes de code dans votre projet.
En séparant les contrôleurs, les modèles, les helpers et les librairies. Mais également en séparant le code applicatif du code de test.
Et en vous donnant un ratio code applicatif / code de test/

Voici un exemple de rake stats (avec des chiffres complètement fictifs).

+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          |   419 |   289 |       7 |      43 |   6 |     4 |
| Helpers              |     9 |     9 |       0 |       1 |   0 |     7 |
| Models               |    34 |    29 |       4 |       2 |   0 |    12 |
| Libraries            |   359 |   212 |       2 |      36 |  18 |     3 |
| Model specs          |    78 |    60 |       0 |       0 |   0 |     0 |
| Controller specs     |   203 |   166 |       0 |       2 |   0 |    81 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                |  1514 |  1028 |      13 |      85 |   6 |    10 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 539     Test LOC: 489     Code to Test Ratio: 1:0.9

Ici, en utilisant RSpec, nous avons les statistiques de nos specs car l’outil modifie cette fonctionnalité afin d’avoir les bons chiffres.
Cependant si en plus de RSpec, et comme moi, vous utilisez Cucumber, vous n’aurez pas ces chiffres. Normal ! RSpec redéfinit la tâche stats. Mais pas cucumber.

Modifions donc cette tâche afin d’avoir nos chiffres. Ouvrez le fichier suivant :

lib/tasks/rspec.rake

La aux alentours de la ligne 110, vous allez trouver la définition des répertoires de code à include dans la tâche stats.

task :statsetup do
    require 'code_statistics'
    ::STATS_DIRECTORIES << %w(Model\ specs spec/models) if File.exist?('spec/models')
    ::STATS_DIRECTORIES << %w(View\ specs spec/views) if File.exist?('spec/views')
    ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers) if File.exist?('spec/controllers')
    ::STATS_DIRECTORIES << %w(Helper\ specs spec/helpers) if File.exist?('spec/helpers')
    ::STATS_DIRECTORIES << %w(Library\ specs spec/lib) if File.exist?('spec/lib')
    ::STATS_DIRECTORIES << %w(Routing\ specs spec/routing) if File.exist?('spec/routing')
    ::STATS_DIRECTORIES << %w(Integration\ specs spec/integration) if File.exist?('spec/integration')
    ::CodeStatistics::TEST_TYPES << "Model specs" if File.exist?('spec/models')
    ::CodeStatistics::TEST_TYPES << "View specs" if File.exist?('spec/views')
    ::CodeStatistics::TEST_TYPES << "Controller specs" if File.exist?('spec/controllers')
    ::CodeStatistics::TEST_TYPES << "Helper specs" if File.exist?('spec/helpers')
    ::CodeStatistics::TEST_TYPES << "Library specs" if File.exist?('spec/lib')
    ::CodeStatistics::TEST_TYPES << "Routing specs" if File.exist?('spec/routing')
    ::CodeStatistics::TEST_TYPES << "Integration specs" if File.exist?('spec/integration')
  end

Il nous faut ajouter nos features Cucumber, et ce deux fois : une pour les statistiques « générales ». Et une seconde afin de comptabiliser ces données comme des données de test. La tâche statsetup sera donc la suivante :

task :statsetup do
    require 'code_statistics'
    ::STATS_DIRECTORIES << %w(Model\ specs spec/models) if File.exist?('spec/models')
    ::STATS_DIRECTORIES << %w(View\ specs spec/views) if File.exist?('spec/views')
    ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers) if File.exist?('spec/controllers')
    ::STATS_DIRECTORIES << %w(Helper\ specs spec/helpers) if File.exist?('spec/helpers')
    ::STATS_DIRECTORIES << %w(Library\ specs spec/lib) if File.exist?('spec/lib')
    ::STATS_DIRECTORIES << %w(Routing\ specs spec/routing) if File.exist?('spec/routing')
    ::STATS_DIRECTORIES << %w(Integration\ specs spec/integration) if File.exist?('spec/integration')
    ::CodeStatistics::TEST_TYPES << "Model specs" if File.exist?('spec/models')
    ::CodeStatistics::TEST_TYPES << "View specs" if File.exist?('spec/views')
    ::CodeStatistics::TEST_TYPES << "Controller specs" if File.exist?('spec/controllers')
    ::CodeStatistics::TEST_TYPES << "Helper specs" if File.exist?('spec/helpers')
    ::CodeStatistics::TEST_TYPES << "Library specs" if File.exist?('spec/lib')
    ::CodeStatistics::TEST_TYPES << "Routing specs" if File.exist?('spec/routing')
    ::CodeStatistics::TEST_TYPES << "Integration specs" if File.exist?('spec/integration')

    #
    # Adding Cucumber features to the stats
    #
    ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features')
    ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features')
  end

Et du coup nous avons maintenant nos données Cucumber ajoutées à nos stats.
Avec les même données fictives que plus haut, nous aurons donc un rake stats équivalent à celui-ci :

+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          |   419 |   289 |       7 |      43 |   6 |     4 |
| Helpers              |     9 |     9 |       0 |       1 |   0 |     7 |
| Models               |    34 |    29 |       4 |       2 |   0 |    12 |
| Libraries            |   359 |   212 |       2 |      36 |  18 |     3 |
| Model specs          |    78 |    60 |       0 |       0 |   0 |     0 |
| Cucumber features    |   412 |   263 |       0 |       1 |   0 |   261 |
| Controller specs     |   203 |   166 |       0 |       2 |   0 |    81 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                |  1514 |  1028 |      13 |      85 |   6 |    10 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 539     Test LOC: 489     Code to Test Ratio: 1:0.9

Nos statistiques de projet sont plus réalistes et le ratio entre le code applicatif et le code de test est plus révélateur de ce que vous avez réellement dans votre application.

Toute application qui se respecte finit par avoir des tâches asynchrones. Qu’elles soient exécutées de manière journalière ou en fonction des actions des visiteurs (avec delayed job par exemple), il faut bien tester celles-ci afin d’être certain de leur bon fonctionnement.

De manière native, Rails ne propose rien de particulièrement simple pour tester vos tâches rake. C’est cependant assez simple à mettre en place.
Nous allons ici voir comment le faire avec rspec.

Il faut, avant chacune de vos spec, initialiser Rake avec le document approprié.

before(:each) do
    @rake = Rake::Application.new
    Rake.application = @rake
    load RAILS_ROOT + '/lib/tasks/maintenance.rb'
    Rake::Task.define_task(:environment)
end
after(:each) do
    Rake.application = nil
end

Nous utilisons load et pas include car nous désirons le recharger à chaque tâche. Pas uniquement la première fois.
Il vous faut par ailleurs modifier le chemin vers le fichier par le chemin approprié dans votre application.

Par la suite dans mon spec_helper.rb, je fais une méthode invoke_task, qui permettra d’invoquer la tâche que je désire.

def invoke_task(name)
    @rake.should_not be_nil
    @rake[name].should_not be_nil
    @rake[name].invoke
end

Et nous écrivons notre test.

it 'should just work fine' do
    invoke_task('task:name')
end

Votre tâche est exécutée avec succès. Vous n’avez plus qu’à vérifier si l’action qu’elle doit réaliser l’a correctement été.
Si par exemple, votre tâche doit supprimer toutes les données de session n’ayant pas été mises à jour depuis plus de 20 minutes, l’un des tests pourrait être :

it 'should delete the sessions if they are too old' do
    # Des sessions doivent être ajoutées auparavant.
    Session.find(:all, :conditions => ['created_at <= ?', 20.minutes.ago]).size.should_not eql(0)
    invoke_task('clear_sessions')
    Session.find(:all, :conditions => ['created_at <= ?', 20.minutes.ago]).size.should eql(0)
end

Notre test passe, cool ! Notre tâche fonctionne donc :)

 
Fork me on GitHub