Paquetes, diagramas de clases y pruebas unitarias en Java
PrincipianteUnidad 10 de 11 del curso Curso de Programación orientada a objetos con Java
Lo que aprenderás
- Agrupa clases relacionadas en paquetes, guarda cada archivo fuente donde lo esperan el compilador y las herramientas de construcción, y utiliza clases entre paquetes mediante importaciones y el nivel de acceso adecuado.
- Declara paquetes y hazlos corresponder con sus carpetasEscribe una declaración de paquete, elige un nombre de paquete convencional, guarda el archivo en la carpeta correspondiente y haz referencia a las clases mediante sus nombres completamente cualificados.
- Utiliza clases de otros paquetes mediante importacionesImporta clases individuales o paquetes completos, identifica qué clases no necesitan importación, resuelve conflictos entre clases con el mismo nombre y utiliza importaciones estáticas.
- Controla el acceso entre paquetesElige entre public, protected, acceso de paquete y private para que cada paquete exponga solo lo que necesitan los demás paquetes.
- Declara paquetes y hazlos corresponder con sus carpetas
- Interpreta y dibuja diagramas de clases que muestran clases, sus miembros y las relaciones entre ellas, y pasa de diagramas a código Java y viceversa.
- Dibuja e interpreta una clase individualRepresenta el nombre, los atributos y las operaciones de una clase con sus tipos y visibilidad, y marca los miembros estáticos, las clases abstractas y las interfaces.
- Representa asociaciones y multiplicidadesConecta clases mediante asociaciones, interpreta y escribe multiplicidades y navegabilidad, y distingue composición, agregación y dependencia.
- Modela la herencia y pasa de diagramas a código y viceversaRepresenta la herencia y la implementación de interfaces, escribe código Java a partir de un diagrama y dibuja un diagrama a partir de código existente.
- Dibuja e interpreta una clase individual
- Divide un programa para que la interfaz de usuario, la lógica de la aplicación y el almacenamiento de datos estén en clases y paquetes separados, con dependencias que apuntan en una sola dirección.
- Separa la interfaz de usuario de la lógica del programaMantén la lectura de entrada y la impresión de salida en una clase de interfaz de usuario, mantén las reglas del programa en clases de lógica y conecta ambas partes en el método main.
- Organiza capas, paquetes y dependenciasOrganiza las clases en una capa de presentación, una capa de lógica y una capa de acceso a datos, mantén las dependencias dirigidas hacia abajo y conecta las capas mediante interfaces.
- Separa la interfaz de usuario de la lógica del programa
- Escribe pruebas unitarias automatizadas con JUnit, elige casos de prueba que revelen errores, mantén las pruebas independientes y legibles, y desarrolla código nuevo escribiendo primero las pruebas.
- Escribe y ejecuta una primera prueba de JUnitCrea una clase de prueba, escribe métodos de prueba con aserciones, ejecútalos e interpreta el resultado de una prueba que pasa y de otra que falla.
- Elige casos de prueba que detecten erroresSelecciona entradas típicas, de valores límite y no válidas, prueba las excepciones esperadas y da a cada prueba un propósito y un nombre claros.
- Mantén las pruebas independientes y fáciles de mantenerPrepara objetos de prueba nuevos antes de cada prueba, evita el estado compartido, ejecuta una prueba con varias entradas y prueba la lógica por separado de la entrada y la salida.
- Desarrolla código escribiendo primero las pruebas con TDDSigue el ciclo rojo, verde y refactorización a partir de una lista de pruebas, avanza en pasos pequeños e identifica lo que promete y lo que no promete el desarrollo guiado por pruebas.
- Escribe y ejecuta una primera prueba de JUnit
- Utiliza una herramienta de construcción para gestionar bibliotecas, sigue la estructura estándar del proyecto y compila, prueba y empaqueta un programa con un solo comando.
- Configura y construye un proyecto MavenDescribe un proyecto en pom.xml mediante coordenadas y dependencias, sigue la estructura estándar de carpetas y ejecuta el ciclo de vida de construcción.
- Configura un proyecto Gradle y corrige los fallos de construcciónEscribe el script de construcción equivalente en Gradle, ejecuta pruebas de JUnit en JUnit Platform, compara las dos herramientas y encuentra la causa de una construcción fallida.
- Configura y construye un proyecto Maven
Guía de estudio de Paquetes en Java
1Organiza un programa Java en paquetes
A medida que crece un programa, los nombres de clases y las carpetas necesitan una estructura que tanto las personas como Java puedan seguir. Los paquetes agrupan tipos relacionados y definen límites de acceso. Las importaciones facilitan el uso de esos tipos sin exponer sus detalles internos. Relacionarás una declaración con su ruta de código fuente, resolverás nombres entre paquetes y decidirás a qué clases, constructores y métodos debe poder llamar otra parte del programa.
1.1 Nombres de paquetes y carpetas de código fuente
Un paquete agrupa clases e interfaces relacionadas bajo un mismo nombre. También separa nombres: dos clases llamadas Parcel pueden coexistir cuando sus nombres de paquete son diferentes. Un paquete proporciona a Java tanto un límite de acceso como una forma de organizar archivos.
Declara primero el paquete
Un archivo fuente ordinario comienza con su declaración de paquete, seguida de las importaciones y después la clase. Los comentarios pueden aparecer antes de la declaración. Por ejemplo:
package com.example.parcels.domain;
public class Parcel {
private int weightGrams;
public Parcel(int weightGrams) {
this.weightGrams = weightGrams;
}
}
Cada tipo de nivel superior de ese archivo pertenece a com.example.parcels.domain. Un archivo tiene como máximo una declaración de paquete. La clase pública Parcel pertenece a Parcel.java; cambiar solo la carpeta no cambia el paquete declarado.
Convierte puntos en carpetas
El directorio raíz del código fuente es la carpeta donde comienza la ruta del paquete. En un proyecto estándar de Maven o Gradle, el código Java principal comienza en src/main/java. Sustituye cada punto del nombre del paquete por una barra inclinada y después añade el nombre de la clase:
src/main/java/
com/example/parcels/domain/Parcel.java
El paquete es com.example.parcels.domain, no src.main.java.com.example.parcels.domain. Las tres primeras carpetas describen la estructura de construcción; no forman parte del nombre Java. Cuando un IDE informa de una discrepancia de paquete, compara la declaración con la ruta dentro del directorio raíz del código fuente. Mueve el archivo o corrige la declaración para que coincidan.
Los nombres de paquetes utilizan letras minúsculas. Las organizaciones suelen invertir su dominio: example.com se convierte en com.example, seguido del proyecto y su área. Un proyecto sin dominio puede utilizar parcels.domain. Si un componente de un dominio invertido contiene un guion, sustitúyelo por un guion bajo, como en org.example.hyphenated_name. Evita el espacio de nombres java y javax de la plataforma.
Utiliza el nombre completo de la clase
Un nombre completamente cualificado une el paquete y el nombre simple de la clase: com.example.parcels.domain.Parcel. Identifica la clase incluso cuando otro paquete también tiene un Parcel. El código puede nombrar por completo una clase accesible sin una importación:
java.util.ArrayList<String> names = new java.util.ArrayList<>();
Para ejecutar una clase principal que pertenece a un paquete, indica su nombre completo y una ruta de clases que apunte a la raíz del árbol de paquetes compilados. Desde la carpeta del proyecto:
javac -d out src/main/java/com/example/parcels/Main.java src/main/java/com/example/parcels/domain/Parcel.java
java -cp out com.example.parcels.Main
Aquí javac -d out crea el árbol de salida y java -cp out busca desde la raíz de ese árbol. Iniciar java Main desde una carpeta de paquete pierde la parte del paquete en la identidad de la clase. Las herramientas de construcción y los IDE suelen gestionar estos comandos.
Distingue paquetes vecinos
com.example.parcels y com.example.parcels.domain son paquetes separados. Compartir un prefijo en el nombre no concede permisos de acceso adicionales. Un archivo sin declaración pertenece al paquete sin nombre. Los paquetes con nombre no pueden importar sus clases, así que utiliza paquetes con nombre en todo programa que tenga varias partes.
Puntos clave
- Coloca la declaración de paquete antes de las importaciones y de la clase.
- Haz coincidir la ruta del paquete dentro del directorio raíz del código fuente; mantén
src/main/javafuera del nombre del paquete. - Utiliza nombres de paquetes en minúsculas y el nombre completamente cualificado de la clase cuando sea necesario.
- Un prefijo compartido en el nombre de paquetes no concede acceso de paquete compartido.
- Ejecuta las clases de paquetes desde la raíz del árbol compilado utilizando sus nombres completamente cualificados.
1.2 Importaciones y conflictos entre nombres de clases
Una importación permite que un archivo fuente utilice el nombre simple de un tipo accesible. Indica al compilador qué significa el nombre. No copia la clase, no descarga una biblioteca ni hace pública una clase oculta.
Lee el comienzo de un archivo fuente
La interfaz de usuario pertenece a un paquete y utiliza clases de otros dos:
package com.example.parcels.ui;
import com.example.parcels.domain.Parcel;
import com.example.parcels.domain.ShippingCalculator;
import java.util.Scanner;
public class TextUI {
private Scanner scanner;
private ShippingCalculator calculator;
}
Parcel y ShippingCalculator proceden del paquete de dominio del programa; Scanner procede de la biblioteca de Java. Cada importación se aplica solo a este archivo. Otro archivo que utilice Scanner necesita su propia importación o el nombre completo java.util.Scanner.
Elige un solo tipo o un comodín
import java.util.Scanner; es una importación de un solo tipo. El lector puede ver la clase exacta. import java.util.*; es una importación con comodín: los tipos accesibles de ese paquete pueden nombrarse cuando se utilizan. El comodín no carga clases no utilizadas en el programa ni lo ralentiza.
El comodín se limita a ese paquete. No incluye java.util.function.Function ni java.util.stream.Collectors, que están en paquetes separados. La misma regla se aplica a tu propio código: import com.example.parcels.*; no alcanza a com.example.parcels.domain.Parcel.
Tres casos no necesitan importación: los tipos del mismo paquete, los tipos de java.lang disponibles automáticamente y los tipos nombrados por completo. Por eso String, System, Math, Integer y Object suelen aparecer sin importaciones.
Resuelve un nombre simple compartido
Tanto java.util como java.awt tienen una clase llamada List. Dos importaciones de un solo tipo para estas clases diferentes provocan un error de compilación. Dos importaciones con comodín pueden coexistir, pero el nombre simple List se vuelve ambiguo cuando se utiliza. Una importación de un solo tipo tiene prioridad sobre una importación con comodín.
Si necesitas ambas clases, importa una y escribe la otra por completo:
import java.util.List;
class NameDisplay {
private List<String> names;
private java.awt.List widget;
}
Las importaciones de Java no ofrecen una sintaxis de alias como import ... as .... El nombre completamente cualificado es la forma de distinguir el segundo tipo.
Importa un miembro estático
Una importación estática elimina el nombre de la clase propietaria de una llamada a un miembro estático:
import static java.lang.Math.sqrt;
El archivo ahora puede llamar a sqrt(16.0). El código de prueba suele utilizar import static org.junit.jupiter.api.Assertions.assertEquals; para que las aserciones se lean como comprobaciones directas. En el código ordinario del programa, utiliza las importaciones estáticas de forma selectiva para que quienes lo lean puedan seguir viendo de dónde proceden las operaciones.
Sigue lo que indica el error
«Cannot find symbol» puede indicar que falta una importación o que un tipo está mal escrito. «Package ... does not exist» apunta a un problema de escritura del paquete, del directorio raíz del código fuente o de una dependencia. «Is not public ... cannot be accessed from outside package» significa que Java encontró el tipo, pero el acceso está bloqueado. Añadir otra importación no puede corregir ese error de acceso; examina la declaración de la clase y los miembros que necesitas.
Puntos clave
- Una importación resuelve nombres en un archivo y no concede permisos de acceso.
- Un comodín incluye tipos de un paquete, sin incluir sus subpaquetes.
- Los tipos del mismo paquete y los de
java.langno necesitan importación. - Utiliza una importación de un solo tipo y un nombre completamente cualificado cuando dos clases necesarias compartan un nombre.
- Una importación estática permite llamar a un miembro estático sin el nombre de su clase.
- Distingue un error de resolución de nombres de un error de acceso.
1.3 Una interfaz pública reducida
Un paquete debe exponer las clases y los métodos que necesitan otras partes y mantener ocultos los detalles de implementación. Los modificadores de acceso permiten hacer cumplir ese límite. Una importación puede abreviar un nombre, pero no puede cruzar un límite que las reglas de acceso de Java cierran.
Elige el acceso para un miembro de clase
| Acceso | ¿Quién puede utilizar el miembro? |
|---|---|
public | Código que puede acceder al tipo que lo declara |
protected | El mismo paquete, además del acceso de subclases entre paquetes |
| Sin modificador | Exactamente el mismo paquete |
private | La clase que lo declara y sus tipos anidados |
La ausencia de modificador significa acceso de paquete para los miembros de clase y constructores ordinarios. No es una palabra clave llamada default. Una clase de nivel superior solo tiene dos opciones: pública o con acceso de paquete. Una clase pública de nivel superior se guarda en un archivo con su nombre, como ShippingCalculator.java.
Comprueba el tipo y la operación
Supón que la interfaz debe construir un paquete. Una clase pública por sí sola no basta:
package com.example.parcels.domain;
public class Parcel {
private int weightGrams;
Parcel(int weightGrams) {
this.weightGrams = weightGrams;
}
}
El constructor tiene acceso de paquete. El código de com.example.parcels.ui puede nombrar Parcel, pero no puede llamar a new Parcel(1200). Haz que ese constructor sea public si crear paquetes forma parte del servicio público del paquete. Mantén weightGrams privado y expón una operación adecuada en lugar de abrir el campo.
El acceso de paquete compara el nombre completo del paquete. Ni un paquete que parece padre ni uno que parece hijo son el mismo paquete. El código de com.example.parcels no obtiene acceso especial a las clases auxiliares de com.example.parcels.domain.
Comprende el acceso protegido
protected permite el acceso desde clases sin relación del paquete que declara el miembro, además de desde subclases. Entre paquetes, se accede a un miembro de instancia mediante el contexto de herencia de la subclase. Una subclase puede utilizar su propio campo heredado, pero no puede utilizar un objeto arbitrario de la superclase para eludir el límite del paquete.
Por ejemplo, si Item declara protected int weightGrams, una clase Letter extends Item de otro paquete puede utilizar this.weightGrams. Dentro de Letter, un parámetro cuyo tipo sea simplemente Item no permite item.weightGrams; una referencia de tipo Letter sí permite el acceso heredado. Esta restricción impide que los privilegios de la subclase se conviertan en acceso general a todos los objetos Item.
Oculta las clases auxiliares de forma deliberada
Haz públicos ShippingCalculator y feeInCents si la interfaz los llama. Una clase auxiliar WeightBands puede mantener acceso de paquete. El código fuera del paquete de dominio depende entonces del comportamiento de la calculadora, no de cómo almacena la tabla. Sustituir esa clase auxiliar requiere menos cambios en otras partes.
Las pruebas pueden utilizar el mismo nombre de paquete que la clase que prueban aunque estén en src/test/java. Esto les da acceso de paquete sin colocar el código de prueba entre los archivos de producción. No concede acceso a los campos privados. Hacer público cada miembro solo para satisfacer una prueba extiende las dependencias de los detalles internos; prueba el comportamiento público o una operación útil con acceso de paquete.
Puntos clave
- Los miembros de clase con acceso de paquete solo son accesibles en el mismo paquete exacto.
- El código que realiza una llamada necesita acceso tanto a la clase como al constructor o método que utiliza.
- El acceso protegido incluye el acceso desde el mismo paquete y el acceso restringido de subclases entre paquetes.
- Mantén privados los campos y expón solo las operaciones que necesita otro paquete.
2Esboza la estructura de un programa con diagramas de clases UML
Un diagrama de clases te ofrece una vista compacta de un programa antes de leer los cuerpos de sus métodos. Los recuadros describen tipos y operaciones; las líneas describen vínculos, cantidades y relaciones entre subtipos. Interpretarás esas marcas con precisión y las traducirás a declaraciones Java. El programa de paquetes proporciona ejemplos de clases individuales, colecciones de objetos asociados e implementaciones intercambiables de repositorios.
2.1 Qué indica un recuadro de clase UML
Un diagrama de clases UML muestra la estructura estática: clases, sus miembros y relaciones. Ayuda a ver qué tipos de objetos existen y qué ofrecen. No sigue el orden de las llamadas a métodos durante la ejecución.
Lee los tres compartimentos
Un recuadro de clase tiene el nombre de la clase arriba, los atributos en el medio y las operaciones abajo. Un atributo suele representar un campo Java; una operación representa la firma visible de un método. Un esbozo puede omitir los compartimentos que no sirvan a su propósito.
Para un paquete, una representación textual del recuadro es:
Parcel
-----------------------------
- weightGrams: int
- recipient: String
-----------------------------
+ Parcel(weightGrams: int, recipient: String)
+ getWeightGrams(): int
+ isHeavierThan(limitGrams: int): boolean
El constructor tiene el nombre de la clase y ningún tipo de retorno. El método isHeavierThan acepta un entero llamado limitGrams y devuelve un booleano.
Traduce el orden de nombres y tipos
UML coloca el nombre antes del tipo: weightGrams: int. Java coloca el tipo primero: int weightGrams. Una operación sigue el mismo patrón de nombre primero para los parámetros y después coloca el tipo de retorno tras el paréntesis de cierre.
private int weightGrams;
public boolean isHeavierThan(int limitGrams) {
return weightGrams > limitGrams;
}
Este código corresponde a - weightGrams: int y + isHeavierThan(limitGrams: int): boolean. El cuerpo de un método suele estar ausente del diagrama; la operación indica a qué se puede llamar, no el algoritmo.
Conserva la visibilidad y las marcas especiales
Los símbolos de visibilidad son + público, - privado, # protegido y ~ acceso de paquete. Coloca el símbolo antes del nombre del miembro. Si un diagrama proporcionado omite un símbolo, no inventes su visibilidad; añade el símbolo previsto al documentar código.
Un miembro estático se subraya. Pertenece a la clase y no a cada objeto. Una clase u operación abstracta se escribe en cursiva o se marca con {abstract} cuando la cursiva no es práctica. La marca de abstracto significa que una subclase debe proporcionar el comportamiento que falta antes de poder crear un objeto a partir de una clase concreta.
Un recuadro de interfaz lleva «interface» sobre su nombre. Por ejemplo, ParcelRepository puede ofrecer + save(parcel: Parcel): void y + findAll(): List<Parcel>. El recuadro establece un contrato; no elige una implementación de almacenamiento.
Elige el detalle que necesita el lector
Un esbozo de planificación podría mostrar solo Parcel, Shipment y sus operaciones principales. Un diagrama para implementar una clase necesita tipos y visibilidad. Enumerar todos los métodos de acceso habituales puede ocultar las relaciones que el diagrama pretendía mostrar. Selecciona los miembros útiles y mantenlos coherentes con el código: un campo Java privado no debe aparecer con un símbolo público +, y un método estático necesita su subrayado.
Puntos clave
- Un recuadro de clase separa el nombre, los atributos y las operaciones.
- UML utiliza
name: Type; Java utilizaType name. - Utiliza
+,-,#y~para la visibilidad pública, privada, protegida y de paquete. - Subraya los miembros estáticos; marca los elementos abstractos con cursiva o
{abstract}. - Un recuadro de interfaz muestra
«interface»y las operaciones de su contrato.
2.2 Interpreta los extremos de las líneas de relación
Una asociación muestra un vínculo estructural entre objetos. En Java, un objeto suele mantener una referencia a otro en un campo. La línea también puede mostrar cuántos objetos se permiten y en qué dirección puede seguirse la referencia.
Cuenta desde el extremo opuesto
La multiplicidad se sitúa junto a la clase que cuenta. Si 1..* aparece junto a Parcel y 1 junto a Shipment, un envío tiene uno o más paquetes y cada paquete pertenece exactamente a un envío. Lee cada extremo por separado, comenzando desde un objeto del extremo opuesto.
| Multiplicidad | Significado |
|---|---|
1 | Exactamente uno |
0..1 | Cero o uno |
* o 0..* | Cualquier número, incluido cero |
1..* | Al menos uno |
2..4 | De dos a cuatro |
Una referencia única puede representar 1 o 0..1; una colección representa un extremo con varios valores. Una List de Java por sí sola no garantiza una cota inferior. Un Shipment con una regla de 1..* paquetes debe rechazar una lista inicial vacía e impedir que operaciones posteriores la dejen vacía. Del mismo modo, una referencia única obligatoria necesita una comprobación frente a null.
Sigue la referencia
Una punta de flecha abierta en Parcel significa que puede seguirse una referencia desde Shipment hasta Parcel. Un diagrama de clases puede dejar la navegabilidad sin especificar. En la convención didáctica utilizada aquí, las flechas marcan referencias unidireccionales; una línea sin marcas puede indicar acceso bidireccional o dirección sin especificar. No deduzcas un campo en sentido inverso solo a partir de una flecha hacia Parcel.
Un nombre de rol como parcels indica qué significa el vínculo desde el envío. Muestra esa referencia como asociación o como - parcels: List<Parcel> en el recuadro. Mostrar ambas formas sin motivo puede hacer que un campo parezca dos partes distintas del estado.
Elige el significado de la relación entre el todo y sus partes
La composición utiliza un rombo relleno en el todo. Una parte pertenece como máximo a un todo compuesto a la vez y normalmente se elimina junto con ese todo, como un pedido y sus líneas de pedido. La agregación utiliza un rombo vacío para una agrupación menos estricta cuyas partes pueden existir de forma independiente. Un equipo puede agrupar jugadores sin ser responsable de su tiempo de vida.
Java no tiene una palabra clave de rombo y no hace cumplir la propiedad de UML solo porque exista una lista. Modela la composición solo cuando el diseño incluya esa regla de propiedad. Cuando el diseño solo indica que un objeto conoce a otro, una asociación simple es más clara que un rombo sin fundamento.
Muestra un uso sin afirmar que existe propiedad
Una dependencia es una línea discontinua con una punta de flecha abierta hacia el tipo utilizado. Una calculadora con feeInCents(Parcel parcel) depende de Parcel porque la firma de su método utiliza ese tipo. Una dependencia es una relación de uso general; no se limita al código sin campos. Una asociación aporta información estructural más específica cuando un campo es importante para el modelo.
Antes de dibujar una línea, pregúntate qué establece el código o el requisito: una referencia, una cantidad permitida, un uso o una relación de propiedad. Una colección establece un almacenamiento con varios valores, pero por sí sola no establece propiedad exclusiva ni una cantidad mínima.
Puntos clave
- La multiplicidad junto a una clase cuenta sus objetos para un objeto del otro extremo.
- Un tipo de colección no garantiza una multiplicidad mínima ni máxima.
- Una flecha de asociación apunta hacia el tipo al que puede navegar el origen.
- Coloca el rombo de composición o agregación en el extremo del todo.
- Una dependencia es una flecha discontinua de uso; una asociación describe un vínculo estructural.
- Muestra una referencia una sola vez, salvo que el diagrama necesite deliberadamente ambas representaciones.
2.3 De un diagrama a las declaraciones Java
Tanto la herencia como la implementación de interfaces conectan un tipo específico con otro más general. UML utiliza un triángulo vacío en el tipo general, pero el estilo de línea distingue las dos relaciones. Esa distinción indica qué declaración Java debes escribir.
Interpreta el triángulo y su línea
Una línea continua con un triángulo vacío en Item significa que una subclase como Letter extiende Item. Esto es generalización. Una línea discontinua con un triángulo vacío en ParcelRepository significa que una clase como InMemoryParcelRepository implementa esa interfaz. Esto es realización.
El triángulo siempre toca la superclase o la interfaz. Una flecha abierta discontinua, sin el triángulo, indica una dependencia. El estilo de línea y la punta de flecha juntos transmiten el significado; una línea discontinua por sí sola no basta.
Construye el esqueleto en un orden útil
- Crea cada clase o interfaz en su paquete.
- Traduce los atributos a campos, conservando nombres, tipos y visibilidad.
- Traduce las asociaciones navegables a campos de referencia o de colección.
- Añade
extendsoimplementsa partir de las líneas de generalización o realización. - Traduce las operaciones a cabeceras de métodos y después completa sus cuerpos.
Supón que una interfaz ofrece save y findAll. Una implementación tiene una asociación con varios valores hacia Parcel, con el rol parcels:
public class InMemoryParcelRepository implements ParcelRepository {
private List<Parcel> parcels = new ArrayList<>();
@Override
public void save(Parcel parcel) {
parcels.add(parcel);
}
@Override
public List<Parcel> findAll() {
return new ArrayList<>(parcels);
}
}
La línea discontinua con triángulo da lugar a implements ParcelRepository. La asociación da lugar al campo de lista. Las firmas de las operaciones dan lugar a los dos métodos públicos. Los cuerpos de los métodos son una decisión de implementación: aquí save añade un objeto y findAll devuelve una lista nueva para que quienes llaman al método no puedan añadir ni eliminar directamente entradas de la lista almacenada. Los propios objetos de tipo paquete siguen compartidos.
Dibuja en la otra dirección
Comienza con un recuadro para cada clase o interfaz. Un campo de valor simple se convierte en un atributo. Una referencia a una clase del programa se convierte en una asociación; una colección sugiere un extremo con varios valores. Examina las comprobaciones de constructores y métodos antes de afirmar que la cota inferior es uno. Traduce extends a una línea continua con triángulo e implements a una línea discontinua con triángulo.
Los campos y las operaciones heredados suelen permanecer en el recuadro de la superclase. Coloca los miembros nuevos o sobrescritos en la subclase. Añade una dependencia para un parámetro, tipo de retorno o uso local cuando esa relación ayude a explicar el diseño.
Los paquetes pueden rodear sus clases en recuadros con forma de carpeta y una pequeña pestaña. Estos grupos ayudan a revelar qué áreas dependen de cuáles. Mantén actualizado un diagrama de documentación cuando cambie el código; identifica un esbozo de planificación inicial para que quienes lo lean entiendan su propósito.
Un Shipment que contiene paquetes no es un tipo de Parcel. Su lista establece una relación de «tiene», así que dibuja una asociación. Utiliza herencia para una relación de «es un tipo de» que el sistema de tipos y el comportamiento de Java admitan realmente.
Puntos clave
- La generalización utiliza una línea continua y un triángulo vacío en la superclase.
- La realización utiliza una línea discontinua y un triángulo vacío en la interfaz.
- Traduce los campos, las asociaciones, la herencia y las firmas de métodos antes de escribir los cuerpos.
- Conserva los tipos y la visibilidad del diagrama al traducirlo.
- El tipo declarado de una colección por sí solo no puede establecer una cantidad mínima.
- Utiliza asociaciones para objetos contenidos y herencia para relaciones de subtipo.
- Mantén los diagramas de documentación coherentes con el código que describen.
3Estructura un programa en capas
Un programa es más fácil de probar y modificar cuando su diálogo, sus reglas y su almacenamiento tienen responsabilidades separadas. Seguirás un comando de paquetes a través de esas partes, sacarás los cálculos del código de consola y pasarás colaboradores mediante constructores. Las interfaces permiten que la lógica funcione con almacenamiento real o con un sustituto en memoria, mientras que una lectura cuidadosa de las importaciones revela de qué tipos depende cada capa.
3.1 Mantén el diálogo fuera de los cálculos
La interfaz de usuario lee comandos y muestra resultados. La lógica de la aplicación contiene las reglas y los datos sobre los que actúan esos comandos. Separarlas permite que una prueba llame directamente a un cálculo y que otra interfaz lo reutilice sin copiar la regla.
Da a cada parte una tarea clara
TextUI se encarga de la conversación: pide un peso, lee una línea con Scanner, convierte el texto en un número, llama a la lógica e imprime el resultado. ShippingCalculator acepta un peso en gramos y devuelve una tarifa en centavos. No pregunta nada al usuario ni imprime una respuesta.
Un método llamado printFee mezcla esas tareas si calcula e imprime. Cambia la operación de lógica a feeInCents(int weightGrams). La interfaz puede entonces imprimir "Fee: " + calculator.feeInCents(weightGrams) + " cents". Una prueba puede comprobar mediante una aserción el entero devuelto sin capturar la salida de consola.
Escribe la regla como un valor de retorno
La regla de paquetes cobra 500 centavos para pesos positivos hasta 2000 g inclusive, 900 centavos hasta 10 000 g inclusive y después 100 centavos adicionales por cada bloque comenzado de 1000 g por encima de 10 000 g.
public int feeInCents(int weightGrams) {
if (weightGrams <= 0) {
throw new IllegalArgumentException("weight must be positive");
}
if (weightGrams <= 2000) {
return 500;
}
if (weightGrams <= 10000) {
return 900;
}
long excess = (long) weightGrams - 10000;
long startedBlocks = (excess + 999) / 1000;
return (int) (900 + 100 * startedBlocks);
}
Las dos primeras ramas que devuelven una tarifa incluyen sus límites superiores. Para 12 500 g, el exceso es de 2500 g. La división entera después de sumar 999 cuenta tres bloques comenzados, lo que da 1200 centavos. El uso intermedio de aritmética long también permite manejar pesos int positivos grandes sin desbordamiento al sumar 999.
Un peso no válido provoca una IllegalArgumentException: el método rechaza un argumento que incumple su regla. La interfaz puede capturar esa excepción y elegir un mensaje legible. La regla pertenece a la lógica; la redacción del mensaje pertenece a la interfaz.
Conecta los objetos en main
Pasa el lector de entrada y la calculadora al constructor de la interfaz:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
ShippingCalculator calculator = new ShippingCalculator();
TextUI ui = new TextUI(scanner, calculator);
ui.start();
}
Crea un solo lector para el flujo de entrada y compártelo con la interfaz. Varios lectores sobre System.in pueden interferir en la entrada almacenada en sus búferes. El método main ensambla el programa; start ejecuta el bucle de diálogo.
Al revisar una clase, busca sus responsabilidades. Un Scanner o System.out dentro de una calculadora de tarifas revela una mezcla de entrada y salida con reglas. Una fórmula de tarifas dentro de la interfaz revela el mismo problema desde el otro lado. Mueve el cálculo a un método con una entrada explícita y un resultado; mantén el análisis de comandos y el texto que se muestra juntos en la interfaz.
Puntos clave
- La interfaz lee e imprime; los métodos de lógica aceptan valores y devuelven resultados.
- Mantén las reglas de tarifas y la validación en la lógica para que las pruebas puedan llamarlas directamente.
- Deja que la interfaz elija cómo mostrar un resultado o una entrada rechazada.
- Crea los colaboradores en
mainy pásalos al constructor de la interfaz. - Cuenta un bloque de peso utilizado parcialmente como un bloque comenzado cuando la regla lo requiera.
3.2 Conecta las capas mediante un repositorio
Un programa en capas agrupa las clases por responsabilidad. La capa de presentación gestiona el diálogo, la capa de lógica aplica las reglas y la capa de acceso a datos guarda y recupera información. Una clase utiliza otra capa mediante una operación clara en lugar de acceder directamente a sus campos internos.
Ubica las clases de paquetes
El paquete de interfaz contiene TextUI. El paquete de dominio contiene Parcel, ShippingCalculator y ParcelService. El paquete de datos contiene ParcelRepository y sus implementaciones de almacenamiento. Main ensambla los objetos por encima de estas capas.
Un comando sigue esta ruta: el usuario introduce «add 1200 Alex»; la interfaz lo analiza y llama a service.register(1200, "Alex"); el servicio valida el peso, crea un paquete, lo guarda mediante el repositorio y devuelve 500 centavos; la interfaz imprime la tarifa. La lectura del texto, la decisión sobre lo que se permite y el almacenamiento del resultado tienen lugares separados.
Depende de un contrato
El servicio necesita un comportamiento de almacenamiento, no un formato de archivo concreto. Expresa ese comportamiento mediante una interfaz:
public interface ParcelRepository {
void save(Parcel parcel);
List<Parcel> findAll();
}
El servicio almacena una referencia de tipo ParcelRepository y la recibe en el constructor:
public class ParcelService {
private final ParcelRepository repository;
public ParcelService(ParcelRepository repository) {
this.repository = repository;
}
}
Esto es inyección por constructor, una forma de inyección de dependencias: quien realiza la llamada proporciona un colaborador. Un servicio que construye new FileParcelRepository(...) dentro de sus propios métodos elige por sí mismo el almacenamiento en archivos y dificulta las pruebas aisladas.
Cambia la implementación durante el ensamblaje
El programa en ejecución puede crear new ParcelService(new FileParcelRepository("parcels.csv")). Una prueba puede proporcionar new InMemoryParcelRepository(). Ambas implementaciones cumplen la misma interfaz. Las llamadas mediante la interfaz utilizan el polimorfismo para llegar a los métodos del objeto real.
Si el contrato del repositorio no cambia, cambiar el almacenamiento requiere una nueva implementación y un cambio durante el ensamblaje. Las reglas de tarifas y el código de diálogo no necesitan saber cómo se almacenan los datos.
Comprueba con precisión la dirección de las dependencias
La ruta descendente de uso de servicios va de la interfaz a la lógica y de esta al almacenamiento. El dominio no debe importar ni llamar a clases de la interfaz. Si la lógica necesita comunicar un resultado al usuario, devuélvelo a la interfaz. Si la interfaz guarda directamente mediante el repositorio, puede eludir la validación del servicio.
Las referencias a tipos Java también importan: una firma del repositorio que nombra Parcel depende de ese tipo de dominio. Con ParcelRepository en el paquete de datos y el servicio en el paquete de dominio, esos paquetes hacen referencia entre sí. Para eliminar ese ciclo de dependencias entre paquetes, coloca la interfaz del repositorio junto a los tipos de dominio y deja que las implementaciones de datos la importen. El servicio sigue utilizando el mismo contrato; solo cambia su ubicación. Examina tanto la ruta de llamadas a métodos como las importaciones al comprobar el diseño.
Los programas más grandes pueden agrupar primero los paquetes por funcionalidad, como pedidos o clientes, y después separar las responsabilidades dentro de cada funcionalidad. Los nombres de paquetes ayudan a localizar responsabilidades; los parámetros de constructores y las firmas de métodos muestran las dependencias reales.
Puntos clave
- Separa las responsabilidades de presentación, lógica y acceso a datos.
- Pasa una interfaz de repositorio al constructor del servicio.
- Elige una implementación con archivos o en memoria donde se ensambla el programa.
- Canaliza las acciones de la interfaz a través de la lógica para que se aplique la validación.
- Devuelve los resultados a la interfaz en lugar de llamar a su código desde la lógica.
- Distingue la dirección de uso de los servicios de las dependencias entre tipos Java al comprobar ciclos entre paquetes.
4Verifica código con pruebas de JUnit
Las pruebas automatizadas convierten el comportamiento esperado en comprobaciones que puedes volver a ejecutar después de cada cambio. Escribir una comprobación es solo el comienzo: las entradas útiles revelan los límites, los entornos de prueba nuevos mantienen la independencia y los fallos legibles ayudan a encontrar la causa. Crearás pruebas de Jupiter para la lógica de paquetes, controlarás sus datos y colaboradores y utilizarás un ciclo breve en el que primero se escriben las pruebas para desarrollar una regla caso por caso.
4.1 Una prueba que comprueba un resultado
Una prueba unitaria llama a una pequeña unidad de código con una entrada elegida y comprueba el resultado automáticamente. Convierte un requisito en algo ejecutable. Volver a ejecutar las pruebas después de un cambio ayuda a encontrar una regresión, un error en un comportamiento que antes funcionaba.
Crea una prueba de Jupiter
JUnit Jupiter es el modelo de programación utilizado por JUnit 5 y 6. JUnit 6 necesita Java 17 o posterior. Las anotaciones y aserciones de prueba utilizan org.junit.jupiter.api.
Coloca ShippingCalculatorTest.java en src/test/java/com/example/parcels/domain, con la misma declaración de paquete que la calculadora. El directorio raíz del código fuente separado mantiene las pruebas aparte del código del programa que se entrega, a la vez que conserva el acceso al mismo paquete.
package com.example.parcels.domain;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class ShippingCalculatorTest {
@Test
void lightParcelCostsBaseFee() {
ShippingCalculator calculator = new ShippingCalculator();
int fee = calculator.feeInCents(1500);
assertEquals(500, fee);
}
}
La clase y el método de prueba pueden tener acceso de paquete. Un método de prueba devuelve void y no debe ser privado. Utiliza @Test de Jupiter; org.junit.Test de JUnit 4 pertenece a otro modelo de programación y el motor de Jupiter no lo descubre. Las aserciones de JUnit 4 pueden funcionar cuando su biblioteca está disponible, pero utilizar importaciones de Jupiter de forma coherente mantiene clara la configuración.
Interpreta preparar, actuar y comprobar
La primera parte prepara una calculadora. La segunda actúa al solicitar la tarifa. La aserción final compara los 500 centavos esperados con el valor devuelto. Las líneas en blanco facilitan identificar estas tareas. Imprimir solo fee dejaría a una persona la decisión de si es correcto; la aserción convierte esa decisión en parte de la prueba.
assertEquals(expected, actual) coloca primero el requisito y después el resultado observado. Un mensaje opcional va al final, como en assertEquals(500, fee, "fee for 1,500 g"). Invertir los dos primeros argumentos suele mantener el resultado de la prueba para estos valores, pero invierte el significado del informe de fallo.
Elige la aserción adecuada
Utiliza assertTrue o assertFalse para un booleano, y assertNull o assertNotNull cuando el resultado sea la presencia de un objeto. Las comprobaciones de igualdad de objetos utilizan equals; sin una sobrescritura, dos objetos distintos con campos coincidentes pueden seguir considerándose desiguales.
Los resultados en coma flotante necesitan una tolerancia adecuada. El cálculo 0.1 + 0.2 produce 0.30000000000000004 en un double de Java:
assertEquals(0.3, 0.1 + 0.2, 1e-9);
El tercer argumento permite una diferencia absoluta de hasta 0.000000001. Selecciona una tolerancia adecuada para el cálculo; una tolerancia demasiado grande puede ocultar un error significativo. Los cálculos con centavos enteros no necesitan tolerancia decimal.
Interpreta un fallo
Ejecuta una prueba desde el IDE o la herramienta de construcción. Un informe como «expected: <500> but was: <900>» indica los resultados requerido y observado; la traza de la pila señala la aserción. Una excepción inesperada también hace fallar una prueba. El verde significa que los casos comprobados pasaron; elegir esos casos sigue siendo una parte independiente de las pruebas.
Puntos clave
- Utiliza anotaciones y aserciones de Jupiter de
org.junit.jupiter.api. - Coloca las pruebas en
src/test/java, normalmente en el paquete de la clase probada. - Prepara los objetos, actúa sobre ellos y comprueba el resultado mediante una aserción.
- Coloca el valor esperado primero en
assertEquals. - Utiliza una tolerancia justificada para las comparaciones en coma flotante.
- Lee el nombre de la prueba fallida, los valores esperado y real y la ubicación de la aserción.
4.2 Elige entradas en los límites de las reglas
Un conjunto útil de pruebas comprueba cómo la especificación divide las entradas en casos. Una entrada ordinaria rara vez prueba toda una regla. Los casos límite en un límite y justo a su lado son especialmente útiles para detectar una condición escrita con el operador de comparación equivocado.
Divide el espacio de entrada
Una clase de equivalencia agrupa entradas que deben tratarse de la misma manera. La regla de envío tiene pesos no válidos en cero o por debajo, una franja de 500 centavos hasta 2000 g inclusive, una franja de 900 centavos hasta 10 000 g inclusive y un cálculo por bloques comenzados por encima de ese límite. Elige entradas representativas de las clases y después examina sus límites.
| Peso en gramos | Resultado esperado |
|---|---|
0, -5 | Rechazar con IllegalArgumentException |
1, 2000 | 500 centavos |
2001, 10000 | 900 centavos |
10001, 11000 | 1000 centavos |
11001 | 1100 centavos |
Cambiar <= 2000 por < 2000 afecta exactamente a 2000 g. Una prueba con 1500 g sigue pasando. Por encima de 10 000 g, prueba tanto un bloque completo como el primer gramo del siguiente bloque para comprobar el redondeo hacia arriba.
Los valores esperados proceden del requisito y del cálculo manual. Para 12 500 g, el exceso es de 2500 g, lo que requiere tres bloques comenzados, por lo que el resultado esperado es 1200 centavos. Ejecutar el método y copiar su respuesta en la prueba también copiaría cualquier error.
Prueba la entrada rechazada
Una prueba de excepción proporciona a JUnit la operación que debe ejecutar:
assertThrows(IllegalArgumentException.class,
() -> calculator.feeInCents(0));
La expresión () -> ... es una expresión lambda: un pequeño fragmento de código que se entrega a JUnit. JUnit lo ejecuta dentro de la comprobación. Llamar al método antes de entregarlo a la aserción dejaría escapar la excepción antes de que la comprobación pudiera examinarla.
assertThrows acepta el tipo de excepción indicado o una subclase. Falla si la operación termina normalmente o lanza un tipo incompatible. Devuelve la excepción, por lo que puede comprobarse un mensaje requerido:
IllegalArgumentException error = assertThrows(
IllegalArgumentException.class,
() -> calculator.feeInCents(0));
assertEquals("weight must be positive", error.getMessage());
Da a cada escenario un nombre legible
Nombres como zeroWeightIsRejected y feeIsBaseFeeAtExactly2000Grams identifican el comportamiento previsto. Una prueba puede tener varias aserciones cuando comprueban el mismo resultado. Después de añadir paquetes de 1200 g y 800 g a un envío, comprobar un recuento de dos y un peso total de 2000 g describe ese único escenario. assertAll puede agrupar comprobaciones para que se informen todos los fallos.
Comprueba los objetos modificados mediante su comportamiento, como parcelCount() y totalWeightGrams(), en lugar de exponer campos privados para las pruebas. Así, una refactorización puede cambiar los detalles de almacenamiento y conservar las comprobaciones. Una cobertura completa de líneas significa que las pruebas ejecutaron las líneas; aun así puede pasar por alto un límite incorrecto o una entrada no válida que no se haya probado.
Puntos clave
- Divide las entradas según la especificación y prueba casos representativos y límites.
- Comprueba el propio límite y los valores cercanos para detectar errores de una unidad.
- Calcula los resultados esperados independientemente de la implementación.
- Pasa la operación a
assertThrowscomo una expresión lambda. - Da nombre a un escenario por prueba; varias aserciones relacionadas pueden comprobar ese escenario.
- Prueba el estado modificado mediante el comportamiento que pueden observar quienes llaman al código.
4.3 Pruebas que se ejecutan solas y en cualquier orden
Cada prueba debe preparar todo lo que necesita. Una prueba que solo pasa después de que otra haya añadido datos depende de un historial oculto. Es difícil confiar en esas pruebas cuando un ejecutor cambia el orden o ejecuta un solo método.
Crea un entorno de prueba nuevo
Un entorno de prueba es el conjunto de objetos y datos que utiliza una prueba. Coloca la preparación repetida para cada prueba en @BeforeEach:
class ShipmentTest {
private Shipment shipment;
@BeforeEach
void setUp() {
shipment = new Shipment();
}
@Test
void newShipmentIsEmpty() {
assertEquals(0, shipment.parcelCount());
}
@Test
void addingParcelIncreasesCount() {
shipment.add(new Parcel(1200, "Alex"));
assertEquals(1, shipment.parcelCount());
}
}
La preparación se ejecuta antes de cada método. La segunda prueba no modifica el envío utilizado por la primera. De forma predeterminada, Jupiter también crea una nueva instancia de la clase de prueba para cada método de prueba. Los campos estáticos mutables siguen compartidos, por lo que una lista estática aún puede transmitir estado entre esas instancias.
@AfterEach realiza la limpieza después de cada prueba. @BeforeAll y @AfterAll se ejecutan una vez para la clase y son estáticos con el ciclo de vida predeterminado. Los datos de referencia compartidos de solo lectura pueden ser adecuados para una preparación por clase; los objetos que cambian suelen necesitar una creación nueva para cada prueba. Las pruebas deben funcionar individualmente sin depender del orden del archivo fuente.
Utiliza un cuerpo de prueba para una tabla
Una prueba parametrizada ejecuta la misma comprobación de comportamiento para varias filas:
@ParameterizedTest
@CsvSource({"1, 500", "2000, 500", "2001, 900",
"10000, 900", "10001, 1000"})
void feeMatchesTable(int grams, int expected) {
assertEquals(expected,
new ShippingCalculator().feeInCents(grams));
}
Cada fila es una invocación separada con su propio informe. @ValueSource(ints = {0, -5}) proporciona un entero por invocación para pruebas de entradas no válidas. Las anotaciones parametrizadas proceden de org.junit.jupiter.params; CsvSource y ValueSource están en su paquete provider. La dependencia agregada junit-jupiter incluye el módulo de pruebas parametrizadas.
@DisplayName añade una etiqueta legible al informe. @Disabled registra una prueba omitida con un motivo; una comprobación omitida no aporta evidencia de que pase el comportamiento que probaría.
Controla el entorno
Una prueba de servicio puede recibir un InMemoryParcelRepository, una implementación simplificada para pruebas funcional que almacena paquetes en memoria. Esto evita el contenido de archivos reales y aísla la lógica. Pasa un Scanner construido a partir de una cadena conocida a una interfaz de texto cuando pruebes cómo gestiona la entrada. Una fecha fija o un Random con una semilla fija también hacen predecible una entrada externa.
Una prueba inestable cambia su resultado entre ejecuciones sin un cambio relevante en el código. Los datos mutables compartidos, la hora actual, los valores aleatorios no controlados y los archivos que quedan de ejecuciones anteriores son causas habituales. Da a la prueba el control de esas entradas. Mantén pequeños los métodos auxiliares y legible la preparación; un entorno de prueba que construye muchos objetos sin relación puede revelar que la clase probada tiene demasiadas tareas.
Puntos clave
- Utiliza
@BeforeEachpara proporcionar entornos de prueba nuevos. - Una nueva instancia de la clase de prueba no restablece el estado estático mutable.
- Las pruebas parametrizadas ejecutan un método con filas de entrada que se registran por separado.
- Utiliza implementaciones simplificadas para pruebas y entradas proporcionadas para mantener las pruebas de lógica independientes del almacenamiento real y del reloj.
- Mantén cada prueba ejecutable por sí sola, sin un orden obligatorio de métodos.
4.4 Desarrolla comportamiento mediante rojo, verde y refactorización
El desarrollo guiado por pruebas (TDD) comienza un cambio de comportamiento con una prueba. Un ciclo breve hace visible el comportamiento que falta, lo implementa y después mejora la estructura del código. Una lista de pruebas conserva los casos que aún deben explorarse sin exigir que se escriban todos a la vez.
Toma un elemento de la lista de pruebas
Para un comprobador de años bisiestos gregorianos, enumera cuatro comportamientos: un año ordinario que no es bisiesto, un año divisible por cuatro, un año de siglo que no es divisible por 400 y un año de siglo que es divisible por 400. Estos casos representan distintas partes de la regla.
Rojo: escribe una prueba y ejecútala. Un método ausente puede provocar un error de compilación. Una vez que exista el método, verifica que la prueba falla por el comportamiento que pretendes añadir. Una aserción ausente, una llamada al método equivocado o una prueba no descubierta pueden hacer que un aparente resultado verde sea engañoso. Un caso nuevo también puede estar ya satisfecho por el código existente; examina ese resultado antes de cambiar la implementación.
Verde: añade el código más sencillo que haga pasar la prueba nueva y todas las anteriores. Refactorización: mejora la estructura sin cambiar el comportamiento y vuelve a ejecutar las pruebas después de cada cambio pequeño. Después elige el siguiente caso.
Deja que los casos desarrollen la regla
La primera comprobación es assertFalse(LeapYears.isLeapYear(2023)). Crear el método con return false; hace que pase este caso. Es un primer paso deliberado, no una regla de calendario terminada.
La siguiente comprobación, assertTrue(LeapYears.isLeapYear(2024)), falla. Cambia el método a return year % 4 == 0;. Ahora pasan ambos casos.
Después, assertFalse(LeapYears.isLeapYear(1900)) revela la excepción de los años de siglo. Cambiar la expresión a year % 4 == 0 && year % 100 != 0 hace que pasen esas tres pruebas.
Por último, assertTrue(LeapYears.isLeapYear(2000)) revela la excepción a la regla de los años de siglo. La implementación queda así:
public static boolean isLeapYear(int year) {
return year % 400 == 0
|| (year % 4 == 0 && year % 100 != 0);
}
Ejecuta las cuatro comprobaciones después del cambio. Cada nuevo fallo señaló una parte del comportamiento que faltaba, en lugar de obligarte a depurar una implementación grande e incompleta.
Mejora la estructura mientras las pruebas están en verde
La refactorización cambia los nombres o la organización mientras conserva el comportamiento. Cambiar el nombre de un método, extraer un método auxiliar, eliminar lógica repetida y mover una clase a un paquete más adecuado son refactorizaciones cuando el comportamiento observable permanece igual. Cambiar la tarifa cobrada en un límite es un cambio de comportamiento y necesita una prueba que lo describa.
Si una limpieza hace fallar una prueba, restablece el comportamiento previsto antes de añadir otra función. Los cambios pequeños ayudan a localizar la causa de un fallo. Omitir la limpieza deja código que pasa las pruebas pero que resulta más difícil de ampliar; escribir una implementación grande para muchos casos futuros crea más código que examinar cuando falle la siguiente comprobación.
TDD depende de la calidad de los requisitos y de los casos seleccionados. Una regla mal entendida puede aparecer tanto en la prueba como en la implementación. La lógica con entradas y salidas claras es un punto de partida útil porque el resultado esperado puede establecerse de forma independiente y comprobarse rápidamente.
Puntos clave
- Mantén una lista de pruebas e implementa un comportamiento cada vez.
- El rojo hace visible el comportamiento que falta; el verde hace que pasen todas las pruebas actuales.
- Examina una prueba que pasa inesperadamente antes de dar por hecho que ha comprobado el cambio previsto.
- Refactoriza la estructura conservando el comportamiento y vuelve a ejecutar las pruebas.
- Utiliza casos sucesivos para revelar reglas ordinarias y excepciones.
5Construye y prueba proyectos con Maven o Gradle
Un proyecto necesita más que archivos fuente: necesita bibliotecas, opciones de compilación, un ejecutor de pruebas y una forma de empaquetar el resultado. Maven y Gradle describen esas decisiones para que un comando pueda repetir la construcción. Utilizarás sus carpetas estándar, declararás dependencias de Jupiter, ejecutarás el lanzador incluido y distinguirás un error de código de un problema de descubrimiento de pruebas o de dependencias mediante la lectura de la salida de construcción.
5.1 Construye un proyecto Maven a partir de su descripción
Una herramienta de construcción convierte archivos fuente en un resultado comprobado y empaquetado. Maven lee una descripción del proyecto, descarga las bibliotecas declaradas, compila código, ejecuta pruebas y crea un artefacto. La misma descripción proporciona a la línea de comandos y al IDE una visión compartida del proyecto.
Utiliza la estructura estándar
Coloca el código Java de producción en src/main/java, las pruebas en src/test/java, los recursos de producción en src/main/resources y los datos de prueba en src/test/resources. Las carpetas de paquetes comienzan dentro de cada directorio raíz del código fuente Java. Maven escribe la salida generada en target; edita los archivos fuente, no las copias compiladas de esa carpeta.
El archivo pom.xml de la raíz del proyecto, el modelo de objetos del proyecto, identifica el proyecto mediante coordenadas: groupId, artifactId y version. Por ejemplo, com.example:parcels:1.0-SNAPSHOT identifica una versión de desarrollo del programa de paquetes. Una dependencia de biblioteca también tiene coordenadas.
Declara JUnit para las pruebas
La siguiente es una configuración pequeña y completa de Maven. Compila para Java 17 y selecciona un ejecutor de pruebas compatible con Jupiter:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>parcels</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
</plugin>
</plugins>
</build>
</project>
Maven obtiene las dependencias de repositorios, normalmente Maven Central, y las guarda en la caché .m2/repository de tu carpeta personal. Las bibliotecas que necesita una dependencia son dependencias transitivas. El ámbito de prueba hace que JUnit esté disponible para compilar y ejecutar pruebas, no para compilar el código principal ni para la ruta de clases de ejecución del programa. Un JAR ordinario mantiene separados los JAR de sus dependencias, salvo que el empaquetado esté configurado para incluirlos.
Solicita una fase del ciclo de vida
El ciclo de vida principal avanza por las fases de compilación, prueba, empaquetado e instalación, entre otras. Solicitar package también ejecuta las fases anteriores. Con el lanzador, ./mvnw test compila y prueba; ./mvnw package también crea el JAR en target; ./mvnw install además coloca el artefacto en el repositorio local. El ciclo de vida de limpieza independiente, ./mvnw clean, elimina la salida generada.
Un fallo de prueba detiene la construcción normal antes de empaquetar un nuevo artefacto. Un JAR anterior puede seguir presente hasta la limpieza, por lo que su presencia no demuestra que la última construcción haya pasado.
Haz visible el descubrimiento de pruebas
Los patrones predeterminados de Surefire incluyen nombres que comienzan por Test o terminan en Test, Tests o TestCase. ShippingChecks no coincide con ellos; utiliza ShippingCalculatorTest y examina el número de pruebas indicado. Las pruebas colocadas en src/main/java no pueden utilizar una dependencia con ámbito de prueba.
El lanzador de Maven, mvnw o mvnw.cmd en Windows, ejecuta la versión de Maven especificada por el proyecto. Utiliza un proyecto que incluya sus archivos de lanzador; un POM por sí solo no crea los scripts. Desde su raíz, el lanzador proporciona un comando de construcción coherente sin una instalación separada de Maven.
Puntos clave
- Mantén el código principal, las pruebas y los recursos en sus carpetas fuente estándar.
- Identifica proyectos y bibliotecas con coordenadas de grupo, artefacto y versión.
- Declara JUnit con ámbito de prueba y utiliza un ejecutor Surefire compatible.
- Una fase solicitada de Maven ejecuta las fases anteriores de su ciclo de vida.
- Examina el número de pruebas; una construcción correcta puede haber omitido una prueba con un nombre incorrecto.
- La salida generada pertenece a
targety puede volver a construirse. - El lanzador ejecuta la versión de Maven seleccionada por el proyecto.
5.2 Ejecuta pruebas de Gradle e interpreta errores de construcción
Gradle describe una construcción mediante un script y ejecuta tareas con sus dependencias. Un script Kotlin se llama build.gradle.kts; un script Groovy se llama build.gradle. Los complementos proporcionan tareas y las configuraciones de dependencias indican dónde se necesitan las bibliotecas.
Configura una aplicación y Jupiter
Un archivo settings.gradle.kts en la raíz del proyecto puede contener rootProject.name = "parcels". Un script de construcción Kotlin correspondiente es:
plugins {
application
}
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
dependencies {
testImplementation(platform("org.junit:junit-bom:6.1.3"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
application {
mainClass = "com.example.parcels.Main"
}
tasks.test {
useJUnitPlatform()
}
El complemento application proporciona tareas de construcción Java y añade run. mavenCentral() selecciona un repositorio de dependencias. La BOM, o lista de versiones de dependencias, alinea las versiones de los módulos de JUnit, por lo que la versión aparece una sola vez. testImplementation proporciona las bibliotecas necesarias para compilar y ejecutar pruebas. testRuntimeOnly proporciona el lanzador solo al ejecutarlas. Las bibliotecas del código principal utilizan implementation, que también las pone a disposición del código de prueba.
useJUnitPlatform() selecciona la plataforma que ejecuta las pruebas de Jupiter. Añadir bibliotecas de Jupiter sin configurar el ejecutor puede dejar esas pruebas sin descubrir. La versión actual de Gradle falla cuando existen archivos fuente de prueba pero no se descubre ninguna prueba con su configuración predeterminada; examina la salida y la configuración de la tarea de pruebas en lugar de eliminar la comprobación.
Ejecuta tareas con el lanzador
Utiliza el lanzador incluido con el proyecto: ./gradlew test ejecuta las pruebas; ./gradlew build compila, prueba y empaqueta; ./gradlew run inicia la clase principal configurada; y ./gradlew clean elimina build. Windows utiliza gradlew.bat. Gradle utiliza la misma estructura de código fuente principal y de prueba que Maven y escribe la salida generada en build.
Una tarea marcada como UP-TO-DATE tiene las entradas y salidas supervisadas sin cambios, por lo que Gradle reutiliza su resultado anterior. Ese mensaje es distinto de una ejecución de pruebas nueva. El informe de pruebas HTML está en build/reports/tests/test/index.html.
Encuentra la etapa que falló
Comienza por el primer error útil. «Cannot find symbol» durante la compilación principal apunta al código indicado, una importación o una dependencia. La ausencia de org.junit.jupiter.api durante la compilación de pruebas apunta a la configuración de dependencias de prueba; el mismo error durante la compilación principal puede indicar que se colocó una prueba en la carpeta de código fuente principal. Un error de resolución de dependencias apunta a las coordenadas, el acceso al repositorio o la disponibilidad de la red.
Un fallo de una prueba ejecutada es diferente: la prueba se compiló y descubrió, pero su comprobación falló. Si aparece «expected: <500> but was: <900>» en el límite de tarifa de 2000 g, examina la comparación que elige la franja. Cambiar las dependencias no corregirá ese cálculo.
Maven ofrece una descripción declarativa en XML con fases de ciclo de vida fijas. Gradle ofrece scripts y tareas configurables, incluida la omisión de trabajo que ya está actualizado. Ambos automatizan construcciones Java repetibles; utiliza la herramienta elegida para el proyecto y lee el primer fallo que permita actuar antes de cambiar el código.
Puntos clave
- Los complementos añaden tareas de Gradle; las configuraciones controlan dónde están disponibles las dependencias.
- Alinea las versiones de JUnit con una BOM y ejecuta Jupiter en JUnit Platform.
- Utiliza el lanzador de Gradle incluido para las tareas de prueba, construcción, ejecución y limpieza.
- Distingue los resultados de tareas reutilizados, los errores de compilación, los errores de dependencias y los fallos de aserción.
- Lee la prueba fallida indicada y los valores esperado y real antes de cambiar la implementación.
Ejemplos de Paquetes en Java
1Mueve una clase de paquetes a un paquete Java
13 pasos
- 1IntroducciónDa a la clase una ubicación clara
Un programa de paquetes tiene una calculadora y una interfaz de texto. A medida que estas clases se trasladan a paquetes separados, Java necesita nombres, carpetas y niveles de acceso que coincidan.
Sigue el traslado en tres pasos: coloca la clase, permite que otro archivo la nombre y después comprueba si quien realiza la llamada puede utilizar su constructor y sus métodos.
- 2EjemploComienza dentro del directorio raíz del código fuente
El directorio raíz del código fuente es
src/main/java. ParaParcelencom.example.parcels.domain, convierte los puntos del paquete en carpetas y añade el nombre del archivo:src/main/java/com/example/parcels/domain/Parcel.javaEscribe
package com.example.parcels.domain;al comienzo del archivo. Las carpetassrc/main/javade la raíz describen la estructura de construcción y quedan fuera de la declaración de paquete. - 3ExplicaciónMantén intacta la identidad de la clase
El nombre simple es
Parcel; el nombre completamente cualificado escom.example.parcels.domain.Parcel. Mover el archivo no reescribe por sí solo esa identidad.Una clase pública llamada
Parcelpertenece aParcel.java. Ejecuta una clase principal que pertenece a un paquete desde la raíz del árbol compilado mediante un comando comojava -cp out com.example.parcels.Main, para que Java pueda encontrar las carpetas de paquetes dentro deout. - 4DesafíoCorrige una ubicación que no coincide
Una clase pública llamada
Receiptdeclarapackage com.example.parcels.domain;, pero está guardada ensrc/main/java/com/example/parcels/ui/. Se pretende mantener su paquete sin cambios. ¿Dónde debe ir el archivo?---reveal---
Muévelo a
src/main/java/com/example/parcels/domain/Receipt.java. La ruta del paquete comienza dentro del directorio raíz del código fuente y el nombre del archivo coincide con la clase pública. Cambiar una importación en otra parte no corregiría la estructura del código fuente. - 5EjemploFacilita el uso del nombre del tipo
La interfaz tiene su propia declaración y después importa el tipo de dominio:
package com.example.parcels.ui; import com.example.parcels.domain.Parcel;El cuerpo de su clase ahora puede utilizar el nombre simple
Parcel. La importación se aplica solo a este archivo. Resuelve un nombre; no copia una clase ni pone una dependencia a disposición de la construcción. - 6Idea claveUn comodín se limita a un paquete
import com.example.parcels.*;no incluye el paquete de dominio. El prefijo compartido del nombre parece un árbol familiar, pero cada nombre completo de paquete designa un paquete separado.La misma regla explica por qué
java.util.*no proporcionajava.util.function.Function. Utiliza una importación de un solo tipo, el comodín del paquete adecuado o un nombre completamente cualificado para ese tipo. - 7DesafíoDistingue dos listas
Un archivo necesita tanto
java.util.Listcomojava.awt.List. Actualmente tiene importaciones de un solo tipo para ambas y la compilación falla. ¿Cómo puede el archivo nombrar ambos tipos sin ambigüedad?---reveal---
Mantén una importación de un solo tipo, como
import java.util.List;, y escribejava.awt.Listpor completo donde necesites ese tipo. Dos importaciones de un solo tipo diferentes no pueden dar dos significados al mismo nombre simple en un archivo. - 8ConsejoReconoce los nombres que no necesitan importación
Un archivo puede utilizar tipos de su propio paquete sin importarlos. Los tipos accesibles de
java.lang, incluidosString,MathySystem, también están disponibles automáticamente.Una importación estática tiene otra función: importar estáticamente
Assertions.assertEqualspermite una llamada directa aassertEquals(...). Abrevia una referencia a un miembro estático, no un nombre de paquete. - 9EjemploComprueba también el constructor
El tipo es público, pero su constructor no tiene modificador:
public class Parcel { Parcel(int grams, String recipient) { } }Ese constructor tiene acceso de paquete. La interfaz puede resolver
Parcelmediante la importación, pero no puede construirlo desde el paquete de interfaz. Marca el constructor como público si crear paquetes forma parte de las operaciones públicas previstas del dominio; mantén privados sus campos. - 10DesafíoAbre la operación y mantén oculta la clase auxiliar
La interfaz debe llamar al método
feeInCentsde una calculadora pública, actualmente declarado sin modificador de acceso. Solo la calculadora utiliza su clase auxiliarWeightBands, que tiene acceso de paquete. ¿Qué cambio de acceso se necesita?---reveal---
Haz público
feeInCentsy dejaWeightBandscon acceso de paquete. La interfaz necesita la operación de cálculo, no la clase auxiliar de la tabla. Importar cualquiera de las dos clases no cambia sus permisos de acceso. - 11ExplicaciónUna subclase tiene privilegios específicos
Los miembros protegidos son accesibles para todo el paquete que los declara y para las subclases según las reglas de herencia entre paquetes. En otro paquete, una subclase puede utilizar su propio miembro de instancia heredado.
No puede utilizar una referencia cuyo tipo sea la superclase para acceder a ese miembro en un objeto arbitrario de la superclase. Por tanto, el acceso protegido es más amplio que el acceso exclusivo de subclases, pero más restrictivo que el acceso público.
- 12DesafíoDiagnostica toda la ruta de acceso
TextUIimporta correctamente una claseParcelpública. Su llamada anew Parcel(800, "Jo")informa de que no se puede acceder al constructor desde fuera del paquete. El constructor no tiene modificador. ¿Qué corrige esta creación prevista entre paquetes?---reveal---
Haz público el constructor. El paquete y la importación ya resuelven el tipo, por lo que mover el archivo o añadir una importación con comodín no resuelve el fallo de acceso indicado. Los campos privados del paquete pueden seguir siendo privados.
- 13RepasoUtiliza tres comprobaciones separadas
Comprueba la ubicación haciendo coincidir la declaración con las carpetas dentro del directorio raíz del código fuente. Comprueba los nombres mediante importaciones o nombres completamente cualificados. Comprueba el acceso al tipo y a la operación que quien realiza la llamada utiliza realmente.
Este orden ayuda a convertir un problema impreciso como «Java no puede utilizar esta clase» en una corrección concreta de la estructura del código fuente, la resolución de nombres o la visibilidad.
2Interpreta un modelo de paquetes y construye su esqueleto Java
13 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónInterpreta la estructura antes de escribir los cuerpos
- 2EjemploTraduce un campo y un método
- 3ExplicaciónIdentifica un miembro de nivel de clase
- 4DesafíoConserva los símbolos
- 5EjemploCuenta los paquetes desde un envío
- 6EjemploElige una representación y después haz cumplir la regla
- 7Idea claveSigue un vínculo unidireccional
- 8DesafíoComprueba una cantidad mínima
- 9ExplicaciónColoca la propiedad en el extremo correcto
- 10EjemploInterpreta dos líneas con triángulos
- 11ConsejoConstruye las declaraciones en orden
- 12DesafíoCombina un contrato y una colección
- 13RepasoDistingue la función de cada símbolo
3Sigue un comando a través de responsabilidades separadas
11 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónSigue un comando a través del programa
- 2EjemploExtrae la regla del código de consola
- 3EjemploCalcula un bloque comenzado
- 4Idea claveMantén la validación junto a la regla
- 5DesafíoHaz reutilizable un cálculo
- 6EjemploPasa el almacenamiento al servicio
- 7ConexiónSustituye los archivos por una implementación simplificada funcional
- 8ConsejoComprueba las llamadas y las importaciones por separado
- 9DesafíoCorrige un atajo que evita el servicio
- 10DesafíoConecta una prueba de servicio controlada
- 11RepasoColoca cada cambio donde corresponde su tarea
4Desarrolla una colección útil de pruebas de Jupiter
19 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónConvierte requisitos en comprobaciones ejecutables
- 2EjemploPrepara, actúa y comprueba
- 3ExplicaciónInterpreta correctamente los valores esperado y real
- 4ConsejoPermite el redondeo numérico de forma deliberada
- 5DesafíoCorrige una prueba que solo imprime
- 6EjemploEncuentra las transiciones de tarifa
- 7Idea claveMantén independiente el valor esperado
- 8EjemploColoca la llamada rechazada dentro de la comprobación
- 9DesafíoElige el caso que revela el cambio
- 10EjemploDa a cada prueba un envío nuevo
- 11EjemploEjecuta una tabla mediante un solo cuerpo
- 12ConexiónControla el colaborador, no el orden de las pruebas
- 13DesafíoDiagnostica datos que dejó otra prueba
- 14EjemploDeja que un año ordinario inicie el ciclo
- 15EjemploAñade la regla general de divisibilidad por cuatro
- 16EjemploDeja que los casos de años de siglo refinen la condición
- 17DesafíoElige un siguiente paso conservando las comprobaciones
- 18DesafíoCombina la selección de casos y el aislamiento
- 19RepasoConfía en evidencia específica y repetible
5Lleva un proyecto del archivo de construcción al informe de pruebas
13 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónHaz que un comando repita la construcción
- 2ExplicaciónSepara los archivos fuente de la salida generada
- 3EjemploDa nombre al proyecto y a su biblioteca de pruebas
- 4EjemploSolicita la fase que necesitas
- 5ConsejoNo confundas cero pruebas con comportamiento comprobado
- 6DesafíoEncuentra una prueba mal ubicada
- 7ExplicaciónSelecciona las tareas de Gradle
- 8EjemploMantén alineados los módulos de Jupiter
- 9EjemploIndica a la tarea de pruebas cómo ejecutar Jupiter
- 10DesafíoCorrige el descubrimiento de pruebas
- 11EjemploSigue el fallo de una prueba ejecutada
- 12DesafíoCombina el descubrimiento con una construcción nueva
- 13RepasoInterpreta la construcción como evidencia por etapas
Ejercicios de Paquetes en Java
Set 1: Relaciones entre clases y responsabilidades en capas10 Preguntas de Práctica
Pregunta 1 de 10
0..* está escrito junto a Book. ¿A cuántos libros puede estar vinculada una biblioteca?Mostrar respuestaOcultar respuesta
Book cuenta los libros para un objeto del extremo de la biblioteca. La cota inferior es cero y la superior no está restringida; interpretar el número como una cantidad de bibliotecas invertiría los extremos de la asociación.Pregunta 2 de 10
List<Session> comienza vacío y ningún método comprueba su tamaño. ¿Qué falta en la implementación?Mostrar respuestaOcultar respuesta
1..*. Los constructores y las operaciones de actualización deben mantener al menos una sesión; cambiar las importaciones o la visibilidad no impone esa regla de tamaño.Pregunta 3 de 10
Mostrar respuestaOcultar respuesta
Pregunta 4 de 10
Printable. ¿Qué declaración de subtipo Java representa esa línea?Mostrar respuestaOcultar respuesta
implements. Una línea continua con triángulo representa la generalización de una clase; una flecha abierta discontinua sin triángulo mostraría una dependencia.Pregunta 5 de 10
Basket contiene private List<Product> products; y no extiende Product. ¿Cómo debe mostrarse la relación principal?Mostrar respuestaOcultar respuesta
Product. Una lista indica un extremo de producto con varios valores, pero no establece un mínimo distinto de cero. La herencia afirmaría que una cesta es un tipo de producto, algo que esta declaración no indica.Pregunta 6 de 10
Mostrar respuestaOcultar respuesta
Pregunta 7 de 10
Mostrar respuestaOcultar respuesta
Pregunta 8 de 10
CatalogRepository. ¿Cómo puede una prueba evitar la base de datos real?Mostrar respuestaOcultar respuesta
Pregunta 9 de 10
Mostrar respuestaOcultar respuesta
Pregunta 10 de 10
rules.OrderService importa storage.OrderRepository. Esa interfaz nombra rules.Order en sus firmas y las implementaciones de almacenamiento la implementan. ¿Cómo puede reubicarse el contrato para eliminar el ciclo entre los paquetes de reglas y almacenamiento y conservar el almacenamiento intercambiable?Mostrar respuestaOcultar respuesta
Order, de modo que el servicio lo utilice sin importar almacenamiento. Las implementaciones de almacenamiento dependen entonces de los tipos de reglas e implementan el mismo contrato. Los nombres completamente cualificados cambian la forma de escribirlos, no las dependencias, por lo que mantienen el ciclo entre paquetes.Set 2: Nombres, carpetas y acceso en Java10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
Ticket declara package org.example.events.model;. ¿Qué ruta debe tener dentro del directorio raíz del código fuente Java principal?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
shop.ui necesita una clase auxiliar que actualmente no tiene declaración de paquete. ¿Cómo debe organizarse la clase auxiliar para que la interfaz pueda importarla?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
import java.util.*; y utiliza java.util.function.Predicate por su nombre simple. ¿Qué importación adicional de un solo tipo proporciona ese nombre?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
java.util.* y java.awt.* y después declara List<String> names. ¿Cómo puede hacer que List se refiera a la interfaz de colección y conservar ambas importaciones con comodín?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
assertTrue(valid) sin el prefijo de la clase Assertions. ¿Qué importación permite esa llamada directa?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
book.data.Record es público y tiene un constructor declarado como Record(int id). Una clase sin relación de book.ui lo importa, pero no puede construirlo. ¿Qué cambio de acceso permite esa llamada?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
class RateTable no tiene modificador de acceso. ¿Dónde pueden utilizarla otras clases de nivel superior?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
# threshold: int y un + reset(): void subrayado. ¿Cómo se traducen esos miembros a Java?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
public boolean accepts(int grams)?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
base tiene la clase Engine con acceso de paquete. Su constructor declarado explícitamente y su método run() no tienen modificador de acceso. Una clase Screen sin relación, en base.ui, debe llamar a new Engine().run(). ¿Qué cambios mínimos de visibilidad permiten esa llamada?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 3: Aserciones, selección de casos y pruebas independientes10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
actual y su resultado requerido es 42. ¿Qué aserción de igualdad de Jupiter etiqueta correctamente los valores?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
double difiere de 0,6 en aproximadamente 0,0000000000000001. La precisión numérica requerida es 0,000001. ¿Cómo debe comprobarse su resultado esperado?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
reading <= 800 a reading < 800. ¿Qué lectura válida revela el error?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
IllegalArgumentException. ¿Cómo debe ejecutar la llamada una prueba de Jupiter?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
250 + ((grams - 3000) / 500) * 50 por encima del límite. ¿Qué nueva prueba revela más directamente su defecto de redondeo?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 4: Configuración de construcción e informes de fallos10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
TotalsChecks.java importa org.junit.Test. Los patrones de inclusión de Surefire no se han cambiado. ¿Qué corrección combinada permite compilar y ejecutar la clase como una prueba de Jupiter descubierta?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
platform(...) de Gradle. ¿Por qué las declaraciones de dependencias de JUnit relacionadas pueden omitir los números de versión individuales?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
org.junit.jupiter.api. Su única declaración de Jupiter es testRuntimeOnly("org.junit.jupiter:junit-jupiter") y la tarea de pruebas ya utiliza JUnit Platform. ¿Qué corrección de configuración resuelve esta etapa?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 5: Diagnostica un cambio completo en Java10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
1 junto a Department y 0..* junto a Employee. ¿Qué permite el modelo para un departamento y qué exige para un empleado?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
base.Item tiene un campo de instancia protegido weight. En otro paquete, Special extends Item tiene un método de instancia con los parámetros Item a y Special b. Dentro de ese método, ¿qué expresiones pueden acceder directamente al campo heredado?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
MemoryArchive realiza la interfaz Archive y contiene uno o más objetos Entry mediante una asociación navegable. Una clase generada indica actualmente extends Archive y crea una lista vacía sin comprobaciones. ¿Qué corrección combinada conserva el modelo?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
year % 4 == 0 y pasa las pruebas para 2031 y 2032. Una nueva prueba para 2100 debe rechazar ese año y otra para 2400 debe aceptarlo después. ¿Qué secuencia de cambios de comportamiento se ajusta a los casos pendientes?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
< 2500 para la franja inferior. ¿Qué par de comprobaciones adicionales revela ambos defectos?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
testRuntimeOnly("org.junit.jupiter:junit-jupiter:6.1.3") y tasks.test { }. Una clase de prueba de Jupiter está guardada en src/main/java y su anotación no compila. ¿Qué cambios permiten compilar y ejecutar la clase con Jupiter?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
target. ¿Qué demuestra ese archivo sobre el código fuente de hoy?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
¿Te atascas con una pregunta tuya? Ayuda con Tareas
Términos Clave
A
Acceso de paquete
Acceso protegido
Agregación
Agrupación por paquetes
Aserción
Aserción de excepción
assertThrows puede verificar que se rechaza un peso de cero y devolver la excepción para comprobar su mensaje.Asociación
parcels en el extremo del paquete.Atributo
- weightGrams: int representa un campo entero privado que contiene el peso del paquete.Á
Ámbito de dependencia
src/test/java sin convertirlo en una dependencia del código principal.C
Capa
Capa de acceso a datos
Capa de presentación
Caso límite
Ciclo de dependencias entre paquetes
Ciclo de vida de construcción
Clase de equivalencia
Complemento
Composición
Configuración de dependencias
Conflicto de nombres
List puede importar uno y utilizar el nombre completamente cualificado del otro.Contrato de interfaz
save mediante el contrato del repositorio tanto si el almacenamiento utiliza memoria como un archivo.Coordenadas del artefacto
D
Declaración de importación
Scanner puede utilizar el nombre simple en la declaración de su campo en lugar de java.util.Scanner.Declaración de paquete
package com.example.parcels.domain; hace que ese paquete forme parte del nombre completo de la clase.Dependencia
Parcel crea una dependencia de la definición del tipo de paquete.Dependencia transitiva
Desarrollo guiado por pruebas
Diagrama de clases UML
Directorio raíz del código fuente
src/main/java para código de producción en una construcción estándar.parcels.domain.Parcel, la ruta dentro del directorio raíz del código fuente es parcels/domain/Parcel.java.E
Ensamblaje de objetos
Entorno de prueba
Expresión lambda
() -> calculator.feeInCents(0) permite que JUnit ejecute la llamada no válida dentro de su aserción.G
Generalización
Letter hacia Item se traduce a una declaración Java en la que Letter extiende Item.H
Herramienta de construcción
I
Implementación simplificada para pruebas
Importación con comodín
java.util.* sigue dejando a java.util.function.Function necesitada de su propia importación o de su nombre completo.Importación de un solo tipo
import java.util.ArrayList; identifica exactamente qué ArrayList utiliza el archivo.Importación estática
assertEquals para que cada comprobación se lea como una llamada directa de aserción.Independencia de las pruebas
Interfaz de usuario
Interfaz pública del paquete
Inyección de dependencias
J
JUnit Jupiter
@Test de Jupiter mantiene la anotación de prueba coherente con el motor seleccionado por la construcción.JUnit Platform
useJUnitPlatform() cuando las pruebas del proyecto utilizan anotaciones de Jupiter.L
Lanzador de la herramienta de construcción
./mvnw test.Lista de pruebas
Lista de versiones de dependencias
Lógica de la aplicación
M
Método del ciclo de vida
@BeforeEach reconstruye los objetos que cambian, mientras que @AfterEach puede eliminar recursos temporales de prueba.Miembro estático
Modificador de acceso
Multiplicidad
1..* junto a Parcel exige que cada envío mantenga al menos un paquete vinculado.N
Navegabilidad
Nombre completamente cualificado
java.util.List distingue la interfaz de colección de otro tipo que también se llama List.O
Operación
+ totalWeightGrams(): int describe un método público sin indicar su algoritmo.P
Paquete
Paquete sin nombre
Preparar, actuar y comprobar
Prueba inestable
Prueba parametrizada
Prueba unitaria
R
Realización
Refactorización
Regresión
Repositorio
Resultado esperado
Rojo, verde y refactorización
S
Separación de responsabilidades
Símbolo de visibilidad
+ público, - privado, # protegido o ~ acceso de paquete.- debe conservar la visibilidad privada cuando el diagrama se traduzca a Java.Superclase
Surefire
ShippingCalculatorTest hace que coincida con un patrón de inclusión predeterminado de Surefire.T
Tarea de Gradle
Tolerancia
- 1Primeros programas en Java
- 2Condiciones y bucles en Java
- 3Métodos en Java
- 4Listas, arreglos y cadenas en Java
- 5Clases y objetos en Java
- 6Objetos y referencias en Java
- 7HashMap y colecciones en Java
- 8Herencia, interfaces y polimorfismo en Java
- 9Excepciones y archivos en Java
- 10Paquetes en Java
- 11Genéricos y expresiones lambda en Java
Sobre esta unidad
Crea tus propios Materiales de Estudio
Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.