È necessario mantenere i test per funzioni semplici (autosufficienti)?


36

Considera questo:

public function polynominal($a, $b, $c, $d)
{
    return  $a * pow($x, 3) + $b * pow($x, 2) + $c * $x + $d;
}

Supponi di scrivere vari test per la funzione sopra e dimostrare a te stesso e agli altri che "funziona".

Perché non rimuovere quei test e vivere felici e contenti? Il mio punto è che alcune funzioni non devono essere testate continuamente dopo che è stato dimostrato che funzionano. Sto cercando contrappunti che affermino, sì, queste funzioni devono ancora essere testate, perché: ... O che sì, queste non hanno bisogno di essere testate ...


32
Non è vero per ogni funzione che se non cambi il codice, se ha funzionato ieri, funzionerà anche domani? Il punto è che il software è cambiato.
5gon12eder

3
Perché nessuno scriverà mai override_function('pow', '$a,$b', 'return $a * $b;');nella loro mente giusta ... o proverà a riscriverlo per gestire numeri complessi.

8
Quindi ... ehm ... quel "dimostrato di essere un codice privo di bug" ... ha un bug .

Per piccole funzioni come queste potresti prendere in considerazione la verifica basata su proprietà. I test basati su proprietà generano automaticamente testcase e test per invarianti predeterminati. Forniscono documentazione attraverso gli invarianti, che li rendono utili da tenere in giro. Per funzioni semplici come questa, sono perfette.
Martijn,

6
Anche quella funzione è un ottimo candidato per un cambiamento nella forma di Horner (($a*$x + $b)*$x + $c)*$x + $dche è facile sbagliare, ma spesso è considerevolmente più veloce. Solo perché pensi che non cambierà non significa che non cambierà.
Michael Anderson,

Risposte:


78

Test di regressione

Si tratta di test di regressione .

Immagina che il prossimo sviluppatore guarderà il tuo metodo e noterà che stai usando numeri magici. Gli fu detto che i numeri magici sono malvagi, quindi crea due costanti, una per il numero due, l'altra per il numero tre: non c'è nulla di sbagliato nel fare questo cambiamento; non è come se stesse modificando la tua implementazione già corretta.

Distratto, inverte due costanti.

Commette il codice e tutto sembra funzionare bene, perché non ci sono test di regressione in esecuzione dopo ogni commit.

Un giorno (potrebbero essere settimane dopo), qualcosa si rompe altrove. E altrove, intendo nella posizione completamente opposta della base di codice, che sembra non avere nulla a che fare con la polynominalfunzione. Ore di doloroso debug portano al colpevole. Durante questo periodo, l'applicazione continua a fallire nella produzione, causando molti problemi ai clienti.

Mantenere i test originali che hai scritto potrebbe prevenire tale dolore. Lo sviluppatore distratto impegnava il codice e quasi immediatamente vedeva che aveva rotto qualcosa; tale codice non raggiungerà nemmeno la produzione. I test unitari saranno inoltre molto precisi sulla posizione dell'errore . Risolvere non sarebbe difficile.

Un effetto collaterale ...

In realtà, la maggior parte dei refactoring è fortemente basata sui test di regressione. Apporta una piccola modifica. Test. Se passa, va tutto bene.

L'effetto collaterale è che se non si hanno test, praticamente qualsiasi refactoring diventa un enorme rischio di infrangere il codice. Dato che in molti casi, è già difficile spiegare alla direzione che il refactoring dovrebbe essere fatto, sarebbe ancora più difficile farlo dopo che i tuoi precedenti tentativi di refactoring introducevano più bug.

Avendo una serie completa di test, stai incoraggiando il refactoring e, quindi, un codice più pulito. Senza rischi, diventa molto allettante rifattorizzare di più, su base regolare.

Cambiamenti nei requisiti

Un altro aspetto essenziale è che i requisiti cambiano. È possibile che ti venga chiesto di gestire numeri complessi e improvvisamente, devi cercare nel registro di controllo della versione per trovare i test precedenti, ripristinarli e iniziare ad aggiungere nuovi test.

Perché tutta questa seccatura? Perché rimuovere i test per aggiungerli in un secondo momento? Avresti potuto tenerli in primo luogo.


Nota pedante: niente è privo di rischi. ;)
jpmc26,

2
La regressione è il motivo numero uno per i test per me - anche se il tuo codice è chiaro sulle responsabilità e utilizza solo tutto ciò che gli viene passato (ad es. Senza globuli), è fin troppo facile rompere qualcosa per caso. I test non sono perfetti, ma sono comunque fantastici (e quasi sempre impediscono la ricomparsa dello stesso bug, cosa che la maggior parte dei clienti non è abbastanza contenta). Se i test funzionano, non ci sono costi di manutenzione. Se smettono di funzionare, probabilmente hai trovato un bug. Sto lavorando a un'applicazione legacy con migliaia di globali - senza test, non oserei fare molte delle modifiche necessarie.
Luaan,

