Nella nostra serie in corso DevOps per la Vittoria, abbiamo documentato il nostro percorso di trasformazione DevOps. Nel Capitolo 1, abbiamo introdotto il Triangolo DevOps: Strumenti, Architettura e Persone nello Sviluppo del Prodotto, dove abbiamo discusso gli elementi fondamentali che guidano il successo di DevOps. Nel Capitolo 2, abbiamo esplorato I Primi Passi nella Nostra Trasformazione DevOps, condividendo come abbiamo gettato le basi per cambiamenti significativi.
Ora, nel Capitolo 3, affrontiamo la prossima sfida principale che abbiamo incontrato: Progettare per la Testabilità. Dopo aver migliorato la nostra struttura del team, affinato la nostra Definizione di Fatto e implementato l'analisi automatizzata del codice, ci siamo resi conto che il principale collo di bottiglia nella nostra pipeline si era spostato sulla progettazione del software, in particolare sulla testabilità. Il lavoro si stava accumulando nella fase di test, impedendoci di raggiungere cicli di rilascio più rapidi. Questo capitolo approfondisce come abbiamo superato questa sfida e migliorato la nostra capacità di fornire software di alta qualità in modo efficiente.
Il Collo di Bottiglia dei Test
La ragione principale di questo ritardo era che i test erano prevalentemente manuali, concentrati sui test dell'interfaccia utente e sui flussi di lavoro degli utenti. Poiché l'interfaccia utente era di solito uno degli ultimi elementi da completare, c'era poca o nessuna automazione in atto, e i test manuali sono diventati l'unico approccio praticabile. I casi di test automatizzati dell'interfaccia utente (ad esempio, i test Selenium) venivano tipicamente scritti dopo il completamento dei test manuali, principalmente a scopo di regressione.
Questa dipendenza dai test manuali ha comportato-
- Ritardi significativi nei rilasci di software.
- La necessità di più implementazioni per consentire test paralleli da parte di più di un membro del team QA.
Ciò ha evidenziato l'importanza di rendere il codice più testabile per ottimizzare il flusso di valore. Tuttavia, raggiungere questo obiettivo si è rivelato più difficile del previsto.
Dare priorità ai Test API e ai Test di unità
Per affrontare questa sfida, ci siamo concentrati su due aree principali:
- Test di Unità - Garantire che i singoli componenti possano essere testati in isolamento.
- Test API - Ridurre la dipendenza dai test basati sull'interfaccia utente e dalle dipendenze del database.
Test API: Spostamento a sinistra per un feedback più rapido.
Abbiamo ridefinito la nostra strategia di test API con questi miglioramenti chiave:
- APIs ben definite - Le APIs sono state progettate per essere semplici, ben documentate e disponibili nelle prime fasi di sviluppo.
- Evitare il database come punto di integrazione - Affidarsi ai database per l'integrazione ha creato dipendenze che hanno rallentato i test. Invece, ci siamo orientati verso l'integrazione basata su API utilizzando REST su HTTP e GraphQL. Ciò ha minimizzato il tempo di configurazione del database e migliorato l'automazione dei test.
Questo cambiamento ha ridotto significativamente i ritardi, consentendo un'automazione più rapida e test in fase iniziale, dimostrando che concentrarsi su design API-first ha migliorato sia la testabilità che l'efficienza.
Test Unitario: Un Cambiamento di Mentalità
Inizialmente, i nostri sforzi di verifica delle singole componenti hanno portato a un rapido aumento della copertura dei test, ma abbiamo subito identificato dei problemi:
- Alcuni test erano superficiali, scritti solo per raggiungere gli obiettivi di copertura del codice.
- Mancava loro una convalida significativa della funzionalità, dei casi limite e degli scenari di fallimento.
Per contrastare ciò, abbiamo enfatizzato:
- Formare gli sviluppatori sulla scrittura di test efficaci.
- Refactoring del codice per migliorare la testabilità.
Sfide nella scrittura di codice testabile.
Il maggiore ostacolo a un efficace unit testing era la mancanza di testabilità nel codice stesso. I problemi principali includevano:
- Funzioni ampie e monolitiche.
- Componenti strettamente accoppiati.
- Scarsa separazione delle responsabilità.
- Astrazione insufficiente.
Questi problemi hanno reso difficile isolare e testare efficacemente le singole unità. Per affrontare ciò, abbiamo:
- Fornite Linee Guida: Abbiamo condiviso le migliori pratiche su:
- Selezione delle funzioni per il test unitario.
- Refactoring del codice per migliorare la testabilità.
- Progettare e utilizzare mock-up per facilitare i test.
- Concentrati sui Miglioramenti Incrementali: Gli sviluppatori sono stati incoraggiati ad apportare piccole, significative modifiche per migliorare la testabilità nel tempo.
Progresso lento ma costante
Nonostante questi sforzi, i progressi sono stati lenti, specialmente per i prodotti esistenti. L'architettura esistente limitava il numero di test unitari che potevamo scrivere. Tuttavia, queste azioni non erano solo volte a migliorare la base di codice attuale, ma rappresentavano un investimento nel futuro:
- Migliorare le competenze degli sviluppatori nella progettazione di codice testabile ha assicurato che i progetti futuri non avrebbero sofferto degli stessi problemi.
- Miglioramenti incrementali hanno prevenuto interruzioni aumentando costantemente la copertura dell'automazione dei test.
- Un cambiamento di mentalità ha aiutato i team a considerare la testabilità non come un peso, ma come una necessità per il successo sostenibile del DevOps.
Per i team e le organizzazioni che intraprendono una trasformazione DevOps, la testabilità è un'area di attenzione critica. Aiuta ad alleviare i colli di bottiglia nella fase di sviluppo. Tuttavia, è importante gestire le aspettative: risultati immediati potrebbero non essere possibili, specialmente quando si tratta di grandi codebase legacy. Il refactoring di tale codice senza sufficienti test unitari e di regressione è una sfida importante.
Lezioni da The Pragmatic Programmer
Per gli scenari in cui iniziare con una nuova codebase non è fattibile, The Pragmatic Programmer: Your Journey to Mastery, di Andy Hunt e David Thomas offre una guida pratica:
- Mirare a cambiamenti incrementali: Concentrarsi sulla rifattorizzazione di parti piccole e gestibili del codice.
- Scollegare i componenti: Ridurre le dipendenze per rendere le singole unità più facili da testare.
- Suddividere le Funzioni Monolitiche: Dividere le funzioni grandi in unità più piccole e più mirate.
- Adottare un Design Modulare: Rendere il codice più testabile e manutenibile migliorando la modularità.
Il messaggio è chiaro: una volta risolte le strozzature iniziali, la testabilità dovrebbe diventare un'area di attenzione fondamentale. Questo faciliterà i vincoli nella fase di test e permetterà una consegna più rapida di software di alta qualità.
Guardando al futuro
Il nostro percorso per migliorare la testabilità ci ha insegnato lezioni preziose sul ruolo della progettazione del software nel consentire trasformazioni DevOps fluide. Concentrandoci sulla testabilità, abbiamo affrontato i colli di bottiglia, migliorato le competenze degli sviluppatori e gettato le basi per il successo futuro. Sebbene i risultati immediati possano essere lenti, i benefici a lungo termine in termini di qualità, efficienza e crescita del team rendono questo investimento utile.
Mentre proseguiamo il nostro percorso DevOps, la misurazione del successo diventa la prossima sfida chiave. Nel Capitolo 4, esploreremo come abbiamo stabilito indicatori chiave di performance e cruscotti per monitorare i nostri progressi e identificare ulteriori aree di miglioramento.
Resta sintonizzato mentre approfondiamo come il processo decisionale basato sui dati ci ha aiutato a perfezionare i nostri processi DevOps e a ottimizzare le prestazioni!