Esiste un principio di ingegneria del software che collega i costi dei test di riutilizzo e regressione su un sistema di produzione?


12

Ho lavorato su un grande sistema di transazioni finanziarie per una banca che si occupava di pensioni e investimenti. Dopo 15 anni di modifiche alle funzionalità, il costo del test di regressione manuale era salito a $ 200.000 per versione. (10 milioni di LOC, $ 10 milioni di transazioni al giorno). Questo sistema si interfaccia anche con altri 19 sistemi all'interno dell'azienda spostando molti dati in giro. Questo sistema è stato implementato in Java.

Ciò che osserviamo, tuttavia, è che più riutilizziamo, più aumentano i costi dei test di regressione. (Il motivo è che devi "testare il codice che tocchi" - e il codice riutilizzato / condiviso influisce su una molteplicità di luoghi quando viene toccato. Quindi, nonostante 'DRY - Non ripetere te stesso' - cioè non copiare e incollare il codice - osserviamo un incentivo finanziario per copiare e incollare il codice. Questo serve a ridurre i costi del test di regressione, perché non vogliamo modificare il codice che potrebbe essere condiviso, perché ciò provocherebbe un grande impatto sul test di regressione.)

La mia domanda è : esiste un principio di ingegneria del software che descrive la relazione tra riutilizzo e costi dei test di regressione?

Il motivo per cui vorrei porre questa domanda è che probabilmente c'è un vantaggio in termini di costi nel decomporre il sistema in parti più piccole da testare.

ipotesi:

  1. "Test di regressione" significa "test di accettazione", ovvero un altro gruppo che trascorre del tempo per scrivere nuovi test e riutilizzare vecchi test sul sistema per conto dell'azienda, comprese le configurazioni di ambiente e dati.

  2. So che la reazione istintiva a un grande costo del test di regressione è "test più automatizzati". Questo è un buon principio In questo ambiente ci sono un paio di sfide.

    (a) I test automatizzati sono meno utili oltre i confini del sistema, a meno che anche quel sistema abbia un'elevata copertura dei test automatizzati. (Sfera della sfera di influenza).

    (b) È culturalmente difficile ottenere lo slancio sul tempo del programmatore o investimenti di capitale su una copertura di test automatizzata elevata quando il sistema è già ampio e complesso.

    (c) Il costo del mantenimento dei test automatizzati è nascosto in un progetto e quindi può essere facilmente scartato a livello di progetto.

    (d) Questa è solo la realtà culturale di lavorare in una banca.

    (e) Sto lavorando per risolvere questo problema in modo diverso (decomposizione).


2
Fai la dichiarazione a ondata di mano che " osserviamo [...] che più" riutilizziamo ", più aumentano i costi del test di " accettazione "- potresti approfondire ? Un test di accettazione non dovrebbe essere agnostico di dettagli di implementazione come le gerarchie di ereditarietà, e invece riprodurre scenari di utilizzo, ad esempio?
amon,

2
La tua domanda è interessante, ma contiene alcuni presupposti molto distorti come "il riutilizzo è una conseguenza di OO" o la parte citata da @amon. Ti suggerisco di eliminare completamente la parte "OO", poiché la tua domanda principale è indipendente dal linguaggio di programmazione o da qualsiasi programmazione OO. Ed è abbastanza poco chiaro quale tipo di "riutilizzo" hai in mente - "riutilizzo" è un termine generico, il riutilizzo di copia e incolla è diverso dal riutilizzo di componenti o librerie.
Doc Brown,

Grazie è utile. Ho rimosso i riferimenti a OO e ampliato l'idea di toccare il codice condiviso (o il codice che diventerà codice condiviso).
falco

1
Nella forma attuale, immagino che risponderei solo "no, non credo" alla tua domanda - che immagino non sarebbe molto soddisfacente per la tua. Oppure sei interessato a principi e pratiche per ridurre effettivamente i costi di test durante la costruzione di un grande sistema come il tuo con alcuni componenti riutilizzabili autocostruiti?
Doc Brown,

Risposte:


11

non vogliamo modificare il codice che potrebbe essere condiviso, poiché ciò provocherebbe un impatto notevole sul test di regressione

Sopra i suoni su di me. Più è importante il codice, più è condiviso, maggiori sono i requisiti di qualità, più dovrebbe essere coinvolta la garanzia della qualità quando cambia.

