Esistono teorie formalizzate / matematiche di test del software?


12

La "teoria del test del software" su Google sembra dare teorie solo nel senso sommesso della parola; Non sono stato in grado di trovare nulla che potesse classificare come teoria in senso matematico, teorico dell'informazione o in qualche altro campo scientifico.

Quello che sto cercando è qualcosa che formalizzi cosa è il testing, le nozioni utilizzate, che cos'è un caso di test, la fattibilità di testare qualcosa, la praticità di testare qualcosa, la misura in cui qualcosa dovrebbe essere testato, la definizione / spiegazione formale di copertura del codice, ecc.

AGGIORNAMENTO: Inoltre, non sono sicuro, intuitivamente, della connessione tra verifica formale e cosa ho chiesto, ma c'è chiaramente una sorta di connessione.


1
Il test del software è molto prezioso (ad es. Mettere in pratica i test unitari) ma la sua teoria avrà sempre dei buchi. Considera questo esempio classico: double pihole(double value) { return (value - Math.PI) / (value - Math.PI); }che ho imparato dal mio insegnante di matematica . Questo codice ha esattamente un foro , che non può essere scoperto automaticamente dai soli test della scatola nera. In matematica non esiste questo buco. Nel calcolo puoi chiudere il buco se i limiti unilaterali sono uguali.
rwong

4
Questo potrebbe far parte di quello che stai cercando - en.wikipedia.org/wiki/Formal_verification
enderland

1
Secondo il suggerimento di @ Enderland. Non importa quanto sia rigoroso il tuo approccio ai test; alcuni bug scivolano ancora attraverso le fessure e man mano che copri più codice con i tuoi test, il costo per trovare nuovi bug aumenta. Questo è probabilmente il motivo per cui nessuno ha avuto il problema di formalizzare la nozione di test: un approccio "euristico" funziona altrettanto bene con meno formazione.
Doval,

Da allora mi sono familiarizzato con la terra della verifica formale tramite tipi dipendenti e sono completamente d'accordo con @Doval e Enderland.
Erik Kaplun,

1
@rwong Suppongo che alludi alla possibilità che sia numeratore che denominatore siano uguali a zero. In parte il problema è dovuto al cattivo design di questa funzione. Quando si parla di verifica formale matematica, è necessario comporre le funzioni non arbitrariamente ma seguendo regole formali, basate su tipi di dati corretti. In questo esempio dovresti usare la funzione di divisione (a,b)=>a/b, che deve essere estesa con un valore di overflow, per essere correttamente compostabile.
Dmitri Zaitsev il

Risposte:


8

Per un libro che esplora la matematica dietro i test del software ... il libro fondamentale da ottenere è The Art of Computer Systems Analysis Performance Analysis: tecniche per la progettazione sperimentale, la misurazione, la simulazione e la modellazione

Mentre è stato pubblicato per la prima volta nel 1991, è ancora popolare oggi perché è un libro di matematica applicata che si concentra esclusivamente sull'analisi delle prestazioni, simulazione e misurazioni.


5

Non posso indicare una buona risorsa online (gli articoli di Wikipedia in inglese su questi argomenti tendono ad essere migliorabili), ma posso riassumere una lezione che ho ascoltato che riguardava anche la teoria dei test di base.

Modalità di test

Esistono diverse classi di test, come test unitari o test di integrazione . Un test unitario afferma che un pezzo coerente di codice (funzione, classe, modulo) preso sui propri lavori come previsto, mentre un test di integrazione afferma che più di questi pezzi funzionano correttamente insieme.

Un caso di test è un ambiente noto in cui viene eseguito un pezzo di codice, ad esempio utilizzando input di test specifici o deridendo altre classi. Il comportamento del codice viene quindi confrontato con il comportamento previsto, ad esempio un valore di ritorno specifico.

Un test può solo dimostrare la presenza di un bug, mai l'assenza di tutti i bug. I test pongono un limite superiore alla correttezza del programma.

Copertura del codice

