Architecture et backend18 min

La structure d'un projet (3) : la fonction que personne ne pouvait tester

Par · fondateur et PDG de Hábil ·

Toute ta logique dans main.rs et tes tests de tests/ ne peuvent pas l'importer, même avec pub. Ce qui va dans lib.rs, ce qui reste privé, et un modèle.

La structure d'un projet · épisode 3 sur 7 · Rust

Scénario : les personnages et l'histoire sont inventés. Les données techniques et les citations ont leur source à la fin.

En 1 minute

  • En Rust, un test de tests/ est un autre crate. Il ne peut utiliser que ce qu'une bibliothèque déclare pub. Si ton projet n'a que main.rs, il n'y a rien à importer [1].
  • La solution est de couper le programme en deux : la logique dans src/lib.rs et un main.rs mince qui ne fait que l'appeler [2][3].
  • En Rust, tout est privé tant que tu ne dis pas pub [3][5]. Ce n'est pas une gêne : c'est une frontière que le compilateur surveille, et elle te laisse changer l'intérieur sans casser personne.
  • Tu repars avec un modèle qui se clone et tourne du premier coup, avec un script qui démontre cette frontière en passant au rouge quand il le faut.
  • Pour lundi : cherche dans ton projet quelles fonctions de main.rs méritent un test. Celles-là vont dans la bibliothèque.

Dans cet article

  1. Que s'est-il passé mardi à 19:20 ?
  2. Que demande le Rust Book ?
  3. Quelles sont cinq erreurs fréquentes ?
  4. Exercice : peux-tu franchir la frontière ?
  5. À retenir
  6. Et ensuite ?
  7. Le modèle et le cours
  8. Que fait Hábil ?
  9. Références

Que s'est-il passé mardi à 19:20 ?

Iván a voulu tester une fonction depuis l'extérieur, et le compilateur lui a répondu qu'il n'y avait rien à importer.

Mardi, 19:20. Troisième étage.

Troisième mission, la première en Rust. Encore le vérificateur, celui de Go et de Python, qui interroge des services en HTTP. La demande : « Que le rapport ne laisse plus voir les jetons des URL ».

Le programme avait été écrit par quelqu'un qui n'était plus là. Tout vivait dans un seul fichier :

src/main.rs      900 líneas
Cargo.toml
Cargo.lock

Iván trouva la fonction qui construisait l'URL du rapport, vers la ligne 600, et la corrigea. Il voulait un test qui dise « le jeton ne sort pas », de ceux qui vivent dans tests/. Il créa tests/reporte.rs et écrivit, comme dans le livre :

use revisor::url_publica;

cargo test. La réponse (raccourcie) :

error[E0432]: unresolved import `revisor`
  = help: if you wanted to use a crate named `revisor`, use `cargo add revisor` to add it to your `Cargo.toml`

Un crate nommé revisor ? Son projet s'appelait ainsi. Il mit pub devant la fonction et relança. La même erreur, mot pour mot. Et la suggestion pointait vers un paquet extérieur ; lui voulait le sien.

À 19:41, il écrivit à Lucía, la responsable technique.

— J'ai mis pub et elle ne la trouve toujours pas.

— Où est ta bibliothèque ? répondit-elle.

— Je n'en ai pas. C'est un programme.

— Tout est là. Un programme n'exporte rien. Tes tests de tests/ sont un autre crate, et un autre crate ne peut utiliser qu'une bibliothèque. Demain, on coupe ce main.rs en deux.

Iván regarda les 900 lignes. Le compilateur ne lui disait pas que son code était faux. Il lui disait qu'il n'avait pas de porte.

Que demande le Rust Book ?

Que la logique vive dans une bibliothèque et que main.rs se contente de l'appeler, parce que seule une bibliothèque peut s'importer depuis l'extérieur.

Le chapitre sur les tests du Rust Book le dit sans détour. Si un projet n'a que src/main.rs, tu ne peux pas créer dans tests/ des tests d'intégration qui importent ses fonctions, parce que « Only library crates expose functions that other crates can use; binary crates are meant to be run on their own » [1] : les binaires sont faits pour s'exécuter seuls.

