Perché il test di una lingua non è una funzionalità supportata a livello di sintassi?


37

È possibile trovare un elenco infinito di blog, articoli e siti Web che promuovono i vantaggi di test unitari del codice sorgente. È quasi garantito che gli sviluppatori che hanno programmato i compilatori per Java, C ++, C # e altri linguaggi tipizzati abbiano utilizzato i test unitari per verificare il loro lavoro.

Allora perché, nonostante la sua popolarità, i test sono assenti dalla sintassi di queste lingue?

Microsoft ha introdotto LINQ in C # , quindi perché non hanno potuto aggiungere test?

Non sto cercando di prevedere quali sarebbero i cambiamenti di lingua, ma per chiarire perché sono assenti all'inizio.

Ad esempio: sappiamo che è possibile scrivere un forciclo senza la sintassi fordell'istruzione. È possibile utilizzare whileo if/ gotodichiarazioni. Qualcuno ha deciso che una fordichiarazione era più efficiente e l'ha introdotta in una lingua.

Perché i test non hanno seguito la stessa evoluzione dei linguaggi di programmazione?


24
È davvero meglio rendere più complessa una specifica del linguaggio aggiungendo la sintassi, invece di progettare un linguaggio con regole semplici che possono essere facilmente estese in modo generico?
KChaloux,

6
Cosa intendi con "a livello di sintassi"? Hai qualche dettaglio / idea su come sarebbe implementato?
SJuan76,

21
Potresti fornire un esempio di pseudo-codice di test come funzionalità supportata dalla sintassi? Sono curioso di vedere come pensi che sarebbe.
FrustratedWithFormsDesigner

12
@FrustratedWithFormsDesigner Vedi il linguaggio D per un esempio.
Doval,

4
Un altro linguaggio con funzionalità di test unità integrate è Rust.
Sebastian Redl,

Risposte:


36

Come per molte altre cose, i test unitari sono supportati al meglio a livello di biblioteca, non a livello di lingua. In particolare, C # ha a disposizione numerose librerie di Unit Testing, nonché cose native di .NET Framework Microsoft.VisualStudio.TestTools.UnitTesting.

Ogni libreria di unit test ha una filosofia e una sintassi di test leggermente diverse. A parità di condizioni, più scelte sono migliori che meno. Se i test unitari fossero integrati nella lingua, saresti bloccato nelle scelte del progettista della lingua, o useresti ... una libreria ed eviterai del tutto le funzionalità di test linguistico.

Esempi

  • Nunit - Framework di test unitario progettato in modo idiomatico che sfrutta appieno le funzionalità del linguaggio di C #.

  • Moq - Framework beffardo che sfrutta appieno le espressioni lambda e gli alberi delle espressioni, senza una metafora di registrazione / riproduzione.

Ci sono molte altre scelte. Le librerie come Microsoft Fakes possono creare simulazioni "shim ..." che non richiedono di scrivere le tue classi utilizzando interfacce o metodi virtuali.

Linq non è una funzionalità linguistica (nonostante il nome)

Linq è una funzione di libreria. Abbiamo avuto molte nuove funzionalità nel linguaggio C # stesso gratuitamente, come espressioni lambda e metodi di estensione, ma l'implementazione effettiva di Linq è in .NET Framework.

C'è dello zucchero sintattico che è stato aggiunto a C # per rendere più pulite le istruzioni linq, ma che lo zucchero non è necessario per usare linq.


1
Sono d'accordo con il fatto che LINQ non sia una funzionalità linguistica, ma per far sì che LINQ si realizzi, ci devono essere alcune caratteristiche linguistiche sottostanti, in particolare generici e tipi dinamici.
Wyatt Barnett,

@WyattBarnett: Sì, e lo stesso vale per test di unità semplici - come la riflessione e gli attributi.
Doc Brown,

8
LINQ aveva bisogno di lambda e alberi di espressione. I generici erano già in C # (e .Net in generale, sono presenti nel CLR) e le dinamiche non hanno nulla a che fare con LINQ; puoi fare LINQ senza dinamica.
Arthur van Leeuwen,

DBIx :: Class di Perl è un esempio di funzionalità simile a LINQ implementata più di 10 anni dopo che tutte le funzionalità necessarie per implementarlo sono state nella lingua.
Slebetman,

2
@ Carson63000 Penso che intendi tipi anonimi in combinazione con la varparola chiave :)
M.Mimpen

21

