|
Java Tutorial - Parte 2 0.1
Un tutorial per esempi
|
In questa versione si scrivono le classi del package mastermind.common cioè le classi comuni a tutto il progetto ed usate in quasi tutti gli altri packages (forse non proprio in tutti). Queste classi non gestiscono la interfaccia utente, non sono affatto classi GUI nè gestiscono la logica del gioco ma servono per alleggerire il lavoro a tutte le altre classi del progetto.
Inoltre, scriveremo anche delle piccole applicazioni CLI di test per queste classi.
Nella tabella seguente l'elenco dei files che compongono i due packages che stiamo analizzando in questo capitolo:
| nome file | package | descrizione |
|---|---|---|
| MMLogger.java | mastermind.common | una utility per il log degli eventi |
| MMProperties.java | mastermind.common | una utility per la gestione delle proprietà della applicazione |
| Params.java | mastermind.common | i parametri di gioco per il Mastermind |
| Results.java | mastermind.common | i risultati del confronto tra la guess e la solution |
| Sequence.java | mastermind.common | gestisce e verifica le sequenze dei simboli |
| Version.java | mastermind.common | traccia le versioni della applicazione |
| MMException.java | mastermind.common | la classe base di tutte le eccezioni in MasterMind |
| PropertyException.java | mastermind.common | eccezione derivata da MMException |
| SequenceException.java | mastermind.common | eccezione derivata da MMException |
| ResultsException.java | mastermind.common | eccezione derivata da MMException |
| ParamsException.java | mastermind.common | eccezione derivata da MMException |
| TestParams.java | mastermind.test.common | app CLI di test |
| TestProperties.java | mastermind.test.common | app CLI di test |
| TestParams.java | mastermind.test.common | app CLI di test |
| TestResults.java | mastermind.test.common | app CLI di test |
| TestSequence.java | mastermind.test.common | app CLI di test |
| TestVersion.java | mastermind.test.common | app CLI di test |
Il nome deriva dal verbo inglese to log che letteralmente significa "registrare" ma viene usato normalmente come parola composta per esempio in logbook che significa "diario di bordo" oppure anche daylog che può essere tradotto in "diario giornaliero".
In informatica viene usato come sostantivo: il logger è una specie di "registratore" che annota tutti gli eventi importanti: in Linux/Unix™ vi è un servizio appositamente dedicato alla registrazione degli eventi; questo servizio fà parte del sistema operativo e si chiama syslog (SYStem LOGger = il logger di sistema).
Si tratta di un servizio molto complesso e articolato usato in maniera estensiva dagli altri servizi presenti sulla macchina come per esempio il server web, il server DNS e, in generale, tutti quei servizi che devono registrare gli accessi al sistema.
Java fornisce una utility molto simile per certi aspetti e anche molto versatile: basti pensare che i messaggi di log in Java possono essere inviati a diversi dispositivi: il terminale, un file su disco e persino su una connessione remota!
La classe che gestisce il logging in Java è la classe Logger che fà parte del package java.util.logging. Useremo questa classe per ottenere sul terminale la cronologia degli eventi che accadono quando la nostra applicazione è in esecuzione: il logger è un formidabile strumento di debugging al pari di un vero debugger.
Ma quali sono gli eventi interessanti che dovremmo registrare? In un certo senso, tutti gli eventi sono interessanti; alcuni però lo sono di più di altri. Per esempio, se l'user muove il mouse potrebbe non essere un evento degno di nota mentre se egli ha completato la scelta della soluzione segreta è sicuramente un evento da registrare.
La classe Logger di Java implementa quello che viene definito un livello di log: un messaggio di log viene effettivamente emesso solo se il suo livello è maggiore o uguale al livello di log stabilito nel oggetto logger che lo deve emettere. Mi spiego meglio com un esempio:
I livelli di log sono espressi come numeri interi e possono avere un range di valori pari a quelli del tipo int.
Tuttavia, il package java.util.logging definisce anche alcune costanti mnemoniche per agevolare il programmatore nel decidere i livelli di log da assegnare ai messaggi. Queste costanti sono le seguenti:
Level.SEVERE (il livello più alto) Level.WARNING Level.INFO Level.CONFIG Level.FINE Level.FINER Level.FINEST (il livello più basso)In aggiunta a questi, vengono definiti anche Level.OFF che sopprime tutti i messaggi di log e Level.ALL che, al contrario, abilita tutti i messaggi di log.
Infine, la classe Logger definisce dei metodi "di comodo" che sostituiscono il metodo log, col quale si emette un messaggio, in modo da utilizzare quale livello lo stesso livello del nome del metodo. In altre parole, i seguenti due costrutti sono equivalenti:
Ho già accennato al fatto che la classe Logger può emettere i messaggi di log su diversi dispositivi quali il terminale, un file su disco oppure anche una connessione remota. E non solo: ogni oggetto logger può emettere i suoi messaggi su tutti e tre questi dispositivi contemporaneamente!
Questo feature viene realizzato consentendo ad ogni oggetto logger di avere una catena di handlers (=gestori) dei messaggi. E questo non è tutto: anche i singoli handlers possiedono un proprio livello di log; quindi posso inviare al terminale i messaggi di livello INFO, per esempio, mentre sulla connessione remota voglio avere solo quelli di livello WARNING.
Quando viene creato, un oggetto logger possiede un handler assegnato automaticamente: il terminale. Questo handler automatico si trova al grado più alto della catena e viene sempre usato nei messaggi di log assieme a quelli di grado più basso.
Poichè nel nostro programma non siamo interessati ad inviare i messaggi di log a nessun altro dispositivo che il terminale, aggiungeremo quale handler la classe ConsoleHandler (il terminale, per l'appunto) e disabiliteremo l'invio di messaggi a qualsiasi altro handler, anche quello assegnato automaticamente.
Scriveremo pertanto una classe logger personalizzata, che chiameremo MMLogger, che definisce due diverse versioni del metodo getLogger:
Come ultima nota, la classe MMLogger disabilita l'handler padre, qualunque esso sia (oggi è il terminale ma domani, chissà): viene usata come handler solo la console creata da noi:
Come già accennato, i messaggi di log sono molto utili in fase di debug della applicazione specialmente per una applicazione GUI: mentre si osserva l'output grafico comparire sullo schermo, a terminale possiamo avere i messaggi di log che sono di questo tipo:
nov 19, 3:24:48 PM mastermind.gui.Main notifyHaveSolution INFO: notifyHaveSolution - player: PlayerAI1, solution: 452 nov 19, 3:24:56 PM mastermind.gui.Main notifyHaveSolution INFO: notifyHaveSolution - player: Lukas, solution: 012 nov 19, 3:24:56 PM mastermind.gui.Main notifyStartGame
Ogni logger viene creato con un nome univoco il quale lo identifica. Creare un oggetto logger con lo stesso nome di uno già creato, non crea una nuova istanza ma restituisce lo stesso logger già creato in precedenza. Vi sono due strategie per assegnare il nome ai logger della applicazione:
Nella applicazione Mastermind adotterò la prima strategia: un unico oggetto logger per tutta la durata della app il cui nome viene definito staticamente nella classe MMLogger stessa:
Per creare il logger che vale per tutta la applicazione:
main Ogni applicazione ha i suoi parametri di funzionamento che possono, normalmente, essere modificati dall'utente. Quando viene eseguita per la prima volta, la applicazione usa parametri di default stabiliti dal programmatore; questi parametri vengono chiamati le proprietà della applicazione.
I parametri di funzionamento dei programmi CLI (Command Line Interface) vengono normalmente impostati mediante opzioni e/o argomenti alla command-line. Abbiamo incontrato numerosi esempi di questo approccio nella prima parte di questo mio tutorial come per esempio in Java Tutorial Parte Prima - la sintassi della applicazione Geometry
Se state leggendo questo tutorial l'autore presume che abbiate già una certa familiarità col concetto di command-line ma, se proprio non ne sapete nulla o volete ripassare l'argomento, clikkate il riferimento seguente: Java Tutorial Parte Prima - la riga di comando.
Il linguaggio Java mette a disposizione del programmatore una classe specializzata per gestire le proprietà di una applicazione: la classe Properties che è contenuta nel package java.utils. Da notare che la classe Properties non si limita a gestire le proprietà della applicazione ma qualsiasi dato che può essere contenuto in una coppia di stringhe del tipo:
KEY = VALUE
dove KEY rappresenta una chiave di ricerca e VALUE rappresenta il valore associato a quella chiave. Entrambi questi dati (la chiave ed il valore) sono di tipo String.
La classe Properties consente di impostare ed ottenere una proprietà attraverso i suoi due metodi principali: getProperty, che ritorna il valore della proprietà e setProperty che imposta il valore di una proprietà. Vi sono altri due metodi molto interessanti definiti nella classe Properties:
load che legge le proprietà da uno stream save che scrive le proprietà su uno streamLo stream deve contenere le proprietà su ogni riga logica in un formato ben definito che si può riassumere nel seguente testo:
#
# questa è una riga di commento
#
KEY1 VALUE1
KEY2=VALUE2
KEY3:VALUE3
KEY4 il value della key 4 è definito \
su tre righe le prime due delle \
quali terminano col backslash
Le regole di formato dello stream che contiene le proprietà sono piuttosto semplici:
Ogni applicazione ha le proprie specifiche proprietà e anche quella che scriveremo noi ha le sue. Una limitazione piuttosto fastidiosa della classe Properties è che può gestire solo valori stringa mentre normalmente tutte le applicazioni hanno valori di proprietà anche di tipo numerico e, molto spesso anche di tipo boolean.
Per maggiori informazioni vedi la documentazione ufficiale della libreria Java java.util.Properties.
Anche la nostra applicazione ha proprietà specifiche; questo è l'elenco delle proprietà del Mastermind che andremo a scrivere:
| Proprietà | Tipo | Default | Descrizione |
|---|---|---|---|
| n | intero | 10 | numero di simboli a disponibili per una sequenza |
| k | intero | 3 | lunghezza di una sequenza |
| repeat | boolean | false | flag di ripetizione dei simboli in una sequenza |
| maxTries | intero | 9 | numero massimo di tentativi |
| swapturn | boolean | true | scambio del primo turno in partite multiple |
| playerType | String | HUMAN | il tipo di giocatore del pannello sinistro |
| playerName | String | null | il nome del giocatore del pannello sinistro |
| opponentType | String | AI | il tipo di giocatore del pannello destro |
| opponentName | String | null | il nome del giocatore del pannello destro |
| guessDelay | intero | 1000 | ritardo in millisecondi per le sequenze guess |
| serverAddr | String | null | indirizzo IP del server MasterMind |
| serverPort | intero | 18862 | porta TCP/IP del server MasterMind |
| codex | String | NUMBERS | tipo di codici della sequenza: NUMBERS,LETTERS,COLORS |
| iconSize | intero | 30 | dimensione dei simboli in pixels |
| logLevel | String | INFO | il livello di log |
| mcInterval | intero | 1000 | intervallo di invio (ms) dei pacchetti UDP |
| mcTimeout | intero | 500 | timeout di ricezione (ms) dei pacchetti UDP |
| mcAddress | String | 230.18.8.0 | indirizzo IP multicast per i pacchetti UDP |
| mcPort | intero | 3963 | porta multicast per i pacchetti UDP |
Come potete osservare, molte proprietà sono di tipo numerico intero e molte altre di tipo booleano. E' comunque possibile impostare una proprietà come stringa usando il metodo toString che si applica sia ai tipi numerici che ai tipi booleani. Di contro è possibile ottenere una proprietà come valore stringa e poi usare i metodi statici:
Integer.parseint per convertirla in un intero Boolean.parseBoolean per convertirla in un booleano MMProperties.Il costruttore della classe MMProperties memorizza i valori di default di tutte le proprietà della applicazione ed ottiene il nome del file dove le proprietà vengono salvate. Quando il giocatore cambia i parametri di gioco si aspetta che questi cambiamenti siano persistenti: ecco perchè le proprietà vanno scritte su un file. Per determinare il nome del file dove scrivere le proprietà ci sono tre strategie:
etc) e di nominare i files con il nome dello user ed una estensione fissa; per esempio: pippo.properties. Lo svantaggio di questa soluzione è che se la applicazione viene rimossa e reinstallata oppure upgradata, si possono perdere i files delle proprietà personalizzati mastermind.properties) ed ubicarlo nella cartella personale di ogni userIl costruttore di MMProperties usa la terza strategia e richiama il metodo System.getProperty per ottenere il nome della cartella personale dello user e il carattere di separatore di files che è diverso tra le varie piattaforme.
Per mezzo del metodo statico System.getProperty si possono ottenere diverse proprietà del sistema in cui viene eseguita la JVM. Per maggiori info vedi: Le proprietà di sistema.
La classe base Properties si trova nel package java.util e mette a disposizione solo due metodi per impostare ed ottenere i valori delle proprietà:
getProperty che ritorna il valore come stringa setProperty che imposta la proprietà da una stringapoichè la classe base di Java gestisce solo stringhe sia come chiave che come valore. Tuttavia, per facilitare il compito delle classi di Mastermind che leggono le proprietà di tipo intero e booleano, la classe specializzata MMProperties definisce dei metodi di comodo:
setProperties sovraccaricati in grado di accettare valori di tipo int e boolean getProperties specializzati che ritornano valori di tipo int e boolean parseCmdline che crea uno stream nel formato corretto da una array di stringhe (la command-line) per essere poi letto dal metodo Properties.load storeDefaults col quale si inizializza l'oggetto properties ai valori di default specificati nella tabella di cui sopraDefiniremo inoltre la classe PropertyException, una eccezione di tipo checked che viene sollevata in caso di errori che possono accadere nei metodi di cui sopra.
Per mezzo della classe specializzata potremmo pertanto allocare un oggetto properties ed impostare i valori delle proprietà per mezzo della command-line in questo modo:
Per esempio, possiamo impostare un log level diverso dal default semplicemente specificandolo sulla command-line:
1>java -ea mastermind logLevel=FINER
Quanto sopra è' più intuitivo del dover specificare una opzione di un solo carattere.
Perchè creare una classe base delle eccezioni dal momento che già esiste ed è la Exception definita nel package java.lang? Benchè la classe Exception possieda un costruttore per mezzo del quale è possibile passare come argomento una altra eccezione, che rappresenta la causa scatenante, essa non possiede alcun metodo per ottenere il messaggio di errore di questa causa.
La classe che andremo a scrivere il cui nome è MMException colma questa lacuna definendo il metodo getCompleteMessage che, come suggerisce il nome, restituisce una stringa che contiene il messaggio di errore di questo oggetto eccezione oltre ai messaggi di errore di tutte le eccezioni passate come argomento cause iterando nella catena completa di oggetti eccezione passati come argomenti.
Per esempio, supponendo di ottenere una eccezione nella fase di connessione con un giocatore remoto sarà sollevata una Exception con un messaggio del tipo:
ERROR: cannot connect to remote host: mastermind.acme.net
Tuttavia non sappiamo nulla sulla causa effettiva della mancata connessione; potrebbe essere un errore di rete oppure potrebbe essere un bug del programma.
Se però noi riportiamo tutte le eccezioni in cascata che hanno causato l'errore possiamo poi trovare più agevolmente la soluzione. Per esempio, se il metodo getCompleteMessage riportasse un messaggio come il seguente:
ERROR: cannot connect to remote host: mastermind.acme.net CAUSE - MessageException: presentation message - error in message field #2 CAUSE - ParamsException: string 'x:y:z' is not valid
comprenderemo subito che la mancata connessione è dovuta al fatto che il messaggio di presentazione del remoto è errato poichè il secondo campo del messaggio, che dovrebbe contenere i parametri di gioco, contiene una stringa ("x.y.x") che non è considerata valida.
Il messaggio suesposto denota un bug del programma piuttosto che una mancata connessione di rete la quale, invece, darebbe un messaggio di questo altro tipo:
ERROR: cannot connect to remote host: mastermind.acme.net CAUSE - IOException: unreacheble address
Nel gioco del MasterMind i giocatori tentano di indovinare una sequenza di simboli che ognuno dei due sceglie segretamente. Tuttavia, ci si deve accordare su alcune regole da condividere:
Per maggiori info vedi anche Le regole del gioco. Chiameremo queste regole parametri di gioco e vengono rappresentati da una classe specializzata chiamata Params.
La classe contiene quattro membri dati:
n: il numero di simboli a disposizione che per default sono 10 e vengono indirizzati con i numeri da "0" a "9". Il numero massimo di simboli a disposizione è 10 mentre il numero minimo è 6. Il default è 10. k: la lunghezza della sequenza con un minimo di tre simboli ed un massimo di quattro; il default è 3 repeat: un booleano il cui valore true indica che la ripetizione dei simboli è ammessa. Il default è false tries: il numero massimo di tentativi per giocatore raggiunto il quale la partita si intende patta. Il default è 9.Potremmo avere altri simboli oltre ai numeri da "0" a "9"? Certo che si, anzi! In fondo il simbolo scelto non ha molta importanza; potremmo usare le casette, gli animali, le navi da guerra etc.
Quello che conta è solo l'indice del simbolo nell'insieme dei simboli a disposizone. Quindi, una sequenza come "012" indica semplicemente il primo, il secondo ed il terzo simbolo nell'insieme dei simboli che possono essere:
Questi che ho elencato sono effettivamente i tipi di codice che implementeremo nella applicazione. I dieci colori della tavolozza dei simboli di tipo COLORS sono elencati di seguito:
Ovviamente, siete liberi di modificarli come meglio credete e non solo: siete invitati anche a creare alri gruppi di simboli ed implementarne le classi che li gestiscono. Grazie alla caratteristica del polimorfismo di Java è possibile implementare un insieme di nuovi simboli in modo semplice e veloce: basta derivare la nuova classe da mastermind.gui.SequenceGUI ed implementarne i metodi astratti.
Ma questo è un argomento che affronteremo più avanti (vedi Il pannello delle sequenze).
Un oggetto di classe Params può essere costruito in quattro modi diversi a cui corrispondono quattro costruttori:
tries) impostato al default n:k:repeat:tries e che viene interpretata come quattro campi separati dal carattere due-punti.Considerato che i parametri di gioco devono essere costruiti con limiti ben precisi passati come argomenti ai costruttori và da se che in caso di valori fuori range sarà sollevata una eccezione di tipo ParamsException. Un caso a parte è costituito dal passare come argomenti n=0,k=0. In questo caso il costruttore della classe Params sceglierà a caso un valore ammissibile per i due parametri di gioco.
Tutti i metodi di Params sono di facile lettura. Tutti i metodi setter possono sollevare una eccezione di tipo ParamsException se gli argomenti a questi metodi non sono validi o sono fuori range:
n deve essere compreso tra 6 e 10 oppure deve essere ZERO k deve essere compreso tra 3 e 4 oppure deve essere ZERO tries deve essere compreso tra 2 e 19 n, k, e tries devono essere numerici e repeat può solo essere true o false.La sequenza è uno dei punti cardine del gioco: nella prima fase i due giocatori scelgono una sequenza segreta (che chiameremo solution) e, dopo che entrambi hanno scelto la solution inizia la seconda fase del gioco nella quale, a turno, i giocatori tentano di indovinare la solution dell'avversario.
Nella applicazione Mastermind una sequenza di simboli viene rappresentata dalla classe String; la sequenza non contiene i simboli ma i loro codici numerici da "0" a "9". Unica eccezione a questa regola è la stringa "resign" (=resa) che significa che il giocatore si è arreso.
Per facilitare il compito di verificare e gestire le sequenze di Mastermind è stata scritta una classe specializzata di nome Sequence che contiene solo metodi statici per:
In particolare, il metodo statico checkValid verifica la validità di una sequenza rispetto a determinati parametri di gioco e solleva una eccezione di tipo SequenceException che descrive nel proprio messaggio quale è il parametro che la sequenza non soddisfa. Questi sono esempi di messaggi di errore che possono essere sollevati:
Invalid sequence: 112 - repetition of chars is not allowed Invalid sequence: 5679 - the char '9' is not allowed (n=8)
Il risultato del confronto tra le due sequenze di codici solution e la ipotesi guess è dato da due valori interi:
Se il numero di reds coincide con la lunghezza della sequenza allora le due sequenze coincidono perfettamente e la ipotesi è vincente. I risultati del confronto tra la solution e la guess vengono rappresentati dalla classe Results.
L'oggetto Results, oltre al numero di reds e whites contiene anche la lunghezza delle sequenze in modo da poter verificare se questo oggetto risultati è vincente; il metodo isWinning della classe Results ritorna TRUE se il numero di reds è uguale alla lunghezza delle sequenze.
Nella seguente tabella sono riportati i risultati di alcuni confronti:
| solution | guess | len | reds | whites | isWinning |
|---|---|---|---|---|---|
| 123 | 105 | 3 | 1 | 0 | false |
| 123 | 267 | 3 | 0 | 1 | false |
| 456 | 435 | 3 | 1 | 1 | false |
| 4567 | 4876 | 4 | 1 | 2 | false |
| 1234 | 1234 | 4 | 4 | 0 | true |
La classe Results non possiede un costruttore di default: questo perchè un argomento è essenziale: la lunghezza delle due sequenze confrontate. Vi sono tre costruttori che accettano come argomenti:
k:r:w lo stesso formato ritornato dal metodo toString Il cui metodo più importante della classe Results è sicuramente il metodo statico seguente:
Questo metodo confronta le due sequenze e fornisce i risultati del confronto in un oggetto di classe Results. Il confronto avviene in questo modo:
guess viene confrontato con i simboli che appaiono in posizione diversa rispetto al simbolo di guess e, se un match viene riscontrato si incrementa il numero di whites Da notare che se le due sequenze da confrontare hanno lunghezze diverse il metodo compareSequences ritorna null; un evento che non dovrebbe mai accadere in una partita di mastermind (salvo bugs, ovviamente).
La classe Version gestisce la versione specifica di ogni rilascio della applicazione. Può essere costruita in due modi:
La classe possiede i metodi per:
Notate che la classe Version restituisce la versione come intero esattamente come descritto in La versione come intero, il byte meno significativo rappresenta il campo bug_fix, il secondo byte meno significativo rappresenta il campo minor_version mentre i due bytes più significativi rappresentano la major_version.
Noterete che la definizione della classe Version è molto diversa da come siamo abituati.
A partire da Java 16 è stato introdotto un nuovo feature in Java chiamato record: Un record è una classe a tutti gli effetti ma con queste caratteristiche particolari:
final) final che sono gli argomenti del record final In sostanza, la definizione di un record come la seguente:
equivale a:
E' comunque possibile aggiungere metodi specializzati, anche statici, e costruttori specializzati oltre alla possibilità di definire membri dati aggiuntivi. Per esempio, nella classe Version è stato aggiunto un costruttore che prende come argomento il numero di versione come intero ed il metodo asInt che, come prevedibile, restituisce l'oggetto Version come un intero.
L'unico vincolo con i record è che qualsiasi costruttore aggiuntivo richiami come prima istruzione il costruttore creato automaticamente dal compilatore:
Per coloro che hanno letto il mio Java tutorial parte prima: suite di test questo argomento non è una novità. Per tutti gli altri descriverò brevemente l'argomento.
Abbiamo scritto diverse classi che contengono molti metodi ma come possiamo essere sicuri che essi funzionino in modo corretto? E sopratutto, cosa si intende per in modo corretto?
Ebbene, la risposta alla seconda domanda è piuttosto semplice e ve la spiegherò nel modo più semplice possibile ed in linea con lo spirito di questo tutorial: con un esempio!
Prendiamo in esame il metodo statico getNotAllowedChar della classe Sequence il quale ritorna il primo carattere non ammesso in una sequenza. Per testare efficacemente questo metodo dobbiamo partire dalla documentazione del metodo stesso che trovate a questo link: scopriamo che il metodo ritorna il carattere non ammesso oppure -1 se tutti i caratteri nella sequenza sono ammessi. I caratteri sono ammessi se:
n-1 Un modo di testare il metodo è quello di scrivere una piccola applicazione che accetta come parametro la sequenza ed il parametro n e verificare la correttezza dell'output.
Questa soluzione è piuttosto inefficente e pure inefficace: inefficente perchè ci fà perdere un mucchio di tempo ed inefficace perchè per un essere umano è difficile non commettere errori nella verifica.
Un modo decisamente più efficente ed efficace è quello di lasciare al computer l'onere della verifica: si tratta solo di fornirgli i dati da analizzare. Essi sono i seguenti:
n cioè il numero massimo di codici a disposizione getNotAllowedChar In questa tabella riepiloghiamo il risultato atteso per tre possibili valori di n e per cinque sequenze:
| Sequenza | n=10 | n=8 | n=6 |
|---|---|---|---|
| 12a3 | 'a' | 'a' | 'a' |
| 12346 | -1 | -1 | '6' |
| 24561a | 'a' | 'a' | '6' |
| g1234 | 'g' | 'g' | 'g' |
| 0123456789 | -1 | '8' | '6' |
Possiamo pertanto scrivere un metodo di test automatizzzato usando il costrutto Java assert il quale verifica che la condizione fornita come operando sia vera. La condizione che deve essere soddisfatta nel costrutto assert è, ovviamente, che il valore restituito dal metodo getNotAllowedChar sia uguale al valore che noi abbiamo individuato come risultato atteso.
Nel file sorgente mastermind/test/common/TestSequence.java è stato scritto il metodo statico test02 che esegue proprio ciò che ho descritto poc'anzi:
Dobbiamo scrivere dei metodi molto simili anche per gli altri metodi della classe Sequence e non solo: anche per le altre classi del package mastermind.common. In effetti, nel package mastermind.test sono contenuti queste piccole applicazioni ognuna delle quali testa i metodi di una classe specifica.
Tutti i test vengono eseguiti in automatico dal programma: unica accortezza da tenere presente è che è assolutamente necessario specificare la opzione -ea (=enable assertion) quando il programma viene mandato in esecuzione. Il programma di test accetta un argomento: il numero del test, che comunque non è obbligatorio. Se il numero del test non viene indicato, il programma di test li esegue tutti in sequenza.
L'insieme delle classi di test che trovate nel package mastermind.test viene comunemente chiamato test suite (non credo esista un corrispondente in italiano abbastanza calzante) ed è una pratica molto diffusa tra i programmatori esperti: verificare che le classi che scriviamo fanno esattamente il lavoro per cui sono state progettate (e documentate!!!) ci risparmia un mucchio di tempo in futuro speso alla ricerca dei bugs.
La documentazione completa dei due packages descritti in questo capitolo è disponibile clikkando il seguente link che riporta alla documentazione Javadoc del progetto: The MasterMind Project Version 0.1
Argomento precedente - Argomento successivo - Indice Generale