Et il donne la solution dans le même paragraphe : un src/main.rs simple qui appelle la logique de src/lib.rs [1]. Le chapitre 12 en fait une recette et laisse peu de chose à main : lire les arguments, préparer la configuration, appeler la bibliothèque et traiter l'erreur [2].

Mesuré sur un projet d'une seule fonction : dans main.rs, privée ou pub, le test donne E0432 ; dans lib.rs et pub, il passe.

Le modèle de cet épisode, estructura-rust, est construit ainsi :

estructura-rust/
├── Cargo.toml · Cargo.lock
├── servicios.yaml           el ejemplo
├── src/
│   ├── main.rs              EL PROGRAMA: opciones, llamada, código de salida
│   ├── lib.rs               LA BIBLIOTECA: declara los módulos y qué es público
│   ├── servicio.rs          el vocabulario. Sin red ni archivos
│   ├── config.rs            lee y valida servicios.yaml
│   ├── revisar.rs           consulta muchos servicios a la vez
│   └── reporte.rs           tabla y JSON
├── tests/                   cada archivo es un crate aparte
│   ├── integracion.rs
│   ├── binario.rs
│   └── comun/mod.rs
├── herramientas/probar-frontera.sh
├── .github/workflows/verificar.yml
└── README.md · LICENSE · LICENSE-CONTENIDO.md

Allons-y pièce par pièce, avec la question habituelle : quel problème cela résout-il aujourd'hui ?

Pourquoi y a-t-il deux racines dans src/ ?

lib.rs est la bibliothèque et main.rs est le programme : deux crates du même paquet, et Cargo trouve les deux sans que tu le lui demandes.

Ce sont les chemins par convention de Cargo : la bibliothèque dans src/lib.rs, l'exécutable dans src/main.rs et les tests d'intégration dans tests/ [6]. C'est pourquoi Cargo.toml n'en déclare aucun.

Le plus intéressant, c'est la façon dont ils se parlent. Le programme est un utilisateur de plus de la bibliothèque, comme un crate externe : « It can only use the public API » [3]. main.rs a les mêmes droits que tests/ et que n'importe quel projet extérieur, et le livre y voit un avantage : étant à la fois auteur et client, tu conçois mieux ton API [3].

Que fait main.rs, alors ?

Le minimum qu'un programme ne peut pas déléguer : lire les options, appeler et décider avec quel code il se termine.

Dans le modèle, main.rs lit trois options, appelle la bibliothèque, affiche le rapport et sort avec 0, 1 ou 2. Il ne décide pas de ce qu'est un service en bonne santé. Le reste se teste en l'exécutant en entier : quand il y a des tests d'intégration, Cargo compile d'abord le programme et laisse son chemin dans CARGO_BIN_EXE_<nombre> [7]. C'est ainsi que travaille tests/binario.rs. Ce qui est dans main.rs ne peut pas s'importer depuis l'extérieur ; s'exécuter, si.

Que signifie « tout est privé » ?

Que rien ne se voit hors du module où il naît, sauf depuis ses sous-modules, tant que tu ne lui mets pas pub ; et le compilateur le fait respecter.

Le Rust Book le dit avec une liste : en Rust, tous les éléments (fonctions, méthodes, structures, enums, modules et constantes) « are private to parent modules by default » [3]. La référence du langage le résume : « By default, everything is private », avec deux exceptions qui ne nous concernent pas ici [5].

Pourquoi ainsi ? Pour que tu saches quelles parties tu peux changer sans casser le code extérieur, dit le livre [3]. Et il appelle l'API publique de ta bibliothèque « your contract with users of your crate » [3] : ton contrat avec qui l'utilise.

Il existe un entre-deux, pub(crate) : visible dans tout le crate courant [5]. Il est trompeur, parce qu'un test de tests/ est un autre crate (mesuré : il donne la même erreur que le privé).

Dans le modèle, reporte.rs offre deux fonctions publiques, tabla et json. Celle qui nettoie l'URL, url_publica, est privée : c'est un détail de la façon dont le rapport est construit.

Alors, comment tester url_publica ?

De l'intérieur : les tests unitaires vivent dans le même fichier et voient bien le privé.

