Codice funzionale digitato staticamente


15

Volevo chiedere a voi persone, in quali casi ha senso testare il codice funzionale tipicamente statico, come scritto in haskell, scala, ocaml, nemerle, f # o haXe (l'ultimo è ciò che mi interessa davvero, ma volevo attingere alla conoscenza delle comunità più grandi).

Lo chiedo perché dalla mia comprensione:

  • Un aspetto dei test unitari è avere le specifiche in forma eseguibile. Tuttavia, quando si utilizza uno stile dichiarativo, che associa direttamente le specifiche formalizzate alla semantica del linguaggio, è addirittura possibile esprimere le specifiche in forma eseguibile in un modo separato, aggiungendo 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 semplice errore come l'utilizzo al xposto di y(entrambi essendo coordinate) nel codice non può essere coperto. OTOH potrebbe anche sorgere un simile errore durante la scrittura del codice di prova, quindi non sono sicuro che valga la pena.

  • I test unitari introducono ridondanza, il che significa che quando cambiano i requisiti, il codice che li implementa e i test che coprono questo codice devono essere entrambi modificati. Questo sovraccarico ovviamente è costante, quindi si potrebbe obiettare, che non importa davvero. In effetti, in linguaggi come Ruby, in realtà non è paragonato ai vantaggi, ma dato come la programmazione funzionale tipicamente statica copre molti dei test delle unità di terra, è come se fosse un overhead costante che si può semplicemente ridurre senza penalità.

Da ciò deduco che i test unitari sono alquanto obsoleti in questo stile di programmazione. Naturalmente una simile affermazione può solo portare a guerre di religione, quindi lasciatemi riassumere in una semplice domanda:

Quando usi un tale stile di programmazione, in quale misura usi i test unitari e perché (che qualità speri di ottenere per il tuo codice)? O viceversa: hai dei criteri in base ai quali puoi qualificare un'unità di codice funzionale tipicamente statico come coperta dall'analizzatore statico e quindi senza la necessità di una copertura di unit test?


4
A proposito, se non hai provato QuickCheck , dovresti assolutamente farlo.
Jon Purdy,

scalacheck.org è l'equivalente Scala
V-Lamp

Risposte:


8

Un aspetto dei test unitari è 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 esprimere le specifiche in forma eseguibile in un modo separato, aggiungendo valore?

Se hai specifiche che possono essere mappate direttamente alle dichiarazioni di funzioni, va bene. Ma in genere si tratta di due livelli completamente diversi di astrazioni. I test unitari hanno lo scopo di testare singoli pezzi di codice, scritti come test in white box dallo stesso sviluppatore che sta lavorando alla funzione. Le specifiche normalmente sembrano "quando inserisco questo valore qui e premo questo pulsante, questo e quello dovrebbe accadere". In genere, una specifica del genere porta a sviluppare e testare più di una funzione.

Tuttavia, non è possibile coprire un semplice errore come l'utilizzo di x invece di y (entrambe essendo coordinate) nel codice. Tuttavia, durante la scrittura del codice di prova potrebbe verificarsi un errore del genere, quindi non sono sicuro che valga la pena.

Il tuo malinteso è qui che i test unitari sono in realtà per trovare in prima persona bug nel tuo codice - questo non è vero, almeno, è solo parzialmente vero. Sono creati per impedirti di introdurre bug in seguito quando il tuo codice si evolve. Quindi, quando hai prima testato la tua funzione e il tuo test unit funzionava (con "x" e "y" correttamente in posizione), e poi mentre esegui il refactoring usi x invece di y, allora il test unitario ti mostrerà quello.

I test unitari introducono ridondanza, il che significa che quando cambiano i requisiti, il codice che li implementa e i test che coprono questo codice devono essere entrambi modificati. Questo sovraccarico ovviamente è costante, quindi si potrebbe obiettare, che non importa davvero. In effetti, in linguaggi come Ruby, in realtà non è paragonato ai benefici, ma dato come la programmazione funzionale tipicamente statica copre molti dei test delle unità di terra, è come se fosse un overhead costante che si può semplicemente ridurre senza penalità.

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


5

Un aspetto dei test unitari è 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 esprimere le specifiche in forma eseguibile in un modo separato, aggiungendo valore?

È molto improbabile che sarai in grado di esprimere completamente le tue specifiche come vincoli di tipo.

Quando usi un tale stile di programmazione, in quale misura usi i test unitari e perché (che qualità speri di ottenere per il tuo codice)?

In effetti, uno dei maggiori vantaggi di questo stile è che le funzioni pure sono più facili da testare: non è necessario impostare lo stato esterno o verificarlo dopo l'esecuzione.

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


1
Un po 'più di dettaglio su QuickCheck: l'idea di base è che scrivi "proprietà" (invarianti nel tuo codice) e specifichi come generare un potenziale input. Quickcheck crea quindi una tonnellata di input casuali e si assicura che il tuo invariante sia sempre valido. È più approfondito del test unitario.
Tikhon Jelvis,

1

Puoi pensare ai test unitari come un esempio di come usare il codice, insieme a una descrizione del perché è prezioso.

Ecco un esempio, in cui lo zio Bob è stato così gentile da accoppiarsi con me in "Game of Life" di John Conway . Penso che sia un esercizio eccellente per questo tipo di cose. La maggior parte dei test sono a sistema completo, testando l'intero gioco, ma quello primo verifica 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 indicato.

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


0

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

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

È possibile forzare prop_encodecon il tipo statico.

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.