Il testing del codice sorgente è uno dei processi più antichi e importanti nell'industria dello sviluppo. Tuttavia, in molti linguaggi, le librerie di testing sono fornite separatamente e l'integrazione dei test nel codice principale può essere poco trasparente o scomoda. In Rust, sin dalle prime versioni, il linguaggio è stato progettato con il supporto per il testing modulare esplicito "out of the box".
In molti linguaggi tradizionali, i test si trovano separati o richiedono la configurazione di framework e builder specifici. Questo complica lo sviluppo collaborativo, aumenta il rischio di disallineamento tra la logica principale e i test, e rende più difficile effettuare testing di integrazione e modulare.
Rust integra il sistema di testing direttamente a livello di linguaggio: il modulo #[cfg(test)], la direttiva #[test] e lo strumento cargo test consentono di creare, eseguire e controllare i test all'interno del codice sorgente. Questo garantisce un legame stretto tra il codice e i test che lo verificano, garantendo facilità di esecuzione dei test e automazione dei processi CI/CD.
Esempio di codice:
// tests automatically compiled and run by `cargo test` #[cfg(test)] mod tests { use super::*; #[test] fn it_adds_two() { assert_eq!(2 + 2, 4); } }
Caratteristiche chiave:
cargo test) esegue tutti i test trovati.#[cfg(test)] consente di escludere i test dalla build di rilascio.È possibile utilizzare funzioni instabili o private nei test, e come?
Sì, è possibile. All'interno del modulo di test, dichiarato come #[cfg(test)], i test possono vedere non solo l'API pubblica, ma anche quella privata del modulo corrente. Questo significa che puoi testare i dettagli di implementazione direttamente. Tuttavia, da un altro modulo, solo tramite l'interfaccia pubblica.
Esempio:
fn private_internal(x: i32) -> i32 { x + 1 } #[cfg(test)] mod tests { use super::*; #[test] fn test_private() { assert_eq!(private_internal(41), 42); } }
I test con nomi identici confliggeranno tra moduli diversi?
No, i nomi dei test sono visibili solo all'interno del loro modulo. I test di moduli diversi possono avere lo stesso nome poiché il compilatore li distingue in base al percorso completo (namespace).
Esempio:
mod a { #[cfg(test)] mod tests { #[test] fn basic() {} } } mod b { #[cfg(test)] mod tests { #[test] fn basic() {} } }
È possibile eseguire un singolo test o parte di un insieme di test?
Sì, utilizzando il filtro per il nome del test: cargo test nome_del_test. Questo è utile per il debugging di grandi insiemi.
Esempio:
cargo test only_this_test
Un sviluppatore ha spostato i test in un progetto separato senza accesso alle funzioni private e con una struttura di directory diversa. A causa delle modifiche alla libreria principale, i test hanno iniziato a diventare rapidamente obsoleti e fuori sincronia.
Vantaggi: Può isolare il testing, evitando che i moduli di test finiscano nella build di rilascio.
Svantaggi: Perdita di sincronia, impossibilità di testare i dettagli interni, elevata manutenibilità e rischio di non attualità dei test.
I test sono scritti direttamente negli stessi moduli della logica principale, piccoli casi di test atomici testano l'API privata e pubblica. Un principiante può rapidamente comprendere il funzionamento del modulo attraverso i test.
Vantaggi: Massima supportabilità di sincronia, trasparenza, testing locale accelerato, idoneità per lo sviluppo driven by tests.
Svantaggi: Aumento del numero di righe di codice nei file sorgenti, potenziale crescita della complessità con un elevato numero di test.