All'inizio di oggi mi è venuta un'idea, basata su un particolare caso d'uso reale, che avrei voluto verificare per fattibilità e utilità. Questa domanda sarà caratterizzata da una buona parte del codice Java, ma può essere applicata a tutte le lingue in esecuzione all'interno di una VM e forse anche al di fuori. Sebbene esista un codice reale, non utilizza nulla di specifico della lingua, quindi per favore leggilo principalmente come pseudo codice.
L'idea
Rendi il testing delle unità meno ingombrante aggiungendo in qualche modo all'autogenerazione del codice in base all'interazione umana con il codebase. Capisco che questo sia contrario al principio del TDD, ma non penso che nessuno abbia mai provato che fare TDD è meglio prima di creare il codice e poi immediatamente dopo i test. Questo può anche essere adattato per adattarsi al TDD, ma questo non è il mio obiettivo attuale.
Per mostrare come è destinato a essere usato, copierò qui uno dei miei corsi, per il quale ho bisogno di fare test unitari.
public class PutMonsterOnFieldAction implements PlayerAction {
private final int handCardIndex;
private final int fieldMonsterIndex;
public PutMonsterOnFieldAction(final int handCardIndex, final int fieldMonsterIndex) {
this.handCardIndex = Arguments.requirePositiveOrZero(handCardIndex, "handCardIndex");
this.fieldMonsterIndex = Arguments.requirePositiveOrZero(fieldMonsterIndex, "fieldCardIndex");
}
@Override
public boolean isActionAllowed(final Player player) {
Objects.requireNonNull(player, "player");
Hand hand = player.getHand();
Field field = player.getField();
if (handCardIndex >= hand.getCapacity()) {
return false;
}
if (fieldMonsterIndex >= field.getMonsterCapacity()) {
return false;
}
if (field.hasMonster(fieldMonsterIndex)) {
return false;
}
if (!(hand.get(handCardIndex) instanceof MonsterCard)) {
return false;
}
return true;
}
@Override
public void performAction(final Player player) {
Objects.requireNonNull(player);
if (!isActionAllowed(player)) {
throw new PlayerActionNotAllowedException();
}
Hand hand = player.getHand();
Field field = player.getField();
field.setMonster(fieldMonsterIndex, (MonsterCard)hand.play(handCardIndex));
}
}
Possiamo osservare la necessità dei seguenti test:
- Test del costruttore con input valido
- Test del costruttore con input non validi
-
isActionAllowed
test con input valido -
isActionAllowed
test con input non validi -
performAction
test con input valido -
performAction
test con input non validi
La mia idea si concentra principalmente sul test isActionAllowed
con input non validi. Scrivendo questi test non è divertente, è necessario garantire una serie di condizioni e di verificare se esso restituisce realmente false
, questo può essere estesa a performAction
, dove un'eccezione deve essere gettato in quel caso.
L'obiettivo della mia idea è quello di generare quei test, indicando (attraverso la GUI di IDE si spera) che si desidera generare i test sulla base di un ramo specifico.
L'implementazione con l'esempio
- L'utente fa clic su "Genera codice per il ramo
if (handCardIndex >= hand.getCapacity())
". -
Ora lo strumento deve trovare un caso in cui sia conservato.
(Non ho aggiunto il codice rilevante in quanto potrebbe ingombrare il post in ultima analisi)
-
Per invalidare il ramo, lo strumento ha bisogno di trovare un
handCardIndex
ehand.getCapacity()
in modo tale che la condizione>=
detiene. - Ha bisogno di costruire un
Player
con unHand
che abbia una capacità di almeno 1. - Si noti che il
capacity
int privato diHand
deve essere almeno 1. - Cerca i modi per impostarlo su 1. Fortunatamente trova un costruttore che prende come argomento
capacity
. Usa 1 per questo. - Occorre fare ancora un po 'di lavoro per costruire con successo un'istanza di
Player
, coinvolgendo la creazione di oggetti che hanno dei vincoli visibili ispezionando il codice sorgente. - Ha trovato
hand
con la minima capacità possibile ed è in grado di costruirlo. - Ora per invalidare il test sarà necessario impostare
handCardIndex = 1
. - Costruisce il test e asserisce che sia falso (il valore restituito del ramo)
Che cosa deve funzionare lo strumento?
Per funzionare correttamente, avrà bisogno della possibilità di eseguire la scansione di tutto il codice sorgente (incluso il codice JDK) per capire tutti i vincoli. Opzionalmente questo potrebbe essere fatto tramite javadoc, ma questo non è sempre usato per indicare tutti i vincoli. Potrebbe anche eseguire alcuni tentativi ed errori, ma si ferma praticamente se non è possibile allegare codice sorgente a classi compilate.
Quindi ha bisogno di alcune conoscenze di base su cosa sono i tipi primitivi , inclusi gli array. E deve essere in grado di costruire una qualche forma di "alberi di modifica". Lo strumento sa che è necessario cambiare una certa variabile in un valore diverso per ottenere il testcase corretto. Quindi dovrà elencare tutti i modi possibili per cambiarlo, senza usare ovviamente il riflesso.
Ciò che questo strumento non sostituirà è la necessità di creare test unitari su misura per testare tutti i tipi di condizioni quando un determinato metodo funziona effettivamente. È puramente utile per testare i metodi quando invalidano i vincoli.
Le mie domande :
- La creazione di uno strumento di questo tipo fattibile ? Funzionerebbe mai, o ci sono dei problemi evidenti?
- Uno strumento del genere sarebbe utile ? È addirittura utile generare automaticamente questi testicoli? Potrebbe essere esteso per fare anche cose più utili?
- Esiste per caso un progetto del genere e reinventerò la ruota?
Se non è stato dimostrato utile, ma è ancora possibile farlo, lo considererò comunque per divertimento. Se è considerato utile, allora potrebbe creare un progetto open source per questo a seconda del tempo.
Per le persone che cercano ulteriori informazioni di base sulla utilizzata Player
e Hand
classi nel mio esempio, si prega di fare riferimento a questo repository . Al momento della scrittura, PutMonsterOnFieldAction
non è stato ancora caricato sul repository, ma ciò avverrà una volta terminato il test delle unità.