Ci sono molte ragioni. Eric Lippert ha affermato molte volte che il motivo feature Xnon è in C # perché non è nel loro budget. I progettisti linguistici non hanno una quantità infinita di tempo o denaro per implementare le cose e ogni nuova funzionalità ha costi di manutenzione associati. Mantenere la lingua il più piccola possibile non è solo più facile per i progettisti della lingua - è anche più facile per chiunque stia scrivendo implementazioni e strumenti alternativi (es. IDE) Inoltre, quando qualcosa viene implementato in termini di linguaggio piuttosto che parte di esso, si ottiene portabilità gratuita. Se il test unitario viene implementato come libreria, devi solo scriverlo una volta e funzionerà in qualsiasi implementazione conforme della lingua.

Vale la pena notare che D ha un supporto a livello di sintassi per i test unitari . Non so perché abbiano deciso di lanciarlo, ma vale la pena notare che D è pensato per essere un "linguaggio di programmazione di sistemi di alto livello". I progettisti volevano che fosse praticabile per il tipo di codice C ++ non sicuro e di basso livello per cui è stato tradizionalmente usato, e un errore nel codice non sicuro è incredibilmente costoso - comportamento indefinito. Quindi suppongo che fosse logico per loro dedicare uno sforzo extra a tutto ciò che ti aiuta a verificare che un codice non sicuro funzioni. Ad esempio, è possibile imporre che solo alcuni moduli attendibili possano eseguire operazioni non sicure come accessi di array non controllati o aritmetica dei puntatori.

Anche lo sviluppo rapido era una priorità per loro, tanto da renderlo un obiettivo di progettazione che il codice D compila abbastanza velocemente da renderlo utilizzabile come linguaggio di scripting. I test delle unità di cottura direttamente nella lingua in modo da poter eseguire i test semplicemente passando un flag aggiuntivo al compilatore aiutano a farlo.

Tuttavia, penso che una grande libreria di unit test faccia molto di più che trovare solo alcuni metodi ed eseguirli. Prendi ad esempio il QuickCheck di Haskell , che ti consente di testare cose come "per tutte x e y f (x, y) == f (y, x)". QuickCheck è meglio descritto come un generatore di unit test e consente di testare le cose a un livello superiore rispetto a "per questo input, mi aspetto questo output". QuickCheck e Linq non sono poi così diversi: sono entrambi linguaggi specifici del dominio. Quindi, piuttosto che fissare il supporto del test di unità in una lingua, perché non aggiungere le funzionalità necessarie per rendere pratiche le DSL? Concluderai non solo con i test unitari, ma di conseguenza con un linguaggio migliore.


3
Per rimanere nel mondo .Net, c'è FsCheck, che è una porta di QuickCheck per F #, con alcuni aiutanti per l'uso da C #.
Arthur van Leeuwen,

Per rimanere nel mondo .NET? Questa domanda non ha nulla a che fare con .NET.
Miles Rout,

@MilesRout C # è una parte di .NET. La domanda e la risposta parlano spesso di C # e la domanda parla anche di LINQ.
Zachary Dow,

La domanda non è taggata .NET o C #.
Miles Rout,

@MilesRout Potrebbe essere vero, ma non si può ragionevolmente dire che non ha nulla a che fare con esso solo perché non ha il tag. :)
Zachary Dow il

13

Perché i test, e in particolare lo sviluppo guidato dai test, sono un fenomeno profondamente contro-intuitivo.

Quasi tutti i programmatori iniziano la loro carriera credendo di essere molto più bravi a gestire la complessità di quanto non siano in realtà. Il fatto che anche il più grande programmatore non possa scrivere programmi grandi e complessi senza gravi errori a meno che non utilizzino molti test di regressione è seriamente deludente e persino vergognoso per molti professionisti. Da qui la prevalente disinclinazione contro i test regolari anche tra i professionisti che dovrebbero già conoscere meglio.

Penso che il fatto che i test religiosi stiano lentamente diventando più mainstream e attesi è in gran parte dovuto al fatto che con l'esplosione della capacità di archiviazione e della potenza di elaborazione, vengono costruiti sistemi più grandi che mai - e i sistemi estremamente grandi sono particolarmente inclini al collasso della complessità che non può essere gestito senza la rete di sicurezza dei test di regressione. Di conseguenza, anche gli sviluppatori particolarmente ostinati e illusi ora ammettono a malincuore di aver bisogno di test e avranno sempre bisogno di test (se questo suona come una confessione in una riunione di AA, questo è abbastanza intenzionale - la necessità di una rete di sicurezza è psicologicamente difficile da ammettere per molti individui).

La maggior parte delle lingue popolari oggi risale a prima di questo cambiamento di atteggiamento, quindi hanno poco supporto integrato per i test: hanno assert, ma non hanno contratti. Sono abbastanza sicuro che se la tendenza persiste, le lingue future avranno più supporto sulla lingua piuttosto che a livello di biblioteca.