Un'altra nota pedante: alcuni numeri "magici" sono in realtà ok e sostituirli con costanti è una cattiva idea.
Deduplicatore,

3
@Deduplicator lo sappiamo, ma molti programmatori junior particolarmente preparati all'università sono estremamente zelanti nel seguire ciecamente i mantra. E "i numeri magici sono cattivi" è uno di quelli. Un altro è "tutto ciò che è stato usato più di una volta deve essere trasformato in un metodo", portando in casi estremi a una pletora di metodi contenenti solo una singola istruzione (e poi si chiedono perché le prestazioni sono crollate all'improvviso, non hanno mai appreso sui callstacks).
jwenting

@jwenting: Ho appena aggiunto quella nota perché MainMa ha scritto "non c'è nulla di sbagliato nel fare questo cambiamento".
Deduplicatore,

45

Perché niente è così semplice che non ci possono essere bug.

Il tuo codice, mentre a prima vista sembra essere privo di bug. In realtà è una semplice rappresentazione programmatica di una funzione polinomiale.

Solo che ha un bug ...

public function polynominal($a, $b, $c, $d)
{
    return  $a * pow($x, 3) + $b * pow($x, 2) + $c * $x + $d;
}

$xnon è definito come input per il tuo codice e, a seconda della lingua o del tempo di esecuzione o dell'ambito, la tua funzione potrebbe non funzionare, potrebbe causare risultati non validi o causare l'arresto di un'astronave .


Addendum:

Mentre per il momento potresti considerare il tuo bug privo di codice, per quanto tempo resta difficile è difficile dirlo. Mentre si potrebbe sostenere che non vale la pena scrivere un test per un codice così banale, dopo aver già scritto il test il lavoro è stato fatto ed eliminarlo significa eliminare una protezione tangibile.

Di ulteriore nota sono i servizi di copertura del codice (come coveralls.io) che forniscono una buona indicazione della copertura fornita da una suite di test. Coprendo ogni riga di codice si fornisce una metrica decente della quantità (se non della qualità) dei test eseguiti. In combinazione con molti piccoli test, questi servizi ti dicono almeno dove non cercare un bug quando succede.

Alla fine, se hai già scritto un test, tienilo . Poiché lo spazio o il tempo risparmiato dall'eliminazione sarà probabilmente molto inferiore al debito tecnico in seguito se si verifica un bug.


Esiste una soluzione alternativa: passare a lingue non dinamiche non deboli. I test unitari sono di solito una soluzione alternativa per una verifica del tempo di compilazione insufficientemente forte e alcune lingue sono abbastanza buone in questo en.wikipedia.org/wiki/Agda_(programming_language)
Den

21

Sì. Se potessimo dire con sicurezza al 100%, con certezza: questa funzione non verrà mai modificata e non verrà mai eseguita in un contesto che potrebbe non riuscire - se potessimo dirlo, potremmo abbandonare i test e risparmiare qualche millisecondo su ogni Build CI.

Ma non possiamo. Oppure, non possiamo con molte funzioni. Ed è più semplice avere una regola di esecuzione di tutti i test per tutto il tempo piuttosto che sforzarsi di determinare esattamente quale soglia di confidenza siamo soddisfatti, ed esattamente quanta fiducia abbiamo nell'immutabilità e nell'infallibilità di una data funzione.

E i tempi di elaborazione sono economici. Quei millisecondi risparmiati, anche moltiplicati molte volte, non si sommano abbastanza da giustificare il tempo impiegato da ogni funzione per chiederci: abbiamo la sicurezza sufficiente di non doverli mai più testare?


Penso che sia un ottimo punto che stai sollevando qui. Riesco già a vedere due ragazzi che passano ore a discutere sul mantenimento dei test per alcune piccole funzionalità.
Kapol,

@Kapol: non un argomento, un incontro per determinare quale può andare e quale può rimanere e quali criteri dovrebbero essere utilizzati per decidere, nonché dove documentare i criteri e chi firma la decisione finale ...
jmoreno

12

Tutto quanto detto nelle altre risposte è corretto, ma ne aggiungerò uno in più.

Documentazione

I test unitari, se ben scritti, possono spiegare a uno sviluppatore esattamente cosa fa una funzione, quali sono le sue aspettative di input / output e, cosa più importante, quale comportamento ci si può aspettare da essa.

Può rendere più facile individuare un bug e ridurre la confusione.

Non tutti ricordano i polinomi, la geometria o persino l'algebra :) Ma un buon test di unità registrato nel controllo della versione ricorderà per me.

Per un esempio di quanto possa essere utile come documentazione, guarda l'introduzione di Jasmine: http://jasmine.github.io/edge/introduction.html Dagli qualche secondo per caricare, quindi scorri verso il basso. Vedrai l'intera API Jasmine documentata come output del test unitario.