La convention du livre est un module tests à la fin de chaque fichier de src/, marqué #[cfg(test)] pour qu'il ne soit compilé qu'avec cargo test [1]. C'est un sous-module enfant du fichier, et « items in child modules can use the items in their ancestor modules » [1] : un enfant voit le privé de ses ancêtres. C'est pourquoi « Rust’s privacy rules do allow you to test private functions » [1]. La répartition dans le modèle :

Alors, comment tester url_publica ?
OùCe qu'il voitCe qu'il teste
À la fin de servicio.rs, config.rs et reporte.rsTout, privé comprisChaque pièce seule : url_publica, clasificar, les erreurs de config
tests/integracion.rsSeulement le pub de la bibliothèqueLes pièces ensemble, contre un serveur HTTP local
tests/binario.rsLe programme, comme processusCe qu'il affiche et avec quel code il sort
La documentation de servicio.rsLe pubQue l'exemple de clasificar reste vrai

Le dernier mérite une ligne. L'exemple qui se trouve dans le commentaire de clasificar est aussi un test : cargo test extrait les exemples de la documentation de la bibliothèque et les exécute [8]. Ceux du programme, non : mesuré avec un exemple faux exprès, dans main.rs personne ne le lance et tout sort vert ; dans lib.rs, le même exemple passe au rouge. Encore une raison pour que la logique vive dans lib.rs.

Voici à quoi ressemble cargo test --locked dans le modèle, seulement les lignes qui comptent (chemins raccourcis ; les temps varient) :

     Running unittests src/lib.rs (…)
test result: ok. 28 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
     Running unittests src/main.rs (…)
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
     Running tests/binario.rs (…)
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.33s
     Running tests/integracion.rs (…)
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.13s
   Doc-tests revisor
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Cela fait 49 tests en cinq blocs. On les compte là, dans les lignes test result, et non dans le code de sortie : si un fichier de tests/ ne compile pas, son bloc n'apparaît même pas.

Pourquoi tests/comun/mod.rs et pas tests/comun.rs ?

Parce que chaque fichier à la racine de tests/ est une suite, et un fichier d'aides apparaîtrait comme une suite vide à chaque exécution.

Les deux tests d'intégration partagent un serveur HTTP de test sur 127.0.0.1. S'il vivait dans tests/comun.rs, Cargo le compilerait comme une suite de plus [1], et il apparaîtrait à chaque exécution avec running 0 tests (mesuré). Le livre montre ce cas et dit que cela « is not what we wanted » [1]. Sa solution est celle du modèle : un sous-dossier, parce que les fichiers des sous-dossiers de tests/ ne sont pas compilés comme des crates séparés [1]. Chaque test l'importe avec mod comun;.

Et les modules de src/, sans mod.rs ?

Un fichier par module, au nom du module. C'est le chemin que le livre dit le plus idiomatique.

Chaque ligne pub mod reporte; de lib.rs dit au compilateur de chercher src/reporte.rs. Le Rust Book enseigne ce chemin et en reconnaît un plus ancien, src/reporte/mod.rs, qui fonctionne toujours [4]. Lequel convient, et ce qui se passe si on les mélange, se trouve dans l'erreur 3.

Que soigne revisar.rs qu'un modèle ne doit pas enseigner de travers ?

Un délai qui couvre toute la requête, un plafond pour ce qui est lu et des erreurs qui ne répètent jamais l'URL.

Le délai couvre la requête complète, du nom du serveur jusqu'au corps, et du corps on lit au plus 64 KiB. Une nuance mesurée : si le serveur de noms ne répond pas, le rapport sort à temps, mais le programme met à se terminer le temps que met le système à abandonner (sous Linux, environ 10 s). Les erreurs de reqwest, la bibliothèque HTTP, « may include the full URL », avertit sa documentation [12] ; le modèle n'utilise jamais leur texte. Ni celui des erreurs YAML, qui répètent la valeur (mesuré) : il indique le service, le champ et, si le YAML ne se lit pas, la ligne et la colonne. Nous avons retiré chaque protection dans une copie, une par une : les dix sont passées au rouge. Et les bords (le code 400, --paralelo 0, 1 et 60 000 ms) ont leur test, qui passe au rouge si le bord bouge.

