Piccolo codice, grande test

7

Ho questo codice in un controller di un'implementazione MVC:

public void execute() {
        try {
            String path = userSelectsFile(); 
            if ( path == null ) return; //Just returns if the user press "Cancel"

            //Load the data
            Collection<Info> infos = Infos.fromJSON(getInputReader(path));
            //Show the data in the interface
            new ShowTransformationsInTree(win(), infos, win().getTree()).execute();
        } catch (Exception e) {
            new ComplainAction(win(), "Unable to...blah, blah", e).execute();
        }
    }

Per verificare ciò, devo simulare l'interfaccia utente per mostrare la finestra di dialogo dei file e rendere getInputReader metodo un metodo factory astratto.

Codice test:

    /**
     * Mocks the LoadTransformationsAction so the factory method getReader returns a ByteInputStream
     */
    public class MockLoadTransformationsAction extends LoadTransformationsAction {
        //CONSTRUCTOR REMOVED FOR CLARITY.....

        @Override
        protected InputStreamReader getInputReader(String path) {
            assertEquals(FAKE_ABSOLUTE_PATH, path);
            return new InputStreamReader(
                    new ByteArrayInputStream(TestHelpers.createDataBytes());
        }
    }

    /**
     * Test the proper loading of the data.
     */
    @Test
    public void testLoadData(@Mocked final FileChooser anyChooser) {
        new Expectations() {{
            //Mocks the IntelliJ idea API
            FileChooser.chooseFile(null, null, null); result = new MyFakeVirtualFile(); //Returns a fixed path
        }};

        //Loads the data from the JSON file
        MainToolWin m = new FakeMainToolWin();
        LoadTransformationsAction action = new MockLoadTransformationsAction(
                m, new FakeProject(), new MockInputProgram());
        action.execute();

        //Test that the transformations are in the UI Tree
        DefaultTreeModel model = (DefaultTreeModel)m.getTree().getModel();
        assertEquals(2, model.getChildCount(model.getRoot()));
        assertEquals(3, model.getChildCount(model.getChild(model.getRoot(), 0)));
    }

Domande:

Sto facendo troppo per testare troppo poco?

Come puoi vedere ci sono oggetti finti e finti fino in fondo. Può fare così tanta derisione che il mio test non è affidabile?

C'è un design migliore?

    
posta El Marce 27.01.2015 - 12:29
fonte

2 risposte

4

Sì, troppi o troppo complicati oggetti finti sono una brutta cosa. Sì, c'è un design migliore, e ho giocato con l'idea nella mia mente da anni. Sfortunatamente, non riesco a spiegarlo completamente perché il documento di cui sto scrivendo non è ancora completo. Ma, in termini grossolani, ecco cosa sta succedendo:

Stai mescolando la logica di business con la logica della GUI e le GUI sono generalmente molto difficili da testare. Stai già facendo qualche sforzo per separare i due, come dimostra il tuo uso di una struttura dati del modello ad albero (presumibilmente GUI-agnostico), ma non stai andando fino in fondo con questa idea, dato che stai rendendo disponibile questo albero solo per la tua logica aziendale tramite una "finestra principale degli strumenti" che è un concetto di interfaccia grafica.

La nozione di presentare all'utente la possibilità di selezionare un file, che possono annullare, è parte della tua logica aziendale. Il fatto che si usi una GUI per ottenere questo risultato è irrilevante: si potrebbe ottenere la stessa cosa con un'interfaccia della riga di comando, chiedendo all'utente di digitare il nome file e di indicare un nome file vuoto che significhi " Annulla'.

Quindi, se hai completamente isolato tutta la tua logica aziendale dal codice relativo alla GUI, allora la tua logica di business sarebbe completamente testabile senza oggetti finti. (Non solo alcuni semplici oggetti mock, ma in realtà zero oggetti fittizi.)

In generale, la presenza di un oggetto fittizio nei test significa che il design non è perfetto. Molto spesso un design imperfetto che richiede di testare oggetti finti è un compromesso pragmatico, ma sembra che tu stia chiedendo cosa è meglio, quindi, questo è ciò che è meglio.

EDIT:

Dopo aver discusso un po ', si scopre che la tua preoccupazione principale è l'accesso ai dati nel file. Ecco cosa devo dire al riguardo:

(E sì, ora sembra che questa domanda sia più adatta per codereview.stackexchange.com.)

Attualmente, la tua logica di business ha bisogno di alcune "informazioni" su cui lavorare, ma contiene una specifica conoscenza hard-coded del fatto che queste "informazioni" provengono da JSON; inoltre, contiene una conoscenza specifica hard-coded del fatto che questo JSON proviene da un file. (In virtù della variabile "percorso"). Penso che sia un po 'problematico. Se la tua logica aziendale ha bisogno di alcune "informazioni" su cui lavorare, non dovrebbe preoccuparsi di come possono essere ottenute. Dovrebbe solo essere dato quelle informazioni dall'esterno. Quindi, invece di dover prendere in giro la creazione di uno stream, il tuo codice di test costruirà solo istanze di test della classe "Infos" per testare la tua logica di business con.

Quindi, è necessario un set separato di test per assicurarsi che il metodo Infos.fromJSON () funzioni correttamente e questi test appartengano alla classe "Infos", non alla classe controller. (Forse li hai già.)

Essenzialmente, ciò che faresti con un simile approccio è che tu stia isolando la tua logica aziendale dal file system e dalle considerazioni JSON (text-file-format-choice), che è cruciale quanto isolarlo dalla GUI.

Se non vuoi fare tutto questo, devi creare esattamente quell'oggetto finto che hai creato; questo non dovrebbe essere una sorpresa, e in realtà non è un gran lavoro rispetto a quello che devi realizzare.

    
risposta data 27.01.2015 - 14:44
fonte
1
  1. Prova a scrivere prima i test e poi a scrivere l'implementazione. In questo modo vedrai che i tuoi test saranno brevi e semplici e anche l'implementazione.
  2. Utilizza quante più interfacce semplici che puoi.
  3. Nel tuo metodo 'esegui' non vedo alcuna storia. Se facessi questo tipo di cose avrei implementazioni del genere:
    • Un oggetto che restituisce il percorso selezionato
    • Un oggetto che convalida il percorso
    • Un oggetto che analizza il file specificato nel percorso

String path = pathProvider.provideSelectedPath () pathValidator.validate (percorso) UseData data = infoReader.readFromPath (percorso) dati di ritorno

Se l'utente ha premuto annulla - è la logica della GUI Mostra dati utente - è la logica della GUI

    
risposta data 31.01.2015 - 16:48
fonte

Leggi altre domande sui tag