Le code et les commentaires 4
Je viens de lire la traduction d’un article parlant des commantaires dans le code source sur le Framablog . C’est un article écrit par Esther Schindler que je ne connais pas. Son article original titré If the comments are ugly, the code is ugly parle donc de code source, de code source avec de vilain commentaires.
Elle signale que les commentaires doivent être irréprochable, sans faute d’orthographe, sans erreur de grammaire, à jour par rapport aux codes sources commenté…
Et bien j’ai trouvé une solution encore plus simple: je supprime les commentaires et j’essaie de rendre le code plus lisible. Les langages de programmations actuel sont assez expressif, utilise des mots anglais clair, certain langage apporte une structure permettant même d’écrire des presques phrases ! Un code propre n’est pas un code avec des commentaires à jour, mais un code lisible, clair et concis
De plus, tout les langages modernes (enfin j’espère) bénéficie d’une librairie de test unitaire, ce qui veut dire que l’on peut écrire un bout de code très simple qui montre l’intention du programme réel, qui montre comment il fonctionne, qui le documente en quelque sorte. Alors pourquoi écrire en plus des commentaires ? Je préfère voir un code lisible, et au pire devoir aller lire des tests unitaires pour comprendre son fonctionnement (voir ajouter des tests pour vérifier que j’ai bien compris).
Alors je ne sais pas qui est cette dame, mais, même si elle a en partie raison, pitié, éviter de faire du code illisible et sans test unitaire programmé sous pretexte que vous avez écrit des commentaire digne d’un grand roman.
TDD Default and Optimistic Lock on Rails 1
RubyOnRails utilise par défaut ActiveRecord comme ORM.
J’ai eu envie de faire quelques tests sur le fonctionnement des accès concurrents. Voici les resultats sur le comportement par défaut et lorsque l’on met en place une gestion optimiste de verrou (ou une gestion de verrou optimiste ;-)).
J’ai effectué mes tests avec un Ruby 1.8.6.p114, un MySQL-server-5.0.51ap1 et un ActiveRecord 2.2.2 le tout sur mon bon openbsd 4.4
On va commencer par mettre en place une table Users avec juste champ Name pour faire nos tests et ajouter un enregistrement avec un ID à 1, ça facilitera le test.
Comportement par défaut
Le test.
require 'test/unit'
require 'rubygems'
require 'active_record'
class DBAccess < Test::Unit::TestCase
def setup
# On va prendre la connection à la base de données ici. Ca me gène dans le cas d'un test unitaire, mais passons
ActiveRecord::Base.establish_connection(:adapter => 'mysql', :database => 'optimistic', :username => 'yaf', :password => 'monpassword', :socket => '/var/run/mysql/mysql.sock')
end
def teardown
# on remet l'enregistrement comme il était avant de venir
user = User.find 1
user.name = "Joe"
joe.save
end
def test_default_db_priority
joe = User.find 1
bill = User.find 1
bill.name = "billy"
assert bill.save
assert_equal "billy", User.find(1).name
joe.name = "joe"
joe.save
assert_equal "joe", User.find(1).name
end
end
class User < ActiveRecord::Base; end;
J’ai glissé le model User dans le même fichier, c’est plus simple.
Executons le test:
$ ruby test.rb
Loaded suite test
Started
.
Finished in 0.173901 seconds.
1 tests, 3 assertions, 0 failures, 0 errorsConclusion
Par défaut, c’est le dernier qui enregistre une modification qui a raison. Cela peut poser des problèmes dans certains cas, c’est pourquoi on va maintenant aller voir comment mettre en place une gestion de verrou optimiste.
Verrou optimiste (Optimistic Lock)
ActiveRecord permet la mise en place d’un verrou optimiste. Pour cela il nous faut ajouter un champ spécifique de type entier qui servira à la gestion des accès.
Cette colonne doit s’appeler lock_version selon les conventions ActiveRecord, mais on peut utiliser un autre nom.
Ajoutons donc cette colonne et écrivons notre test:
Le test
require 'test/unit'
require 'rubygems'
require 'active_record'
class DBAccess < Test::Unit::TestCase
def setup
ActiveRecord::Base.establish_connection(:adapter => 'mysql', :database => 'optimistic', :username => 'yaf', :password => 'monpassword', :socket => '/var/run/mysql/mysql.sock')
end
def teardown
user = User.find 1
user.name = "Joe"
joe.save
end
def test_optimistic_lock
joe = User.find 1
bill = User.find 1
bill.name = "billy"
assert_equal joe.lock_version, bill.lock_version
# et oui, maintenant nos objet on un attribut lock_version
assert bil.save
assert_equal "billy", User.find(1).name
joe.name = "joe"
assert_raise(ActiveRecord::StaleObjectError) { joe.save }
assert_equal "billy", User.find(1).name
end
end
class User < ActiveRecord::Base; end;Executons le test:
$ ruby test.rb
Loaded suite test
Started
.
Finished in 0.437534 seconds.
1 tests, 5 assertions, 0 failures, 0 errorsConclusion
Maintenant, au moment d’enregistrer des modifications, ActiveRecord est capable de nous prevenir par le biais d’un ActiveRecord::StaleObjectError. On peut donc imaginer encapsuler le save dans un begin/rescue pour gérer la situation.
la methode reload permet de recharger l’objet à partir de la base de donnée, ça peut être pratique dans ce cas
Un certain jqr propose un stale_object_destroyer qui apparemment permet de facilité la gestion des tentative de mise à jour plus facilement
Il serait interessant de faire tourner ces tests sur les divers base de données suporté par ActiveRecord. Notamment avec sqlite3. J’essayerais de faire le test sur une base oracle. N’hésitez pas à me tenir au courant si vous executez ces tests sur d’autres bases ;-)
On verra les verrou en mode pessimiste dans un autre billet
Développement dont vous êtes le héros mais piloté par les tests
Un soir au Dojo , après avoir fait un Bowling en Ruby en mode Randori Emmanuel nous a parlé d’un papier qu’il avait rédigé sur ce sujet, en déroulant cette exercice en Kata dont vous êtes le héros. Après quelque discussion et autres découverte, il a décidé de publié ce texte.
Je vous invite donc à dérouler ce “Kata Bowling en Ruby” dont vous êtes le héros Reader driven . Bon test !
Ce format est très interessant, je pense qu’il pourrais être adapté et pourrais donner des idées aux participants du Dojo d’ici ou d’ailleurs
TDD C'est quoi ? (En ruby bien sur !)
Voici un petit billet d’initiation au Développement piloté par les Test (dit TDD pour Test Driven Development) avec Ruby. Initialiement publié sur le site de l’association "RubyFrance":http://rubyfrance.org
Imaginons que nous ayons besoin d’un petit objet nous permettant d’afficher un nom. En bon développeur, nous allons d’abord écrire notre test.
require "test/unit"
class TestMonObjet < Test::Unit::TestCase
def test_attribut
monObjet = MonObjet.new("un titre")
assert_equal "un titre", monObjet.nom
end
endExecutons le test:
rubyFrance:~ $ ruby testMonObjet.rb
Loaded suite testMonObjet
Started
E
Finished in 0.001079 seconds.
1) Error:
test_attribut(MonObjetTest):
NameError: uninitialized constant MonObjetTest::MonObjet
testMonObjet.rb:6:in `test_attribut'
1 tests, 0 assertions, 0 failures, 1 errorsMince une erreur. Vous allez me dire, c’était couru d’avance, on a encore rien codé. Bien. Allons-y alors. D’abord nous allons ajouter le fichier contenant l’objet que nous allons créer.
require "monobjet.rb"Ensuite créons ce fichier:
rubyFrance:~ $ cat monobjet.rb class MonObjet end
Cela suffira largement pour empecher l’erreur précedente. C’est un point important dans l’univers TDD. Il ne faut rien faire de plus que ce que les tests nous demande. Cela rejoint également un autre concept: YAGNI (You Ain’t Gonna Need It).
Executons encore ce test:
rubyFrance:~ $ ruby testMonObjet.rb
Loaded suite testMonObjet
Started
E
Finished in 0.00193 seconds.
1) Error:
test_attribut(MonObjetTest):
ArgumentError: wrong number of arguments (1 for 0)
testMonObjet.rb:7:in `initialize'
testMonObjet.rb:7:in `new'
testMonObjet.rb:7:in `test_attribut'
1 tests, 0 assertions, 0 failures, 1 errorsHmm, encore une erreur, mais cette fois ce sont les paramètres de notre objet qui pose problème. Bien, corrigeons notre objet.
class MonObjet
def initialize un_nom
end
endExecutons encore ce test:
rubyFrance:~ $ ruby testMonObjet.rb
Loaded suite testMonObjet
Started
E
Finished in 0.001091 seconds.
1) Error:
test_attribut(MonObjetTest):
NoMethodError: undefined method `nom' for #<MonObjet:0x80e4d100>
testMonObjet.rb:8:in `test_attribut'
1 tests, 0 assertions, 0 failures, 1 errorsEncore une erreur. Mais cette fois c’est la method nom qui est manquante pour MonObjet. Ajoutons la:
class MonObjet
def initialize un_nom
end
def nom
end
endExecutons le test (oui, en tdd, on passe notre temps à tester ! :-)):
rubyFrance:~ $ ruby testMonObjet.rb Loaded suite testMonObjet Started F Finished in 0.198677 seconds. 1) Failure: test_attribut(MonObjetTest) [testMonObjet.rb:8]: <"un titre"> expected but was <nil>. 1 tests, 1 assertions, 1 failures, 0 errors
Voilà qui deviens interessant. Cette fois, ce n’est pas une erreur, c’est un echec du test. La méthode “nom” ne renvoi pas la bonne valeur.
La situation d’echec dans le test unitaire est aussi appelé “la barre rouge”. Et quand il y a une barre rouge, le principe est de la faire redevenir verte le plus rapidement possible (en ajoutant très peu de code voir en enlevant du code).
Modifions donc rapidement notre code pour répondre au besoin du test:
class MonObjet
def initialize un_nom
end
def nom
"un titre"
end
endDoucement, doucement, je vous vois venir, oui j’ai mis une valeur en dur, executons le test (c’est barre rouge), nous en parlons juste après.
rubyFrance:~ $ ruby testMonObjet.rb Loaded suite testMonObjet Started . Finished in 0.000902 seconds. 1 tests, 1 assertions, 0 failures, 0 errors
Voilà, le test passe. Nous pouvons maintenant parler. J’ai mis une valeur en dur dans la méthode “nom”, cela vous dérange ? Et bien pas moi. Je répond ici au besoin exprimé dans le test. Mais je n’ai pas dit que nous allions nous arreter là ! Ajoutons un test pour bien préciser notre besoin.
require "test/unit"
class TestMonObjet < Test::Unit::TestCase
def test_attribut
monObjet = MonObjet.new("un titre")
assert_equal "un titre", monObjet.nom
end
def test_attribut_autre
monObjet = MonObjet.new("un autre titre")
assert_equal "un autre titre", monObjet.nom
end
endExecutons le test unitaire maintenant enrichi d’un test.
rubyFrance:~ $ ruby testMonObjet.rb Loaded suite testMonObjet Started .F Finished in 0.024359 seconds. 1) Failure: test_attribut_autre(MonObjetTest) [testMonObjet.rb:12]: <"un autre titre"> expected but was <"un titre">. 2 tests, 2 assertions, 1 failures, 0 errors
Forcement, avec une valeur en dur, cela ne vas pas. Faisons passer la barre au vert avant de discuter:
rubyFrance:~ $ cat monobjet.rb
class MonObjet
def initialize un_nom
@nom = un_nom
end
def nom
@nom
end
endExecutons le test:
rubyFrance:~ $ ruby testMonObjet.rb Loaded suite testMonObjet Started .. Finished in 0.001734 seconds. 2 tests, 2 assertions, 0 failures, 0 errors
Parfait ! Barre verte !
Bien, maintenant, on peut laisser étaler nos connaissance en ruby pour effectuer un petit refactoring:
class MonObjet
attr_reader :nom
def initialize un_nom
@nom = un_nom
end
endExecutons le test a nouveau pour être sur que ce refactoring n’a pas changé la donne:
rubyFrance:~ $ ruby testMonObjet.rb Loaded suite testMonObjet Started .. Finished in 0.001734 seconds. 2 tests, 2 assertions, 0 failures, 0 errors
Un des interêt de faire un développement piloté par les tests c’est de tendre une sorte de filet de sécurité permettant de donnée plus de courage, ou au moins de tranquillité pour effectuer le refactoring. Mais il existe bien d’autre avantage à ce mode de développement. Notamment celui de ne pas faire plus que nécessaire.
Les tests ainsi écrit, modifié, mis à jour permette de disposer à tout moment d’une documentation sur l’execution du programme.
Ruby : Test Unitaire 5
Ruby bénéficie comme beaucoup d’autres langages modernes de son framework de test. Voici donc une petite documentation sur comment écrire un test unitaire pour Ruby.
Pour ceux qui ne savent pas ce qu’est un test unitaire, je vous renvoie sur l’article Test Unitaire [wikipedia]_unitaire
Pour commencer il nous faut importer la librairie “Test::Unit”
require "test/unit"
Rien que cela nous permet déjà de faire un premier test en executant notre script.
yannick@libellule:~/Code/RubyFrance/testUnit $ ruby testUnit.rb Loaded suite testUnit Started Finished in 0.000607 seconds. 0 test, 0 assertions, 0 failures, 0 errors
Le fait d’inclure la librairie de test unitaire permet d’avoir un comportement par défaut qui va:
- Charger la suite de test à executer
- lancer les tests
- faire l’affichage des resultats de test
- faire un compte rendu de cette execution
Ajoutons un test maintenant
require "test/unit"
class StringTest < Test::Unit::TestCase
def test_length
s = "Bon test à tous !"
assert_equal(17, s.length)
end
end
Nous avons défini une classe, celle-ci doit étendre TestCase. Cela permet au framework de test de s’y retrouver.
Chaque méthode de test définie ensuite doit contenir test en début de nom (le caractère _ n’est placé que pour une meilleur lisibilité et selon les conventions couramment appliquées en Ruby)
Les méthodes assert (ici assert_equal, mais il en existe beaucoup d’autres) permettent d’effectuer un test. Ici un test d’égalité, mais nous pourrions également vérifié une différence, un bouléen répondant vrai ou faux et d’autres encore (voir la documentation sur le module Test::Unit::Assertions).
Après execution, voici le résultat:
yannick@libellule:~/Code/RubyFrance/testUnit $ ruby testUnit.rb Loaded suite testUnit Started . Finished in 0.000802 seconds. 1 test, 1 assertions, 0 failures, 0 errors
Un test a été executé avec succès.
Ajoutons encore un test pour avancer:
require "test/unit"
class StringTest < Test::Unit::TestCase
def test_length
s = "Bon test à tous !"
assert_equal(17, s.length)
end
def test_expression_substitution
assert_equal("", "#{'ah! ' * 3}")
end
end
Après exécution nous obtenons:
yannick@libellule:~/Code/RubyFrance/testUnit $ ruby testUnit.rb Loaded suite testUnit Started F. Finished in 0.000827 seconds. 1) Failure: test_expression_substitution(StringTest) [testUnit.rb:12]: <""> expectedbut was <"ah! ah! ah! ">. 2 test, 2 assertions, 1 failures, 0 errors
Et voilà, comme vous l’aviez deviné, nous avons une erreur. Dans notre cas, l’erreur viens de notre test.
On vois ici l’interêt de mettre chaque test sur un domaine différent dans une méthode différente: on vois facilement quel type de test nous voulions effectuer. Dans le développement d’une application complète, avec plusieurs dizaines d’objets à tester, et plusieurs dizaines de méthodes sur chacun d’eux, les erreurs d’exécution de test peuvent devenir un vrai casse-tête.
Effectuons la correction:
require "test/unit"
class StringTest < Test::Unit::TestCase
def test_length
s = "Bon test à tous !"
assert_equal(17, s.length)
end
def test_expression_substitution
assert_equal("ah! ah! ah! ", "#{'ah! ' * 3}")
end
end
exécution du test:
yannick@libellule:~/Code/RubyFrance/testUnit $ ruby testUnit.rb Loaded suite testUnit Started .. Finished in 0.001273 seconds. 2 test, 2 assertions, 0 failures, 0 errors
Et voilà. Vous devriez être capables de commencer à écrire quelques tests, mais ce n’est qu’un début !.
Cet article a été écrit pour le site de l’association RubyFrance, vous pourrez le retrouver dans les documentations proposées par l’association: "RubyFrance: TestUnitaire":http://rubyfrance.org/documentations/test-unitaire/
Dojo 1
Lundi soir, comme apparemment presque tout les lundi soir, c’est rendez-vous au dojo. Non pas celui des arts martiaux, mais celui du développement. Pour rester agiles, l’association XP-France organise des rencontres au dojo.
Dans une salle gentillement fourni par EpiConcept, des praticiens agiles se retrouvent pour un Kata voir un Randori.
J’ai passé une super soirée. J’ai découvert Haskell un langage fonctionnel pur (c’est a préciser apparemment ;-)). J’ai vu des tests, et encore des tests et c’est beau. Vivement le prochain !
Si vous voulez en savoir plus: Voir le projet Dojo sur le wiki de l’asso.
Moi je vais tenter d’y aller tout les lundi :-D
5e apéro rubyFrance
une semaine plus tard
Cet session fût très bonne. Pas loin de 30 personnes ont fait le déplacement pour cette “apéro” qui en fait ressemblait plus à une bonne présentation.
Le thème principal de ce lundi était l’agilité, l’extreme programming, les tests. En effet, des membres de l’association XP-France sont venus nous présenter l’agilité, le développement piloté par les tests le tout dans sous la forme d’un kata, une des pratiques de dojo.
Ensuite, Jean-François nous a présenter une toute nouvelle librairie ruby qui gagne à être connu: Arel également appelé ActiveRessource. Un librairie visant à permettre la création d’ORM(Object Relationnal Mapping). Disont pour résumé que cela enlèverais la couche “concaténation de chaine de caractères” dans ActiveRecord par exemple, et du coup nous aurions le moyen de construire plus joliement des requête SQL. A suivre donc.
Depuis quelque temps déjà je m’interesse aux méthodes agiles, à l’extreme programming, cette présentation à fini de me convaincre qu’il faut absoluement que j’aille en Dojo pour pratiquer le code, échanger avec d’autres personnes aguéri à ces techniques de tests et de façon de voir le code.
On en reparle plus tard ;-)