diff --git a/book/src/part05.md b/book/src/part05.md index 8048b4da10162d536e0c915952ce56add5bc396c..7e9ab709ffd07afc7ac3e6d9ebbdf3491acc71c1 100644 --- a/book/src/part05.md +++ b/book/src/part05.md @@ -8,8 +8,7 @@ Les concepts abordés dans cet exemple sont: 1. [La documentation.](#la-documentation) 2. [Les tests.](#les-tests) -3. [Les tests de documentation.](#les-tests-de-documentation) -4. [Les outils en plus du compilateur](#les-outils-en-plus-du-compilateur) +3. [Les outils en plus du compilateur](#les-outils-en-plus-du-compilateur) ## Discussion @@ -89,18 +88,130 @@ ligne de documentation (on peut également utiliser la syntaxe `/*! ... */`), co #### Markdown -La documentation supporte la syntaxe du [Common Markdown](https://commonmark.org/) +La documentation supporte la syntaxe du [Common Markdown](https://commonmark.org/) comme on peut le voir dans le code ci-dessus. On a en particulier +la possibilité de mettre des titres avec des `#` ou du code avec des code fences. Il est également possible de mettre des liens vers +d'autres parties de la documentation (avec les annotations tu type `[MyStruct]`) ce qui fait de la syntaxe un outil très puissant et intégré fortement au processus de développement. #### Génération de la documentation -Tout ce travail +Tout ce travail d'annotation du code source permet d'utiliser `rustdoc` qui est un outil puissant de génération de documentation +sous la forme principal d'un site web. Dans le répertoire où se trouve le fichier `Cargo.toml`, on peut exécuter la commande +```bash +cargo doc +``` +et cela va générer la documentation dans un sous répertoire du projet. On peut également automatiquement ouvrir la +documentation dans un navigateur à l'aide de la commande +```bash +cargo doc --open +``` ### Les tests -### Les tests de documentation +La documentation aide grandement à la (ré-)utilisation d'une librairie et à son développement (collaboratif ou non). +Durant le processus de développement, il est également très utile (et important) d'écrire des tests pour le code. +Rust propose un framework de tests totalement intégré au langage. On peut ainsi très facilement écrire +des fonctions de test en ajoutant l'annotation #[test] directement au dessus de n'importe quelle fonction + +```rust,ignore +{{#include ../../codes/rust_lang/part05/src/main.rs:test_creation}} +``` + +La fonction `test_creation()` sera automatiquement appelée lors de l'appel à la commande +```bash +cargo test +``` +Aucune fonction non annotée par `#[test]` n'est appelée quand on exécute `cargo test`. +Si la fonction se termine sans erreur, le test est réussi (il est échoué si la fonction +s'arrête en cours de route). +On voit dans la fonction `test_creation()` l'appel à la macro `assert!()` qui prend en argument +une expression booléenne, ici `n1 == SomethingOrNothing::Nothing` dans le premier appel. +Si l'expression prend la valeur `true`, le code continue normalement son exécution, sinon +il fait appel à la macro `panic!()!` et l'exécution est interrompue et un code d'erreur est retourné par le programme. + +Dans l'appel à `n1 == SomethingOrNothing::Nothing`, on constate qu'on a besoin de vérifier +si `n1` est égale à `SomethingOrNothing::Nothing`. L'opérateur `==` n'est pas implémenté +pour les types complexes, ainsi le code suivant ne compile pas +```rust,compile_fail +enum SomethingOrNothing<T> { + Nothing, + Something(T), +} +fn main() { + let n1 = SomethingOrNothing::<i32>::Nothing; + let b = n1 == SomethingOrNothing::<i32>::Nothing; +} +``` +et nous donne un message d'erreur incluant +```bash +note: an implementation of `PartialEq<_>` might be missing for `SomethingOrNothing<i32>` +``` +Ainsi, on doit implémenter le trait `PartialEq` pour le type `SomethingOrNothing<T>`, +qui permet de tester l'égalité entre deux instances d'un type +```rust +{{#include ../../codes/rust_lang/part05/src/main.rs:something_or_nothing}} +{{#include ../../codes/rust_lang/part05/src/main.rs:partial_eq}} +fn main() { + let n1 = SomethingOrNothing::<i32>::Nothing; + assert!(n1 == SomethingOrNothing::<i32>::Nothing); +} +``` +On constate que dans notre implémentation, il est nécessaire que `T` implémente également +le trait `PartialEq`. Ainsi, deux `Nothing` sont égales, un `Nothing` et un `Something` sont différentes, et seulement quand les deux valeurs encapsuleés dans deux `Something` sont égales +alors nous avons égalité. + +On peut également vouloir construire des tests qui échouent comme dans l'exemple ci-dessous +```rust,ignore +{{#include ../../codes/rust_lang/part05/src/main.rs:test_should_fail}} +``` +où on a annoté le test avec un `#[should_panic]`. Ce test, bien qu'il panique, sera considéré +comme réussi. + +Finalement, il y a également la possibilité de regroupes les tests dans un module séparé +comme ci-dessous +```rust,ignore +{{#include ../../codes/rust_lang/part05/src/main.rs:cfg_test}} +``` +Pour ce faire, il faut créer un module, `mod test` et l'annoter avec une configuration spéciale `#[cfg(test)]`. Cela permet de séparer les tests totalement du reste du code et devoir +importer les différentes implémentations. + +#### Rapport sur l'exécution des tests + +Lors de l'appel à `cargo test` tous les tests sont exécutés et un rapport est généré. Sur +[ce code](#le-code), on obtient +```bash +$ cargo est + Compiling part05 v0.1.0 (/rust-101/codes/rust_lang/part05) + Finished test [unoptimized + debuginfo] target(s) in 0.52s + Running unittests src/main.rs (target/debug/deps/part05-5a74a11c06bd3179) + +running 5 tests +test test_min ... ok +test test_creation ... ok +test tests::test_min ... ok +test tests::test_min_something_or_nothing ... ok +test test_failure_creation - should panic ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +``` ### Les outils en plus du compilateur +La chaîne de compilation Rust vient avec deux autres outils très pratiques (en plus de `cargo` et `rustdoc`): `rustfmt` un formatteur de code, et `clippy` un linter. + +#### `rustfmt` + +Rust a fait le choix fasciste de définir des tas de conventions pour le nommage +des variables, des types, etc. L'outil `rustfmt` permet de formatter automatiquement +le code pour avoir un style uniforme au travers de tout votre code et ainsi améliorer +sa lisibilité. + +#### `clippy` + +L'outil `clippy` est un détecteur automatique de mauvaise pratiques de codage, telles que définies +par la communauté du Rust. Il permet d'écrire du code le plus idiomatique possible +et en général d'éviter des mauvaises pratiques et/ou de simplifier +le code avec des *patterns* connus. + ## Le code ```rust diff --git a/codes/rust_lang/part05/src/main.rs b/codes/rust_lang/part05/src/main.rs index 163cc130f4cd2bd9b4c93e45f9432b49acbc5cf7..5dab5a93371c52e0f9708afbefd554dc53b902d5 100644 --- a/codes/rust_lang/part05/src/main.rs +++ b/codes/rust_lang/part05/src/main.rs @@ -49,6 +49,7 @@ enum SomethingOrNothing<T> { } // ANCHOR_END: something_or_nothing +// ANCHOR: static_function impl<T: std::fmt::Display> SomethingOrNothing<T> { /// A static function that prints the content of a SomethingOrNothing. fn print(&self) { @@ -58,16 +59,18 @@ impl<T: std::fmt::Display> SomethingOrNothing<T> { } } } +// ANCHOR_END: static_function -// ANCHOR: static_function +// ANCHOR: default impl<T> Default for SomethingOrNothing<T> { /// By Default a [SomethingOrNothing] is a nothing. fn default() -> Self { SomethingOrNothing::Nothing } } -// ANCHOR_END: static_function +// ANCHOR_END: default +// ANCHOR: partial_eq impl<T: PartialEq> PartialEq for SomethingOrNothing<T> { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -79,6 +82,7 @@ impl<T: PartialEq> PartialEq for SomethingOrNothing<T> { } } } +// ANCHOR_END: partial_eq // ANCHOR: minimum /// The [Minimum] trait computes the minimum value between two values of a type @@ -170,6 +174,7 @@ fn main() { min.print(); } +// ANCHOR: test_creation #[test] fn test_creation() { let n1: SomethingOrNothing<i32> = SomethingOrNothing::default(); @@ -177,7 +182,9 @@ fn test_creation() { let n2: SomethingOrNothing<i32> = SomethingOrNothing::Something(1); assert!(n2 == SomethingOrNothing::Something(1)); } +// ANCHOR_END: test_creation +// ANCHOR: test_should_fail #[test] #[should_panic] fn test_failure_creation() { @@ -185,6 +192,7 @@ fn test_failure_creation() { assert!(n2 == SomethingOrNothing::Nothing); assert!(n2 == SomethingOrNothing::Something(2)); } +// ANCHOR_END: test_should_fail #[test] fn test_min() { @@ -193,6 +201,7 @@ fn test_min() { assert!(min == SomethingOrNothing::Something(-1)); } +// ANCHOR: cgf_test #[cfg(test)] mod tests { #[test] @@ -216,3 +225,4 @@ mod tests { assert!(z.min(z) == z); } } +// ANCHOR_END: cgf_test