class: center, gif, start background-image: url(gifs/testtest.gif) # Testing in Rust ### [Rust Cologne/Bonn](http://www.meetup.com/Rust-Cologne-Bonn/events/229919455/) — 2016-04-06 — [@badboy_](https://twitter.com/badboy_) --- class: center, middle, huge # #[test] ??? Testen in Rust simpel aber barebones --- class: middle, huge-code ```rust #[test] fn it_works() { } ``` ??? Einfache Test-Funktionen inline Kleine Annotation kennzeichnet Test --- class: middle, command ``` $ cargo test Compiling book-examples v0.1.0 Running target/debug/book_examples-c3fb7bc5636b01fe running 1 test test it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured Doc-tests book-examples running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured ``` ??? Ausgeführt mit Cargo gibt Überblick --- class: middle, command ``` $ cargo test Compiling book-examples v0.1.0 Running target/debug/book_examples-c3fb7bc5636b01fe running 1 test *test it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured Doc-tests book-examples running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured ``` ??? Listet jede Testfunktion und das Ergebnis ok = Alles super --- class: gif background-image: url(gifs/as-expected.gif) ??? Just as I expected --- class: middle, huge-code ```rust #[test] fn it_works() { assert!(false); } ``` ??? Für die eigentlichen Tests Assertions --- class: middle, command ``` $ cargo test Compiling book-examples v0.1.0 Running target/debug/book_examples-c3fb7bc5636b01fe running 1 test *test it_works ... FAILED failures: ---- it_works stdout ---- * thread 'it_works' panicked at 'assertion failed: false', src/lib.rs:3 failures: it_works test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured ``` ??? Dann zeigt Cargo auch was fehlgeschlagen ist --- class: middle, huge-code ```rust #[test] fn it_works() { assert!(false, "Hi!"); } ``` ??? Test-Debugging vereinfachen: Eigenen Fehler angeben --- class: middle, command ``` $ cargo test Compiling book-examples v0.1.0 Running target/debug/book_examples-c3fb7bc5636b01fe running 1 test *test it_works ... FAILED failures: ---- it_works stdout ---- * thread 'it_works' panicked at 'Hi!', src/lib.rs:3 failures: it_works test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured ``` ??? Wird dann angezeigt wenn Test fehlschlägt --- class: middle, big-code, command ```rust assert!(42 == 40+1); // assertion failed: 42 == 40 + 1 ``` ??? Bislang nur assert! für true oder false Teils wenig hilfreich, da generischer Fehler --- class: middle, big-code, command ```rust assert_eq!(42, 40+1); // assertion failed: // (left == right) // (left: '42', right: '41') ``` ??? Daher: assert_eq Vergleich zweier Werte Test-mäßig äquivalent zu == aber bessere Fehlermeldung, cargo zeigt Werte links und rechts Benötigt das Wert auch gezeigt werden kann (Debug) --- class: middle, bigger-code # Test-Modul ```rust #[cfg(test)] mod test { #[test] fn it_works() { } } ``` ??? Inline-Tests unaufgeräumt Besser: Test-Modul inline. Striktere Trennung, einfacher für den Compiler --- class: middle, bigger-code # Test-Modul ```rust #[cfg(test)] mod test { * use super::*; #[test] fn it_works() { } } ``` ??? Explizites Einbinden von Resourcen nötig Eine der wenigen Sachen wo glob-Import ok ist --- class: middle. big-code # Attribute ```rust #[test] #[should_panic] fn it_works() { assert!(false); } ``` ??? Nur Vergleiche nicht ausreichend Daher: weitere Attribute z.B.: Check auf panic --- class: middle. big-code # Attribute ```rust #[test] #[should_panic(expected="failed")] fn it_works() { assert!(false); } ``` ??? Aber besser: Check auf expliziten panic-Fehler macht simples string matching. Fehler muss irgendwo im panic vorkommen Teilstück reicht --- class: middle. big-code # Attribute ```rust #[test] #[ignore] fn it_works() { assert!(expensive_computation()); } // cargo test -- --ignored ``` ??? Tests explizit ignorieren, z.B. weil sie lange dauern Cargo kann diese ausführen, wenn explizit gefragt Nicht vergessen auf CI! --- class: middle. big-code # Integration-Tests ```rust // in `tests/lib.rs` extern crate calc; #[test] fn it_works() { assert_eq!(4, calc::add_two(2)); } ``` ??? Bislang Unit-Tests inline Zugriff auf interne Module und Funktionen Nächster Schritt: Integrationstest. Nur nach außen sichtbare API testen Dafür: test Ordner --- class: middle. big-code # Integration-Tests ```rust // in tests/lib.rs *extern crate calc; #[test] fn it_works() { assert_eq!(4, calc::add_two(2)); } ``` ??? explizites Einbinden des crates benötigt wiederhole: nach außen sichtbare API. Durch Compiler enforced --- class: middle. big-code # Dokumentations-Tests ```rust //! The calc crate provides //! functions that add numbers //! to other numbers. //! //! # Examples //! //! ``` //! assert_eq!(4, calc::add_two(2)); //! ``` ``` ??? Nichts schlimmeres als Beispiel in der Doku die nicht funktionieren Daher: Doku-Tests. Alle Doc-Strings automatisch getestet. Gleiches Prinzip, gleiche Assertions --- class: middle. big-code # Dokumentations-Tests ```rust //! The calc crate provides //! functions that add numbers //! to other numbers. //! //! # Examples fn _0() { assert_eq!(4, calc::add_two(2)); } ``` ??? Intern macht Cargo daraus einfach eine Funktion Zeilen mit # als erstes Zeichen nicht in Doku, aber in Test-Code Möglichkeit Dependencies zu `use`n Erlaubt auch main funktion, nützlich für Macro Test --- class: middle. command # Dokumentations-Tests ``` $ cargo test Compiling book-examples v0.1.0 Running target/debug/book_examples-c3fb7bc5636b01fe […] * Doc-tests book-examples running 1 test *test _0 ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured ``` ??? Doc-Tests immer Teil der Test-Suite Annotationen am Code erlauben überspringen, nur kompilieren oder auch nicht-Rust code --- class: middle. big-code # Benchmark-Tests ```rust #![feature(test)] extern crate test; use test::Bencher; #[bench] fn bench_add_two(b: &mut Bencher) { b.iter(|| add_two(2)); } ``` ??? Wenn Code fehlerfrei, dann die Frage wie schnell ist er Microbenchmarks nützlich als Anhaltspunkt --- class: middle. big-code # Benchmark-Tests ```rust *#![feature(test)] extern crate test; use test::Bencher; #[bench] fn bench_add_two(b: &mut Bencher) { b.iter(|| add_two(2)); } ``` ??? Benötigen das Test-Feature, daher nightly only Außerdem: test crate einbinden, Bencher laden Bencher übernimmt den komplizierten Part Aufpassen: Compiler optimiert. Wenn Rückgabewert nicht benutzt, kann es wegoptimiert werden. Explizites Nutzen oder explizite Blackbox --- class: middle. command # Benchmark-Tests ``` $ # nightly required $ cargo bench Compiling book-examples v0.1.0 Running target/debug/book_examples-c3fb7bc5636b01fe running 1 test test bench_add_two ... bench: 0 ns/iter (+/- 0) test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured ``` ??? Ausführen von Benchmarks eigenes Kommando Außerdem: Benchmarks in benches Ordner auslagern. Klare Trennung. Rest des Codes funktioniert weiterhin mit stable cargo-benchcmp für Vergleich von Bench Runs Achtung: Benchmarks alleine nicht aussagekräftig, nur im Vergleich --- class: # Extensions * [compiletest_rs](https://crates.io/crates/compiletest_rs) * Test auf Syntaxfehler & Warnungen * [quickcheck](https://github.com/BurntSushi/quickcheck) * Property-based testing * [stainless](https://github.com/reem/stainless) * DSL: `describe! { … }` ??? cargo testing sehr bare bones Aber erweiterungen rustc selbst hat Tests das Code nicht kompiliert oder Warnungen wirft quickcheck erlaubt automatisiertes Property-based testing. Randomisierter Input andere Fuzzer (afl) DSLs für spec-like tests. generiert intern auch nur normalen Test Code, aber setup, teardown, etc --- class: middle, center # The Rust Book: Testing # [doc.rust-lang.org/book/testing.html](https://doc.rust-lang.org/book/testing.html)