Disporre le classi di utilità e iniettarle per scopi di test unitari

4

Ho scoperto che è così difficile testare le classi che dipendono da altre classi di utilità come java.nio.file.Files . È anche impossibile deriderli usando il classico stack di test delle unità (junit, mockito, ..) a meno che non si utilizzino altri framework di simulazione pesante come powermock che considero una pratica scorretta in caso di progetti di costruzione da zero e anche un incubo se cerchi di integrarlo con i plugin Maven (surefire, jacoco, strumentazione offline, ...)

Quindi quello che ho deciso di fare per facilitare il mio test di unità, è di avvolgere queste classi di utilità all'interno di una normale classe instantiable e di iniettarle nella mia classe principale come segue:

public class IOManager {
    private final FileSystemUtilsWrapper fileSystemUtilsWrapper;
    private final HttpUtilsWrapper httpUtilsWrapper;

    public IOManager() {
        this.fileSystemUtilsWrapper = new FileSystemUtilsWrapper();
        this.httpUtilsWrapper = new HttpUtilsWrapper();
    }

    public IOManager(
            FileSystemUtilsWrapper fileSystemUtilsWrapper, 
            HttpUtilsWrapper httpUtilsWrapper
    ) {
        if (fileSystemUtilsWrapper == null 
            || httpUtilsWrapper == null) {
            throw new NullPointerException("...");
        }

        this.fileSystemUtilsWrapper = fileSystemUtilsWrapper;
        this.httpUtilsWrapper = httpUtilsWrapper;
    }

    public boolean doIOOperations(String filePath, String url) throws IOException {
        if (fileSystemUtilsWrapper.isMyFileReachable(filePath) 
            && httpUtilsWrapper.isMyURLReachable(url)) {
            // do something and returns IO state
        }
        return false;
    }
}

/**
 * FileSystem utils wrapper
 */
class FileSystemUtilsWrapper {
    public boolean isMyFileReachable(String filePath) {
        Path path = Paths.get(filePath);
        return Files.exists(path) && Files.isWritable(path);
    }
}

/**
 * http utils wrapper
 */
class HttpUtilsWrapper {
    public boolean isMyURLReachable(String url) throws IOException {
        HttpURLConnection urlConnection = (HttpURLConnection) new URL(url)
            .openConnection()
        ;
        // i keep it simple
        return urlConnection.getResponseCode() == 200;
    }
}

Lo tengo semplice per ottenere una rapida comprensione.

Nota che ho anche preso in considerazione l'utilizzo diretto delle classi di utilità all'interno del mio SUT e il mocking delle risorse I / O (creazione ed eliminazione di un file fittizio e utilizzo di wiremock per deridere l'endpoint http). Anche se può sembrare la via più facile da seguire, IMO non dovrebbe farlo, perché non è più il test unitario ma i test di integrazione.

Gradirei sentire la tua opinione sulla mia analisi. grazie in anticipo e Cordiali saluti,

    
posta isqo 10.11.2018 - 15:54
fonte

1 risposta

1

Quello che mi piace qui sono i valori di default sovrascrivibili. Quello è buono. Evita codici rigidi. Lo consiglio a prescindere dai test. E lo hai fatto prima .

Ciò di cui non sono sicuro è che IOManager usi i wrapper non solo come valori predefiniti ma come tipi. Tutto ciò che significa sostituire i wrapper deve ereditare da loro. I wrapper sono concreti, non astratti. Ciò significa che questo è l'inizio di un problema yo-yo .

Un buon principio che può guidarti lontano da questo problema è il principio di inversione delle dipendenze . Insegna che:

  • I moduli di alto livello non dovrebbero dipendere da moduli di basso livello. Entrambi dovrebbero dipendere dalle astrazioni.
  • Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni.

Il che non vuol dire che devi usare le interfacce qui. Ma sarebbe bello se almeno ci fossero classi astratte chiamate FileSystemUtils e HttpUtils che IOManager può usare come tipi. In questo modo, quando prendo il tuo codice e passi da new URL(url) a new SecureURL(url) creando HttpsUtilsWrapper , non imporrò un codice di manutenzione scadente per esaminare il codice non utilizzato e sottoposto a override.

    
risposta data 12.11.2018 - 02:49
fonte

Leggi altre domande sui tag