Et où va la correction d'Iván ?

Dans la bibliothèque, dans reporte.rs, à côté de qui l'utilise ; et son test, à la fin du même fichier.

Le mercredi, Iván et Lucía coupèrent le main.rs en deux. La fonction de l'URL resta dans reporte.rs, privée, avec son test unitaire à côté. Le test que voulait Iván, « le jeton ne sort pas », resta dans tests/, mais il utilise la fonction publique json. De l'extérieur, on teste ce qui est promis ; de l'intérieur, comment on le tient.

Comment savoir que la frontière existe ?

Parce qu'un script la franchit exprès à chaque changement et exige que le compilateur la rejette pour la raison exacte.

herramientas/probar-frontera.sh travaille sur une copie temporaire. Il sème tests/frontera.rs, un test qui appelle revisor::reporte::url_publica, et le compile deux fois sur la même copie. Entre les deux, un seul mot de src/reporte.rs change, pub :

  1. Contrôle, avec pub fn url_publica. Le test doit compiler et passer. Sinon, l'expérience est mal montée (la fonction a changé de nom, le test semé a une erreur) et le script s'arrête au rouge.
  2. Violation, avec src/reporte.rs tel quel. Le même test doit ne pas compiler, avec l'erreur E0603, dans le test et non dans la bibliothèque.

Voici la sortie réelle :

1/2 control: con «pub fn url_publica», una prueba de tests/ la llama
   ✅ compiló y pasó (1 de 1): la prueba sembrada está bien escrita
2/2 violación: la misma prueba, con src/reporte.rs tal como está
     error[E0603]: function `url_publica` is private
        --> tests/frontera.rs:6:27
   ✅ el compilador la rechazó: lo que no es pub no se ve desde tests/
✅ frontera: se sostiene

Pourquoi le contrôle ? Parce qu'une expérience qui n'attend qu'un rouge passe aussi au « vert » quand elle échoue pour la mauvaise raison. Nous l'avons cassée de quatre façons : avec la fonction renommée ou le test semé mal écrit, il s'arrête sur « el control NO pasó » ; avec un autre code d'erreur attendu, il dit qu'il a échoué « pero NO por la privacidad » ; avec la fonction pub, il dit ❌ COMPILÓ. Avec pub(crate), elle tient.

La CI de GitHub est écrite pour lancer le formatage, clippy, les tests et la frontière avec la version stable de Rust et avec la 1.85.0, et la frontière aussi sur macOS avec le /bin/bash du système, à chaque changement et chaque lundi. Le modèle est déjà publié sur GitHub. S'il passe 60 jours sans activité, GitHub désactive l'exécution du lundi [20].

Quelles sont cinq erreurs fréquentes ?

Les deux premières viennent de ne pas savoir ce que voit tests/ ; la troisième, du mélange des styles ; les deux dernières, du fait d'ouvrir trop : l'API ou le projet.

1. Tout dans main.rs, sans lib.rs

Ça fonctionne, mais depuis tests/ on ne peut rien importer, même avec pub.

C'est l'erreur d'Iván [1]. Le chapitre 12 donne la raison de fond : comme tu ne peux pas tester directement la fonction main, sortir la logique de celle-ci te permet de la tester entièrement, et ce qui reste se vérifie en le lisant [2]. Ce qui la trahit, c'est E0432, avec une suggestion de cargo add qui pointe ailleurs (mesuré). Tu n'as pas besoin d'un autre paquet : tu as besoin de ta bibliothèque.

2. Les aides de test dans tests/common.rs

Cargo le compile comme une suite de plus, et elle sort vide à chaque exécution.

Le livre le montre : tests/common.rs apparaît avec running 0 tests, même sans aucun test [1]. Ça ne casse rien, mais ça salit le décompte. La solution est le sous-dossier, tests/common/mod.rs [1].

3. Mélanger mod.rs et des fichiers qui portent le nom du module

Pour le même module, le compilateur s'arrête ; pour des modules différents, ça compile, mais ça embrouille.

Le Rust Book distingue deux cas. Si tu utilises les deux styles pour le même module, « you’ll get a compiler error » [4]. Mesuré dans le modèle, avec src/reporte.rs et src/reporte/mod.rs à la fois :

