Unit test di codice funzionale tipizzato staticamente

15

Volevo chiederti delle persone, in quali casi ha senso testare un codice funzionale tipizzato in modo unitario, come scritto in haskell, scala, ocaml, nemerle, f # o haXe (l'ultimo è quello a cui sono veramente interessato, ma Volevo sfruttare la conoscenza delle comunità più grandi).

Chiedo questo perché dalla mia comprensione:

  • Un aspetto dei test unitari è quello di avere le specifiche in forma eseguibile. Tuttavia quando si utilizza uno stile dichiarativo, che mappa direttamente le specifiche formalizzate alla semantica del linguaggio, è addirittura possibile per esprimere le specifiche in forma eseguibile in un modo separato, che aggiunge valore?

  • L'aspetto più ovvio dei test unitari è quello di rintracciare gli errori che non possono essere rivelati attraverso l'analisi statica. Dato che il tipo di codice funzionale sicuro è un buon strumento per codificare estremamente vicino a ciò che il tuo analizzatore statico comprende, sembrerebbe che tu possa spostare molta sicurezza verso l'analisi statica. Tuttavia un errore semplice come l'utilizzo di x invece di y (essendo entrambe le coordinate) nel tuo codice non può essere coperto. OTOH un tale errore potrebbe sorgere anche durante la scrittura del codice di test, quindi non sono sicuro che valga la pena.

  • I test unitari introducono la ridondanza, il che significa che quando i requisiti cambiano, il codice che li implementa ei test che coprono questo codice devono essere entrambi modificati. Questo overhead ovviamente è costante, quindi si potrebbe obiettare che non ha molta importanza. Infatti, in linguaggi come Ruby non è paragonato ai benefici, ma dato che la programmazione funzionale tipizzata in modo statico copre molti dei test per le unità di terra, è come se si trattasse di un overhead costante che si può semplicemente ridurre senza penalità.

Da questo deduco che i test unitari sono in qualche modo obsoleti in questo stile di programmazione. Ovviamente una tale affermazione non può che portare a guerre di religione, quindi lasciatemi riassumere una semplice domanda:

Quando usi un tale stile di programmazione, a quali estensioni usi i test unitari e perché (qual è la qualità che speri di ottenere per il tuo codice)? O viceversa: hai dei criteri in base ai quali puoi qualificare un'unità di codice funzionale tipizzato staticamente come coperto dall'analizzatore statico e quindi senza necessità di copertura del test unitario?

    
posta back2dos 24.11.2011 - 11:11
fonte

4 risposte

8

One aspect of unit tests is to have the specs in runnable form. However when employing a declarative style, that directly maps the formalized specs to language semantics, is it even actually possible to express the specs in runnable form in a separate way, that adds value?

Se hai specifiche che possono essere mappate direttamente alle dichiarazioni di funzioni - bene. Ma tipicamente quelli sono due livelli completamente diversi di astrazioni. I test unitari hanno lo scopo di testare singoli pezzi di codice, scritti come test white box dallo stesso sviluppatore che sta lavorando sulla funzione. Normalmente le specifiche sembrano "quando inserisco questo valore qui e preme questo pulsante, questo e quello dovrebbero accadere". In genere tale specifica porta a molto più di una funzione da sviluppare e testare.

However a simple mistake like using x instead of y (both being coordinates) in your code cannot be covered. However such a mistake could also arise while writing the test code, so I am not sure whether it is worth the effort.

Il tuo equivoco è qui che i test unitari sono in realtà per trovare i bug nel tuo codice di prima mano - non è vero, almeno, è solo parzialmente vero. Sono fatti per impedirti di introdurre bug successivamente quando il tuo codice si evolve. Quindi, quando hai testato per la prima volta la tua funzione e il tuo test di unità funzionante (con "x" e "y" correttamente in posizione), e poi mentre refactoring usi x invece di y, il test dell'unità lo mostrerà a te.

Unit tests do introduce redundancy, which means that when requirements change, the code implementing them and the tests covering this code must both be changed. This overhead of course is about constant, so one could argue, that it doesn't really matter. In fact, in languages like Ruby it really doesn't compared to the benefits, but given how statically typed functional programming covers a lot of the ground unit tests are intended for, it feels like it's a constant overhead one can simply reduce without penalty.

In ingegneria, la maggior parte dei sistemi di sicurezza si basa sulla ridondanza. Ad esempio, due pause in auto, un paracadute ridondante per un sommozzatore ecc. La stessa idea si applica ai test unitari. Ovviamente, avere più codice da modificare quando i requisiti cambiano può essere uno svantaggio. Quindi, specialmente nei test unitari, è importante tenerli asciutti (seguire il principio "Non ripetere te stesso"). In un linguaggio tipizzato staticamente, potresti dover scrivere meno test unitari rispetto a un linguaggio tipizzato debolmente. Soprattutto i test "formali" potrebbero non essere necessari, il che è positivo, poiché ti dà più tempo per lavorare sugli importanti test unitari che testano le cose sostanziali. E non pensare solo perché hai tipi statici, non hai bisogno di test unitari, c'è ancora molto spazio per introdurre bug durante il refactoring.

    
risposta data 24.11.2011 - 13:31
fonte
5

One aspect of unit tests is to have the specs in runnable form. However when employing a declarative style, that directly maps the formalized specs to language semantics, is it even actually possible to express the specs in runnable form in a separate way, that adds value?

È molto improbabile che tu possa completamente esprimere le tue specifiche come vincoli di tipo.

When you use such a programming style, to which extents do you use unit tests and why (what quality is it you hope to gain for your code)?

In effetti, uno dei principali vantaggi di questo stile è che le semplici funzioni sono più facili da testare in unità: non è necessario impostare lo stato esterno o controllarlo dopo l'esecuzione.

Spesso la specifica (o parte di essa) di una funzione può essere espressa come proprietà che rapporta il valore restituito agli argomenti. In questo caso, utilizza QuickCheck (per Haskell) o ScalaCheck (per Scala) consente di annotare queste proprietà come espressioni della lingua e controllare che siano valide per input casuali.

    
risposta data 24.11.2011 - 11:55
fonte
1

Puoi pensare ai test delle unità come esempio di come utilizzare il codice, insieme a una descrizione del perché è prezioso.

Ecco un esempio, in cui lo zio Bob era Abbastanza gentile da accoppiarmi con "Game of Life" di John Conway . Penso che sia un esercizio eccellente per questo genere di cose. La maggior parte dei test sono a sistema completo, testando l'intero gioco, ma il primo prova solo una funzione, quella che calcola i vicini attorno a una cella. Puoi vedere che tutti i test sono scritti in forma dichiarativa, con il comportamento che stiamo cercando chiaramente enunciato.

È anche possibile simulare funzioni usate nelle funzioni; o passandoli nella funzione (l'equivalente dell'iniezione di dipendenza) o con framework come Midje di Brian Marick .

    
risposta data 11.12.2011 - 20:14
fonte
0

Sì, i test unitari hanno già senso con il codice funzionale tipizzato staticamente. Un semplice esempio:

prop_encode a = (decode . encode $ a) == a

Puoi forzare prop_encode con tipo statico.

    
risposta data 24.11.2011 - 11:55
fonte

Leggi altre domande sui tag