Generazione dei mutanti

Idealmente i mutanti generati dovrebbero essere il meno differenti possibile dal programma di partenza, ovvero dovrebbe esserci un mutante per ogni singola anomalia che sarebbe possibile inserire nel programma.

Questo richiederebbe però di generare infiniti mutanti, mentre per mantenere la suite di test eseguibile in tempi ragionevoli il numero di mutanti non dovrebbe essere troppo elevato: un centinaio è una buona stima, ma un migliaio sarebbe auspicabile.
Visto il numero limitato è necessario dunque concentrarsi sulla “qualità” dei mutanti generati, dove i mutanti sono tanto più buoni quanto più permettono di scovare degli errori. Per questo motivo vengono creati degli specifici operatori che dato un programma restituiscono dei mutanti utili.

Operatori mutanti

Come già accennato, gli operatori mutanti sono delle funzioni (o piccoli programmi) che dato un programma \(P\) generano un insieme di mutanti \(\Pi\). Essi operano eseguendo piccole modifiche sintattiche che modifichino la semantica del programma senza però causare errori di compilazione.

Tali operatori si distinguono in classi in base agli oggetti su cui operano:

  • costanti e variabili, per esempio scambiando l’occorrenza di una con l’altra;
  • operatori ed espressioni, per esempio trasformando < in <=, oppure true in false;
  • comandi, per esempio trasformando un while in if, facendo così eseguire il ciclo una sola volta.

Alcuni operatori possono essere anche specifici su alcuni tipi di applicazioni, come nel caso di:

  • operatori per sistemi concorrenti: operano principalmente sulle primitive di sincronizzazione – come eseguire una notify() invece che una notifyAll();
  • operatori per sistemi object-oriented: operano principalmente sulle interfacce dei moduli.

Poiché la generazione dei mutanti è un’attività tediosa, il compito di applicare questi operatori viene spesso affidato a tool automatici. Esistono però numerosi problemi di prestazioni, in quanto per ogni mutante occorre modificare il codice, ricompilarlo, controllare che non si sovrapponga allo spazio di compilazione delle classi di altri mutanti e fare una serie di altre operazioni che comportano un pesante overhead. Per questo motivo i tool moderni lavorano spesso sull’eseguibile in sé (sul bytecode nel caso di Java): sebbene questo diminuisca il lavoro da fare per ogni mutante è possibile che il codice eseguibile così ottenuto sia un programma che non sarebbe possibile generare tramite compilazione. Si espande quindi l’universo delle possibili anomalie anche a quelle non ottenibili, un aspetto che bisognerà tenere in considerazione nella valutazione della metrica di copertura.

High Order Mutation

Normalmente i mutanti vengono generati introducendo una singola modifica al programma originale. Nella variante HOM (High Order Mutation) si applicano invece modifiche a codice già modificato.

La giustificazione per tale tecnica è che esistono alcuni casi in cui trovare errori dopo aver applicato più modifiche è più difficile rispetto ad applicarne solo una. Può essere che un errore mascheri parzialmente lo stato inconsistente dell’altro rendendo più difficile il rilevamento di malfunzionamenti, cosa che porta a generare test ancora più approfonditi.