Numeri
I numeri sono un tipo di dato relativamente elementare. Guile implementa una Torre di tipi numerici, offrendo all'hacker una varietà di tipi di numeri con caratteristiche proprie.
Qui mi concentrerò sui numeri interi e sulla loro addizione, ma sentitevi liberi di sperimentare ciò che vi serve. L'obiettivo di questo capitolo sarà quello di creare una procedura `integer-add' parzialmente implementata per vedere come funziona il tutto.
Scrivere prima il test
Per prima cosa crea la nuova cartella ~/Workspace/guile-handbook/numbers
nel tuo workspace e crea un nuovo file numbers-test.scm
. Di seguito, è stato aggiunto il primo test. Questo è il caso che mi sembra più semplice.
(use-modules (srfi srfi-64)
(numbers))
(test-begin "harness-numbers")
(test-equal "add-zero-zero"
0
(integer-add 0 0))
(test-end "harness-numbers")
Avrai notato che ho utilizzato il modulo numbers
, che contiene la funzione integer-add
.
Provare a eseguire il test
$ guile -L . numbers-test.scm
Il compilatore protesta:
no code for module (numbers)
Scrivere la quantità minima di codice per l'esecuzione del test e controllare l'output del test non riuscito.
Adesso crea il modulo numbers
nel file ~/Workspace/guile-handbook/numbers/numbers.scm
per soddisfare il compilatore e nient'altro!
(define-module (numbers))
Rilancia il test. Adesso c'è un altro errore di compilazione:
;;; numbers-test.scm:8:2: warning: possibly unbound variable `integer-add'
Ripeti il processo, ogni volta, lasciando che il compilatore ti guidi finché il tuo test non fallirà per il motivo giusto. Il file ~/Workspace/guile-handbook/numbers/numbers.scm
diventa:
(define-module (numbers))
(define-public (integer-add int1 int2)
-1)
Ora possiamo vedere che il test fallisce. Come puoi verificare nel report del test, il valore atteso è 0 ma il valore ottenuto è -1 (sì, è stato fatto di proposito!).
%%%% 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
Scrivere abbastanza codice per farlo passare
Ora modificherete il codice minimo per superare il test:
(define-module (numbers))
(define-public (integer-add int1 int2)
0)
Lanciando il testo possiamo verificare che le modifiche sono sufficienti. Adesso è il momento del...
Refactor
C'è poco codice qui, ma è comunque qualcosa di notevole: il valore zero è codificato in modo rigido e lasciato così com'è. Tuttavia, nella nostra applicazione, ha un significato speciale, diciamo un significato commerciale. Infatti, per l'addizione, zero è l'elemento neutro. Espliciterò quindi questa informazione.
(define-module (numbers))
(define-public (integer-add int1 int2)
(let ((NEUTRAL_ELEMENT 0))
NEUTRAL_ELEMENT))
Questa variabile è definita localmente nella procedura integer-add
, il suo unico utilizzo.
Ok, iniziamo un altro ciclo TDD!
Scrivere prima il test
Nuovo test nel file 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")
Provare a eseguire il test
Lancia il test e analizza gli errori.
$ guile -L . numbers-test.scm
Scrivere la quantità minima di codice per l'esecuzione del test e controllare l'output del test non riuscito.
Nessun errore di compilazione. Il test fallisce correttamente!
[…]
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
Scrivere abbastanza codice per farlo passare
La differenza tra il primo e il secondo test è il valore del primo parametro dato a integer-add
. Ecco cosa propongo: il valore restituito dipende dal valore di questo primo parametro.
(define-module (numbers))
(define-public (integer-add int1 int2)
(let ((NEUTRAL_ELEMENT 0))
(if (equal? NEUTRAL_ELEMENT int1)
NEUTRAL_ELEMENT
1)))
Lancia ancora i test.
$ guile -L . numbers-test.scm
Tutto "green"!
%%%% Starting test harness-numbers (Writing full log to "harness-numbers.log")
# of expected passes 2
Refactor
In questo secondo test ho utilizzato il valore 1 in modo arbitrario. Avrei potuto scegliere un altro valore, purché non fosse l'elemento neutro dell'addizione. Per questo motivo, renderò esplicito questo intento nel nome di una variabile che avrà questo valore.
Ecco il file 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")
Dopo ogni piccola modifica apportata durante il refactoring, si consiglia di eseguire i test per assicurarsi che tutto sia in ordine.
$ 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
Siamo a posto. Ora mi rendo conto che la variabile NEUTRAL_ELEMENT
sarebbe utile anche nei test.
Modifica il file numbers.scm
:
(define-module (numbers))
(define-public NEUTRAL_ELEMENT 0)
(define-public (integer-add int1 int2)
(if (equal? NEUTRAL_ELEMENT int1)
NEUTRAL_ELEMENT
1))
e anche il file 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")
Controllo di non aver "rotto" nessun test:
$ 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
Ora mi rendo conto che i due test raccontano due storie con una cosa in comune: uno degli operandi è l'elemento neutro. Quindi lo renderò esplicito. In questo modo, si eliminerà il duplicato.
Controlla il nuovo 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")
Un'ultima esecuzione del test:
$ 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
Il dettaglio dei risultati mostra l'interesse della procedura 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
Conclusione
- Maggiore pratica del workflow TDD
- Numeri, addizione
- Rimovere duplicati nel codice e nei test