Sviluppo test-first con FitNesse

Negli ultimi anni, ho lavorato in tutti i ruoli del processo di test, utilizzando JavaScript lato server, Perl, PHP, Struts, Swing e architetture model-driven. Tutti i progetti differivano, ma tutti avevano alcuni punti in comune: le scadenze erano in ritardo e i progetti avevano difficoltà a realizzare ciò di cui il cliente aveva veramente bisogno.

Ogni progetto aveva un qualche tipo di requisito, alcuni erano molto dettagliati, altri erano lunghi solo poche pagine. Tali requisiti di solito sono stati sottoposti a tre fasi:

  • Sono stati scritti (dal cliente o dall'appaltatore) e hanno ricevuto una sorta di accettazione ufficiale
  • I tester hanno cercato di lavorare con i requisiti e li hanno trovati più o meno inadeguati
  • Il progetto è entrato in una fase di test di accettazione e il cliente ha improvvisamente ricordato tutti i tipi di cose che il software doveva fare in modo aggiuntivo / diverso

L'ultima fase ha portato a modifiche, che hanno portato a scadenze mancate, che hanno messo sotto stress gli sviluppatori, il che a sua volta ha portato a più errori. Il conteggio dei bug ha iniziato a crescere rapidamente e la qualità complessiva del sistema è diminuita. Suona familiare?

Consideriamo cosa è andato storto nei progetti sopra descritti: il cliente, lo sviluppatore e il tester non hanno lavorato insieme; trasmettevano i requisiti, ma ogni ruolo aveva esigenze diverse. Inoltre, gli sviluppatori di solito sviluppavano una sorta di test automatizzati e i tester cercavano anche di automatizzare alcuni test. Di solito, non potevano coordinare sufficientemente il test e molti elementi sono stati testati due volte, mentre altri (di solito le parti difficili), per niente. E il cliente non ha visto alcun test. Questo articolo descrive un modo per risolvere questi problemi combinando i requisiti con i test automatici.

Inserisci FitNesse

FitNesse è un wiki con alcune funzioni aggiuntive per l'attivazione dei test JUnit. Se questi test sono combinati con i requisiti, servono come esempi concreti, rendendo così i requisiti ancora più chiari. Inoltre, i dati del test sono organizzati logicamente. La cosa più importante dell'utilizzo di FitNesse, tuttavia, è l' idea alla base, nel senso che i requisiti risultano essere scritti (in parte) come test, rendendoli testabili e, quindi, verificabile il loro adempimento.

Utilizzando FitNesse, il processo di sviluppo potrebbe essere simile al seguente: l'ingegnere dei requisiti scrive i requisiti in FitNesse (anziché in Word). Cerca di coinvolgere il cliente il più possibile, ma di solito non è possibile realizzarlo su base giornaliera. Il tester dà una occhiata al documento ripetutamente e fa domande difficili fin dal primo giorno. Poiché il tester pensa in modo diverso, non pensa: "Cosa farà il software?" ma "Cosa potrebbe andare storto? Come posso romperlo?" Lo sviluppatore pensa più come l'ingegnere dei requisiti; vuole sapere: "Cosa deve fare il software?"

Il tester inizia a scrivere i suoi test in anticipo, quando i requisiti non sono ancora stati soddisfatti. E li scrive nei requisiti. I test diventano parte non solo dei requisiti, ma anche del processo di revisione / accettazione dei requisiti, che presenta alcuni importanti vantaggi:

  • Il cliente pensa anche ai test. Di solito, viene persino coinvolta nella loro creazione (potresti essere sorpreso di quanto possa divertirsi con questo).
  • La specifica diventa molto più dettagliata e precisa, poiché i test sono generalmente più precisi del semplice testo.
  • Pensare in anticipo a scenari reali, fornire dati di test e calcolare esempi si traduce in una visione molto più chiara del software, come un prototipo, solo con più funzioni.

Infine, i requisiti vengono passati allo sviluppatore. Ora ha un lavoro più facile, poiché è meno probabile che le specifiche cambino e a causa di tutti gli esempi inclusi. Diamo un'occhiata a come questo processo semplifica il lavoro di uno sviluppatore.

Implementazione di test-first

Di solito, la parte più difficile dell'avvio dello sviluppo test-first è che nessuno vuole spendere così tanto tempo a scrivere test, solo dopo trovare un modo per farli funzionare. Utilizzando il processo sopra descritto, lo sviluppatore riceve i test funzionali come parte del suo contratto. I suoi compiti cambiano da "Costruisci la cosa che voglio e hai finito, fino a quando non esamino il tuo lavoro e apporto le modifiche" a "Fai funzionare questi test e hai finito". Ora tutti hanno un'idea migliore di cosa fare, quando il lavoro deve essere completato e dove si trova il progetto.

Non tutti questi test saranno automatizzati e non tutti saranno test unitari. Di solito dividiamo i test nelle seguenti categorie (i dettagli seguiranno):

  • Test basati sui dati che devono essere implementati come test unitari. I calcoli sono il tipico esempio.
  • Test basati su parole chiave che automatizzano l'utilizzo dell'applicazione. Questi sono test di sistema e richiedono che l'applicazione sia in esecuzione. Si fa clic sui pulsanti, si immettono dati e le pagine / schermate risultanti vengono controllate per contenere determinati valori. Il team di test di solito implementa questi test, ma anche alcuni sviluppatori ne traggono vantaggio.
  • Test manuali. Questi test sono troppo costosi da automatizzare e gli eventuali errori non abbastanza gravi, oppure sono così fondamentali (pagina iniziale non visualizzata) che la loro rottura verrebbe subito scoperta.

Quando ho letto per la prima volta di FitNesse nel 2004, ho riso e ho detto che non avrebbe mai funzionato. L'idea di scrivere i miei test in un wiki che li trasformasse automaticamente in test sembrava troppo assurda. Si è scoperto che mi sbagliavo. FitNesse è davvero così semplice come sembra.

Questa semplicità inizia con l'installazione. Basta scaricare la distribuzione completa di FitNesse e decomprimerlo. Nella discussione seguente, presumo che tu abbia decompresso la distribuzione in C: \ fitnesse.

Avvia FitNesse eseguendo lo script run.bat( run.shsu Linux) in C: \ fitnesse. Per impostazione predefinita, FitNesse esegue un server Web sulla porta 80, ma è possibile specificare una porta diversa, ad esempio 81, aggiungendola -p 81alla prima riga del file batch. Questo è tutto ciò che c'è da fare; ora puoi accedere a FitNesse su // localhost: 81.

In questo articolo, utilizzo la versione Java di FitNesse su Windows. Tuttavia, gli esempi possono essere utilizzati anche per altre versioni (Python, .Net) e piattaforme.

Alcuni test

La documentazione in linea di FitNesse fornisce alcuni semplici esempi (paragonabili al famigerato esempio di denaro di JUnit) per iniziare. Vanno bene per imparare a usare FitNesse, ma non sono sufficientemente complicati da persuadere alcuni scettici. Pertanto, userò un esempio del mondo reale da uno dei miei progetti recenti. Ho notevolmente semplificato il problema e il codice, non preso direttamente dal progetto, è stato scritto a scopo illustrativo. Tuttavia, questo esempio dovrebbe essere sufficientemente complicato per dimostrare la potenza della semplicità di FitNesse.

Supponiamo di lavorare a un progetto che implementa un software aziendale complesso basato su Java per una grande compagnia di assicurazioni. Il prodotto gestirà l'intera attività dell'azienda, inclusi i pagamenti e la gestione dei clienti e dei contratti. Per il nostro esempio, esamineremo una piccola parte di questa applicazione.

In Svizzera, i genitori hanno diritto a un assegno per figli a figlio. Ricevono questa indennità solo se vengono soddisfatte determinate circostanze e l'importo varia. Quella che segue è una versione semplificata di questo requisito. Inizieremo con i requisiti "tradizionali" e successivamente li trasferiremo a FitNesse.

Esistono diverse fasi dell'assegno familiare. La richiesta inizia il primo giorno del mese in cui il bambino nasce e termina l'ultimo giorno del mese in cui il bambino raggiunge il limite di età, termina il suo apprendistato o muore.

Al compimento dei 12 anni, il credito viene elevato a 190 CHF (simbolo della valuta ufficiale della Svizzera) a partire dal primo giorno del mese di nascita.

L'occupazione a tempo pieno e a tempo parziale dei genitori porta a richieste diverse, come dettagliato nella Figura 1.

Il tasso di occupazione attuale è calcolato sui contratti di lavoro attivi. Il contratto deve essere valido e, se è impostata una data di fine, deve essere situata nel "periodo di attivazione". La figura 2 mostra a quanti soldi ha diritto un genitore, a seconda dell'età del bambino.

I regolamenti che disciplinano questi pagamenti vengono adattati ogni due anni.

In prima lettura, la specifica potrebbe sembrare esatta e uno sviluppatore dovrebbe essere in grado di implementarla facilmente. Ma siamo davvero sicuri delle condizioni al contorno? Come testeremo questi requisiti?

Condizioni al contorno
Boundary conditions are the situations directly on, above, and beneath the edges of input and output equivalence classes. Experiences show that test cases exploring boundary conditions have a higher payoff than test cases that do not. A typical example is the infamous "one-off" on loops and arrays.

Scenarios can be a great help in finding exceptions and boundary conditions, as they provide a good way to get domain experts to talk about business.

Scenarios

For most projects, the requirements engineer hands the specification to the developer, who studies the requirements, asks some questions, and starts to design/code/test. Afterwards, the developer hands the software to the test team and, after some rework and fixes, passes it on to the customer (who will likely think of some exceptions requiring changes). Moving the text to FitNesse won't change this process; however, adding examples, scenarios, and tests will.

Scenarios are especially helpful for getting the ball rolling during testing. Some examples follow. Answering the question of how much child allowance is to be paid to each will clarify a lot:

  • Maria is a single parent. She has two sons (Bob, 2, and Peter, 15) and works part-time (20 hours per week) as a secretary.
  • Maria loses her job. Later, she works 10 hours per week as a shop assistant and another 5 hours as a babysitter.
  • Paul and Lara have a daughter (Lisa, 17) who is physically challenged and a son (Frank, 18) who is still in university.

Just talking through these scenarios should help the testing process. Executing them manually on the software will almost certainly find some loose ends. Think we can't do that, since we don't have a prototype yet? Why not?

Keyword-driven testing

Keyword-driven testing can be used to simulate a prototype. FitNesse allows us to define keyword-driven tests (see "Totally Data-Driven Automated Testing" for details). Even with no software to (automatically) execute the tests against, keyword-driven tests will help a lot.

Figure 3 shows what a keyword-driven test might look like. The first column represents keywords from FitNesse. The second column represents methods in a Java class (we write those, and they need to follow the restrictions put on method names in Java). The third column represents data entered into the method from the second column. The last row demonstrates what a failed test might look like (passed tests are green). As you can see, it is quite easy to find out what went wrong.

Such tests are easy and even fun to create. Testers without programming skills can create them, and the customer can read them (after a short introduction).

Defining tests this way, right next to the requirement, has some important advantages over the traditional definition of test cases, even without automation:

  • The context is at hand. The test case itself can be written with the least possible amount of work and is still precise.
  • If the requirement changes, there is a strong chance that the test will change as well (not very likely when several tools are used).
  • The test can be executed at once to show what needs to be fixed to make this new/changed requirement work.

To automate the test, a thin layer of software is created, which is delegated to the real test code. These tests are especially useful for automating manual GUI tests. I developed a test framework based on HTTPUnit for automating the testing of Webpages.

Here is the code automatically executed by FitNesse:

package stephanwiesner.javaworld;

import fit.ColumnFixture;

public class ChildAllowanceFixture extends ColumnFixture { public void personButton() { System.out.println("pressing person button"); } public void securityNumber(int number) { System.out.println("entering securityNumber " + number); } public int childAllowance() { System.out.println("calculating child allowance"); return 190; } [...] }

The output of the tests can be examined in FitNesse as well (see Figure 4), which greatly helps with debugging. In contrast to JUnit, where one is discouraged from writing debug messages, I find them absolutely necessary when working with automated Web tests.

When testing a Web-based application, error pages are included in the FitNesse page and displayed, making debugging much easier than working with log files.

Data-driven testing

Mentre il test basato su parole chiave va bene per l'automazione della GUI, il test basato sui dati è la prima scelta per testare il codice che esegue qualsiasi tipo di calcolo. Se hai già scritto unit test, qual è la cosa più fastidiosa di quei test? È probabile che tu pensi ai dati. I tuoi test saranno pieni di dati, che spesso cambiano, rendendo la manutenzione un incubo. Testare combinazioni diverse richiede dati diversi, probabilmente rendendo i tuoi test complicati, brutte bestie.