[Aggiornamento basato sul feedback di @Warbo] I test sono garantiti come aggiornati e, in caso contrario, falliranno, il che generalmente causerà un errore di compilazione se si utilizza CI. La documentazione esterna cambia indipendentemente dal codice e quindi non è necessariamente aggiornata.


1
C'è un grande vantaggio nell'utilizzare i test come (una parte della) documentazione che hai lasciato implicito: vengono controllati automaticamente. Altre forme di documentazione, ad es. commenti esplicativi o frammenti di codice in una pagina Web possono non essere aggiornati con l'evoluzione del codice. I test unitari generano un errore se non sono più precisi. Quella pagina di gelsomini ne è un esempio estremo.
Warbo,

Vorrei aggiungere che alcune lingue, come D e Rust, integrano la loro generazione di documentazione con i loro test unitari, in modo da poter avere lo stesso pezzo di codice sia compilare in un test unitario che essere inseriti nella documentazione HTML
Idan Arye

2

Controllo di realtà

Sono stato in ambienti difficili in cui i test sono "una perdita di tempo" durante il budget e la pianificazione, e quindi "una parte fondamentale dell'assicurazione della qualità" quando il cliente ha a che fare con i bug, quindi la mia opinione è più fluida di quanto potrebbero essere gli altri.

Hai un budget. Il tuo compito è quello di ottenere il miglior prodotto possibile con quel budget, per qualsiasi definizione di "migliore" puoi mettere insieme (non è una parola facile da definire). Fine della storia.

I test sono uno strumento nel tuo inventario. Dovresti usarlo, perché è un buon strumento , con una lunga storia di risparmio di milioni, o forse anche miliardi di dollari. Se è data la possibilità, è necessario aggiungere test a queste semplici funzioni. Potrebbe salvarti la pelle un giorno.

Ma nel mondo reale, con vincoli di budget e pianificazione, potrebbe non accadere. Non renderti schiavo della procedura. Le funzioni di test sono utili, ma a un certo punto, le ore di lavoro potrebbero essere meglio impiegate a scrivere documentazione per sviluppatori in parole, anziché in codice, quindi lo sviluppatore successivo non ha bisogno di test tanto. Oppure potrebbe essere meglio spendere rifattorizzare la base di codice in modo da non doverti mantenere così difficile da bestia. O forse è meglio passare il tempo a parlare con il tuo capo del budget e del programma in modo che capisca meglio a cosa stanno offrendo quando il prossimo round di finanziamento scenderà.

Lo sviluppo del software è un equilibrio. Conta sempre il costo opportunità di qualsiasi cosa tu stia facendo per assicurarti che non ci sia un modo migliore per passare il tempo.


4
In questo caso, c'era già un test, quindi la domanda non è se scriverli, ma se impegnarli ed eseguirli con ogni build o release o qualunque programma ci sia per eseguire tutti i test.
Paŭlo Ebermann,

0

Sì, mantieni i test, mantienili in esecuzione e farli passare.

I test unitari sono lì per proteggere te (e gli altri) da te stesso (e da loro stessi).

Perché mantenere i test è una buona idea;

  • Convalida della funzionalità dei requisiti precedenti a fronte di nuovi requisiti e funzionalità aggiuntive
  • Verifica che gli esercizi di refactoring siano corretti
  • Documentazione interna: ecco come dovrebbe essere usato il codice
  • Test di regressione, le cose cambiano
    • Le modifiche rompono il vecchio codice?
    • Le modifiche richiedono ulteriori richieste di modifica o aggiornamenti alle funzioni o al codice correnti?
  • Dato che i test sono già stati scritti, conservali; è il tempo e il denaro già spesi che ridurranno ulteriormente i costi di manutenzione

2
Ciò non sembra offrire nulla di sostanziale rispetto alle altre risposte che sono state fornite.

1
Si è verificato un problema con la pubblicazione prima, ma a posteriori è un bel riassunto. Lo rimuoverò.
Niall,

-1

Documentazione per gli sviluppatori

  • Come faccio a sapere (come un altro sviluppatore) che questo è stato testato?
  • Se voglio correggere un bug nella funzione autonoma , come faccio a sapere che non sto introducendo un bug che hai già considerato?
  • Indicatore di complessità: il numero di test può essere una buona misura di quanto sia complesso qualcosa. Ciò può indicare che non dovresti toccarlo perché è maturo e stabile o che è gonfio e accoppiato.

Documentazione per l'utente

  • In attesa di un documento scadente, posso vedere se {zero, valori negativi, set vuoti, ecc.) Sono accettati e qual è il valore di ritorno previsto.
  • Fornisce anche un buon esempio di come dovrei usare l'oggetto / funzione

1
ciò non sembra offrire nulla di sostanziale rispetto ai punti formulati e spiegati nelle risposte precedenti. Anche "bel riassunto" è già stato pubblicato (per i miei gusti non è così bello ma vabbè)
moscerino
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.