3
Esagerate un po 'il vostro caso. Mentre i test unitari sono senza dubbio di fondamentale importanza, la costruzione dei programmi nel modo giusto , il modo in cui minimizza la complessità non necessaria, è altrettanto importante, se non di più. Linguaggi come Haskell hanno sistemi di tipi che migliorano l'affidabilità e la dimostrabilità dei programmi scritti in essi, e le migliori pratiche di codifica e progettazione evitano molte delle insidie ​​del codice non testato, rendendo i test unitari meno critici di quanto altrimenti sarebbe.
Robert Harvey,

1
@RobertHarve ... e test di unità è un molto buon modo per spingere gli sviluppatori indirettamente in quella direzione. Lavorare sull'API durante la creazione dei test, anziché utilizzarlo con altri codici, aiuta a metterli nella giusta mentalità per limitare o rimuovere le astrazioni che perdono o le chiamate ai metodi dispari. Non penso che KillianFoth stia sopravvalutando nulla.
Izkata,

@Robert: L'unica cosa che credo esagerare è che "i test religiosi stanno lentamente diventando più mainstream" Se lentamente viene definito in termini di tempi geografici, probabilmente non è lontano ..... :)
mattnz

+1 Nel mio attuale lavoro, sono colpito dallo spostamento delle maree negli atteggiamenti di sviluppo con le più recenti librerie e tecnologie disponibili. Per la prima volta nella mia carriera, questi ragazzi mi stanno insegnando a utilizzare un processo di sviluppo più sofisticato, invece di sentirmi come se fossi in cima a una collina che grida al vento. Sono d'accordo che è solo una questione di tempo prima che questi atteggiamenti trovino espressione nella sintassi della lingua madre.
Rob

1
Scusa, un altro pensiero: il commento di Robert Harvey. In questo momento i sistemi di tipi sono un buon controllo, ma in realtà sono incredibilmente a corto di definizione dei vincoli sui tipi: affrontano l'interfaccia, ma non vincolano il comportamento corretto. (ovvero 1 + 1 == 3 verrà compilato, ma potrebbe essere vero o no, a seconda dell'implementazione di +) Mi chiedo se la prossima tendenza vedrà sistemi di tipo più espressivi che combinano ciò che consideriamo test unitario ora.
Rob,

6

Molte lingue supportano i test. Le affermazioni di C sono test che il programma può fallire. È qui che la maggior parte delle lingue si ferma, ma Eiffel e più recentemente Ada 2012 hanno preinvariants (cose che devono passare gli argomenti a una funzione) e postinvariants (cose che devono passare l'output di una funzione), con Ada che offre la possibilità di fare riferimento agli argomenti iniziali nella postinvariant. Ada 2012 offre anche invarianti di tipo, quindi ogni volta che viene chiamato un metodo su una classe Ada, il tipo invariante viene controllato prima del ritorno.

Questo non è il tipo di test completo che può offrirti un buon framework di test, ma è un tipo importante di test che le lingue possono supportare al meglio.


2
Dopo aver lavorato un po 'a Eiffel ed essere un grande utilizzatore di affermazioni, la mia esperienza è stata che qualsiasi cosa tu possa fare che sia di natura contrattuale aiuta a scovare molti bug.
Blrfl

2

Alcuni sostenitori di linguaggi funzionali fortemente tipizzati sostengono che queste caratteristiche del linguaggio riducono o eliminano la necessità di test unitari.

Due, imho, un buon esempio di questo per questo è tratto da F # for Fun and Profit qui e qui

Personalmente, credo ancora nel valore dei test unitari, ma ci sono alcuni punti validi. Ad esempio, se uno stato illegale non è rappresentabile nel codice, non è solo inutile scrivere un test unitario per questo caso, è impossibile.


2

Direi che hai perso l'aggiunta di alcune delle funzionalità necessarie perché non sono state evidenziate come per i test unitari .

Ad esempio, il test unitario in C # è principalmente guidato dall'uso degli attributi. La funzione Attributi personalizzati fornisce un ricco meccanismo di estensione che consente a framework come NUnit di iterare e competere, con test basati su teoria e parametri .

Questo mi porta al mio secondo punto importante: non sappiamo abbastanza su ciò che rende buona testabilità per renderlo in una lingua. Il ritmo dell'innovazione nei test è molto più veloce rispetto ad altri costrutti linguistici, quindi abbiamo bisogno di meccanismi flessibili nelle nostre lingue per lasciare la libertà di innovare.

Sono un utente, ma non sono fanatico di TDD: in alcuni casi è molto utile soprattutto per migliorare il tuo modo di pensare. Non è necessariamente utile per i sistemi legacy più grandi: i test di livello superiore con dati e automazione validi probabilmente produrranno maggiori benefici insieme a una solida cultura dell'ispezione del codice .

Altre letture rilevanti:

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.