Per definire le metriche di copertura del codice, il codice sorgente può essere tradotto in un diagramma di flusso di controllo in cui ciascun nodo contiene un segmento lineare del codice. Il controllo scorre tra questi nodi solo alla fine di ciascun blocco ed è sempre condizionale (se condizione, quindi vai al nodo A, altrimenti vai al nodo B). Il grafico ha un nodo iniziale e un nodo finale.

  • Con questo grafico, la copertura delle istruzioni è il rapporto tra tutti i nodi visitati e tutti i nodi. La copertura completa delle dichiarazioni non è sufficiente per test approfonditi.
  • La copertura del ramo è il rapporto tra tutti i bordi visitati tra i nodi nel CFG e tutti i bordi. Ciò verifica insufficientemente i loop.
  • La copertura del percorso è il rapporto tra tutti i percorsi visitati e tutti i percorsi, in cui un percorso è qualsiasi sequenza di bordi dall'inizio al nodo finale. Il problema è che con i loop può esserci un numero infinito di percorsi, quindi la copertura completa dei percorsi non può essere testata praticamente.

È quindi spesso utile verificare la copertura delle condizioni .

  • Nella copertura delle condizioni semplici , ogni condizione atomica è una volta vera e una volta falsa, ma ciò non garantisce una copertura completa delle dichiarazioni.
  • Nella copertura a più condizioni , le condizioni atomiche hanno assunto tutte le combinazioni di truee false. Ciò implica una copertura completa delle filiali, ma è piuttosto costoso. Il programma potrebbe avere ulteriori vincoli che escludono determinate combinazioni. Questa tecnica è utile per ottenere la copertura delle filiali, può trovare il codice morto, ma non riesce a trovare i bug derivanti da una condizione errata .
  • Nella copertura Minima condizione multipla , ogni condizione atomica e composita è una volta vera e falsa. Implica ancora la copertura completa del ramo. È un sottoinsieme di copertura a più condizioni, ma richiede meno casi di test.

Quando si costruisce un input di test utilizzando la copertura delle condizioni, è necessario prendere in considerazione il corto circuito. Per esempio,

function foo(A, B) {
  if (A && B) x()
  else        y()
}

deve essere testato con foo(false, whatever), foo(true, false)e foo(true, true)per una copertura minima a condizioni multiple.

Se si dispone di oggetti che possono trovarsi in più stati, testare tutte le transizioni di stato analoghi ai flussi di controllo sembra ragionevole.

Esistono metriche di copertura più complesse, ma sono generalmente simili alle metriche presentate qui.

Questi sono metodi di test in white box e possono essere parzialmente automatizzati. Si noti che una suite di test di unità dovrebbe mirare ad avere una elevata copertura del codice da qualsiasi scelta metrica, ma al 100% non è sempre possibile. È particolarmente difficile testare la gestione delle eccezioni, in cui i guasti devono essere iniettati in posizioni specifiche.

Test funzionali