error[E0761]: file for module `reporte` found at both "src/reporte.rs" and "src/reporte/mod.rs"

Dans des modules différents, ça compile, mais cela « might be confusing for people navigating your project » [4], et avec mod.rs tu te retrouves avec beaucoup de fichiers du même nom ouverts dans l'éditeur [4]. Choisis un style ; le modèle utilise celui que le livre dit le plus idiomatique.

4. Mettre pub partout parce que le compilateur se plaint

Chaque pub agrandit ce que les autres peuvent utiliser, et le compilateur cesse de t'avertir s'il est en trop.

Face à un E0603, demande-toi d'abord qui a besoin de cette fonction. Si c'est un test, il va à la fin du fichier, comme test unitaire [1]. Si c'est un autre module du même crate, pub(crate) suffit [5].

Si tu publies la bibliothèque ou si un autre paquet l'utilise, le coût est écrit dans le guide de compatibilité de Cargo, qui traite des nouvelles versions d'un paquet : « Major: renaming/moving/removing any public items » [11]. Le modèle n'est pas publié (publish = false) : ce qui est pub n'est utilisé que par main.rs et tests/, qui se corrigent dans le même changement. Même ainsi, il y a un coût mesuré : pub éteint l'avertissement de code mort. Avec pub(crate) et sans usage, le compilateur dit `warning: function url_publica is never used ; avec pub, il se tait. C'est pourquoi la frontière du modèle passe au rouge si url_publica devient pub`.

5. Ne pas versionner Cargo.lock quand ta CI en a besoin, ou monter un workspace trop tôt

Le premier te laisse sans trace de ce avec quoi on a testé ; le second, avec une structure qui n'a encore aucune utilité.

La FAQ de Cargo dit que Cargo.lock enregistre l'état d'une compilation qui a fonctionné, pour la refaire à l'identique à un autre moment et sur une autre machine, et qu'ainsi la CI n'échoue qu'à cause de changements nouveaux et non de facteurs externes [9]. cargo new le versionne par défaut ; la FAQ précise que cela dépend de ce dont ton paquet a besoin [9]. Parmi ses usages, elle en cite un que le modèle fait avec sa ligne 1.85.0 de la CI : « Verifying a minimum-supported Rust version (MSRV) that is less than the latest version of a dependency supports » [9]. Et elle prévient de la limite : ce déterminisme « can give a false sense of security », parce que Cargo.lock n'affecte pas qui utilise ton paquet [9]. Le modèle tourne avec --locked, qui échoue si le fichier manque ou si cargo voulait le changer [8]. Mesuré, sans Cargo.lock :

error: cannot create the lock file …/Cargo.lock because --locked was passed to prevent this

L'autre extrême, c'est un workspace dès le premier jour : un ensemble de paquets qui partagent Cargo.lock et le dossier de compilation [10], que le Rust Book présente pour quand le projet a déjà grandi et que tu veux le couper en plusieurs crates [10]. Avec un seul paquet, il n'y a rien à partager.

5. Ne pas versionner Cargo.lock quand ta CI en a besoin, ou monter un workspace trop tôt
ErreurCe qui la trahitSource
1. Tout dans main.rsE0432: unresolved import depuis tests/[1][2]
2. Aides dans tests/common.rsUne suite avec running 0 tests[1]
3. mod.rs et fichier au nom du moduleE0761: file for module … found at both[4]
4. pub partoutAucun avertissement never used ; si tu publies, chaque retrait est un changement majeur[3][11]
5. Pas de Cargo.lock avec une CI qui en a besoin, ou un workspace avec un seul paquetcannot create the lock file avec --locked ; une structure que rien n'utilise[8][9][10]

Exercice : peux-tu franchir la frontière ?

Tu vas essayer d'utiliser une fonction privée depuis tests/, voir combien il faut ouvrir pour y arriver et voir comment le script t'avertit que tu as trop ouvert.

Il te faut Rust 1.85 ou plus récent (cargo --version), installé avec rustup, plus Git et bash ; environ 20 minutes, plus quelques minutes la première fois, le temps que les dépendances se compilent.

  • Sous Linux Mint 22, n'utilise pas apt install cargo : Mint 22 est basé sur Ubuntu 24.04 [16], qui fournit Rust 1.75 [15], et l'édition 2024 est arrivée avec la 1.85 [18]. Mesuré avec ce cargo : `feature edition2024 is required, plus une suggestion d'ajouter cargo-features à Cargo.toml. Ne la suis pas : mets Rust à jour. Si tu l'as déjà installé avec apt, retire-le avec sudo apt remove cargo rustc ; sinon, l'installateur de rustup t'avertit qu'il y a déjà un Rust dans /usr/bin (mesuré). Ensuite, lance sudo apt install build-essential curl et la commande de rustup` [13][14] :
  curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Sans build-essential, tu obtiens `error: linker cc not found` (mesuré). La leçon 0 du cours de Rust l'explique pas à pas. - Sur macOS, xcode-select --install puis la même commande de rustup [13]. - Si tu as déjà rustup, rustup update stable. Sous Windows, utilise WSL. - À la fin, ouvre un autre terminal et vérifie que cargo --version indique 1.85 ou plus : dans le même terminal, cargo est encore introuvable ou reste l'ancien (mesuré).

