Laboratorio — Entorno de trabajo

Todo el trabajo práctico del curso (ejemplos de clase, laboratorios y el proyecto Bodega) se compila y se prueba dentro de un contenedor Docker idéntico para todos, con el JDK 25 y JUnit ya instalados. Así evitamos el “en mi computadora sí funciona”: si las pruebas pasan en el contenedor, pasan en la revisión.

1. Requisitos

Sistema Qué instalar
Linux Docker Engine
Windows 10/11 WSL2 + Docker Desktop con integración WSL. Trabajen dentro del sistema de archivos de WSL (~/), no en C:\.
macOS (Intel o Apple Silicon) Docker Desktop

La imagen es nativa: en una laptop con procesador ARM (Apple Silicon, por ejemplo) corre como ARM, y en una con procesador Intel o AMD corre como x86-64. No hay emulación, así que compilar y correr las pruebas es rápido.

Si no pueden instalar Docker, vean la sección 6.

2. Levantar el contenedor

Desde la carpeta del repositorio del curso (o la de su proyecto):

./env/run.sh                 # la primera vez construye la imagen (unos minutos)

run.sh monta el directorio actual en /work dentro del contenedor. Los archivos se editan con el editor de siempre en su computadora y se compilan y ejecutan en el contenedor. También acepta un comando:

./env/run.sh make -C code/week-03 test
./env/run.sh --rebuild       # reconstruye la imagen (si cambió el Dockerfile)

Si su proyecto está en otra carpeta, llamen a run.sh con su ruta desde esa carpeta: /ruta/al/curso/env/run.sh.

La imagen (env/Dockerfile, basada en Eclipse Temurin 25) incluye:

  • el JDK 25: javac, java, jshell, javadoc, jar
  • JUnit 6 en un solo jar, en la ruta de la variable $JUNIT_JAR
  • make, git y los editores de terminal vim y nano
  • gcc, para compilar los ejemplos en C que comparamos con Java (semana 1)

3. Java desde la terminal

jshell: probar una idea en segundos

jshell evalúa expresiones y declaraciones de Java una por una, sin crear un archivo. Sirve para explorar la API y para probar un método antes de escribirlo en una clase.

$ jshell
jshell> var nombres = new java.util.ArrayList<String>();
jshell> nombres.add("arroz");
jshell> nombres.size()
$3 ==> 1
jshell> /exit

/help muestra los comandos; /vars y /methods listan lo que se definió.

Un archivo: java Archivo.java

Un programa de un solo archivo se ejecuta sin compilarlo antes: java lo compila en memoria y lo corre. Desde Java 25 el archivo puede ser compacto, sin declarar la clase (JEP 512):

// Hola.java
void main() {
    IO.println("Hola, " + 2 * 21);
}
java Hola.java

Así trabajamos en las semanas 1 y 2. Desde la semana 3 el código va en clases normales, en varios archivos.

Varios archivos: javac y java

javac -Xlint:all -d build *.java    # compila; los .class quedan en build/
java -cp build Main                 # ejecuta la clase Main

-Xlint:all activa todos los warnings: el código del curso compila sin ninguno. -cp (class path) le dice a java dónde buscar las clases compiladas. Con paquetes, el nombre de la clase va completo: java -cp build bodega.Main.

4. make y las pruebas con JUnit

Cada carpeta code/week-NN/ (y cada entrega del proyecto) trae un Makefile:

Comando Qué hace
make Compila todo a build/
make test Compila, corre los programas de demostración y las pruebas JUnit (*Test.java)
make clean Borra build/

Por dentro, make test hace algo como esto:

javac -Xlint:all -d build -cp "$JUNIT_JAR" *.java
java -jar "$JUNIT_JAR" execute --class-path build --scan-class-path

Una prueba es un método con @Test en una clase cuyo nombre termina en Test:

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

class CounterTest {
    @Test
    void incrementAddsOne() {
        Counter c = new Counter(5);
        c.increment();
        assertEquals(6, c.value());
    }
}

Al final, JUnit imprime un resumen: tests successful y tests failed. Si una prueba falla, muestra el valor esperado, el obtenido y el stack trace. Para correr solo una clase de prueba: java -jar "$JUNIT_JAR" execute --class-path build --select-class CounterTest.

5. Editar el código

Cualquier editor sirve, porque se compila en el contenedor. Recomendamos uno que entienda Java (autocompletado, errores mientras escriben, refactoring):

Para que el editor no marque errores en las pruebas, necesita conocer el jar de JUnit (ver la sección 6). Aunque el editor compile por su cuenta, la referencia es siempre make test en el contenedor.

6. Alternativa: JDK local y un editor

Si no pueden usar Docker, instalen un JDK 25 en su computadora; por ejemplo, Eclipse Temurin. Comprueben la versión:

java -version        # debe decir 25

Descarguen el jar de JUnit (el mismo que trae la imagen) y apunten JUNIT_JAR a él:

mkdir -p ~/lib
curl -fL -o ~/lib/junit.jar \
  https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/6.1.3/junit-platform-console-standalone-6.1.3.jar
export JUNIT_JAR=~/lib/junit.jar     # agréguenlo a ~/.bashrc o ~/.zshrc

Con eso, make test funciona igual que en el contenedor (en Windows, dentro de WSL). En VS Code o IntelliJ agreguen junit.jar como biblioteca del proyecto. Antes de entregar, corran las pruebas al menos una vez en el contenedor.

7. Solución de problemas

Síntoma Causa probable
Cannot connect to the Docker daemon Docker Desktop no está abierto.
error: class X is public, should be declared in a file named X.java El nombre del archivo no coincide con el de la clase pública.
Error: Could not find or load main class Main Falta -cp build, o la clase está en un paquete y hay que escribir el nombre completo.
package org.junit.jupiter.api does not exist Falta -cp "$JUNIT_JAR" al compilar, o JUNIT_JAR no está definida (JDK local).
JUnit dice 0 tests found La clase de prueba no termina en Test, falta @Test, o no se compiló a build/.
Unknown options: '--class-path' Falta el subcomando execute después de -jar "$JUNIT_JAR".
unsupported class file major version Se compiló con un JDK distinto del que ejecuta; usen JDK 25 para ambos.
Archivos cuyo owner es root en su computadora (Linux) Agregar --user "$(id -u):$(id -g)" al docker run de run.sh.
Todo muy lento en Windows El código está en C:\; muévanlo a ~/ dentro de WSL.