Quindi ci sono test funzionali che affermano che il codice aderisce alle specifiche visualizzando l'implementazione come una scatola nera. Tali test sono utili sia per i test unitari sia per i test di integrazione. Poiché è impossibile testare con tutti i possibili dati di input (ad esempio testare la lunghezza della stringa con tutte le possibili stringhe), è utile raggruppare l'input (e l'output) in classi equivalenti - se length("foo")è corretto, foo("bar")è probabile che funzioni anche. Per ogni possibile combinazione tra classi di equivalenza di input e output, viene scelto e testato almeno un input rappresentativo.

Uno dovrebbe inoltre testare

  • casi limite length(""), foo("x"), length(longer_than_INT_MAX),
  • valori consentiti dalla lingua, ma non dal contratto della funzione length(null), e
  • possibili dati spazzatura length("null byte in \x00 the middle")...

Con i valori numerici, ciò significa test 0, ±1, ±x, MAX, MIN, ±∞, NaNe con confronti in virgola mobile test due float vicini. Come ulteriore aggiunta, i valori di test casuali possono essere scelti dalle classi di equivalenza. Per facilitare il debug, vale la pena registrare il seme utilizzato ...

Test non funzionali: test di carico, test di stress

Un software ha requisiti non funzionali, che devono anche essere testati. Questi includono test ai limiti definiti (test di carico) e oltre (test di stress). Per un gioco per computer, ciò potrebbe far valere un numero minimo di fotogrammi al secondo in un test di carico. Un sito Web può essere sottoposto a stress test per osservare i tempi di risposta quando il doppio dei visitatori previsti colpisce i server. Tali test non sono rilevanti solo per interi sistemi ma anche per singole entità: in che modo una tabella hash si degrada con un milione di voci?

Altri tipi di test sono test dell'intero sistema in cui vengono simulati scenari o test di accettazione per dimostrare che il contratto di sviluppo è stato rispettato.

Metodi non di prova

Recensioni

Esistono tecniche non sperimentali che possono essere utilizzate per la garanzia della qualità. Esempi sono procedure dettagliate, revisioni del codice formali o programmazione di coppie. Mentre alcune parti possono essere automatizzate (ad es. Utilizzando linters), in genere richiedono molto tempo. Tuttavia, le revisioni del codice da parte di programmatori esperti hanno un alto tasso di individuazione dei bug e sono particolarmente utili durante la progettazione, dove non è possibile eseguire test automatici.

Quando le revisioni del codice sono così eccezionali, perché scriviamo ancora dei test? Il grande vantaggio delle suite di test è che possono essere eseguite (principalmente) automaticamente e sono quindi molto utili per i test di regressione .

Verifica formale

La verifica formale va e dimostra certe proprietà del codice. La verifica manuale è principalmente praticabile per le parti critiche, tanto meno per interi programmi. Le prove mettono un limite inferiore alla correttezza del programma. Le prove possono essere automatizzate in una certa misura, ad esempio tramite un controllo statico del tipo.

Alcuni invarianti possono essere controllati esplicitamente usando le assertdichiarazioni.


Tutte queste tecniche hanno il loro posto e sono complementari. TDD scrive i test funzionali in anticipo, ma i test possono essere valutati in base alle loro metriche di copertura una volta implementato il codice.

Scrivere codice testabile significa scrivere piccole unità di codice che possono essere testate separatamente (funzioni di supporto con granularità adeguata, principio di responsabilità singola). Meno argomenti prende ogni funzione, meglio è. Tale codice si presta anche per l'inserimento di oggetti finti, ad esempio tramite iniezione di dipendenza.


2
Apprezzo la risposta elaborata, ma temo che non abbia quasi nulla a che fare con quello che ho chiesto :) Ma, per fortuna, programmers.stackexchange.com/questions/78675/… sembra essere il più vicino possibile a ciò che ero mirare a.
Erik Kaplun,

Questa è roba fantastica. Puoi consigliarmi libri o cose?
Marcin,

4

Forse i "test basati sulle specifiche" rispondono anche alla tua domanda. Controlla questi moduli di test (che non ho ancora usato). Richiedono di scrivere un'espressione matematica per specificare insiemi di valori di test, piuttosto che scrivere un unit test utilizzando valori di dati singoli selezionati.

Test :: Lectrotest

Come dice l'autore, questo modulo Perl è stato ispirato dal modulo Quick-Check di Haskell . Ci sono più collegamenti in questa pagina, alcuni dei quali sono morti.


2

Un approccio matematicamente basato è il test di tutte le coppie . L'idea è che la maggior parte dei bug sono attivati ​​da una singola opzione di configurazione, e la maggior parte del resto è attivata da una certa coppia di opzioni prese contemporaneamente. Quindi la maggior parte può essere catturata testando "tutte le coppie". Una spiegazione matematica (con generalizzazioni) è qui:

Il sistema AETG: un approccio ai test basato sulla progettazione combinatoria

(ci sono molti altri riferimenti simili)


2

Esistono alcune equazioni matematiche utilizzate, ma dipende dal tipo di test del software che si sta utilizzando. Ad esempio, l' Assunzione di guasti critici presuppone che i guasti non siano quasi il prodotto di 2 o più guasti simultanei. La seguente equazione è: f = 4n + 1. f = funzione che calcola il numero di casi di test per un dato numero di variabili ( n) + 1 è l'aggiunta di quella costante in cui tutte le variabili assumono il valore nominale.

Un altro tipo di test che richiede equazioni matematiche è il Test di robustezza sta testando la robustezza o la correttezza dei casi di test in un processo di test. In questo test, si immettono le variabili all'interno dell'intervallo di input legittimo (casi di test puliti) e le variabili di input al di fuori dell'intervallo di input (casi di test sporchi). Dovresti usare la seguente equazione matematica: f = 6n + 1 . 6n indica che ogni variabile deve assumere 6 valori diversi mentre gli altri valori assumono il valore nominale. * + 1 * rappresenta l'aggiunta della costante 1.

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.