Entornos de Desarrollo en DAM y DAW: pruebas, JUnit y UML con ejercicios de examen resueltos

Entornos de Desarrollo es el módulo de primero de DAM y DAW en el que aprendes todo lo que rodea al código: cómo un programa fuente llega a ejecutarse, cómo se usa un IDE y su depurador, cómo se diseñan pruebas de caja blanca y de caja negra, cómo se escriben tests con JUnit, cómo se refactoriza y se documenta el código, cómo se trabaja con Git y cómo se dibujan diagramas UML de clases y de casos de uso. Para aprobarlo tienes que saber calcular la complejidad ciclomática de un método, sacar casos de prueba de un enunciado y pasar de un enunciado a un diagrama de clases, y de ahí a Java.
Mucha gente lo trata como "la asignatura fácil" y lo deja para el final. El problema es que cada ejercicio tiene un método fijo y se corrige paso a paso: si cuentas mal un nodo del grafo, todo lo que viene detrás está mal, y si inviertes la flecha de un <<include>>, el diagrama dice otra cosa. Aquí tienes qué entra, seis ejercicios de examen resueltos, las preguntas de test que más se repiten, los errores que más vemos en clase y cómo prepararlo.
Qué entra en Entornos de Desarrollo
Entornos de Desarrollo es el módulo 0487 y es común a los dos ciclos: aparece con el mismo código en el título de DAM (Real Decreto 450/2010) y en el de DAW (Real Decreto 686/2010), y se da en primer curso. Las herramientas (Eclipse, NetBeans, IntelliJ IDEA o VS Code; StarUML, Modelio, draw.io o PlantUML) y el peso de cada bloque cambian según la comunidad y el centro:
| Bloque | Qué te piden | Lo que más falla |
|---|---|---|
| Desarrollo de software | Fases del desarrollo, modelos de ciclo de vida, lenguajes compilados e interpretados, código fuente, objeto y ejecutable | Confundir análisis con diseño; no saber qué hace la máquina virtual de Java |
| Entornos integrados (IDE) | Instalar y configurar un IDE, plugins, compilar, ejecutar y depurar desde él | Depender del IDE y no saber compilar con javac |
| Diseño y realización de pruebas | Caja blanca (grafo de flujo, complejidad ciclomática, caminos), caja negra (clases de equivalencia, valores límite), JUnit | Contar mal los nodos de las condiciones compuestas; tests que no comprueban nada |
| Optimización y documentación | Refactorización, malos olores del código, Javadoc, control de versiones con Git | Refactorizar cambiando lo que hace el código |
| Diagramas de clases | Clases, atributos, métodos, visibilidad, relaciones y multiplicidad; paso a código | El rombo en el lado equivocado; confundir agregación y composición |
| Diagramas de comportamiento | Casos de uso, secuencia, actividad y estados | Invertir <<include>> y <<extend>> |
Lo normal es que se evalúe con un examen de ejercicios (grafo de flujo, casos de prueba y diagramas, a menudo en papel), prácticas en el ordenador con JUnit, Git y una herramienta UML y, en muchos centros a distancia, un examen tipo test.
Caja blanca y caja negra: la diferencia que siempre preguntan
Las pruebas de caja blanca miran el código por dentro: diseñas los casos para que se ejecuten todas las sentencias, todas las decisiones o todos los caminos independientes. Las pruebas de caja negra ignoran el código: diseñas los casos a partir de lo que el programa debe hacer, con sus entradas y las salidas esperadas.
No son alternativas, se complementan. La caja blanca encuentra código que nunca se ejecuta y caminos que nadie ha probado. La caja negra encuentra requisitos que el código no cumple, incluidas funciones que faltan, algo que la caja blanca no puede ver porque solo mira lo que está escrito.
Seis ejercicios típicos resueltos
El código es Java, el lenguaje del módulo en casi todos los centros. Los tests usan JUnit 5; si tu centro usa JUnit 6, el código es el mismo, porque mantiene las anotaciones y aserciones de JUnit 5.
1. Complejidad ciclomática, caminos independientes y casos de prueba
Enunciado: dado el siguiente método, dibuja el grafo de flujo, calcula la complejidad ciclomática de las tres formas, indica los caminos independientes y diseña un caso de prueba para cada uno.
public static int contarAprobados(int[] notas) {
int aprobados = 0; // 1
int i = 0; // 1
while (i < notas.length) { // 2
if (notas[i] >= 5 // 3
&& notas[i] <= 10) { // 4
aprobados++; // 5
}
i++; // 6
}
return aprobados; // 7
}
Paso 1: numerar los nodos. Las sentencias seguidas sin decisiones van en un mismo nodo (el 1). Cada condición es un nodo, y aquí está la trampa: notas[i] >= 5 && notas[i] <= 10 son dos condiciones, así que son dos nodos (3 y 4). Si la primera es falsa, Java no evalúa la segunda y salta directamente al nodo 6. Con || pasa lo mismo al revés: si la primera condición es verdadera, la segunda se salta.
Paso 2: dibujar el grafo.
1
│
▼
┌──► 2 ──── no ────► 7
│ │ sí
│ ▼
│ 3 ──── no ───┐
│ │ sí │
│ ▼ │
│ 4 ── no ──┐ │
│ │ sí │ │
│ ▼ │ │
│ 5 │ │
│ │ │ │
│ ▼ ▼ ▼
└─── 6 ◄───────┴──┘
Aristas: 1→2, 2→3, 2→7, 3→4, 3→6, 4→5, 4→6, 5→6 y 6→2. Son 9 aristas y 7 nodos.
Paso 3: calcular V(G) de las tres formas. Las tres tienen que dar lo mismo; si no, has contado mal algo.
| Fórmula | Cálculo | Resultado |
|---|---|---|
| Aristas − nodos + 2 | 9 − 7 + 2 | 4 |
| Nodos predicado + 1 | Nodos 2, 3 y 4: 3 + 1 | 4 |
| Número de regiones | 3 regiones cerradas + la exterior | 4 |
Paso 4: caminos independientes y casos de prueba. V(G) = 4 es el número de caminos independientes, así que hay que diseñar un caso de prueba para cada uno:
| Camino | Recorrido | Entrada | Salida esperada |
|---|---|---|---|
| C1 | 1-2-7 | {} |
0 |
| C2 | 1-2-3-6-2-7 | {3} |
0 |
| C3 | 1-2-3-4-6-2-7 | {11} |
0 |
| C4 | 1-2-3-4-5-6-2-7 | {7} |
1 |
Lo que hay que entender: un camino es independiente si recorre al menos una arista que los anteriores no habían usado; por eso cada fila de la tabla prueba una rama nueva, y que los nodos 2 y 6 se repitan es normal. V(G) mide también el riesgo del método: de 1 a 10, código sencillo y fácil de probar; de 11 a 20, más complejo y con riesgo moderado; de 21 a 50, complejo y con riesgo alto; por encima de 50, prácticamente imposible de probar entero. Si el enunciado usa un for, la inicialización va en el nodo de las sentencias anteriores, la condición es el nodo predicado y el incremento es un nodo al final del cuerpo, como aquí el i++. Un switch con n case cuenta como n nodos predicado.
2. Clases de equivalencia y valores límite
Enunciado: un formulario de inscripción a un gimnasio pide la edad, que debe ser un número entero entre 16 y 65, y el tipo de abono, que puede ser MENSUAL, TRIMESTRAL o ANUAL, cada uno con un precio distinto. Diseña los casos de prueba con clases de equivalencia y análisis de valores límite.
Paso 1: clases de equivalencia. Una clase agrupa valores que el programa debería tratar igual: si funciona con uno, debería funcionar con todos. Las reglas son estas:
- Si la condición es un rango (edad entre 16 y 65): una clase válida y dos no válidas, por debajo y por encima.
- Si es un conjunto de valores que el programa trata de forma distinta (cada abono tiene su precio): una clase válida por valor y una no válida.
- Si es una condición lógica ("debe ser un número entero"): una válida y una no válida.
| Condición de entrada | Clases válidas | Clases no válidas |
|---|---|---|
| Edad | (1) 16 ≤ edad ≤ 65 | (2) edad < 16 · (3) edad > 65 · (4) no es un número entero |
| Tipo de abono | (5) MENSUAL · (6) TRIMESTRAL · (7) ANUAL | (8) otro valor · (9) vacío |
Paso 2: casos de prueba. Cada caso válido cubre todas las clases válidas que pueda a la vez. Cada caso no válido cubre una sola clase no válida, con el resto de datos correctos:
| Caso | Edad | Abono | Clases que cubre | Resultado esperado |
|---|---|---|---|---|
| CP1 | 30 | MENSUAL | 1, 5 | Inscripción con precio mensual |
| CP2 | 40 | TRIMESTRAL | 1, 6 | Inscripción con precio trimestral |
| CP3 | 50 | ANUAL | 1, 7 | Inscripción con precio anual |
| CP4 | 10 | MENSUAL | 2 | Error: edad fuera de rango |
| CP5 | 70 | MENSUAL | 3 | Error: edad fuera de rango |
| CP6 | veinte | MENSUAL | 4 | Error: la edad debe ser un número |
| CP7 | 30 | SEMANAL | 8 | Error: abono no válido |
| CP8 | 30 | (vacío) | 9 | Error: el abono es obligatorio |
Paso 3: valores límite. Los errores se concentran en los bordes, donde alguien escribió > en vez de >=. Para el rango 16-65 se prueban los extremos y los valores justo fuera: 15, 16, 65 y 66, cada uno con el abono MENSUAL. 15 y 66 deben dar error y 16 y 65 deben aceptarse. Muchos profesores piden también los de justo dentro, 17 y 64.
Lo que hay que entender: si mezclas dos clases no válidas en un caso (edad 10 y abono SEMANAL), el programa da un error y no sabes cuál de las dos validaciones ha funcionado; puede que la del abono ni se haya ejecutado. Las clases de equivalencia reducen infinitos valores posibles a unos pocos representativos, y los valores límite van a buscar los errores donde más aparecen. El análisis de valores límite se aplica también al número de elementos (si se admiten de 1 a 5 acompañantes, prueba con 0, 1, 5 y 6) y a las salidas (un descuento que no puede pasar del 50 %).
3. Pruebas unitarias con JUnit 5
Enunciado: escribe las pruebas unitarias de la clase CuentaBancaria. Comprueba los casos normales, el límite de retirar todo el saldo y que se lanzan las excepciones correctas.
public class CuentaBancaria {
private double saldo;
public CuentaBancaria(double saldoInicial) {
if (saldoInicial < 0) {
throw new IllegalArgumentException("El saldo inicial no puede ser negativo");
}
this.saldo = saldoInicial;
}
public void ingresar(double cantidad) {
if (cantidad <= 0) {
throw new IllegalArgumentException("La cantidad debe ser positiva");
}
saldo += cantidad;
}
public void retirar(double cantidad) {
if (cantidad <= 0) {
throw new IllegalArgumentException("La cantidad debe ser positiva");
}
if (cantidad > saldo) {
throw new IllegalStateException("Saldo insuficiente");
}
saldo -= cantidad;
}
public double getSaldo() {
return saldo;
}
}
La clase de test:
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class CuentaBancariaTest {
private CuentaBancaria cuenta;
@BeforeEach
void setUp() {
cuenta = new CuentaBancaria(100.0);
}
@Test
@DisplayName("Ingresar una cantidad positiva aumenta el saldo")
void ingresarAumentaElSaldo() {
cuenta.ingresar(50.0);
assertEquals(150.0, cuenta.getSaldo(), 0.001);
}
@Test
void retirarDisminuyeElSaldo() {
cuenta.retirar(30.0);
assertEquals(70.0, cuenta.getSaldo(), 0.001);
}
@Test
void retirarTodoElSaldoLoDejaACero() {
cuenta.retirar(100.0);
assertEquals(0.0, cuenta.getSaldo(), 0.001);
}
@Test
void retirarMasDelSaldoLanzaExcepcionYNoCambiaElSaldo() {
IllegalStateException ex = assertThrows(IllegalStateException.class,
() -> cuenta.retirar(100.01));
assertEquals("Saldo insuficiente", ex.getMessage());
assertEquals(100.0, cuenta.getSaldo(), 0.001);
}
@ParameterizedTest
@ValueSource(doubles = {0.0, -1.0, -50.0})
void ingresarUnaCantidadNoPositivaLanzaExcepcion(double cantidad) {
assertThrows(IllegalArgumentException.class, () -> cuenta.ingresar(cantidad));
}
@Test
void crearUnaCuentaConSaldoNegativoLanzaExcepcion() {
assertThrows(IllegalArgumentException.class, () -> new CuentaBancaria(-1.0));
}
}
Con Maven, la dependencia es org.junit.jupiter:junit-jupiter con <scope>test</scope>. El asistente de NetBeans, Eclipse o IntelliJ para crear un test la añade por ti y coloca la clase de test en src/test/java, en el mismo paquete que la clase que prueba.
Lo que hay que entender: cada test sigue tres pasos: preparar, ejecutar y comprobar. @BeforeEach crea una cuenta nueva antes de cada test, así que ninguno depende de lo que hizo otro ni del orden en que se ejecuten. assertThrows comprueba que se lanza la excepción esperada y te la devuelve para revisar el mensaje. Con double, assertEquals lleva un tercer parámetro, el margen de error, porque 0.1 + 0.2 no da exactamente 0.3. @ParameterizedTest repite el mismo test con varios valores. Fíjate en que retirar 100,01 € con 100 € de saldo es un valor límite del ejercicio anterior: los tests se diseñan con las mismas técnicas. Y un test sin assert siempre pasa, aunque el método esté mal. En una aplicación real el dinero no se guarda en double, sino en BigDecimal o en céntimos con un long; aquí se usa double porque es lo que traen casi todos los enunciados.
Muchos apuntes y cuestionarios siguen en JUnit 4. Estas son las equivalencias que más se preguntan:
| JUnit 4 | JUnit 5 |
|---|---|
import org.junit.Test |
import org.junit.jupiter.api.Test |
@Before / @After |
@BeforeEach / @AfterEach |
@BeforeClass / @AfterClass |
@BeforeAll / @AfterAll |
@Ignore |
@Disabled |
@Test(expected = MiExcepcion.class) |
assertThrows(MiExcepcion.class, () -> ...) |
Mensaje como primer parámetro de assertEquals |
Mensaje como último parámetro |
4. Diagrama de clases UML y paso a Java
Enunciado: una academia quiere gestionar sus cursos. Cada curso tiene un código y un título, lo imparte un profesor y se compone de varias lecciones, que no existen fuera del curso. Un profesor puede impartir varios cursos y pertenece a un departamento, que agrupa a varios profesores. Los alumnos se matriculan en uno o más cursos, y de cada matrícula se guarda la fecha. Hay alumnos presenciales y online; de los online se guarda su usuario de la plataforma. Dibuja el diagrama de clases.
Este es el diagrama escrito en PlantUML, que se escribe como texto: pégalo en el editor online de PlantUML o en el plugin de tu IDE para verlo dibujado. En StarUML, Modelio o draw.io se dibuja lo mismo con el ratón.
@startuml
abstract class Alumno {
- dni : String
- nombre : String
+ {abstract} calcularCuota() : double
}
class AlumnoPresencial {
+ calcularCuota() : double
}
class AlumnoOnline {
- usuarioPlataforma : String
+ calcularCuota() : double
}
class Curso {
- codigo : String
- titulo : String
+ agregarLeccion(titulo : String, minutos : int) : void
}
class Leccion {
- titulo : String
- minutos : int
}
class Profesor {
- nombre : String
- email : String
}
class Departamento {
- nombre : String
}
class Matricula {
- fecha : LocalDate
}
Alumno <|-- AlumnoPresencial
Alumno <|-- AlumnoOnline
Departamento "1" o-- "1..*" Profesor
Profesor "1" -- "0..*" Curso : imparte
Curso "1" *-- "1..*" Leccion
Alumno "0..*" -- "1..*" Curso
(Alumno, Curso) .. Matricula
@enduml
El método calcularCuota no sale del enunciado; está para enseñar cómo se dibuja un método abstracto (en cursiva) y su redefinición en las subclases. Así se lee cada relación y así pasa a Java:
| Relación | Cómo se dibuja | Qué significa | En Java |
|---|---|---|---|
| Asociación | Línea continua | Una clase conoce a otra de forma duradera | Un atributo del tipo de la otra clase |
| Agregación | Rombo vacío junto al todo | Todo-parte; las partes existen por sí mismas | Una lista de partes que llegan ya creadas |
| Composición | Rombo relleno junto al todo | Todo-parte; las partes nacen y mueren con el todo | El todo crea sus partes dentro |
| Herencia | Flecha con triángulo vacío hacia la clase padre | "Es un" | extends |
| Realización | Línea discontinua con triángulo vacío | Implementa una interfaz | implements |
| Dependencia | Línea discontinua con flecha abierta | Usa a otra clase de forma puntual | Un parámetro o una variable local |
Y así queda en Java la parte del curso, el departamento y la matrícula (cada clase va en su fichero):
// Curso.java
public class Curso {
private String codigo;
private String titulo;
private Profesor profesor; // asociación
private final List<Leccion> lecciones = new ArrayList<>(); // composición
public Curso(String codigo, String titulo, Profesor profesor) {
this.codigo = codigo;
this.titulo = titulo;
this.profesor = profesor;
}
public void agregarLeccion(String titulo, int minutos) {
lecciones.add(new Leccion(titulo, minutos)); // el curso crea sus lecciones
}
}
// Departamento.java
public class Departamento {
private String nombre;
private final List<Profesor> profesores = new ArrayList<>(); // agregación
public Departamento(String nombre) {
this.nombre = nombre;
}
public void agregarProfesor(Profesor profesor) { // el profesor ya existía
profesores.add(profesor);
}
}
// Matricula.java
public class Matricula {
private final Alumno alumno;
private final Curso curso;
private final LocalDate fecha;
public Matricula(Alumno alumno, Curso curso, LocalDate fecha) {
this.alumno = alumno;
this.curso = curso;
this.fecha = fecha;
}
}
Lo que hay que entender: para distinguir agregación de composición, pregúntate si las partes desaparecen al borrar el todo. Si borras un curso, sus lecciones desaparecen (composición); si cierras un departamento, sus profesores siguen existiendo (agregación). El rombo va siempre pegado al todo, y en la composición la multiplicidad de ese lado es 1, porque una parte no puede pertenecer a dos todos. La multiplicidad se lee desde la otra clase: el 0..* junto a Curso dice cuántos cursos imparte un profesor, y el 1 junto a Profesor, cuántos profesores tiene un curso. Matricula es una clase asociación: guarda datos de la relación (la fecha), no de un alumno ni de un curso, y en Java se convierte en una clase con una referencia a cada extremo. Si dibujas la asociación con Profesor, no pongas además - profesor : Profesor como atributo de Curso: es la misma información dos veces. La visibilidad se marca con + (pública), - (privada), # (protegida) y ~ (de paquete), y lo estático va subrayado. El diagrama se parece al modelo entidad-relación de Bases de Datos, pero aquí hay métodos y herencia, y una relación muchos a muchos no se convierte en tabla.
5. Diagrama de casos de uso con include y extend
Enunciado: en una tienda online, el cliente puede buscar productos y realizar pedidos. Para realizar un pedido tiene que haber iniciado sesión y pagar, y el pago lo procesa una pasarela de pago externa. Al hacer el pedido puede, si quiere, aplicar un cupón de descuento. El administrador gestiona el catálogo, para lo que también tiene que iniciar sesión. Dibuja el diagrama de casos de uso.
@startuml
left to right direction
actor Cliente
actor Administrador
actor "Pasarela de pago" as Pasarela
rectangle "Tienda online" {
usecase "Buscar producto" as Buscar
usecase "Realizar pedido" as Pedido
usecase "Iniciar sesión" as Login
usecase "Pagar pedido" as Pagar
usecase "Aplicar cupón" as Cupon
usecase "Gestionar catálogo" as Catalogo
}
Cliente --> Buscar
Cliente --> Pedido
Pedido ..> Login : <<include>>
Pedido ..> Pagar : <<include>>
Cupon ..> Pedido : <<extend>>
Pagar --> Pasarela
Administrador --> Catalogo
Catalogo ..> Login : <<include>>
@enduml
Lo que hay que entender: <<include>> es un paso obligatorio que se reutiliza en varios casos de uso: realizar un pedido siempre incluye iniciar sesión y pagar. La flecha va del caso base al incluido. <<extend>> es un comportamiento opcional que solo ocurre a veces: aplicar un cupón amplía realizar un pedido cuando el cliente quiere. La flecha va al revés, del caso que extiende al caso base. Para no liarte, la flecha sale del caso que conoce la relación: realizar un pedido sabe que necesita iniciar sesión, pero no sabe nada del cupón; el cupón sí sabe a qué caso amplía. Un actor es un rol, no una persona concreta, y puede ser otro sistema, como la pasarela de pago; los actores se dibujan fuera del rectángulo del sistema. Cada caso de uso se nombra con un verbo en infinitivo y describe un objetivo completo del actor ("Realizar pedido"), no un paso suelto ("Pulsar botón de comprar").
6. Refactorización y documentación con Javadoc
Enunciado: el siguiente método funciona, pero es difícil de mantener. Identifica los malos olores, refactorízalo sin cambiar su comportamiento y documéntalo con Javadoc.
public double calc(double p, int t) {
double r = 0;
if (t == 1) {
r = p - p * 0.15;
r = r + r * 0.21;
} else if (t == 2) {
r = p - p * 0.05;
r = r + r * 0.21;
} else {
r = p + p * 0.21;
}
return r;
}
Los malos olores:
- Nombres que no dicen nada:
calc,p,tyr. - Números mágicos:
0.15,0.05,0.21,1y2. Nadie sabe qué es un cliente de tipo 2 sin preguntar. - Código duplicado: el IVA se aplica en las tres ramas.
- Un
intpara representar un tipo:calc(100, 7)compila y se trata como cliente general sin avisar.
Refactorizado:
public enum TipoCliente { GENERAL, SOCIO, ESTUDIANTE }
public class CalculadoraPrecios {
private static final double IVA = 0.21;
private static final double DESCUENTO_SOCIO = 0.15;
private static final double DESCUENTO_ESTUDIANTE = 0.05;
/**
* Calcula el precio final de un producto aplicando el descuento del cliente y el IVA.
*
* @param precioBase precio sin descuento ni IVA, en euros
* @param tipoCliente tipo de cliente que hace la compra
* @return el precio final con el descuento y el IVA aplicados
*/
public double calcularPrecioFinal(double precioBase, TipoCliente tipoCliente) {
double precioConDescuento = precioBase * (1 - porcentajeDescuento(tipoCliente));
return aplicarIva(precioConDescuento);
}
private double porcentajeDescuento(TipoCliente tipoCliente) {
return switch (tipoCliente) {
case SOCIO -> DESCUENTO_SOCIO;
case ESTUDIANTE -> DESCUENTO_ESTUDIANTE;
case GENERAL -> 0.0;
};
}
private double aplicarIva(double precio) {
return precio * (1 + IVA);
}
}
Las refactorizaciones aplicadas, con el nombre que tienen en el menú Refactor del IDE: renombrar (calc → calcularPrecioFinal), introducir constante (los porcentajes), extraer método (porcentajeDescuento y aplicarIva, que elimina la duplicación) y cambiar la firma del método (el int pasa a ser un enum).
Lo que hay que entender: refactorizar es cambiar la estructura del código sin cambiar lo que hace. Por eso se refactoriza con los tests en verde antes y después: si un test falla tras refactorizar, no has refactorizado, has metido un error. Usa el menú del IDE en vez de hacerlo a mano: Renombrar cambia el nombre en todos los sitios donde se usa y Cambiar firma actualiza todas las llamadas. Javadoc genera la documentación en HTML a partir de los comentarios /** */, desde el IDE o con javadoc -d doc CalculadoraPrecios.java TipoCliente.java; por defecto solo documenta lo público y lo protegido, y con -private incluye también lo privado. Las etiquetas que más se preguntan son @param, @return, @throws, @author, @version, @see y @deprecated.
Preguntas de test típicas de Entornos de Desarrollo
Son las preguntas cortas que más se repiten en exámenes y en cuestionarios tipo Daypo.
| Fase del desarrollo | Qué se hace |
|---|---|
| Análisis | Decidir qué tiene que hacer el programa: requisitos funcionales y no funcionales |
| Diseño | Decidir cómo se va a hacer: arquitectura, datos, módulos y diagramas |
| Codificación | Escribir el código fuente |
| Pruebas | Comprobar que funciona y que cumple los requisitos |
| Documentación | Manuales técnicos y de usuario; se hace a lo largo de todo el proceso |
| Explotación | Instalar el programa y ponerlo en marcha en el entorno del cliente |
| Mantenimiento | Corregir, adaptar y mejorar el programa mientras se usa |
| Tipo de mantenimiento | Para qué |
|---|---|
| Correctivo | Corregir errores que aparecen durante el uso |
| Adaptativo | Adaptar el programa a cambios en su entorno: nuevo sistema operativo, nueva versión de Java o de la base de datos |
| Perfectivo | Añadir funciones o mejorar el rendimiento a petición del usuario |
| Preventivo | Mejorar la calidad interna para facilitar cambios futuros sin cambiar lo que hace, por ejemplo refactorizando |
- ¿Lenguaje compilado o interpretado? En uno compilado, el compilador traduce todo el programa a código máquina antes de ejecutarlo (C, C++). En uno interpretado, el intérprete lo traduce y lo ejecuta instrucción a instrucción (Python, PHP, JavaScript). Java es mixto:
javaclo compila a bytecode (ficheros.class) y la máquina virtual de Java (JVM) lo ejecuta en cualquier sistema, interpretándolo y compilando a código máquina las partes que más se usan (compilación JIT). - ¿Código fuente, objeto y ejecutable? El fuente es el que escribes. El objeto es el resultado de compilarlo, en código máquina pero todavía sin unir a las bibliotecas que usa. El ejecutable lo genera el enlazador (linker) uniendo el código objeto con esas bibliotecas.
- ¿Qué modelo de ciclo de vida? Cascada: las fases van una detrás de otra; sirve cuando los requisitos están claros y no van a cambiar. Iterativo e incremental: el producto se entrega por partes que van creciendo. Espiral: iterativo y con análisis de riesgos en cada vuelta; para proyectos grandes. Ágiles (Scrum, XP): iteraciones cortas, entregas frecuentes y cambios bienvenidos. En Scrum, cada iteración es un sprint de un mes o menos, el Product Owner prioriza el product backlog y el Scrum Master ayuda al equipo a aplicar Scrum.
- ¿Tipos de pruebas según su alcance? Unitarias (un método o una clase), de integración (varios módulos juntos), de sistema (el sistema completo, incluido rendimiento y seguridad), de aceptación (el cliente comprueba que es lo que pidió: alfa, con el desarrollador delante, y beta, en el entorno del usuario sin él) y de regresión (repetir pruebas tras un cambio para comprobar que no se ha roto nada).
- ¿Qué es la cobertura de código? El porcentaje de código que ejecutan tus tests. Se mide con JaCoCo, con EclEmma en Eclipse o con Run with Coverage en IntelliJ. Un 100 % de cobertura no garantiza que no haya errores: solo que cada línea se ha ejecutado alguna vez.
- ¿Step Over, Step Into o Step Out? Step Over ejecuta la línea actual sin entrar en los métodos que llama. Step Into entra en el método. Step Out termina el método actual y vuelve al que lo llamó. Un punto de ruptura condicional solo detiene la ejecución cuando se cumple una condición, como
i == 500. - ¿Cuántos diagramas tiene UML? UML 2.5 tiene 14: siete estructurales (clases, objetos, componentes, despliegue, paquetes, estructura compuesta y perfiles) y siete de comportamiento (casos de uso, actividad, máquina de estados y los cuatro de interacción: secuencia, comunicación, tiempos y visión global de la interacción).
- ¿Qué muestra un diagrama de secuencia? Los mensajes entre objetos ordenados en el tiempo, de arriba abajo. Cada objeto tiene una línea de vida vertical, las barras de activación indican cuándo está ejecutando algo, y los fragmentos
alt,optylooprepresentan alternativas, opciones y bucles. - ¿Git es centralizado o distribuido? Distribuido: cada copia del repositorio tiene el historial completo y puedes hacer commits sin conexión. Subversion (SVN) es centralizado: el historial vive en un servidor. El flujo completo con ramas, etiquetas y remotos está resuelto en Despliegue de Aplicaciones Web.
- ¿IDE o editor? Un IDE (Eclipse, NetBeans, IntelliJ IDEA) integra editor, compilador, depurador, refactorización y gestión del proyecto. VS Code es un editor que, con extensiones, se acerca mucho a un IDE.
Los errores que más suspenden en Entornos de Desarrollo
Son los que más vemos cuando un alumno nos enseña un examen corregido o una práctica que no funciona.
Contar una condición compuesta como un solo nodo. if (a > 0 && b > 0) son dos nodos predicado. Si los cuentas como uno, V(G) sale uno menos, te falta un camino y te falta un caso de prueba.
Calcular V(G) y quedarse ahí. El ejercicio pide grafo, V(G), caminos y casos de prueba con su entrada y su salida esperada. Mucha nota se pierde en la tabla final, que es la que de verdad sirve para probar.
Varias clases no válidas en el mismo caso de prueba. Un error tapa al otro y no sabes qué validación ha funcionado.
Olvidar los valores justo fuera del rango. Probar 16 y 65 sin probar 15 y 66 deja sin comprobar si el programa rechaza lo que debe.
Mezclar JUnit 4 y JUnit 5. Un @Before de JUnit 4 en un test de JUnit 5 se ignora sin avisar: el setUp no se ejecuta y los tests fallan con NullPointerException. Y un test con import org.junit.Test no lo ejecuta JUnit 5 si el proyecto no tiene el motor de JUnit 4. Revisa que todos los imports empiecen por org.junit.jupiter.
Tests que no comprueban nada. Llamar al método sin ningún assert siempre da verde. Un test tiene que poder fallar.
El rombo en el lado de la parte. El rombo va pegado a la clase que representa el todo: el curso, no la lección.
Invertir las flechas de include y extend. La de <<include>> sale del caso base; la de <<extend>> llega al caso base.
Refactorizar sin tests. Si no tienes tests en verde antes y después, no puedes demostrar que el comportamiento no ha cambiado.
Depender del IDE para todo. Si el examen es en papel, no hay autocompletado ni botón de generar el diagrama. Ejercicios como los de este artículo hay que saber hacerlos con lápiz.
Cómo prepararlo
Haz cada ejercicio a mano y compruébalo en el ordenador. Dibuja el grafo y saca los casos en papel; después escribe esos casos como tests de JUnit y ejecútalos con cobertura para ver si de verdad recorren todas las ramas.
Usa tus ejercicios de Programación como material. Cualquier método con bucles e if sirve para practicar la complejidad ciclomática, y cualquier práctica con varias clases sirve para dibujar su diagrama de clases. Así preparas los dos módulos a la vez, y Programación es la que más pesa: tienes cómo aprobarla sin memorizar código.
Practica en los dos sentidos: de enunciado a diagrama y de diagrama a código. Si te cuesta decidir qué es una clase y qué es un atributo, repasa la programación orientada a objetos explicada con un ejemplo.
Aprende el depurador con un error real. Puntos de ruptura, Step Into, Step Over e inspección de variables entran en el examen y te ahorran horas en todas las asignaturas. Tienes el método en cómo depurar código paso a paso.
Repasa la teoría con preguntas cortas. Los cuestionarios de Daypo sirven para repasar fases, mantenimiento, tipos de pruebas y UML, pero los crean otros usuarios y algunas respuestas están mal marcadas: contrasta con tus apuntes lo que no te cuadre.
Usa Git en todas tus prácticas desde el primer día. Commits pequeños con mensajes claros. Al final de curso lo harás sin pensar, y lo vas a necesitar en el proyecto final de DAM y DAW.
Cuándo pedir ayuda
Si haces los ejercicios sin mirar la solución y sabes explicar por qué está cada caso de prueba, vas bien. Merece la pena buscar ayuda cuando:
- Calculas V(G) y te sale un número distinto según la fórmula que uses.
- No sabes qué poner como salida esperada en los casos de prueba, o tus tests de JUnit no se ejecutan y no sabes por qué.
- Ante el enunciado de un diagrama de clases no sabes qué es una clase, qué es un atributo y qué es una relación.
- Programación tampoco va bien: Entornos se apoya en ella y conviene reforzar las dos a la vez.
En nuestras clases de refuerzo para DAM y DAW trabajamos sobre tus enunciados y tus prácticas: dibujas tú el grafo o el diagrama, lo revisamos juntos y lo pasas a código y a tests, para que en el examen sepas hacerlo solo. Son clases online uno a uno a 25 € la hora, sin matrícula ni permanencia.
Preguntas frecuentes
¿Qué es Entornos de Desarrollo?
Es el módulo 0487 de primer curso de los ciclos de Desarrollo de Aplicaciones Multiplataforma (DAM) y Desarrollo de Aplicaciones Web (DAW). Enseña las herramientas y técnicas que rodean a la programación: el IDE y el depurador, el diseño de pruebas, JUnit, la refactorización, la documentación, el control de versiones con Git y los diagramas UML.
¿Qué se da en Entornos de Desarrollo?
Seis bloques: desarrollo de software (fases, ciclos de vida y lenguajes), entornos integrados de desarrollo, diseño y realización de pruebas, optimización y documentación del código, diagramas de clases y diagramas de comportamiento (casos de uso, secuencia, actividad y estados).
¿Es difícil Entornos de Desarrollo?
Suele ser de los módulos más llevaderos de primero si Programación va bien, porque casi todo lo que hace es aplicar técnicas sobre código Java: probarlo, documentarlo, mejorarlo y representarlo en diagramas. Si Programación se te atraganta, Entornos también. Tienes una visión general del curso en si es difícil DAM o DAW.
¿Cómo se calcula la complejidad ciclomática?
Con cualquiera de estas tres fórmulas sobre el grafo de flujo, que deben dar el mismo resultado: aristas − nodos + 2, nodos predicado + 1, o número de regiones contando la exterior. Para comprobarlo rápido sin dibujar el grafo, cuenta cada if, while, do-while, for y case, y cada && y || dentro de una condición, y suma 1. En el ejercicio 1 de este artículo: un while, un if y un &&, más 1, dan 4.
¿Qué diferencia hay entre include y extend?
<<include>> es un comportamiento obligatorio que forma parte del caso de uso base siempre (realizar un pedido incluye pagar); la flecha va del caso base al incluido. <<extend>> es un comportamiento opcional que amplía el caso base solo en ciertas condiciones (aplicar un cupón); la flecha va del caso que extiende al caso base.
¿Se convalida Entornos de Desarrollo entre DAM y DAW?
Sí. Es el mismo módulo en los dos ciclos, con el mismo código (0487), así que si lo apruebas en uno no tienes que volver a cursarlo en el otro; lo pides en la secretaría del centro al matricularte. Lo mismo ocurre con los demás módulos comunes de primero: Programación, Bases de Datos, Sistemas Informáticos y Lenguajes de Marcas.