Testien organisointi
Kuten luvun alussa mainittiin, testaus on monimutkainen ala, ja eri ihmiset käyttävät eri terminologiaa ja organisointia. Rust-yhteisö ajattelee testejä kahden pääkategorian kautta: yksikkötestit ja integraatiotestit. Yksikkötestit ovat pieniä ja keskittyneempiä, testaten yhtä moduulia erillään muusta koodista kerrallaan, ja ne voivat testata yksityisiä rajapintoja. Integraatiotestit ovat täysin kirjastosi ulkopuolella ja käyttävät koodiasi samalla tavalla kuin mikä tahansa muu ulkoinen koodi, käyttäen vain julkista rajapintaa ja mahdollisesti testaten useita moduuleja per testi.
Molempien testityyppien kirjoittaminen on tärkeää varmistaaksesi, että kirjastosi osat tekevät odottamasi asiat erikseen ja yhdessä.
Yksikkötestit
Yksikkötestien tarkoitus on testata jokainen koodin yksikkö erillään muusta
koodista nopeasti paikantaakseen, missä koodi toimii ja missä ei odotetulla
tavalla. Laitat yksikkötestit src-hakemistoon kuhunkin tiedostoon sen koodin
kanssa, jota ne testaavat. Käytäntö on luoda tests-niminen moduuli kuhunkin
tiedostoon testifunktioiden säilyttämiseksi ja merkitä moduuli cfg(test)-
attribuutilla.
tests-moduuli ja #[cfg(test)]
#[cfg(test)]-merkintä tests-moduulissa kertoo Rustille kääntää ja ajaa
testikoodin vain, kun ajat cargo test, ei kun ajat cargo build. Tämä säästää
käännösaikaa, kun haluat vain rakentaa kirjaston, ja säästää tilaa syntyneessä
käännettyssä artefaktissa, koska testejä ei sisällytetä. Näet, että koska
integraatiotestit menevät eri hakemistoon, niillä ei tarvitse olla #[cfg(test)]-
merkintää. Koska yksikkötestit menevät samoihin tiedostoihin koodin kanssa,
käytät #[cfg(test)]:ää määrittääksesi, ettei niitä sisällytetä käännettyyn
tulokseen.
Muista, että kun loimme uuden adder-projektin tämän luvun ensimmäisessä
osiossa, Cargo loi meille tämän koodin:
Filename: src/lib.rs
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-01/src/lib.rs}}
Automaattisesti luodussa tests-moduulissa cfg-attribuutti tarkoittaa
konfiguraatiota (configuration) ja kertoo Rustille, että seuraava kohde
tulisi sisällyttää vain tietyn konfiguraatiovaihtoehdon ollessa voimassa. Tässä
tapauksessa konfiguraatiovaihtoehto on test, jonka Rust tarjoaa testien
kääntämiseen ja ajamiseen. Käyttämällä cfg-attribuuttia Cargo kääntää
testikoodimme vain, jos aktiivisesti ajamme testit cargo test -komennolla.
Tämä sisältää kaikki apufunktiot, jotka voivat olla tässä moduulissa, sekä
#[test]-merkityt funktiot.
Yksityisten funktioiden testaaminen
Testausyhteisössä on keskustelua siitä, pitäisikö yksityisiä funktioita testata
suoraan, ja muut kielet tekevät yksityisten funktioiden testaamisesta vaikeaa
tai mahdotonta. Riippumatta siitä, mihin testausideologiaan sitoudut, Rustin
näkyvyyssäännöt sallivat yksityisten funktioiden testaamisen. Harkitse listauksen
11-12 koodia yksityisellä internal_adder-funktiolla.
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-12/src/lib.rs}}
Huomaa, että internal_adder-funktiota ei ole merkitty pub-määritteellä.
Testit ovat vain Rust-koodia, ja tests-moduuli on vain toinen moduuli. Kuten
käsiteltiin osiossa ”Polut moduulipuun kohteeseen viittaamiseen”,
lapsimoduulien kohteet voivat käyttää esi-isämoduuliensa kohteita. Tässä
testissä tuomme kaikki tests-moduulin vanhemman moduulin kohteet näkyviin
use super::* -lauseella, ja sitten testi voi kutsua internal_adder-funktiota.
Jos et mielestäsi yksityisiä funktioita pitäisi testata, Rustissa ei ole mitään,
mikä pakottaisi sinua tekemään niin.
Integraatiotestit
Rustissa integraatiotestit ovat täysin kirjastosi ulkopuolella. Ne käyttävät kirjastoasi samalla tavalla kuin mikä tahansa muu koodi, mikä tarkoittaa, että ne voivat kutsua vain kirjastosi julkisen API:n funktioita. Niiden tarkoitus on testata, toimivatko kirjastosi monet osat yhdessä oikein. Koodin yksiköt, jotka toimivat oikein yksinään, voivat aiheuttaa ongelmia integroituna, joten integroidun koodin testikattavuus on myös tärkeää. Luodaksesi integraatiotestejä tarvitset ensin tests-hakemiston.
tests-hakemisto
Luomme tests-hakemiston projektihakemiston ylätasolle, src-hakemiston viereen. Cargo tietää etsiä integraatiotestitiedostoja tästä hakemistosta. Voimme sitten tehdä niin monta testitiedostoa kuin haluamme, ja Cargo kääntää jokaisen tiedoston erillisenä cratena.
Luodaan integraatiotesti. Kun listauksen 11-12 koodi on vielä src/lib.rs- tiedostossa, tee tests-hakemisto ja luo uusi tiedosto nimeltä tests/integration_test.rs. Hakemistorakenteesi pitäisi näyttää tältä:
adder
├── Cargo.lock
├── Cargo.toml
├── src
│ └── lib.rs
└── tests
└── integration_test.rs
Syötä listauksen 11-13 koodi tests/integration_test.rs-tiedostoon.
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-13/tests/integration_test.rs}}
adder-craten funktiolleJokainen tiedosto tests-hakemistossa on erillinen crate, joten meidän täytyy
tuoda kirjastomme kunkin testicraten näkyvyysalueelle. Siksi lisäämme use adder::add_two; koodin alkuun, jota emme tarvinne yksikkötesteissä.
Emme tarvitse merkitä mitään koodia tests/integration_test.rs-tiedostossa
#[cfg(test)]-attribuutilla. Cargo käsittelee tests-hakemistoa erityisesti
ja kääntää tämän hakemiston tiedostot vain, kun ajamme cargo test. Aja
cargo test nyt:
{{#include ../listings/ch11-writing-automated-tests/listing-11-13/output.txt}}
Tulosteen kolme osiota sisältävät yksikkötestit, integraatiotestin ja dokumentaatiotestit. Huomaa, että jos mikä tahansa testi osiossa epäonnistuu, seuraavia osioita ei ajeta. Esimerkiksi jos yksikkötesti epäonnistuu, integraatio- ja dokumentaatiotesteistä ei ole tulostetta, koska nämä testit ajetaan vain, jos kaikki yksikkötestit läpäisevät.
Yksikkötestien ensimmäinen osio on sama kuin olemme nähneet: yksi rivi
kullekin yksikkötestille (yksi nimeltä internal, jonka lisäsimme listauksessa
11-12) ja sitten yhteenvetorivi yksikkötesteille.
Integraatiotestien osio alkaa rivillä Running tests/integration_test.rs.
Seuraavaksi on rivi kullekin integraatiotestin testifunktiolle ja
yhteenvetorivi integraatiotestin tuloksille juuri ennen kuin Doc-tests adder-
osio alkaa.
Jokaisella integraatiotestitiedostolla on oma osionsa, joten jos lisäämme lisää tiedostoja tests-hakemistoon, tulee lisää integraatiotestien osioita.
Voimme silti ajaa tietyn integraatiotestifunktion määrittämällä testifunktion
nimen argumentiksi cargo test -komennolle. Ajaaaksesi kaikki testit tietyssä
integraatiotestitiedostossa, käytä cargo test -komennon --test-argumenttia
ja tiedoston nimeä:
{{#include ../listings/ch11-writing-automated-tests/output-only-05-single-integration/output.txt}}
Tämä komento ajaa vain tests/integration_test.rs-tiedoston testit.
Alimodulit integraatiotesteissä
Kun lisäät integraatiotestejä, saatat haluta tehdä lisää tiedostoja tests- hakemistoon järjestääksesi niitä; esimerkiksi voit ryhmitellä testifunktiot sen toiminnallisuuden mukaan, jota ne testaavat. Kuten aiemmin mainittiin, jokainen tiedosto tests-hakemistossa käännetään omana erillisenä cratenaan, mikä on hyödyllistä erillisten näkyvyysalueiden luomiseen, jotka jäljittelevät läheisemmin sitä, miten loppukäyttäjät käyttävät crateasi. Tämä tarkoittaa kuitenkin, että tests-hakemiston tiedostot eivät jaa samaa käyttäytymistä kuin src-hakemiston tiedostot, kuten opit luvussa 7 koodin jakamisesta moduuleihin ja tiedostoihin.
tests-hakemiston tiedostojen erilainen käyttäytyminen näkyy selvimmin, kun
sinulla on joukko apufunktioita useissa integraatiotestitiedostoissa, ja yrität
seurata luvun 7 osion ”Moduulien erottaminen eri tiedostoihin”
vaiheita erottaaksesi ne yhteiseen moduuliin. Esimerkiksi jos luomme
tests/common.rs-tiedoston ja sijoitamme siihen setup-nimisen funktion,
voimme lisätä setup-funktioon koodia, jota haluamme kutsua useista testifunktioista
useissa testitiedostoissa:
Filename: tests/common.rs
{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-12-shared-test-code-problem/tests/common.rs}}
Kun ajamme testit uudelleen, näemme uuden osion testitulosteessa common.rs-
tiedostolle, vaikka tässä tiedostossa ei ole testifunktioita eikä me kutsunut
setup-funktiota mistään:
{{#include ../listings/ch11-writing-automated-tests/no-listing-12-shared-test-code-problem/output.txt}}
common-näkyminen testituloksissa running 0 tests -tulosteella ei ole sitä,
mitä halusimme. Halusimme vain jakaa koodia muiden integraatiotestitiedostojen
kanssa. Välttääksemme common-näkymisen testitulosteessa, sen sijaan että
loisimme tests/common.rs-tiedoston, luomme tests/common/mod.rs-tiedoston.
Projektihakemisto näyttää nyt tältä:
├── Cargo.lock
├── Cargo.toml
├── src
│ └── lib.rs
└── tests
├── common
│ └── mod.rs
└── integration_test.rs
Tämä on vanhempi nimeämiskäytäntö, jonka Rust myös ymmärtää ja josta mainittiin
luvun 7 osiossa ”Vaihtoehtoiset tiedostopolut”.
Tämän niminen tiedosto kertoo Rustille, ettei se käsittele common-moduulia
integraatiotestitiedostona. Kun siirrämme setup-funktion koodin
tests/common/mod.rs-tiedostoon ja poistamme tests/common.rs-tiedoston,
testitulosteen osio ei enää näy. tests-hakemiston alihakemistojen tiedostoja
ei käännetä erillisinä crateina eikä niillä ole osioita testitulosteessa.
Kun olemme luoneet tests/common/mod.rs-tiedoston, voimme käyttää sitä
miltä tahansa integraatiotestitiedostolta moduulina. Tässä on esimerkki
setup-funktion kutsumisesta it_adds_two-testistä tests/integration_test.rs-
tiedostossa:
Filename: tests/integration_test.rs
{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-13-fix-shared-test-code-problem/tests/integration_test.rs}}
Huomaa, että mod common; -ilmoitus on sama kuin moduuli-ilmoitus, jonka
demonstroimme listauksessa 7-21. Sitten testifunktiossa voimme kutsua
common::setup()-funktiota.
Integraatiotestit binääricrateille
Jos projektimme on binääricrate, joka sisältää vain src/main.rs-tiedoston
eikä src/lib.rs-tiedostoa, emme voi luoda integraatiotestejä tests-
hakemistoon ja tuoda src/main.rs-tiedostossa määriteltyjä funktioita näkyviin
use-lauseella. Vain kirjastocratet paljastavat funktioita, joita muut cratet
voivat käyttää; binääricratet on tarkoitettu ajettaviksi itsenäisesti.
Tämä on yksi syy siihen, miksi Rust-projektit, jotka tarjoavat binäärin, käyttävät
suoraviivaista src/main.rs-tiedostoa, joka kutsuu logiikkaa, joka asuu
src/lib.rs-tiedostossa. Tällä rakenteella integraatiotestit voivat testata
kirjastocratea use-lauseella tuodakseen tärkeän toiminnallisuuden näkyviin.
Jos tärkeä toiminnallisuus toimii, pieni määrä koodia src/main.rs-tiedostossa
toimii myös, eikä sitä pientä koodimäärää tarvitse testata.
Yhteenveto
Rustin testausominaisuudet tarjoavat tavan määrittää, miten koodin pitäisi toimia varmistaaksesi, että se jatkaa toimimista odottamallasi tavalla, vaikka teet muutoksia. Yksikkötestit testaavat kirjaston eri osia erikseen ja voivat testata yksityisiä toteutustietoja. Integraatiotestit tarkistavat, että kirjaston monet osat toimivat yhdessä oikein, ja ne käyttävät kirjaston julkista API:a testatakseen koodia samalla tavalla kuin ulkoinen koodi käyttää sitä. Vaikka Rustin tyyppijärjestelmä ja omistajuussäännöt auttavat estämään joitakin virhetyyppejä, testit ovat silti tärkeitä vähentämään logiikkavirheitä, jotka liittyvät siihen, miten koodisi odotetaan käyttäytyvän.
Yhdistetään tässä luvussa ja aiemmissa luvuissa oppimasi tieto ja työskennellään projektin parissa!