Miért lassú alapból a cargo test nagyobb projekteknél
A beépített cargo test egyetlen futtatható binárisba fordítja az összes tesztet egy crate-ből (integration és unit tesztek is), és ezt egy process-en belül, egy szálkészletben futtatja. Ez elsőre nem hangzik rossznak, de van pár bosszantó mellékhatása, ami nagyobb workspace-eknél igazán fájni kezd:
- A kimenet lineáris és nehezen olvasható. Ha 500 teszt fut, és az 480. bukik el, addig görgetned kell, mire meglátod a hibát — vagy várnod kell a végére, mire kiírja az összefoglalót.
- Egy panic az egész binárist megállíthatja, ha nem használsz
#[should_panic]-ot vagy izolált process-eket, ami zajos, nehezen debuggolható állapotot eredményez. - A párhuzamosítás nem mindig optimális. A
cargo testalapból is párhuzamosan futtatja a teszteket szálakon, de nincs finomhangolt slot-kezelés, retry logika, vagy per-teszt timeout. - Nagy workspace-eknél a build és a futtatás összefolyik, és nehéz átlátni, melyik crate teszteli épp mi.
A cargo test maga nem "rossz", csak egy bizonyos méret felett kényelmetlen. Kis projekteknél bőven elég.
Ezen problémákra született válasz a cargo-nextest, ami egy teljesen új teszt-futtató motor, miközben a te tesztjeid (#[test] függvények) egy sort sem változnak.
cargo-nextest telepítése és első futtatása
A telepítés a szokásos cargo install úton megy, vagy — ha gyorsabbat szeretnél — előre fordított binárisokat is letölthetsz a projekt GitHub release lapjáról.
cargo install cargo-nextest --locked
A --locked flag azért fontos, mert a nextest saját Cargo.lock-ját használja a fordításhoz, így garantáltan reprodukálható build-et kapsz.
A telepítés után egyetlen paranccsal lecserélheted a cargo test-et:
cargo nextest run
Ez a parancs automatikusan felismeri a workspace tagjait, lefordítja a teszt binárisokat, majd egy process-per-teszt modellben futtatja le mindegyiket. Ez a legfontosabb architekturális különbség: minden egyes teszt saját process-ben fut, nem osztott szálban.
Nézzünk egy egyszerű példát, amit ki is próbálhatsz:
// src/lib.rs
pub fn osszead(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alap_osszeadas() {
assert_eq!(osszead(2, 3), 5);
}
#[test]
fn negativ_szamok() {
assert_eq!(osszead(-4, -6), -10);
}
#[test]
#[should_panic]
fn tul_nagy_ertek() {
let eredmeny = osszead(i32::MAX, 1);
assert_eq!(eredmeny, i32::MIN); // ez itt csak demonstráció
}
}
Ezt lefuttatva a cargo nextest run egy tisztább, színkódolt, per-teszt státuszú kimenetet ad, ahol azonnal látod, melyik teszt melyik crate-hez tartozik, és mennyi ideig futott.
Ha csak egy adott teszt nevet szeretnél futtatni, ugyanaz a szűrési szintaxis működik, mint a cargo test-nél: cargo nextest run negativ.
Párhuzamosítás és riportolás összehasonlítása a beépített teszterrel
A legnagyobb gyakorlati különbség a process-izoláció. Ha egy teszt panic-kel vagy szegmentálási hibát (SIGSEGV) dob, a beépített cargo test-nél ez könnyen az egész binárist megzavarhatja, és nehéz megállapítani, hogy pontosan melyik teszt volt a ludas, ha több is fut egyszerre egy szálban. A nextest-nél minden teszt saját process — ha egy elhal, a többi zavartalanul folytatódik, és a riportban egyértelműen látod, melyik teszt bukott el, milyen kilépési kóddal.
Ennek van egy fontos velejárója is:
Mivel minden teszt külön process-ben fut, a tesztek közötti megosztott mutable állapot (pl. static mut, vagy fájlrendszeri erőforrások, amikre több teszt is ír) más módon viselkedhet, mint a cargo test-nél. Ha eddig burkoltan számítottál a közös memóriaterületre,érdemes átnézni a teszteket migráláskor.
A riportolás oldalán a nextest alapból is jóval informatívabb kimenetet ad: látod a lassú teszteket ("slow tests" szekció), a bukásokat kiemelve a végén összegyűjtve, és van beépített retry mechanizmus is instabil ("flaky") tesztekhez:
cargo nextest run --retries 2
Ezzel a beállítással egy elbukott teszt legfeljebb kétszer fut újra, mielőtt véglegesen hibásnak jelölnéd. Ez különösen hasznos, ha vannak időzítés-érzékeny integrációs teszteid.
A konfigurációt egy .config/nextest.toml fájlban is rögzíthetjük, hogy a csapat mindenkinél ugyanazokat a beállításokat használja:
[profile.default]
retries = 1
slow-timeout = { period = "30s", terminate-after = 2 }
Ezzel a projekt gyökerében elhelyezett fájllal biztosítod, hogy CI-ban és lokálisan is konzisztens legyen a viselkedés.
cargo-audit bemutatása: sebezhető crate-ek felderítése
Amíg a nextest a tesztelést gyorsítja, a cargo-audit a függőségeid biztonságát vizsgálja. A RustSec projekt Advisory Database-ét használja, ami folyamatosan frissülő, közösségi karbantartású adatbázis a Rust ecosystem ismert sebezhetőségeiről.
Telepítés:
cargo install cargo-audit --locked
Futtatás egy Cargo projektben:
cargo audit
A parancs beolvassa a Cargo.lock fájlt, és minden pontos verziójú függőséget összevet az advisory adatbázissal. Ha talál egyezést, kiírja a sebezhetőség azonosítóját (RUSTSEC-XXXX-XXXX formátumban), a súlyosságát, és — ha van — a javított verziót, amire érdemes upgrade-elni.
A cargo audit a Cargo.lock fájlt vizsgálja, nem a Cargo.toml-t. Ha nincs commitolt lock fájl (pl. egy library crate-nél gyakori eset), előbb futtatnod kell egy cargo generate-lockfile parancsot, vagy egy build-et, hogy legyen mit ellenőrizni.
Ha a lock fájl naprakész, de az adatbázis változott (új CVE jelent meg), frissítened kell az advisory adatbázist is:
cargo audit --update
Ez alapból automatikusan is megtörténik minden futásnál, hacsak nem tiltod le a --no-fetch flaggel — offline environmentben (pl. légmentesen zárt CI runner) ez utóbbi hasznos lehet, ha előre letöltött másolatot használsz.
Mindkét eszköz integrálása egy egyszerű CI workflow-ba
A két eszköz remekül kiegészíti egymást egy CI pipeline-ban: a nextest gyorsítja a build-verify ciklust, az audit pedig biztonsági kaput ad minden pull request elé. Egy GitHub Actions workflow-ban ez így nézhet ki:
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
- name: Tesztek futtatása nextest-tel
run: cargo nextest run --profile ci
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-audit
- name: Függőségek auditálása
run: cargo audit
Az install-action egy közkedvelt megoldás, ami előre fordított binárisokat tölt le a megadott eszközökhöz, így nem kell minden futásnál a forrásból fordítani — ez jelentősen csökkenti a CI futási idejét mindkét eszköz esetén.
Ha érzékeny vagy a build percekre, tedd külön job-ba a tesztelést és az auditot, mert így párhuzamosan futnak, és egy audit-hiba nem blokkolja a teszt job kimenetét (bár ez csapatpolitika kérdése — lehet, hogy pont azt szeretnéd, hogy blokkoljon).
Érdemes egy .config/nextest.toml-ban külön ci profilt is definiálni, ami esetleg más retry vagy timeout beállításokat használ, mint a lokális fejlesztői profil:
[profile.ci]
retries = 3
fail-fast = false
A fail-fast = false biztosítja, hogy a CI job ne álljon meg az első hibás tesztnél, hanem az összeset lefuttatva egyben lásd, mi bukott el — ez főleg nagyobb PR-eknél, sok módosított fájlnál hasznos.
Összefoglalás
A cargo-nextest és a cargo-audit két olyan eszköz, amit érdemes minél korábban beépíteni a workflow-dba, függetlenül attól, hogy hobbi projektet vagy production kódot írsz. A nextest process-izolációja stabilabb, gyorsabb és informatívabb tesztfuttatást ad, míg az audit egy apró, de kritikus biztonsági réteget húz a függőségeid fölé. A kettő kombinálva egy CI pipeline-ban minimális overhead-del jelentősen javítja a projekt megbízhatóságát — és mindkettő telepítése és beállítása kevesebb, mint tíz percet vesz igénybe.