Egy hagyományos egységteszt megmutat egy bemenetet és a hozzá tartozó helyes eredményt. Ez különösen jó dokumentáció, de könnyű úgy kiválasztani a példákat, hogy mind ugyanazt az egyszerű ágat járják be. A property-based teszt ehelyett egy egész bemeneti tartományra fogalmaz meg elvárást, majd sok konkrét esetet generál. A haszna azon múlik, hogy valóban a követelményt írod-e le. Egy tautológia több ezer futtatástól sem lesz erős teszt.
A proptest dokumentációja generátorokat, ellenőrző makrókat és hibakeresést segítő zsugorítást biztosít. A kísérőprojekt szándékosan rögzített proptest 1.11.0 verziót használ; Rust 1.97.1 és edition 2024 mellett teszteltük. A teljes példa az examples/articles/proptest könyvtárban van. Nem külső szolgáltatást tesztelünk, ezért a futás gyors és külön adatbázist sem igényel.
Egyértelmű szerződésből következnek a tulajdonságok
Tegyük fel, hogy egy API azonosítólistát kap. A sorrend nem számít, az ismétlődés nem hordoz új információt, a tároláshoz viszont növekvő, ismétlésmentes alak kell. Az azonosító típusa u16, az üres lista megengedett. A kanonizáló függvény átveszi a vektort, rendezi, majd összevonja a szomszédos ismétléseket. Az ownership átvétele itt kényelmes: a bemeneti tárolót újrahasználhatjuk, és nem kell új vektort építeni.
pub fn canonical_ids(mut values: Vec<u16>) -> Vec<u16> {
values.sort_unstable();
values.dedup();
values
}
fn main() {
assert_eq!(canonical_ids(vec![3, 1, 3, 2]), vec![1, 2, 3]);
}
A szerződésből legalább három, egymást kiegészítő elvárás adódik. Az eredmény szigorúan növekvő; az eredményben pontosan a bemenet elemei szerepelnek; az ismételt kanonizálás nem változtat semmin. Az első kettő együtt azt is megakadályozza, hogy egy hibás implementáció minden bemenetre üres listát adjon. Az üres eredmény ugyanis rendezett és idempotens lenne, de elveszítené a tagságot.
A típusválasztás is része a tesztnek. Ha az alkalmazásban szöveges azonosítók vannak, ez a számpélda nem ellenőrzi az ékezeteket, a kisbetűsítést vagy a normalizálást. Az alapvető típusokról szóló lecke segít a tartomány megválasztásában, a Cargo build, run és test pedig a tesztkörnyezetet mutatja be.
Generálj a problémához illő bemeneteket
A projektben az általános generátor legfeljebb 199 darab tetszőleges u16 értéket ad. Az idempotencia vizsgálatához külön, szűk tartományt használunk: tizenhat különböző azonosítóból épül a lista. Így gyakran keletkeznek ismétlődések. Ha mindig hatalmas tartományból választanánk rövid listát, a teszt ritkábban járná be a duplikátumok kezelését. A generátor eloszlása ezért ugyanolyan tudatos döntés, mint egy kézzel írt határeset kiválasztása.
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(512))]
#[test]
fn idempotent(input in prop::collection::vec(0u16..16, 0..200)) {
let once = canonical_ids(input);
prop_assert_eq!(canonical_ids(once.clone()), once);
}
}
A példában a tartomány felső határa kizáró: a lista hossza nem lehet kétszáz. Ha fontos a pontos maximális hossz, külön határeset vagy befoglaló tartomány szükséges. Érdemes közvetlenül ellenőrizni az üres bemenetet és a típus legkisebb, illetve legnagyobb értékét is. Ezek a kézi tesztek könnyen olvasható, stabil példaként maradnak a generált vizsgálatok mellett.
Legyen az orákulum független az implementációtól
A negyedik tulajdonság egy BTreeSet segítségével állítja elő az elvárt vektort. Ez más algoritmust használ, mint a rendezés és a dedup, ezért kisebb az esélye, hogy a teszt megismétli az implementáció hibáját. A tagságot külön halmazegyezőség is ellenőrzi. A Proptest könyv generátorokra és zsugorításra épülő gondolkodásmódja itt válik gyakorlati eszközzé: a teszt nem egy előre kiválasztott listához kötődik.
Az orákulum sem tévedhetetlen. Ha a követelmény valójában az eredeti sorrend megtartása lenne, a rendezett halmaz rossz választás. Ezért kódolás előtt mondd ki, melyik viselkedést akarod. A név, a dokumentáció és a tulajdonságok ugyanazt a szerződést írják le. A tesztelés így a követelmény tisztázására is kényszerít, nem pusztán további esetszámot ad egy meglévő programhoz.
A talált hiba maradjon reprodukálható
A proptest egy hibás bemenetet általában egyszerűsíteni próbál, hogy kisebb ellenpéldát kapj. Ez nagy segítség, de nem kell minden futásnál ugyanazt a legkisebb példát várni. A hibaeltárolás dokumentációja leírja a regressziós magok megőrzését. Ha ilyen fájl keletkezik, kezeld verziózott tesztadatként; emellett a megértett hibához írj rövid, névvel ellátott egységtesztet is.
cd examples/articles/proptest
cargo test --locked
A helyi ellenőrzésben négy generált tulajdonság és egy kézi határeset-teszt sikeres volt. A konfiguráció tulajdonságonként 512 sikeres esetet kér, de ez nem teljes bizonyítás a lehetséges bemenetek összességére. Egy új, nagyobb adatmennyiség, más típus vagy párhuzamos végrehajtás új kockázatot hozhat. Az invariánsokat akkor is újra kell gondolni, ha maga a teszt futtatási parancsa változatlan marad.
Hasznos következő gyakorlat, ha ideiglenesen elhagyod a rendezést, majd megnézed, melyik tulajdonság találja meg a hibát. Ezután állítsd vissza a kódot, és ellenőrizd, hogy a teszt ismét zöld. Egy ilyen célzott mutáció többet mond a teszt erejéről, mint az esetszám önmagában. Az itt megismert módszer konfigurációs feldolgozókra, formátumkonverzióra és állapotgépekre is alkalmazható, amennyiben előbb pontos, ellenőrizhető szerződést fogalmazol meg.
A példa ellenőrzési környezete
Rust 1.97.1, edition 2024 és proptest 1.11.0 mellett öt teszt sikeres volt: négy tulajdonság egyenként 512 esettel és egy kézi határeset. Egy elkülönített ideiglenes másolatban elhagyott rendezést három teszt észlelt; a projektben a helyes implementáció maradt. Ez a generált tartományok helyi ellenőrzése, nem matematikai bizonyítás.
AI-val segített szerkesztőségi anyag. A technikai ellenőrzés környezetét a cikk végén jelezzük; emberi szakmai lektorálást ez a megjelölés nem állít.