Étape 1. Clone et vérifie que tout est au vert.

git clone https://github.com/HabilMX/estructura-rust
cd estructura-rust
cargo test --locked
bash herramientas/probar-frontera.sh

Ce que tu dois voir : cinq lignes test result: ok. qui totalisent 49 tests et, à la fin du script, ✅ frontera: se sostiene.

Étape 2. Prédis avant de toucher. Tu vas écrire dans tests/ un test qui appelle url_publica, qui est privée. Note trois choses : est-ce que ça compile tel quel ? Et si tu la rends pub(crate) ? Et si tu la rends pub ?

Étape 3. Essaie de franchir.

cat > tests/mia.rs <<'EOF'
#[test]
fn quita_el_token() {
    assert_eq!(
        revisor::reporte::url_publica("https://pagos.ejemplo.com/salud?token=x"),
        "https://pagos.ejemplo.com/salud"
    );
}
EOF
cargo test --locked --test mia

Ça ne compile pas. La première ligne de l'erreur :

error[E0603]: function `url_publica` is private

Étape 4. Ouvre-la au crate. Copie seulement la ligne de sed de ton système : celui de macOS demande un argument vide après -i (si sur ton Mac sed est celui de GNU, utilise la ligne de Linux).

# En Linux:
sed -i 's/^fn url_publica(/pub(crate) fn url_publica(/' src/reporte.rs

# En macOS:
sed -i '' 's/^fn url_publica(/pub(crate) fn url_publica(/' src/reporte.rs

cargo test --locked --test mia

Le même E0603. pub(crate) ouvre la fonction à tout le crate de la bibliothèque, et ton test est un autre crate.

Étape 5. Ouvre-la complètement.

# En Linux:
sed -i 's/^pub(crate) fn url_publica(/pub fn url_publica(/' src/reporte.rs

# En macOS:
sed -i '' 's/^pub(crate) fn url_publica(/pub fn url_publica(/' src/reporte.rs

cargo test --locked --test mia
bash herramientas/probar-frontera.sh

Ton test passe (test result: ok. 1 passed). Mais le script passe au rouge :

❌ COMPILÓ: una prueba de tests/ pudo llamar a revisor::reporte::url_publica. La frontera no existe.

Tu as réussi à l'utiliser depuis l'extérieur, et du même coup tu l'as fait entrer dans ton contrat. C'est le contrôle qui travaille.

Étape 6. Remets tout en place.

git checkout -- . && git clean -fd
bash herramientas/probar-frontera.sh

Attention : git clean -fd efface aussi les nouveaux fichiers que tu as créés là, comme tes notes.

