TP-1 — Setup de l’environnement

Docker, Java 17, Gradle, JUnit 5, Python et Git : l’environnement des TP du cours

TP-1 de Structures de données et algorithmes avancés : setup de l’environnement.
Auteur

Dr. El Hadji Bassirou TOURÉ · DMI/FST/UCAD

ImportantObjectif

Installer Docker et l’image du cours, puis vérifier, commande par commande, la chaîne de travail de tous les TP : compiler et exécuter un programme Java, créer un projet Gradle, lancer des tests JUnit 5, produire une figure avec Python, enregistrer les versions du projet avec Git. Le TP-1 est à terminer avant la Séance 1, dont le TP commence par new-project et gradle --no-daemon test. Durée : une séance de TP.

À rendre avant la Séance 1. Un compte rendu d’une à deux pages (PDF, nommé rapport-tp1-PRENOM-NOM.pdf) : les outils installés et leurs versions, les pannes rencontrées et leur solution, les réponses aux questions marquées Observer, Expliquer et Modifier, et trois captures d’écran : le message d’accueil du conteneur, le résultat de gradle --no-daemon test avec les tests passés, la figure test_complexites.png.

Principe et prérequis

Les fichiers s’éditent sur la machine, avec l’éditeur de son choix (VS Code, Sublime Text, Notepad++…) réglé en UTF-8. Ils se compilent, se testent et s’exécutent dans un conteneur Docker : un environnement Linux isolé, construit à partir de l’image elbachirtoure/dsa-java:v2.0, qui voit le dossier de travail de la machine sous le nom /workspace. Tous les étudiants disposent ainsi des mêmes versions, quelle que soit leur machine.

Outil de l’image Commandes Usage
OpenJDK 17 (Temurin) javac, java compiler et exécuter du Java
Gradle 8.14.3 gradle --no-daemon … construire, tester et lancer un projet
JUnit 5 (par Gradle, sans réseau) tests unitaires
Python 3 et matplotlib python3 figures des TP
Git git versions du projet

Machine. Windows, macOS ou Linux en 64 bits, 8 Go de mémoire (minimum de Docker Desktop sous Windows), 10 Go d’espace disque libre et, sous Windows, la virtualisation activée dans le BIOS ou l’UEFI. Une connexion Internet n’est nécessaire qu’au premier lancement, pour télécharger l’image : JUnit y est déjà rangé, et les commandes gradle des TP s’exécutent ensuite sans réseau. Une machine qui ne peut pas faire tourner Docker suit l’annexe « Poste sans Docker ».

Fichiers fournis (tp1-fichiers.zip) :

Fichier Rôle
Hello.java premier programme, compilé à la main (étape 3)
hello-dsa-fichiers/src/main/java/Main.java classe du premier projet Gradle (étape 4)
hello-dsa-fichiers/src/test/java/MainTest.java trois tests JUnit 5 de Main (étape 5)
hello-dsa-fichiers/.gitignore fichiers et dossiers que Git ne doit pas enregistrer (étape 7)
test_figure.py figure de contrôle de matplotlib (étape 6)
COMMANDES.sh aide-mémoire : les commandes de la fiche, dans l’ordre

Étape 1 : installer Docker