Poiché il tuo sistema è implementato in Java, puoi vedere un esempio qui sopra, nelle librerie standard Java (JDK). Le sue versioni principali sono poco frequenti e sono accompagnate da test che richiedono molto sforzo. E anche le versioni minori eseguono una suite di test JCK molto completa per verificare l'assenza di regressioni.

Potresti pensare che questo in qualche modo soffoca l'evoluzione del codice condiviso, e ... sì, è giusto. Maggiore è l'impatto e il rischio associati alla modifica del codice, più attenta dovresti essere nel farlo, maggiore sarà lo sforzo necessario per testare le sue versioni.

Idealmente, la qualità delle versioni di codice ampiamente condiviso dovrebbe essere tale da non richiedere affatto grandi cambiamenti (salvo miglioramenti poco frequenti). Questa linea di pensiero si riflette in una famosa citazione di Joshua Bloch :

Le API pubbliche, come i diamanti, sono per sempre. Hai una possibilità per farlo bene, quindi fai del tuo meglio.


Con quanto detto sopra, tuttavia, sembra che alcuni dei problemi che descrivi siano causati da una strategia inefficiente di sviluppo del codice condiviso. In particolare, sembra particolarmente problematico che per il codice riutilizzato vengano prese in considerazione solo due opzioni: duplicare questo codice o includerlo immediatamente nelle librerie condivise "core".

Limitare solo a queste due opzioni non è necessario e, ancora una volta, puoi trovare esempi di come farlo meglio nel JDK che usi. Dai un'occhiata ai java.util.concurrentpacchetti ( JSR 166 ): fino alla versione Java 5, questi erano una libreria separata, non una parte delle versioni JDK di base.

Pensaci, questa è una terza opzione che hai trascurato, e abbastanza pragmatica, quella che devi considerare all'inizio del nuovo codice condiviso. Quando si calcola solo un codice che può essere condiviso tra 2-3 componenti, nulla ti obbliga a includerlo immediatamente nell'API principale del sistema.

È possibile impacchettare e rilasciare questo codice condiviso "immaturo" come libreria separata, proprio come è stato fatto per le utility simultanee Java. In questo modo ti allevia dalla necessità di test di regressione completi, perché puoi utilizzare solo una quantità relativamente piccola di componenti coinvolti. Di conseguenza, hai più margine di manovra per modificare e migliorare questo codice condiviso e per testare come funziona in produzione.

Dopo che la tua libreria è maturata e si è stabilizzata abbastanza da darti la certezza che sono improbabili ulteriori cambiamenti in essa, puoi considerare la sua inclusione nelle librerie core del sistema, proprio come le utility simultanee sono state infine incluse in JDK.


Un esempio concreto di quanti sforzi (inclusi i test) possono essere coinvolti nella modifica del codice fortemente riutilizzato può essere trovato, ancora una volta, in JDK. Nella versione 7u6 hanno cambiato la Stringrappresentazione interna che ha comportato un cambiamento nelle substringprestazioni. I commenti di uno sviluppatore di funzionalità di Reddit descrivono la quantità di sforzi coinvolti in questa modifica:

L'analisi iniziale è emersa dal gruppo GC nel 2007 ...

Internamente il team delle prestazioni di Oracle mantiene un insieme di app e benchmark rappresentativi e importanti che usano per valutare i cambiamenti delle prestazioni. Questo set di app è stato fondamentale per valutare la modifica alla sottostringa. Abbiamo esaminato attentamente sia i cambiamenti nelle prestazioni che i cambiamenti nel footprint. Inevitabilmente, come nel caso di qualsiasi cambiamento significativo, ci sono state regressioni in alcune app e guadagni in altre. Abbiamo studiato le regressioni per vedere se le prestazioni erano ancora accettabili e la correttezza è stata mantenuta ...

La mia risposta non intende essere esaustiva, ma è un breve riassunto di quello che è stato quasi sei mesi di lavoro dedicato ...


Sembra che entrambi abbiamo lavorato su una risposta in parallelo, con molti pensieri simili ...
Doc Brown il

@DocBrown sì, è interessante che abbiamo anche risposto quasi contemporaneamente, circa un'ora dopo che la domanda è stata modificata in forma
moscerino

9