Comment savoir que tu as réussi : tu as lancé sept exécutions et vu trois issues. Vert, quatre fois : les deux de l'étape 1, ton test à l'étape 5 et la frontière de l'étape 6. Rouge E0603, deux fois : les étapes 3 et 4. Et le rouge « COMPILÓ » du script à l'étape 5. Si à l'étape 4 ton test compile, vérifie que tu n'as pas écrit pub au lieu de pub(crate). Si à l'étape 5 le E0603 reste, la ligne de sed n'a pas trouvé pub(crate) fn url_publica( : revois l'étape 4.

Pour aller plus loin (facultatif) : déplace tests/comun/mod.rs vers tests/comun.rs (efface le dossier vide) et lance cargo test --locked. Avant, prédis combien de blocs test result tu vas voir. Réponse : six, un de plus qu'avant, Running tests/comun.rs avec running 0 tests (mesuré). Reviens en arrière avec le même git checkout -- . && git clean -fd.

À retenir

Trois questions à poser à ton dépôt cette semaine.

  1. Ton projet a-t-il un src/lib.rs ? Sinon, quelle fonction de main.rs aimerais-tu tester depuis l'extérieur sans le pouvoir aujourd'hui ?
  2. De ce qui est pub dans ta bibliothèque, qu'est-ce qu'un autre crate utilise vraiment ? Ce que personne n'utilise de l'extérieur pourrait-il être privé ou pub(crate) ?
  3. Ta CI tourne-t-elle avec --locked ? Sinon, avec quelles versions de tes dépendances le dernier vert a-t-il été testé ?

Et ensuite ?

En Go, la frontière est surveillée par l'outil ; en Python, par l'installation ; en Rust, par le compilateur. En TypeScript, il y en a une que l'éditeur accepte et que Node ne voit jamais.

Une semaine plus tard, Iván hérita d'une API en TypeScript. Un import avec un joli alias, @/revisar, compilait sans une plainte. L'éditeur le complétait tout seul.

Node, au démarrage, ne le trouva pas.

Comment un programme qui ne démarre pas peut-il compiler ? Le dimanche 22 novembre, dans l'épisode 4 : ce que décide tsconfig.json, ce qu'il ne décide pas, et pourquoi les tests et le code se compilent séparément.

Le modèle et le cours

  • Modèle : github.com/HabilMX/estructura-rust. Il s'utilise comme modèle GitHub ou se clone [19]. Le README explique chaque dossier, ce qu'il n'apporte pas, quand ajouter chaque chose et comment retirer l'exemple. Le code est sous MIT ; le texte et les explications, sous CC BY-SA 4.0.
  • Versions : édition 2024 et rust-version = "1.85", la première avec cette édition [18]. Il a été testé avec Rust 1.99.1, la stable au 22 octobre [17], sur macOS arm64 (aussi avec le /bin/bash du système) et sur Linux arm64 (conteneur ubuntu:24.04), et avec la 1.85.0 sur macOS arm64. Linux x86-64 n'a pas encore été testé. La CI est écrite pour la stable de chaque semaine et la 1.85.0 sous Linux, et la stable sur macOS [21]. Les dépendances sont figées dans Cargo.lock.
  • Cours de Rust de Hábil : si les modules, pub ou les tests d'intégration sont nouveaux pour toi, la leçon 6 les présente depuis zéro : Cours de Rust · Leçon 6, Modules, tests et Cargo.

J'écris à partir de ce que nous voyons tous les jours chez Hábil, où nous construisons des systèmes pour la banque, l'assurance et les entreprises au Mexique. Si, au travail ou à l'école, ton équipe tombe sur un dépôt que personne ne sait par où prendre, parlons-en : habil.mx

Que fait Hábil ?

Nous enseignons la programmation avec ce que nous utilisons pour construire : du code testé, expliqué et ouvert.

Cette série naît du même endroit que nos cours. Le Cours de Rust est le deuxième, après Go : il enseigne à qui appartient chaque donnée et quelles erreurs le compilateur attrape avant l'exécution, et nous le publions en accès ouvert, sous licence Creative Commons ; sa leçon 6 présente les modules, les tests et Cargo qu'utilise le modèle de cet article. Et le catalogue compte d'autres cours de programmation, gratuits, avec du code testé et expliqué pas à pas.

Découvre le Cours de Rust et le catalogue des Cours de programmation.

Si ton équipe hérite d'un système que personne ne sait par où prendre, ou doit en construire un qui puisse continuer à être maintenu, nous le faisons aussi : découvre Construire votre système sur mesure. Demande ton premier échange, sans frais. Nous y examinons s'il vaut mieux configurer ce que ton équipe a déjà, utiliser un produit ou construire, et ce qu'elle recevrait dans chaque cas. Écris-nous sur WhatsApp.

Sources consultées le 22 octobre 2026. Les citations en anglais sont reproduites telles quelles.

Références

  1. Steve Klabnik, Carol Nichols et Chris Krycho, « Test Organization » (11.3), The Rust Programming Language. https://doc.rust-lang.org/book/ch11-03-test-organization.html
  2. Steve Klabnik, Carol Nichols et Chris Krycho, « Refactoring to Improve Modularity and Error Handling » (12.3) : « Separation of Concerns for Binary Projects », The Rust Programming Language. https://doc.rust-lang.org/book/ch12-03-improving-error-handling-and-modularity.html
  3. Steve Klabnik, Carol Nichols et Chris Krycho, « Paths for Referring to an Item in the Module Tree » (7.3), y compris « Best Practices for Packages with a Binary and a Library », The Rust Programming Language. https://doc.rust-lang.org/book/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html
  4. Steve Klabnik, Carol Nichols et Chris Krycho, « Separating Modules into Different Files » (7.5) : « Alternate File Paths », The Rust Programming Language. https://doc.rust-lang.org/book/ch07-05-separating-modules-into-different-files.html
  5. The Rust Reference, « Visibility and privacy ». https://doc.rust-lang.org/reference/visibility-and-privacy.html
  6. The Cargo Book, « Package Layout ». https://doc.rust-lang.org/cargo/guide/project-layout.html
  7. The Cargo Book, « Cargo Targets » : « Binaries » et « Integration tests ». https://doc.rust-lang.org/cargo/reference/cargo-targets.html
  8. The Cargo Book, « cargo test » : « Documentation tests » et --locked. https://doc.rust-lang.org/cargo/commands/cargo-test.html
  9. The Cargo Book, « Frequently Asked Questions » : « Why have Cargo.lock in version control? ». https://doc.rust-lang.org/cargo/faq.html
  10. Steve Klabnik, Carol Nichols et Chris Krycho, « Cargo Workspaces » (14.3), The Rust Programming Language. https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html
  11. The Cargo Book, « SemVer Compatibility ». https://doc.rust-lang.org/cargo/reference/semver.html
  12. reqwest 0.13.5, « Struct Error », docs.rs. https://docs.rs/reqwest/0.13.5/reqwest/struct.Error.html
  13. Steve Klabnik, Carol Nichols et Chris Krycho, « Installation » (1.1), The Rust Programming Language. https://doc.rust-lang.org/book/ch01-01-installation.html
  14. The Rust Project, « Install Rust », rust-lang.org. https://www.rust-lang.org/tools/install
  15. Launchpad (Canonical), paquet rustc publié dans Ubuntu 24.04 « noble » : 1.75.0. https://api.launchpad.net/1.0/ubuntu/+archive/primary?ws.op=getPublishedSources&source_name=rustc&exact_match=true&distro_series=https://api.launchpad.net/1.0/ubuntu/noble
  16. Linux Mint, « Linux Mint 22 Release Notes » (« Linux Mint 22.x is based on Ubuntu 24.04 »), linuxmint.com. https://www.linuxmint.com/rel_wilma.php
  17. The Rust Release Team, « Announcing Rust 1.99.1 », Rust Blog, 15 octobre 2026. https://blog.rust-lang.org/2026/10/15/Rust-1.99.1/
  18. The Rust Release Team, « Announcing Rust 1.85.0 and Rust 2024 », Rust Blog, 20 février 2025. https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/
  19. GitHub Docs, « Creating a template repository ». https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-template-repository
  20. GitHub Docs, « Disabling and enabling a workflow ». https://docs.github.com/en/actions/how-tos/manage-workflow-runs/disable-and-enable-workflows
  21. David Tolnay, « dtolnay/rust-toolchain », README, GitHub. https://github.com/dtolnay/rust-toolchain

Votre activité fait face à ces défis ?

Parlons de votre cas

Vous préférez l'e-mail ? Écrivez-nous à hola@habil.mx