Windows.

  1. Vérifier la version de Windows (commande winver) : Windows 11 à jour (23H2, build 22631, ou plus récent), en 64 bits. Windows 10 22H2 (build 19045) fait encore tourner Docker Desktop, mais n’est plus suivi par Microsoft depuis octobre 2025.
  2. Dans PowerShell ouvert en administrateur, installer ou mettre à jour WSL 2 : wsl --install --no-distribution (ou wsl --update s’il est déjà présent), puis redémarrer.
  3. Télécharger Docker Desktop (https://www.docker.com/products/docker-desktop/), lancer Docker Desktop Installer.exe en gardant l’option WSL 2, redémarrer, puis lancer Docker Desktop et attendre que le moteur soit démarré (Engine running).
  4. Installer Git for Windows (https://gitforwindows.org/), qui fournit le terminal Git Bash. Les commandes docker et celles du conteneur se tapent dans Git Bash, ni dans CMD ni dans PowerShell ; seules les commandes wsl ci-dessus et winget de l’annexe se tapent dans PowerShell.

macOS. Télécharger Docker.dmg à la même adresse, dans la version de la puce (menu Pomme, « À propos de ce Mac » : Apple silicon ou Intel), glisser Docker dans Applications et le lancer. Docker Desktop prend en charge la version courante de macOS et les deux précédentes. Les commandes se tapent dans le Terminal. L’image du cours existe pour les deux familles de processeurs (linux/amd64 et linux/arm64) : Docker télécharge d’office celle de la machine, Intel ou Apple silicon, qui tourne sans émulation.

Linux (Ubuntu, Debian).

sudo apt-get update
sudo apt-get install docker.io
sudo usermod -aG docker $USER
# fermer la session puis la rouvrir (le groupe docker n'est lu qu'a l'ouverture)

Vérification 1. Dans le terminal (Git Bash sous Windows), docker --version affiche un numéro de version, et docker run hello-world affiche Hello from Docker! : le moteur tourne et le registre d’images est joignable.

Étape 2 : lancer le conteneur du cours

Créer le dossier de travail du cours :

mkdir ~/dsa
cd ~/dsa

Décompresser tp1-fichiers.zip dans ce dossier : le dossier ~/dsa/tp1-fichiers/ apparaît. Sous Windows, ~ désigne le dossier de l’utilisateur, C:\Users\<nom>. Le conteneur se lance depuis ce dossier :

# Linux, macOS
docker run -it --rm -v "$(pwd):/workspace" elbachirtoure/dsa-java:v2.0

# Windows (Git Bash)
MSYS_NO_PATHCONV=1 docker run -it --rm -v "$(pwd):/workspace" elbachirtoure/dsa-java:v2.0

L’option -it relie le terminal au conteneur ; --rm fait effacer le conteneur par Docker à sa sortie (exit) ; -v "$(pwd):/workspace" rend le dossier courant de la machine visible dans le conteneur sous /workspace ; sous Windows, MSYS_NO_PATHCONV=1 empêche Git Bash de réécrire /workspace en chemin Windows. Au premier lancement, Docker télécharge l’image (environ 600 Mo à télécharger) ; les lancements suivants la réutilisent. Le message d’accueil du cours s’affiche : help-tp donne l’aide-mémoire. L’invite du terminal change : les commandes s’exécutent désormais dans le conteneur, jusqu’à exit.

cd /workspace
ls
java --version
javac --version
gradle --version
python3 --version
git --version

Vérification 2. Java 17, Gradle 8.14.3, Python 3 et Git répondent, et ls montre tp1-fichiers.

  1. Observer. Recopier la première ligne affichée par chacune des cinq commandes de version.
  2. Observer. Créer sur la machine, avec l’éditeur, un fichier note.txt dans ~/dsa, puis taper ls /workspace dans le conteneur. Que montre la commande ?
  3. Expliquer. Dans le conteneur, taper touch /workspace/a.txt puis touch /root/b.txt ; quitter par exit, relancer la même commande docker run et chercher les deux fichiers. Lequel existe encore, et pourquoi ? Où se trouve-t-il sur la machine ?
  4. Expliquer. Que contient /workspace quand docker run est lancé depuis un autre dossier que ~/dsa ?

Étape 3 : Java en ligne de commande

public class Hello {
    public static void main(String[] args) {
        System.out.println("Structures de donnees et algorithmes");
        System.out.println("Environnement pret !");
    }
}

Dans le conteneur :

cd /workspace/tp1-fichiers
javac Hello.java
java Hello

Sortie attendue \(\rightarrow\)

Structures de donnees et algorithmes
Environnement pret !

Vérification 3. Le programme affiche les deux lignes. Le modifier sur la machine pour qu’il affiche aussi le nom de l’étudiant, recompiler et relancer.

  1. Observer. Quel fichier javac a-t-il créé dans le dossier ? Est-il visible sur la machine ?
  2. Expliquer. La commande javap -v Hello.class | grep major affiche un numéro : lequel ? Que désigne-t-il, et quel lien a-t-il avec Java 17 ?
  3. Modifier. Lancer java Hello.class au lieu de java Hello. Quel message s’affiche ? Qu’attend la commande java comme argument ?
  4. Modifier. Copier le fichier dans un dossier essai/ : mkdir essai, cp Hello.java essai/. Sur la machine, dans essai/Hello.java, renommer la classe en Bonjour sans renommer le fichier ; puis, dans le conteneur, cd essai et javac Hello.java. Quel message javac donne-t-il, et quelle règle de Java rappelle-t-il ?

Étape 4 : premier projet Gradle

Gradle compile le projet, trouve ses dépendances (JUnit, déjà rangé dans l’image), lance les tests et le programme. La commande new-project de l’image crée un projet dans un nouveau dossier ; les fichiers fournis remplacent ensuite le Main.java, le MainTest.java et le .gitignore qu’elle a créés.

cd /workspace
new-project hello-dsa
cd hello-dsa
cp -r ../tp1-fichiers/hello-dsa-fichiers/. .

Sous Linux : sur la machine, sudo chown -R $USER: ~/dsa/hello-dsa avant de modifier les fichiers dans l’éditeur ; Git demandera alors, dans le conteneur, git config --global --add safe.directory /workspace/hello-dsa à chaque docker run.

Le projet suit la disposition standard de Gradle, celle de tous les TP du cours :

hello-dsa/
  build.gradle                  configuration : plugins, dépendances (JUnit), classe principale
  settings.gradle               nom du projet
  src/main/java/Main.java       code du programme
  src/test/java/MainTest.java   tests JUnit
  .gitignore                    fichiers que Git ignore
public class Main {
    private String nom;

    public Main(String nom) {
        this.nom = nom;
    }

    public String message() {
        return "Bienvenue en DSA, " + nom + " !";
    }

    public static void main(String[] args) {
        String nom = args.length > 0 ? args[0] : "Fatou";
        Main s = new Main(nom);
        System.out.println(s.message());
    }
}
gradle --no-daemon build
gradle --no-daemon run

Ligne écrite par le programme (parmi celles de Gradle) \(\rightarrow\)

Bienvenue en DSA, Fatou !
NotePourquoi --no-daemon ?

Par défaut, Gradle laisse en mémoire un processus (le daemon) pour accélérer les commandes suivantes. Le conteneur est recréé à chaque docker run, ce processus n’y sert donc pas, et sur les dossiers partagés avec Windows il peut provoquer des erreurs d’entrée/sortie. Avec --no-daemon, tout le travail se fait dans un processus qui s’arrête avec la commande. Le premier build prend de 10 à 20 secondes, les suivants moins.

Vérification 4. gradle --no-daemon run affiche Bienvenue en DSA, Fatou !.

  1. Observer. Afficher build.gradle (cat build.gradle) et recopier la ligne qui désigne la classe lancée par gradle run et celle qui déclare JUnit.
  2. Observer. Après le build, où Gradle a-t-il rangé Main.class et MainTest.class (find build -name "*.class") ?
  3. Modifier. Lancer successivement gradle --no-daemon run --args="Awa", --args="Awa Diop" et --args="'Awa Diop'". Recopier les trois affichages.
  4. Expliquer. Comment le texte de --args parvient-il à la méthode main ? Expliquer la deuxième et la troisième sortie. Le TP de la Séance 1 choisit ainsi un mode d’observation : --args="observer".

Étape 5 : premiers tests JUnit

Un test appelle une méthode sur des données choisies et compare le résultat à la valeur attendue.

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class MainTest {

    @Test
    void testMessage() {
        Main s = new Main("Moussa");
        assertEquals("Bienvenue en DSA, Moussa !", s.message());
    }

    @Test
    void testAutreNom() {
        Main s = new Main("Aminata");
        assertTrue(s.message().contains("Aminata"));
    }

    @Test
    void testNonVide() {
        Main s = new Main("Cheikh");
        assertFalse(s.message().isEmpty());
    }
}

Le build de l’étape 4 a déjà lancé ces tests ; cleanTest efface leur résultat, et test les relance en affichant une ligne par test.

gradle --no-daemon cleanTest test

Sortie attendue \(\rightarrow\)

MainTest > testAutreNom() PASSED
MainTest > testMessage() PASSED
MainTest > testNonVide() PASSED
BUILD SUCCESSFUL in 7s
NoteJUnit 5 : les assertions des TP
  • assertEquals(attendu, obtenu) : vérifie l’égalité (avec equals pour les objets) ;
  • assertTrue(condition), assertFalse(condition) : vérifient qu’une condition est vraie, fausse ;
  • assertThrows(UneException.class, () -> …) : vérifie que l’instruction lève l’exception indiquée.

Gradle écrit aussi un rapport HTML, build/reports/tests/test/index.html, qui s’ouvre sur la machine dans un navigateur.

Vérification 5. Les trois tests passent (PASSED) et la dernière ligne est BUILD SUCCESSFUL.

  1. Observer. Dans quel ordre les tests s’exécutent-ils ? Est-ce l’ordre du fichier ? Qu’en déduire pour l’écriture d’un test ?
  2. Modifier. Dans message(), remplacer Bienvenue par Bienvenu, puis relancer les tests. Quels tests échouent ? Relever, dans le rapport HTML, la valeur attendue et la valeur obtenue. Pourquoi les autres tests passent-ils ? Revenir au code correct.
  3. Modifier. Ajouter à MainTest un quatrième test, au choix, et relancer les tests.
  4. Expliquer. Relancer gradle --no-daemon test sans rien modifier. Que montre la sortie, et pourquoi ? Quelle option de gradle relance toutes les tâches ?

Étape 6 : Python et matplotlib

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np

n = np.arange(1, 50)
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(n, np.ones_like(n), label='O(1)', linewidth=2)
ax.plot(n, np.log2(n), label='O(log n)', linewidth=2)
ax.plot(n, n, label='O(n)', linewidth=2)
ax.plot(n, n * np.log2(n), label='O(n log n)', linewidth=2)

ax.set_xlabel('n')
ax.set_ylabel("Operations")
ax.set_title('Comparaison des complexites')
ax.legend()
ax.set_ylim(0, 200)
ax.grid(True, alpha=0.3)
plt.tight_layout()
fig.savefig('test_complexites.png', dpi=150)
print("Figure sauvegardee : test_complexites.png")
cd /workspace/tp1-fichiers
python3 test_figure.py

Le fichier test_complexites.png apparaît dans ~/dsa/tp1-fichiers/ : l’ouvrir sur la machine.

Vérification 6. La figure montre quatre courbes, avec leur légende et les noms des axes.

  1. Expliquer. Une des courbes sort du cadre avant \(n=49\). Laquelle, vers quelle valeur de \(n\), et quelle ligne du script en décide ? Calculer \(n\log_2 n\) pour \(n=38\) et \(n=39\).

Étape 7 : Git, les bases

Le dépôt se crée dans le dossier du projet. L’identité de l’auteur des versions s’enregistre dans le projet lui-même (option --global absente).

cd /workspace/hello-dsa
git init -b main
git config user.name "Prenom Nom"
git config user.email "prenom.nom@ucad.edu.sn"
git status
git add .
git commit -m "TP-1 : premier projet avec JUnit"

Modifier ensuite Main.java pour que le prénom par défaut (ligne 13) soit celui de l’étudiant, relancer gradle --no-daemon run, puis :

git add .
git commit -m "TP-1 : prenom par defaut"
git log --oneline

Sortie attendue \(\rightarrow\)

b3ab077 TP-1 : prenom par defaut
3ee982c TP-1 : premier projet avec JUnit

Vérification 7. git log --oneline affiche deux versions (les identifiants à 7 caractères diffèrent d’une machine à l’autre).

  1. Observer. Quels fichiers et dossiers git status liste-t-il avant git add ? Les dossiers build/ et .gradle/ existent pourtant : quel fichier les écarte, et pourquoi ne pas les enregistrer ?
  2. Expliquer. Afficher .git/config. Pourquoi cette identité est-elle encore là après exit et un nouveau docker run, alors qu’un git config --global tapé dans le conteneur serait à refaire ?

Pannes fréquentes

Symptôme Cause Remède
docker: command not found (sur la machine) Docker absent, ou terminal ouvert avant l’installation installer Docker (étape 1), rouvrir le terminal
failed to connect to the docker API … if the daemon is running (sous Windows, message qui cite dockerDesktopLinuxEngine) moteur Docker arrêté lancer Docker Desktop et attendre Engine running ; sous Linux, sudo systemctl start docker
Windows : Docker Desktop ne démarre pas, avec un message sur la virtualisation ou sur WSL virtualisation désactivée dans le BIOS ou l’UEFI, ou WSL 2 absent ou trop ancien activer la virtualisation (Intel VT-x, AMD-V ou SVM) dans le BIOS ou l’UEFI ; dans PowerShell en administrateur, wsl --update, puis redémarrer
permission denied while trying to connect to the docker API (Linux) utilisateur hors du groupe docker, ou session pas rouverte sudo usermod -aG docker $USER, puis fermer et rouvrir la session
Git Bash refuse -it (message qui parle de TTY ou de winpty) le terminal de Git Bash ne fournit pas de terminal à Docker MSYS_NO_PATHCONV=1 winpty docker run -it --rm -v "$(pwd):/workspace" elbachirtoure/dsa-java:v2.0, ou Git Bash ouvert dans le Terminal Windows
/workspace vide ou sans tp1-fichiers docker run lancé depuis un autre dossier exit, cd ~/dsa, relancer
Windows : erreur sur le chemin du volume, ou /workspace vide depuis le bon dossier MSYS_NO_PATHCONV=1 oublié retaper la commande avec ce préfixe
javac: command not found commande tapée sur la machine et non dans le conteneur (sans Docker : PATH) vérifier l’invite, lancer docker run ; sans Docker, voir l’annexe
UnsupportedClassVersionError … class file version 65.0 … up to 61.0 classe compilée par un JDK 21 et lancée par Java 17 recompiler dans le conteneur, ou avec javac --release 17 ; comparer java --version et javac --version
ERROR: JAVA_HOME is set to an invalid directory JAVA_HOME désigne un dossier qui n’est pas un JDK donner à JAVA_HOME le dossier du JDK, celui qui contient bin/javac
package org.junit.jupiter.api does not exist JUnit absent du classpath : test rangé dans src/main/java, ou compilation à la main sans le jar ranger les tests dans src/test/java ; à la main, -cp avec le jar de JUnit (annexe)
0 tests found (lanceur console de JUnit) dossier des classes de test absent du -cp ajouter build/test-classes au -cp
Directory '/workspace' does not contain a Gradle build. gradle lancé hors du dossier du projet cd /workspace/hello-dsa
Could not resolve … dépendance absente de l’image (ajoutée ou changée dans build.gradle) et Internet injoignable garder les dépendances écrites par new-project, ou rétablir la connexion et relancer
unmappable character (0xC3) for encoding US-ASCII, ou é affiché à la place de é fichier en UTF-8 lu dans un autre encodage (environnement sans UTF-8, Java 17 sous Windows) javac -encoding UTF-8 ; le code du cours n’a pas d’accents, commentaires compris
fatal: detected dubious ownership in repository dossier partagé qui appartient à un autre utilisateur que celui de Git git config --global --add safe.directory /workspace/hello-dsa, à retaper après chaque docker run
Linux : fichiers créés dans le conteneur non modifiables sur la machine le conteneur travaille en root : ces fichiers appartiennent à root sudo chown -R $USER: ~/dsa
Author identity unknown identité absente du dépôt git config user.name et user.email dans le projet (étape 7)

Annexe : poste sans Docker

Sur une machine qui ne peut pas faire tourner Docker, un JDK installé directement compile et exécute le code des TP, et le lanceur console de JUnit remplace gradle test.

JDK 17 (Eclipse Temurin). Dans un terminal neuf, java --version et javac --version doivent ensuite afficher 17.0.*x*. Un JDK 21 convient aussi, à condition de compiler avec --release 17.

# Windows (PowerShell) ; ou l'installateur .msi de https://adoptium.net, option JAVA_HOME cochee
winget install EclipseAdoptium.Temurin.17.JDK
# macOS (Homebrew) ; ou l'installateur .pkg de https://adoptium.net
brew install --cask temurin@17
# Ubuntu, Debian 12
sudo apt-get install openjdk-17-jdk
# Debian 13 : JDK 21, compilation avec --release 17
sudo apt-get install openjdk-21-jdk

JUnit. Télécharger le lanceur console de JUnit 5.10.1, junit-platform-console-standalone-1.10.1.jar, depuis Maven Central (https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.10.1/) et le placer dans le dossier du projet (le .gitignore fourni l’écarte de Git). Ce jar contient JUnit (la version de l’image) et son lanceur : il sert à compiler les tests et à les exécuter.

Commandes (dans tp1-fichiers/hello-dsa-fichiers/ ; sous Windows, le séparateur du -cp est ; et la liste se met entre guillemets : -cp "build/classes;junit-platform-console-standalone-1.10.1.jar").

javac --release 17 -d build/classes src/main/java/*.java
java -cp build/classes Main
javac --release 17 -d build/test-classes -cp build/classes:junit-platform-console-standalone-1.10.1.jar src/test/java/*.java
java -jar junit-platform-console-standalone-1.10.1.jar execute -cp build/classes:build/test-classes --scan-class-path

Le lanceur affiche l’arbre des tests, puis un résumé dont voici les lignes sur les tests :

Sortie attendue \(\rightarrow\)

[         3 tests found           ]
[         3 tests successful      ]
[         0 tests failed          ]

Dans les TP suivants, les deux commandes ci-dessous font le même travail :

gradle --no-daemon run --args="observer"    # avec Docker
java -cp build/classes Main observer        # sans Docker

Une option de la machine virtuelle se place avant -cp : le TP de la Séance 4, qui agrandit la pile par -Xss16m dans build.gradle, se lance ainsi par java -Xss16m -cp build/classes Main <mode>.

Aide-mémoire

Commande Rôle
docker run -it --rm -v "$(pwd):/workspace" elbachirtoure/dsa-java:v2.0 lancer le conteneur depuis ~/dsa (préfixe MSYS_NO_PATHCONV=1 sous Windows)
exit quitter le conteneur
help-tp aide-mémoire du conteneur
new-project <nom> créer un projet Gradle
javac Fichier.java, java Classe compiler, exécuter (hors projet)
gradle --no-daemon build compiler et tester
gradle --no-daemon test tester
gradle --no-daemon cleanTest test relancer des tests déjà passés
gradle --no-daemon run --args="…" exécuter Main, avec ses arguments
python3 script.py exécuter un script Python
git add . puis git commit -m "message" enregistrer une version
git status, git log --oneline état du dépôt, liste des versions

Pour cette séance

À télécharger

Retour au sommet