Nombres
Les nombres sont un type de données relativement basique. Guile met en oeuvre une Tour de types numériques, offrant au hacker pléthore de types de nombres avec leurs lots de fonctionnalités.
Ici, je me focaliserai sur les entriers et l’addition, mais libre à toi d’expérimenter avec ce dont tu as besoin. L’objectif de ce chapitre sera de créer une procédure integer-add
, partiellement implémentée, et voir comment tout ça fonctionne.
Écrire le test d’abord
Donc, dans ton espace de travail, crées un répertoire ~/Workspace/guile-handbook/numbers
où tu éditeras le fichier numbers-test.scm
. Ci-dessous, le premier test a été ajouté. Il s’agit du cas qui me semble être le plus simple.
(use-modules (srfi srfi-64)
(numbers))
(test-begin "harness-numbers")
(test-equal "add-zero-zero"
0
(integer-add 0 0))
(test-end "harness-numbers")
Tu peux remarquer que je fais appel au module numbers
, il contiendra la fonction integer-add
.
Essayer et lancer le test
$ guile -L . numbers-test.scm
En inspectant le message d’erreur retourné par le compilateur :
no code for module (numbers)
Écrire le minimum de code pour faire compiler le test et vérifier la raison de son échec
Crées maintenant le module numbers
dans le fichier ~/Workspace/guile-handbook/numbers/numbers.scm
pour satisfaire le compilateur et rien d’autre !
(define-module (numbers))
Une nouvelle exécution des tests m’indique que :
;;; numbers-test.scm:8:2: warning: possibly unbound variable `integer-add'
Réitères et chaque fois, tu te laisses guider par le compilateur jusqu’à voir ton test échouer pour la bonne raison. Le fichier ~/Workspace/guile-handbook/numbers/numbers.scm
devient :
(define-module (numbers))
(define-public (integer-add int1 int2)
-1)
Maintenant, on voit bien le test échouer, car, la valeur attendue est 0 mais la valeur obtenue est −1 (oui, c’est fait exprès !).
%%%% Starting test harness-numbers
Group begin: harness-numbers
Test begin:
test-name: "add-zero-zero"
source-file: "numbers-test.scm"
source-line: 6
source-form: (test-equal "add-zero-zero" 0 (integer-add 0 0))
Test end:
result-kind: fail
actual-value: -1
expected-value: 0
Group end: harness-numbers
# of unexpected failures 1
Écrire juste assez de code pour faire passer le test
Tu vas maintenant modifier le minimun de code pour faire passer le test. Attention les yeux :
(define-module (numbers))
(define-public (integer-add int1 int2)
0)
L'exécution du test prouve que cette modification est suffisante. C'est l'heure du…
Réusiner
Il y a peu de code ici, mais il y a quand même quelque chose de remarquable : la valeur zéro est codée en dure et laissée telle quelle. Pourtant, dans notre application, elle a une signification particulière, on dit une signification métier. En effet, pour l’addition, zéro est l’élément neutre. Je vais donc faire ressortir cette information explicitement.
(define-module (numbers))
(define-public (integer-add int1 int2)
(let ((NEUTRAL_ELEMENT 0))
NEUTRAL_ELEMENT))
Cette variable est localement définie dans la procédure integer-add
, sa seule utilisation.
Écrire le test d’abord
Passons au deuxième test dans numbers-test.scm
:
(use-modules (srfi srfi-64)
(numbers))
(test-begin "harness-numbers")
(test-equal "add-zero-zero"
0
(integer-add 0 0))
(test-equal "add-one-zero"
1
(integer-add 1 0))
(test-end "harness-numbers")
Essayer et lancer le test
$ guile -L . numbers-test.scm
Écrire le minimum de code pour faire compiler le test et vérifier la raison de son échec
Pas d’erreur à la compilation. Le test échoue correctement !
[…]
Test begin:
test-name: "test-interger-add-one-zero"
source-file: "numbers-test.scm"
source-line: 10
source-form: (test-equal "add-one-zero" 1 (integer-add 1 0))
Test end:
result-kind: fail
actual-value: 0
expected-value: 1
Écrire juste assez de code pour faire passer le test
La différence entre le premier et le deuxième test est la valeur du premier paramètre donné à integer-add
. Voilà ce que je te propose : la valeur retournée dépend de la valeur de ce premier paramètre.
(define-module (numbers))
(define-public (integer-add int1 int2)
(let ((NEUTRAL_ELEMENT 0))
(if (equal? NEUTRAL_ELEMENT int1)
NEUTRAL_ELEMENT
1)))
Exécute les tests à nouveau.
$ guile -L . numbers-test.scm
Et voilà que tout passe !
%%%% Starting test harness-numbers (Writing full log to "harness-numbers.log")
# of expected passes 2
Réusiner
Dans ce second test, j'ai utilisé la valeur 1
arbitrairement. J'aurais pu en choisir une autre, du moment que ce n'est pas l'élément neutre de l'addition. Je vais donc faire ressoirtir cette intention dans le nom d'une variable qui portera cette valeur.
Modifies le fichier numbers-test.scm
:
(use-modules (srfi srfi-64)
(numbers))
(define DUMMY_NON_NEUTRAL_VALUE 1)
(test-begin "harness-numbers")
(test-equal "add-zero-zero"
0
(integer-add 0 0))
(test-equal "add-one-zero"
DUMMY_NON_NEUTRAL_VALUE
(integer-add DUMMY_NON_NEUTRAL_VALUE 0))
(test-end "harness-numbers")
Après chaque petite passe de ton refactoring, il est recommander de lancer les tests pour s'assurer que tout est en ordre.
$ guile -L . numbers-test.scm
;;; note: source file /home/jeko/Workspace/guile-handbook/numbers/numbers-test.scm
;;; newer than compiled /home/jeko/.cache/guile/ccache/3.0-LE-8-4.3/home/jeko/Workspace/guile-handbook/numbers/numbers-test.scm.go
;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;; or pass the --no-auto-compile argument to disable.
;;; compiling /home/jeko/Workspace/guile-handbook/numbers/numbers-test.scm
;;; compiled /home/jeko/.cache/guile/ccache/3.0-LE-8-4.3/home/jeko/Workspace/guile-handbook/numbers/numbers-test.scm.go
%%%% Starting test harness-numbers (Writing full log to "harness-numbers.log")
# of expected passes 2
On est bon. Maintenant, je me rend compte que la variable NEUTRAL_ELEMENT
serait utile dans les tests également.
Modifies le fichier numbers.scm
:
(define-module (numbers))
(define-public NEUTRAL_ELEMENT 0)
(define-public (integer-add int1 int2)
(if (equal? NEUTRAL_ELEMENT int1)
NEUTRAL_ELEMENT
1))
Modifies le fichier numbers-test.scm
:
(use-modules (srfi srfi-64)
(numbers))
(define DUMMY_NON_NEUTRAL_VALUE 1)
(test-begin "harness-numbers")
(test-equal "add-zero-zero"
NEUTRAL_ELEMENT
(integer-add NEUTRAL_ELEMENT NEUTRAL_ELEMENT))
(test-equal "add-one-zero"
DUMMY_NON_NEUTRAL_VALUE
(integer-add DUMMY_NON_NEUTRAL_VALUE NEUTRAL_ELEMENT))
(test-end "harness-numbers")
Assures-toi que tu n'as pas cassé de tests :
$ guile -L . numbers-test.scm
;;; note: source file /home/jeko/Workspace/guile-handbook/numbers/numbers-test.scm
;;; newer than compiled /home/jeko/.cache/guile/ccache/3.0-LE-8-4.3/home/jeko/Workspace/guile-handbook/numbers/numbers-test.scm.go
;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;; or pass the --no-auto-compile argument to disable.
;;; compiling /home/jeko/Workspace/guile-handbook/numbers/numbers-test.scm
;;; note: source file ./numbers.scm
;;; newer than compiled /home/jeko/.cache/guile/ccache/3.0-LE-8-4.3/home/jeko/Workspace/guile-handbook/numbers/numbers.scm.go
;;; compiling ./numbers.scm
;;; compiled /home/jeko/.cache/guile/ccache/3.0-LE-8-4.3/home/jeko/Workspace/guile-handbook/numbers/numbers.scm.go
;;; compiled /home/jeko/.cache/guile/ccache/3.0-LE-8-4.3/home/jeko/Workspace/guile-handbook/numbers/numbers-test.scm.go
%%%% Starting test harness-numbers (Writing full log to "harness-numbers.log")
# of expected passes 2
Maintenant, je réalise que les deux tests racontent deux histoires ayant un point en commun : un des opérandes est l'élément neutre. Je vais donc faire ressortir ce détail pour éliminer la dupplication.
Modifies le fichier numbers-test.scm
:
(use-modules (srfi srfi-64)
(numbers))
(define DUMMY_NON_NEUTRAL_VALUE 1)
(define (test-add-neutral-element-to value)
(test-equal (build-test-name value)
value
(integer-add value NEUTRAL_ELEMENT)))
(define (build-test-name value)
(string-append "add-neutral-element-to-" (number->string value)
"-should-return-" (number->string value)))
(test-begin "harness-numbers")
(test-add-neutral-element-to NEUTRAL_ELEMENT)
(test-add-neutral-element-to DUMMY_NON_NEUTRAL_VALUE)
(test-end "harness-numbers")
Une dernière exécution des tests pour se rassurer et le tour est joué !
$ guile -L . numbers-test.scm
;;; note: source file /home/jeko/Workspace/guile-handbook/numbers/numbers-test.scm
;;; newer than compiled /home/jeko/.cache/guile/ccache/3.0-LE-8-4.3/home/jeko/Workspace/guile-handbook/numbers/numbers-test.scm.go
;;; note: auto-compilation is enabled, set GUILE_AUTO_COMPILE=0
;;; or pass the --no-auto-compile argument to disable.
;;; compiling /home/jeko/Workspace/guile-handbook/numbers/numbers-test.scm
;;; compiled /home/jeko/.cache/guile/ccache/3.0-LE-8-4.3/home/jeko/Workspace/guile-handbook/numbers/numbers-test.scm.go
%%%% Starting test harness-numbers (Writing full log to "harness-numbers.log")
# of expected passes 2
Le détail des résultats montre l'intérêt de la procédure build-test-name
:
$ cat harness-numbers.log
%%%% Starting test harness-numbers
Group begin: harness-numbers
Test begin:
test-name: "add-neutral-element-to-0-should-return-0"
source-file: "numbers-test.scm"
source-line: 7
source-form: (test-equal (build-test-name value) value (integer-add value NEUTRAL_ELEMENT))
Test end:
result-kind: pass
actual-value: 0
expected-value: 0
Test begin:
test-name: "add-neutral-element-to-1-should-return-1"
source-file: "numbers-test.scm"
source-line: 7
source-form: (test-equal (build-test-name value) value (integer-add value NEUTRAL_ELEMENT))
Test end:
result-kind: pass
actual-value: 1
expected-value: 1
Group end: harness-numbers
# of expected passes 2
Conclusion
- Plus de pratique du TDD
- Nombres, addition
- Retirer les duplications dans le code ET dans les tests