Come verrebbero rilevati errori di digitazione durante la creazione di simulazioni in un linguaggio dinamico?


10

Il problema si verifica durante l'esecuzione del TDD. Dopo un paio di passaggi di test, i tipi restituiti di alcune classi / moduli cambiano. In un linguaggio di programmazione tipicamente statico, se nei test di un'altra classe è stato utilizzato un oggetto deriso precedente e non è stato modificato per riflettere la modifica del tipo, si verificheranno errori di compilazione.

Per i linguaggi dinamici, tuttavia, la modifica dei tipi restituiti potrebbe non essere rilevata e i test dell'altra classe continueranno comunque. Sicuramente potrebbero esserci dei test di integrazione che dovrebbero fallire in seguito, ma i test unitari passerebbero erroneamente. C'è un modo per evitarlo?

Aggiornamento con un esempio banale (su un linguaggio inventato) ...

Versione 1:

Calc = {
    doMultiply(x, y) {return x * y}
}
//.... more code ....

// On some faraway remote code on a different file
Rect = {
    computeArea(l, w) {return Calc.doMultipy(x*y)}
}

// test for Rect
testComputeArea() { 
    Calc = new Mock()
    Calc.expect(doMultiply, 2, 30) // where 2 is the arity
    assertEqual(30, computeArea)
}

Ora, nella versione 2:

// I change the return types. I also update the tests for Calc
Calc = {
    doMultiply(x, y) {return {result: (x * y), success:true}}
}

... Rect genererà quindi un'eccezione in fase di esecuzione, ma il test avrà comunque esito positivo.


1
Ciò che le risposte finora sembrano mancare è che la domanda non riguarda i test che riguardano il cambiamento class X, ma i cui test class Ydipendono Xe quindi vengono testati rispetto a un contratto diverso rispetto a ciò che si scontra nella produzione.
Bart van Ingen Schenau,

Ho appena fatto questa domanda su SO , me stesso, per quanto riguarda l'iniezione di dipendenza. Vedi motivo 1: una classe dipendente può essere cambiata in fase di esecuzione (pensa al test) . Entrambi abbiamo la stessa mentalità ma mancano di grandi spiegazioni.
Scott Coates,

Rileggo la tua domanda ma mi sto confondendo un po 'sull'interpretazione. Puoi fornire un esempio?
Scott Coates,

Risposte:


2

In una certa misura, questo è solo una parte del costo di fare affari con linguaggi dinamici. Ottieni molta flessibilità, altrimenti nota come "abbastanza corda per impiccarti". Stai attento.

Per me, il problema suggerisce di usare tecniche di refactoring diverse da quelle che si farebbero in un linguaggio tipicamente statico. In un linguaggio statico, si sostituiscono in parte i tipi restituiti in modo da poter "appoggiarsi al compilatore" per trovare quali luoghi potrebbero rompersi. È sicuro da fare e probabilmente più sicuro che modificare il tipo di ritorno in atto.

In un linguaggio dinamico, non puoi farlo, quindi è molto più sicuro modificare il tipo di ritorno in atto, piuttosto che sostituirlo. Probabilmente, lo modifichi aggiungendo la tua nuova classe come membro, per le classi che ne hanno bisogno.


2

Se il tuo codice cambia e i tuoi test continuano a passare, allora c'è qualcosa di sbagliato nei tuoi test (cioè stai perdendo un'asserzione), o il codice non è effettivamente cambiato.

Cosa intendo con questo? Bene, i tuoi test descrivono i contratti tra parti del tuo codice. Se il contratto è "il valore restituito deve essere iterabile", la modifica del valore restituito da un array a un elenco non è in realtà una modifica del contratto e pertanto non determinerà necessariamente un errore del test.

Per evitare asserzioni mancanti, puoi usare strumenti come l'analisi della copertura del codice (non ti dirà quali parti del tuo codice sono testate, ma ti dirà quali parti sicuramente non sono testate), complessità ciclomatica e complessità NPath (che ti danno un limite inferiore al numero di asserzioni richieste per ottenere la copertura completa dei codici C1 e C2) e tester di mutazione (che iniettano mutazioni nel tuo codice come trasformarsi truein false, numeri negativi in ​​positivi, oggetti in nullecc. e controllare se quello fa fallire i test).


+1 O qualcosa di sbagliato nei test o nel codice non è effettivamente cambiato. Mi ci è voluto un po 'per abituarmi dopo anni di C ++ / Java. Il contratto tra parti in linguaggi di codice dinamici non dovrebbe essere COSA viene restituito, ma cosa contiene quella cosa restituita e cosa può fare.
MrFox,

@suslik: In realtà, non si tratta di linguaggi statici o dinamici, ma piuttosto di astrazione dei dati utilizzando tipi di dati astratti rispetto all'astrazione dei dati orientata agli oggetti. La capacità di un oggetto di simulare un altro oggetto purché si comporti in modo indistinguibile (anche se sono di tipi completamente diversi e istanze di classi completamente diverse) è fondamentale per l'intera idea di OO. O in altri termini: se due oggetti si comportano allo stesso modo, sono dello stesso tipo, indipendentemente da ciò che dicono le loro classi. In altre parole: le classi non sono tipi. Sfortunatamente, Java e C # sbagliano.
Jörg W Mittag,

Supponendo che l'unità derisa sia coperta al 100% e non manchi alcuna affermazione: che ne dici di un cambiamento più sottile come "dovrebbe restituire null per foo" a "dovrebbe restituire una stringa vuota per foo". Se qualcuno cambia quell'unità e il suo test alcune unità consumanti potrebbero rompersi e per via della simulazione questo è trasparente. (E no: al momento della scrittura o dell'utilizzo del modulo deriso, per "contratto", non è necessario gestire le stringhe vuote come valore di ritorno perché dovrebbe restituire stringhe non vuote o solo null;)). (Solo un corretto test di integrazione di entrambi i moduli in interazione potrebbe catturarlo.)
try-catch-finally
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.