Come testare le astrazioni di alto livello che trasformano dati complessi

1

Mi sto allenando per testare le unità (in particolare con il Test-driven design o Red-Green-Refactor) mentre scrivevo un parser di discesa ricorsivo per un semplice linguaggio specifico del dominio. Nel tentativo di testare unitamente un metodo che accetta i token e gli output di un albero della sintassi, ho scoperto che il mocking dell'input è eccessivamente complesso. Avevo pensato che il mio progetto iniziale per l'interfaccia pubblica fosse testabile; è questo problema nella progettazione dell'interfaccia pubblica, o inerente ai test di trasformazione dei dati di alto livello?

Il mio test è stato semplificato per mostrare l'interfaccia di base ...

[TestFixture]
public class ParserTests
{
    [TestFixture]
    public class Parser_ParseTests
    {
        ...
        [Test]
        public void Parser_Parse_IfTokenSourceIsNullThrowException()
        {
            Parser parser = new Parser ();
            TokenSource tokens = null;

            Action action = () => parser.Parse(tokens);

            Assert.Throws<System.NullReferenceException>(() => action.Invoke ());
        }
        ...
    }

}

Ovviamente, i test con cui ho un problema sono quelli che necessitano di TokenSource che riempi con un gran numero di token.

Il mio primo pensiero è che forse il mio test copre troppo. Forse dovrei solo prendere in giro e verificare che i token siano richiesti dalla sorgente token e lasciare il testing delle trasformazioni token alle classi dipendenti. Ciò lascerebbe comunque una lacuna nella descrizione dei miei test dello scopo di questa interfaccia e la rende dipendente da altri test per convalidarlo.

Potrebbe anche darsi che il mio codice non sia verificabile, ma sto faticando a vedere un altro modo per strutturarlo. So che voglio un'interfaccia che trasformi un input in un Syntax Tree e che dividerò il processo in Tokenisation (di una stringa di origine) e analizzerò i Token. Forse non dovrebbe esserci un livello di astrazione che conosca l'analisi dei token ma non la tokenizzazione?

Per cercare di ottenere idee ho esaminato i test unitari di Fitnesse, che conoscevo avevano una buona copertura e devono trasformare set di dati complessi (codice Wiki in HTML). Non sono sicuro che segua le buone pratiche che dovrei emulare però. Ad esempio, BaseWikiPage sembra utilizzare un numero di oggetti dall'effettivo progetto ( WikiPageUtil , PathParser ecc.) per produrre dati di test.

Questa è una buona pratica? Il mio problema principale è che mi richiede di scrivere codice relativo alle astrazioni di livello inferiore come i token che non ero ancora pronto per progettare. Inoltre, il codice Test richiede che io scriva prima il codice funzionale, il che significa scrivere prima altri test Unit, implicitamente facendo dipendere questo test da quelli. Fondamentalmente, mi impedisce di scrivere questo test.

Mi piacerebbe conoscere sia la soluzione del mondo reale per questo tipo di test sia la soluzione ideale. O la mia intera comprensione dello scopo di questi test rende la domanda irrilevante?

    
posta James 08.06.2018 - 13:18
fonte

1 risposta

1

Ho il sospetto che il luogo in cui ti stai murando sia il processo che stai utilizzando per tracciare i tuoi confini. Vedi questo discorso Gary Bernhardt .

euristica: fai attenzione al modo in cui data si sposta oltre i limiti, invece di tracciare capacità .

Prendendo un approccio esterno, si inizia con un problema del tipo:

Input -> AST

E hai scomposto il problema in

Input -> data-structure-of[Token]
data-structure-of[Token] -> AST

Quindi il problema di testare una struttura dati con un numero elevato di token è "solo" il problema di costruire molti token nel test.

Se puoi stare a crearli uno alla volta, ottimo.

Se la struttura dei dati di [Token] è serializzabile, allora un'opzione potrebbe essere quella di creare un campione di dati serializzato che viene caricato in memoria dal test. Perdi qualcosa sulla chiarezza del test (perché i dati sono separati dal test), ma sposta in parte la complessità.

Se Input è serializzabile, puoi creare un input e caricare la logica di test. Stessi problemi di separazione come in precedenza, inoltre probabilmente finirai per utilizzare la funzione di analisi durante il test: l'intera cosa inizia ad assomigliare più a un test "end-to-end" che a un test "unitario".

Questo approccio è ancora "test first" e "test driven". Vedi, ad esempio, questa dimostrazione della Mars Rover kata.

Un'altra possibilità è costruire un DSL che il test usa per costruire la struttura dei dati di [Token]. Questa è una tecnica comune per la gestione dei dati, spesso utilizzata per migliorare la leggibilità del test. Quindi se "un gran numero di token" presenta cose come esecuzioni di modelli deterministici, allora usare il DSL può darti quello che ti serve senza I / O o separare i dati dai test.

Se che non funziona ... potrebbe essere necessario prendere in considerazione un'API che permetta di suddividere la struttura dei dati di [Token] in più frame / batch. Cioè, la difficoltà nel test potrebbe provare a dirti che è necessario esporre una "piega"; vale a dire che potresti aver bisogno di un'API per creare il parser in uno stato intermedio.

Given : the parser has consumed these 100 tokens
When  : the parser consumes these 5 additional tokens
Then  : the right thing happens

Se puoi mettere il parser direttamente nello stato corretto (cioè seminare con un AST parzialmente costruito e così via), allora puoi eseguire una scorciatoia al test che vuoi eseguire.

    
risposta data 08.06.2018 - 14:57
fonte

Leggi altre domande sui tag