Non credo che esistano metriche per calcolare "costo per test di regressione / LOC del codice riutilizzato costruito". E non credo che nessuno abbia mai investito così tanto tempo e denaro per costruire due volte lo stesso sistema "grande", una versione con molti componenti resuable e una senza, per fare delle ricerche serie al riguardo.

Ma ho già visto problemi causati dal riutilizzo come i tuoi in precedenza, e forse sei interessato ad alcuni pensieri su come gestirlo meglio.

In primo luogo, in realtà non è il riutilizzo il tuo problema: è piuttosto il tentativo di creare i tuoi componenti riutilizzabili e utilizzarli in tutto il sistema. Sono sicuro che stai facendo un sacco di riutilizzo di grandi pacchetti software dove non sorgono i tuoi problemi: pensa a tutto lo stack Java che stai utilizzando, o forse ad alcuni componenti di terze parti (supponendo che tu fossi soddisfatto di quei componenti). Ma cosa c'è di diverso in quel software, ad esempio le librerie Java, mentre i tuoi componenti riutilizzabili ti stanno causando così tanti costi aggiuntivi per i test di regressione? Ecco alcuni punti che immagino possano essere diversi:

  • quei componenti sono molto maturi e stabili

  • sono sviluppati e testati completamente in modo indipendente da un'organizzazione completamente diversa

  • per (ri) usarli, non è necessario cambiarli (in effetti non si potrebbe nemmeno se lo si desidera, poiché non si mantiene il codice sorgente)

  • non ricevi quotidianamente una nuova versione, solo aggiornamenti minori (al massimo al mese) o aggiornamenti importanti a intervalli all'anno

  • la maggior parte degli aggiornamenti è progettata per essere compatibile al 100% verso il basso, in particolare aggiornamenti minori

Quindi, per rendere più efficaci i tuoi componenti riutilizzabili, dovresti adattare alcune di queste cose dall'alto per il tuo sviluppo:

  • per qualsiasi componente riutilizzabile, avere la chiara responsabilità di chi fa la manutenzione e assicurarsi che tutte le persone che riutilizzano un componente possano essere sicuri di ottenere immediatamente una correzione di bug in caso di problemi.

  • stabilire un rigoroso controllo delle versioni e politiche di rilascio. Quando si evolve un componente riutilizzabile, non rilasciarlo "a tutti" ogni giorno (almeno, se ciò implicherebbe un test di regressione completo da $ 200K sul sistema). Consentire invece di pubblicare periodicamente nuove versioni e fornire meccanismi che consentano all'utente di quel componente di rinviare la modifica alla nuova versione.

  • più spesso un componente viene riutilizzato, più è importante che fornisca un'interfaccia stabile e un comportamento compatibile verso il basso.

  • i componenti riutilizzabili necessitano di suite di test molto complete per testarli separatamente.

Molte di queste cose significheranno che il costo di costruzione del componente stesso aumenterà, ma ridurrà anche i costi delle modifiche causate da regressioni fallite.


0

Mentre potrebbe esserci un aumento "osservabile" dei costi a causa della necessità di ulteriori test, quel tipo di refactoring di solito rende il codice più gestibile in futuro poiché si sta riducendo il debito tecnico nel sistema.

Ciò dovrebbe sperare di ridurre i futuri bug e rendere più facili da implementare nuove funzionalità o modifiche alle funzionalità esistenti.

Più facile, intendo che dovrebbero richiedere meno tempo e quindi costare di meno.

Ridurre, più facile e meno sono tutti termini piuttosto nebulosi qui e qualsiasi risparmio futuro (o piuttosto sperato per il risparmio) è impossibile da calcolare in quanto non è ancora avvenuto.

Una base di codice più semplice dovrebbe consentire ai nuovi dipendenti o ai dipendenti esistenti che si spostano nel progetto di accelerare più rapidamente, soprattutto per i sistemi di grandi dimensioni.

Può anche ridurre il turnover del personale in quanto il morale dei membri del progetto esistente potrebbe essere migliorato.

Ovviamente non è garantito che otterrai questi benefici, ma queste sono cose che dovrebbero essere considerate insieme ai costi (come un aumento dei test) che possono essere misurati.

In effetti, un codice migliore dovrebbe eventualmente ridurre i costi dei test nel tempo, anche se c'è un aumento iniziale dovuto a ciò che descrivi.

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.