Paquetes, diagramas de clases y pruebas unitarias en Java

Aprende a organizar un programa Java de mayor tamaño para que siga siendo fácil de entender, modificar y comprobar. Agrupa clases en paquetes mediante importaciones y los niveles de acceso adecuados, esboza la estructura en diagramas de clases UML y separa la interfaz de usuario, la lógica del programa y el almacenamiento de datos en capas. Después, verifica la lógica con pruebas de JUnit, escribe las pruebas antes que el código y deja que Maven o Gradle compile, pruebe y empaquete todo el proyecto con un solo comando.

PrincipianteUnidad 10 de 11 del curso Curso de Programación orientada a objetos con Java

Lo que aprenderás

  1. 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 carpetas
      Escribe 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 importaciones
      Importa 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 paquetes
      Elige entre public, protected, acceso de paquete y private para que cada paquete exponga solo lo que necesitan los demás paquetes.
  2. 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 individual
      Representa 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 multiplicidades
      Conecta 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 viceversa
      Representa 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.
  3. 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 programa
      Manté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 dependencias
      Organiza 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.
  4. 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 JUnit
      Crea 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 errores
      Selecciona 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 mantener
      Prepara 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 TDD
      Sigue 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.
  5. 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 Maven
      Describe 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ón
      Escribe 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.

Guía de estudio de Paquetes en Java

5 capítulos

1

Organiza 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/java fuera 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.lang no 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?
publicCódigo que puede acceder al tipo que lo declara
protectedEl mismo paquete, además del acceso de subclases entre paquetes
Sin modificadorExactamente el mismo paquete
privateLa 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.
2

Esboza 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 utiliza Type 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.

MultiplicidadSignificado
1Exactamente uno
0..1Cero o uno
* o 0..*Cualquier número, incluido cero
1..*Al menos uno
2..4De 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
  1. Crea cada clase o interfaz en su paquete.
  2. Traduce los atributos a campos, conservando nombres, tipos y visibilidad.
  3. Traduce las asociaciones navegables a campos de referencia o de colección.
  4. Añade extends o implements a partir de las líneas de generalización o realización.
  5. 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.
3

Estructura 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 main y 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.
4

Verifica 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 gramosResultado esperado
0, -5Rechazar con IllegalArgumentException
1, 2000500 centavos
2001, 10000900 centavos
10001, 110001000 centavos
110011100 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 assertThrows como 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 @BeforeEach para 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.
5

Construye 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 target y 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

5 Lecciones Interactivas

1

Mueve una clase de paquetes a un paquete Java

13 pasos
Relaciona una ruta de código fuente, las importaciones y las comprobaciones de acceso mientras separas el código de la interfaz del código de dominio.
  1. 1
    Introducció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.

  2. 2
    EjemploComienza dentro del directorio raíz del código fuente

    El directorio raíz del código fuente es src/main/java. Para Parcel en com.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.java
    

    Escribe package com.example.parcels.domain; al comienzo del archivo. Las carpetas src/main/java de la raíz describen la estructura de construcción y quedan fuera de la declaración de paquete.

  3. 3
    ExplicaciónMantén intacta la identidad de la clase

    El nombre simple es Parcel; el nombre completamente cualificado es com.example.parcels.domain.Parcel. Mover el archivo no reescribe por sí solo esa identidad.

    Una clase pública llamada Parcel pertenece a Parcel.java. Ejecuta una clase principal que pertenece a un paquete desde la raíz del árbol compilado mediante un comando como java -cp out com.example.parcels.Main, para que Java pueda encontrar las carpetas de paquetes dentro de out.

  4. 4
    DesafíoCorrige una ubicación que no coincide

    Una clase pública llamada Receipt declara package com.example.parcels.domain;, pero está guardada en src/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.

  5. 5
    EjemploFacilita 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.

  6. 6
    Idea 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 proporciona java.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.

  7. 7
    DesafíoDistingue dos listas

    Un archivo necesita tanto java.util.List como java.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 escribe java.awt.List por completo donde necesites ese tipo. Dos importaciones de un solo tipo diferentes no pueden dar dos significados al mismo nombre simple en un archivo.

  8. 8
    ConsejoReconoce los nombres que no necesitan importación

    Un archivo puede utilizar tipos de su propio paquete sin importarlos. Los tipos accesibles de java.lang, incluidos String, Math y System, también están disponibles automáticamente.

    Una importación estática tiene otra función: importar estáticamente Assertions.assertEquals permite una llamada directa a assertEquals(...). Abrevia una referencia a un miembro estático, no un nombre de paquete.

  9. 9
    EjemploComprueba 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 Parcel mediante 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.

  10. 10
    DesafíoAbre la operación y mantén oculta la clase auxiliar

    La interfaz debe llamar al método feeInCents de una calculadora pública, actualmente declarado sin modificador de acceso. Solo la calculadora utiliza su clase auxiliar WeightBands, que tiene acceso de paquete. ¿Qué cambio de acceso se necesita?

    ---reveal---

    Haz público feeInCents y deja WeightBands con 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.

  11. 11
    Explicació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.

  12. 12
    DesafíoDiagnostica toda la ruta de acceso

    TextUI importa correctamente una clase Parcel pública. Su llamada a new 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.

  13. 13
    RepasoUtiliza 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.

2

Interpreta un modelo de paquetes y construye su esqueleto Java

13 pasos
Traduce recuadros UML, extremos de relaciones y líneas de subtipos a declaraciones Java coherentes.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónInterpreta la estructura antes de escribir los cuerpos
  2. 2
    EjemploTraduce un campo y un método
  3. 3
    ExplicaciónIdentifica un miembro de nivel de clase
  4. 4
    DesafíoConserva los símbolos
  5. 5
    EjemploCuenta los paquetes desde un envío
  6. 6
    EjemploElige una representación y después haz cumplir la regla
  7. 7
    Idea claveSigue un vínculo unidireccional
  8. 8
    DesafíoComprueba una cantidad mínima
  9. 9
    ExplicaciónColoca la propiedad en el extremo correcto
  10. 10
    EjemploInterpreta dos líneas con triángulos
  11. 11
    ConsejoConstruye las declaraciones en orden
  12. 12
    DesafíoCombina un contrato y una colección
  13. 13
    RepasoDistingue la función de cada símbolo
3

Sigue un comando a través de responsabilidades separadas

11 pasos
Saca un cálculo de tarifas de la interfaz y conecta un servicio a un almacenamiento sustituible.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónSigue un comando a través del programa
  2. 2
    EjemploExtrae la regla del código de consola
  3. 3
    EjemploCalcula un bloque comenzado
  4. 4
    Idea claveMantén la validación junto a la regla
  5. 5
    DesafíoHaz reutilizable un cálculo
  6. 6
    EjemploPasa el almacenamiento al servicio
  7. 7
    ConexiónSustituye los archivos por una implementación simplificada funcional
  8. 8
    ConsejoComprueba las llamadas y las importaciones por separado
  9. 9
    DesafíoCorrige un atajo que evita el servicio
  10. 10
    DesafíoConecta una prueba de servicio controlada
  11. 11
    RepasoColoca cada cambio donde corresponde su tarea
4

Desarrolla una colección útil de pruebas de Jupiter

19 pasos
Escribe una primera aserción, elige límites, aísla el estado y desarrolla una regla mediante pequeños cambios en los que se escribe primero la prueba.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónConvierte requisitos en comprobaciones ejecutables
  2. 2
    EjemploPrepara, actúa y comprueba
  3. 3
    ExplicaciónInterpreta correctamente los valores esperado y real
  4. 4
    ConsejoPermite el redondeo numérico de forma deliberada
  5. 5
    DesafíoCorrige una prueba que solo imprime
  6. 6
    EjemploEncuentra las transiciones de tarifa
  7. 7
    Idea claveMantén independiente el valor esperado
  8. 8
    EjemploColoca la llamada rechazada dentro de la comprobación
  9. 9
    DesafíoElige el caso que revela el cambio
  10. 10
    EjemploDa a cada prueba un envío nuevo
  11. 11
    EjemploEjecuta una tabla mediante un solo cuerpo
  12. 12
    ConexiónControla el colaborador, no el orden de las pruebas
  13. 13
    DesafíoDiagnostica datos que dejó otra prueba
  14. 14
    EjemploDeja que un año ordinario inicie el ciclo
  15. 15
    EjemploAñade la regla general de divisibilidad por cuatro
  16. 16
    EjemploDeja que los casos de años de siglo refinen la condición
  17. 17
    DesafíoElige un siguiente paso conservando las comprobaciones
  18. 18
    DesafíoCombina la selección de casos y el aislamiento
  19. 19
    RepasoConfía en evidencia específica y repetible
5

Lleva un proyecto del archivo de construcción al informe de pruebas

13 pasos
Configura las dependencias y el descubrimiento de pruebas, ejecuta los lanzadores y diagnostica los fallos en la etapa donde se producen.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónHaz que un comando repita la construcción
  2. 2
    ExplicaciónSepara los archivos fuente de la salida generada
  3. 3
    EjemploDa nombre al proyecto y a su biblioteca de pruebas
  4. 4
    EjemploSolicita la fase que necesitas
  5. 5
    ConsejoNo confundas cero pruebas con comportamiento comprobado
  6. 6
    DesafíoEncuentra una prueba mal ubicada
  7. 7
    ExplicaciónSelecciona las tareas de Gradle
  8. 8
    EjemploMantén alineados los módulos de Jupiter
  9. 9
    EjemploIndica a la tarea de pruebas cómo ejecutar Jupiter
  10. 10
    DesafíoCorrige el descubrimiento de pruebas
  11. 11
    EjemploSigue el fallo de una prueba ejecutada
  12. 12
    DesafíoCombina el descubrimiento con una construcción nueva
  13. 13
    RepasoInterpreta la construcción como evidencia por etapas

Ejercicios de Paquetes en Java

50 Preguntas de Práctica en 5 sets

Set 1: Relaciones entre clases y responsabilidades en capas10 Preguntas de Práctica

Pregunta 1 de 10

En una asociación entre biblioteca y libro, 0..* está escrito junto a Book. ¿A cuántos libros puede estar vinculada una biblioteca?
  1. Al menos un libro.
  2. Exactamente un libro.
  3. Cero o más libros.
  4. Cero o un libro.
Mostrar respuestaOcultar respuesta
Respuesta: C
La multiplicidad junto a 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

Un curso debe tener una o más sesiones. Su campo List<Session> comienza vacío y ningún método comprueba su tamaño. ¿Qué falta en la implementación?
  1. Comprobaciones que establezcan y mantengan el número mínimo de sesiones.
  2. Una importación de lista diferente para que la colección imponga un mínimo.
  3. Un campo de sesiones estático compartido por todas las instancias del curso.
  4. Un campo de lista protegido que permita a las sesiones llenarlo directamente.
Mostrar respuestaOcultar respuesta
Respuesta: A
Una lista puede contener cualquier número de entradas, incluida ninguna, por lo que la colección declarada no hace cumplir 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

Un modelo de carpeta agrupa documentos que pueden compartirse y seguir existiendo después de eliminar la carpeta. ¿Respalda el requisito una composición de documentos por esa carpeta?
  1. Sí; toda colección de referencias establece propiedad compuesta.
  2. Sí; una carpeta es el todo, así que no importa la vida del documento.
  3. No; la composición se reserva para clases sin campos de colección.
  4. No; los documentos compartidos e independientes carecen de propiedad compuesta.
Mostrar respuestaOcultar respuesta
Respuesta: D
La composición da a una parte como máximo un propietario compuesto a la vez y normalmente la vincula al tiempo de vida de ese todo. Los documentos compartidos que sobreviven a la eliminación no cumplen ese modelo. Una colección por sí sola establece referencias almacenadas, no composición; una asociación simple puede expresar este diseño.

Pregunta 4 de 10

Una clase tiene una línea UML discontinua que termina en un triángulo vacío en Printable. ¿Qué declaración de subtipo Java representa esa línea?
  1. extends Printable
  2. implements Printable
  3. imports Printable
  4. new Printable()
Mostrar respuestaOcultar respuesta
Respuesta: B
Una línea discontinua con un triángulo vacío representa la realización de una interfaz, que Java expresa con 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?
  1. Una asociación hacia Product, con varios valores en el extremo de Product.
  2. Una línea continua de generalización desde Basket, con el triángulo en Product.
  3. Una línea discontinua de realización desde Basket, con el triángulo en Product.
  4. Una asociación navegable desde Product hasta Basket, con varios valores en el extremo de Basket.
Mostrar respuestaOcultar respuesta
Respuesta: A
El campo almacena referencias desde una cesta hacia productos, por lo que proporciona una asociación navegable hacia 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

Una calculadora de calificaciones calcula una media y también la imprime. Una interfaz web necesita el cálculo. ¿Qué cambio hace que esa lógica sea directamente reutilizable?
  1. Copiar el cálculo en la interfaz web.
  2. Añadir un formato de impresión web a la calculadora.
  3. Devolver la media y dejar que las interfaces le den formato.
  4. Hacer que la calculadora lea primero el nombre de la interfaz.
Mostrar respuestaOcultar respuesta
Respuesta: C
Devolver la media calculada proporciona a quien llama un valor que puede mostrar o comprobar mediante una aserción en una prueba. Copiar el cálculo crea dos implementaciones de la regla; mantener las opciones de presentación dentro de la calculadora sigue mezclando interacción y lógica.

Pregunta 7 de 10

Una interfaz de texto rechaza entradas no numéricas antes de llamar a una calculadora. La calculadora también debe rechazar valores numéricos negativos para todas las llamadas. ¿Dónde debe estar esa regla?
  1. Solo en la interfaz de texto, junto al análisis numérico.
  2. En la calculadora, con los mensajes gestionados por la interfaz.
  3. En el método main, después de crear la calculadora.
  4. En el repositorio, después de guardar el resultado calculado.
Mostrar respuestaOcultar respuesta
Respuesta: B
La calculadora es responsable de su regla de validez, por lo que debe hacerla cumplir incluso cuando la llaman pruebas u otra interfaz. La interfaz se encarga del análisis y la presentación del texto; una comprobación solo en la interfaz permite que otras llamadas pasen argumentos numéricos no válidos.

Pregunta 8 de 10

El constructor de un servicio acepta una interfaz CatalogRepository. ¿Cómo puede una prueba evitar la base de datos real?
  1. Pasar un nombre de archivo de base de datos a la implementación real existente.
  2. Hacer que el constructor del servicio cree el repositorio de base de datos.
  3. Mover el servicio al paquete de presentación para la prueba.
  4. Inyectar una implementación de CatalogRepository en memoria.
Mostrar respuestaOcultar respuesta
Respuesta: D
El constructor acepta un contrato, por lo que cualquier implementación adecuada puede proporcionar sus operaciones de almacenamiento. Una implementación simplificada en memoria permite que la prueba controle los datos sin una base de datos. Crear el repositorio real dentro del servicio impediría que la prueba seleccionara ese colaborador.

Pregunta 9 de 10

Una pantalla de reservas guarda directamente mediante un repositorio. El servicio contiene una comprobación del límite de plazas y crea su propio repositorio de archivos. Las pruebas necesitan verificar ese límite sin acceder al disco. ¿Qué par de cambios sirve tanto para la pantalla como para las pruebas?
  1. Canalizar las solicitudes de la pantalla por el servicio e inyectarle el repositorio.
  2. Mantener el guardado directo e inyectar el repositorio solo en la pantalla.
  3. Canalizar las solicitudes por el servicio y compartir un repositorio de archivos estático.
  4. Mover la comprobación de plazas a la pantalla y dejar que el servicio siga eligiendo archivos.
Mostrar respuestaOcultar respuesta
Respuesta: A
Canalizar las solicitudes por el servicio aplica la regla de plazas antes de guardar; la inyección por constructor permite que las pruebas proporcionen almacenamiento controlado. Inyectar almacenamiento solo en la pantalla no corrige que se omita la validación del servicio, mientras que un archivo real compartido sigue haciendo que las pruebas dependan del estado del disco.

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?
  1. Mover Order a storage; OrderService importa un almacén concreto.
  2. Mover la interfaz a rules y hacer que storage implemente ese contrato.
  3. Mover OrderRepository a la interfaz de usuario y hacer que ambas capas inferiores lo importen.
  4. Mantener la estructura y sustituir las importaciones por nombres completamente cualificados.
Mostrar respuestaOcultar respuesta
Respuesta: B
El contrato puede estar junto a 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

Una clase pública Ticket declara package org.example.events.model;. ¿Qué ruta debe tener dentro del directorio raíz del código fuente Java principal?
  1. org/example/events/Ticket/model.java
  2. org/example/events/model/Ticket.java
  3. src/main/java/org/example/events/model/Ticket.java
  4. org.example.events.model/Ticket.java
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Una clase del paquete 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?
  1. Dejarla sin nombre y añadir import Helper; al archivo fuente de la interfaz.
  2. Colocarla en una carpeta llamada shop sin añadir una declaración de paquete.
  3. Dejarla sin nombre y cualificarlo con la ruta del directorio raíz principal.
  4. Colocarla en un paquete con nombre y declaraciones accesibles.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Un archivo tiene import java.util.*; y utiliza java.util.function.Predicate por su nombre simple. ¿Qué importación adicional de un solo tipo proporciona ese nombre?
  1. import java.util.function.Predicate;
  2. import java.util.Predicate;
  3. import java.util.function;
  4. import static java.util.function.Predicate;
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Un archivo importa 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?
  1. Añadir import static java.util.List;.
  2. Sustituir el tipo de la declaración por Object.
  3. Añadir import java.util.List;.
  4. Mover ambos comodines antes de la declaración de paquete.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Una prueba de Jupiter llama a assertTrue(valid) sin el prefijo de la clase Assertions. ¿Qué importación permite esa llamada directa?
  1. import org.junit.jupiter.api.Assertions.assertTrue;
  2. import static org.junit.jupiter.api.Test.assertTrue;
  3. import org.junit.jupiter.api.Assertions;
  4. import static org.junit.jupiter.api.Assertions.assertTrue;
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?
  1. Hacer público el constructor.
  2. Hacer protegido el constructor.
  3. Importar también book.data.*.
  4. Cambiar el nombre del paquete de interfaz a book.data.ui.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Una clase auxiliar ordinaria declarada como class RateTable no tiene modificador de acceso. ¿Dónde pueden utilizarla otras clases de nivel superior?
  1. En los paquetes que comparten su nombre de nivel superior.
  2. Solo en el paquete exacto donde se declara.
  3. En cualquier paquete después de una importación explícita.
  4. Solo en subclases, en cualquier paquete.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Un recuadro de clase contiene # threshold: int y un + reset(): void subrayado. ¿Cómo se traducen esos miembros a Java?
  1. Un campo entero privado y un método reset público y abstracto.
  2. Un campo entero con acceso de paquete y un método reset público y estático.
  3. Un campo entero protegido y un método reset público y estático.
  4. Un campo entero protegido y un método reset público de instancia.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

¿Qué operación UML describe public boolean accepts(int grams)?
  1. + boolean accepts(int: grams)
  2. - accepts(grams: int): boolean
  3. + accepts(grams: boolean): int
  4. + accepts(grams: int): boolean
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

El paquete 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?
  1. Hacer público Engine; dejar el resto con acceso de paquete.
  2. Hacer públicos Engine y run, no el constructor.
  3. Hacer públicos Engine, su constructor y run.
  4. Hacer públicos solo el constructor y run.
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

Un método devuelve actual y su resultado requerido es 42. ¿Qué aserción de igualdad de Jupiter etiqueta correctamente los valores?
  1. assertEquals(actual, 42);
  2. assertTrue(actual);
  3. assertNotNull(42);
  4. assertEquals(42, actual);
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un cálculo correcto de tipo 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?
  1. assertEquals(0.6, actual);
  2. assertEquals(0.6, actual, 1.0);
  3. assertEquals(0.6, actual, 1e-6);
  4. assertNotNull(actual);
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Una regla de tamaño acepta enteros de 1 a 500, ambos inclusive. ¿Es 0 una entrada válida, una entrada válida en el límite o una entrada no válida?
  1. Una entrada no válida.
  2. Una entrada válida en el límite.
  3. Una entrada válida.
  4. Una entrada no especificada.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Un sensor acepta lecturas enteras hasta 800 inclusive. Una persona cambia su comprobación del límite superior de reading <= 800 a reading < 800. ¿Qué lectura válida revela el error?
  1. 799
  2. 800
  3. 801
  4. 400
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Un método debe rechazar una cadena vacía con IllegalArgumentException. ¿Cómo debe ejecutar la llamada una prueba de Jupiter?
  1. assertThrows(IllegalArgumentException.class, () -> parser.parse(""));
  2. assertThrows(IllegalArgumentException.class, parser.parse(""));
  3. assertEquals(IllegalArgumentException.class, parser.parse(""));
  4. assertNotNull(() -> parser.parse(""));
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Una tarifa cobra 250 centavos hasta 3000 g inclusive y después 50 centavos por cada bloque comenzado de 500 g por encima de ese límite. Su implementación utiliza 250 + ((grams - 3000) / 500) * 50 por encima del límite. ¿Qué nueva prueba revela más directamente su defecto de redondeo?
  1. 3500 g deben costar 300 centavos.
  2. 4000 g deben costar 350 centavos.
  3. 3001 g deben costar 300 centavos.
  4. 3000 g deben costar 250 centavos.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Se crea una nueva instancia de la clase de prueba para cada prueba, pero una lista estática mutable conserva entradas de una prueba anterior. ¿Qué preparación da a cada escenario su propia lista?
  1. Cambiar los nombres de los métodos de prueba para forzar un orden alfabético.
  2. Utilizar una lista no estática inicializada para cada prueba.
  3. Crear una lista en un método BeforeAll estático.
  4. Conservar la lista estática y añadir etiquetas DisplayName.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Una prueba de descuentos lee la fecha actual y utiliza un repositorio estático que contiene clientes anteriores. Pasa por sí sola, pero cambia de resultado entre ejecuciones de la colección de pruebas. ¿Cómo debe rediseñarse su preparación?
  1. Mantener la fecha de hoy y ejecutar la prueba primero en cada colección.
  2. Utilizar una fecha fija y reutilizar el repositorio compartido con datos.
  3. Volver a crear solo el servicio y dejar sin cambios ambas entradas externas.
  4. Proporcionar una fecha fija y un repositorio nuevo para cada escenario.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Una regla de tarifas tiene ocho filas de entrada y resultado esperado que utilizan la misma aserción. ¿Cómo puede Jupiter mantener casos registrados por separado sin ocho cuerpos de método copiados?
  1. Utilizar una prueba parametrizada con las filas como fuente de argumentos.
  2. Utilizar una prueba que imprima cada resultado, pero compruebe solo el primero.
  3. Utilizar BeforeAll para ejecutar el cálculo de la tarifa una vez para toda la clase.
  4. Utilizar Disabled en los métodos repetidos y conservar un método activo.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Todas las pruebas actuales pasan. Quieres cambiar el nombre de un método auxiliar y extraer lógica repetida sin cambiar ninguna salida. ¿Qué actividad de TDD es esta?
  1. Añadir un nuevo caso rojo para una función diferente.
  2. Cambiar los resultados esperados para que coincidan con la salida del método auxiliar.
  3. Refactorizar mientras se vuelven a ejecutar las pruebas que pasan.
  4. Omitir las pruebas anteriores hasta implementar otro comportamiento.
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

¿Dónde coloca un proyecto Maven estándar los archivos fuente Java de pruebas unitarias?
  1. src/test/java
  2. src/main/java
  3. target/test-classes
  4. src/test/resources
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un proyecto utiliza JUnit solo en pruebas. ¿Qué ámbito de dependencia de Maven lo mantiene disponible para compilar y ejecutar pruebas sin añadirlo a las rutas de clases principales?
  1. compile
  2. test
  3. runtime
  4. provided
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Con un ciclo de vida normal de Maven y sin omisiones configuradas, ¿qué ocurre antes de que la fase de empaquetado cree un JAR?
  1. Solo se compila el código principal; las pruebas se ejecutan por separado.
  2. Un JAR instalado más antiguo se copia al código fuente.
  3. Se compilan el código principal y el de prueba, y se ejecutan las pruebas.
  4. El ciclo de limpieza siempre elimina primero la salida.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Un proyecto Maven con Java 17 declara solo JUnit Jupiter 6.1.3, pero configura Surefire 2.12.4. Su archivo de prueba 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?
  1. Renombrarla TotalsTest y usar @Test de Jupiter, pero mantener Surefire 2.12.4.
  2. Mantener TotalsChecks, usar @Test de Jupiter y elegir Surefire 3.5.4.
  3. Renombrarla TotalsTest y elegir Surefire 3.5.4, pero mantener org.junit.Test.
  4. Renombrarla TotalsTest, usar @Test de Jupiter y elegir Surefire 3.5.4.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

¿Cuál es el nombre habitual de un archivo de script de construcción de Gradle escrito en Kotlin?
  1. settings.gradle.kts
  2. build.gradle.kts
  3. build.gradle
  4. pom.xml
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Un proyecto Gradle tiene pruebas de Jupiter y declara sus bibliotecas. ¿Qué llamada dentro de la tarea de pruebas selecciona su plataforma de ejecución?
  1. mavenCentral()
  2. testImplementation()
  3. mainClass.set()
  4. useJUnitPlatform()
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Se declara una BOM de JUnit con platform(...) de Gradle. ¿Por qué las declaraciones de dependencias de JUnit relacionadas pueden omitir los números de versión individuales?
  1. El compilador Java infiere la versión instalada más reciente de cada módulo.
  2. El lanzador sustituye cada versión de dependencia por su versión de Gradle.
  3. La BOM proporciona restricciones de versión alineadas para los módulos de JUnit.
  4. La tarea de pruebas descarga cualquier módulo cuyo nombre de clase aparezca en una prueba.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Un proyecto Gradle no puede compilar una prueba porque falta 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?
  1. Declarar Jupiter con testImplementation para que su API esté disponible al compilar pruebas.
  2. Añadir otra llamada a useJUnitPlatform sin cambiar la dependencia.
  3. Mover la prueba al código fuente principal y dejar Jupiter solo para ejecución.
  4. Mantener Jupiter solo para ejecución y añadir una anotación DisplayName a la prueba.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Una prueba de servicio reutiliza un repositorio de archivos estático. Pasa en el IDE después de un restablecimiento, pero falla durante la colección completa de Gradle porque pruebas anteriores guardaron registros adicionales. El servicio ya acepta una interfaz de repositorio. ¿Qué corrección aísla su comportamiento?
  1. Inyectar un repositorio nuevo en memoria preparado solo con los registros de esta prueba.
  2. Utilizar BeforeAll para compartir el mismo repositorio de archivos en toda la colección.
  3. Ejecutar la limpieza de Gradle y mantener las suposiciones sobre los datos compartidos.
  4. Hacer pública la prueba y depender del orden actual de ejecución de la colección.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Una nueva comprobación de TDD pasa inmediatamente, aunque pretendías que revelara un comportamiento ausente. ¿Cuál es la siguiente investigación adecuada?
  1. Añadir código de producción hasta que la prueba que pasa empiece a fallar.
  2. Confirmar que se ejecuta, comprueba el resultado previsto y puede que ya se cumpla.
  3. Eliminar la aserción porque pasar demuestra que el nuevo comportamiento está ausente.
  4. Refactorizar clases sin relación antes de comprobar a qué método llama la prueba.
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

Una prueba en ejecución informa «expected: <18> but was: <21>» desde una aserción de igualdad con el orden correcto. ¿Qué valor devolvió el método?
  1. 18
  2. 3
  3. 21
  4. 39
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Una asociación muestra 1 junto a Department y 0..* junto a Employee. ¿Qué permite el modelo para un departamento y qué exige para un empleado?
  1. Un departamento exige un empleado; cada empleado puede no tener departamentos.
  2. Un departamento puede no tener empleados; cada empleado tiene exactamente un departamento.
  3. Un departamento exige muchos empleados; cada empleado tiene exactamente un departamento.
  4. Un departamento puede no tener empleados; cada empleado puede tener muchos departamentos.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Un método devuelve una lista que contiene tres valores especificados, pero el tipo de colección interno puede cambiar. ¿Cómo debe comprobar la prueba el resultado?
  1. Leer el arreglo interno privado y comprobar su capacidad exacta.
  2. Comprobar que su clase de colección concreta coincida con la actual.
  3. Imprimir los elementos y dejar que el ejecutor los compare automáticamente.
  4. Comparar el contenido devuelto con los tres valores requeridos.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

La clase 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?
  1. this.weight y b.weight
  2. this.weight y a.weight
  3. a.weight y b.weight
  4. this.weight, a.weight y b.weight
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Un diagrama muestra que 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?
  1. Usar implements Archive y mantener la lista vacía sin comprobaciones porque List garantiza uno o más.
  2. Usar implements Archive y rechazar entradas vacías en el constructor, pero permitir eliminar la última entrada.
  3. Usar implements Archive y garantizar una colección de entradas no vacía durante la construcción y las actualizaciones.
  4. Usar implements Archive y sustituir la colección por una referencia opcional a un solo Entry.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Un servicio de informes importa elementos de formato de la interfaz y crea su propio almacén de archivos. Una segunda interfaz y las pruebas sin acceso al disco necesitan su cálculo. ¿Qué corrección de diseño resuelve ambas dependencias?
  1. Devolver texto formateado para consola y dejar que el servicio siga creando el almacén de archivos.
  2. Mover el almacén de archivos al paquete de interfaz y mantener las llamadas de formato en el servicio.
  3. Hacer estáticos el formateador y el almacén del servicio para que ambas interfaces los compartan.
  4. Devolver los datos del informe, mantener el formato en las interfaces e inyectar un contrato de almacenamiento.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Un comprobador desarrollado con TDD devuelve actualmente 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?
  1. Excluir los años divisibles por 400 para 2100 y después admitir de nuevo los divisibles por 100 para 2400.
  2. Excluir los años divisibles por 100 para 2100 y después admitir de nuevo los divisibles por 400 para 2400.
  3. Excluir primero los años divisibles por 100 y después exigir divisibilidad por 400 para todo año bisiesto.
  4. Excluir primero los años divisibles por 100 y después permitir múltiplos de 400 solo si no son divisibles por 100.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Una regla de empaquetado rechaza pesos no positivos, cobra 400 centavos hasta 2500 g inclusive y 700 centavos por encima. Sus pruebas solo cubren 1000 g. Un cambio acepta cero accidentalmente y utiliza < 2500 para la franja inferior. ¿Qué par de comprobaciones adicionales revela ambos defectos?
  1. Cero debe lanzar una excepción; 2500 g deben devolver 400 centavos.
  2. Cero debe lanzar una excepción; 2501 g deben devolver 700 centavos.
  3. 1 g debe devolver 400 centavos; 2501 g deben devolver 700 centavos.
  4. −1 g debe lanzar una excepción; 2499 g deben devolver 400 centavos.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Un script de Gradle contiene 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?
  1. Mantenerla en el código fuente principal, usar testImplementation para Jupiter y seleccionar JUnit Platform.
  2. Moverla al código fuente de prueba, mantener Jupiter solo para ejecución y seleccionar JUnit Platform.
  3. Moverla al código fuente de prueba, usar testImplementation para Jupiter y conservar el ejecutor predeterminado.
  4. Moverla al código fuente de prueba, usar testImplementation para Jupiter y seleccionar JUnit Platform.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Una ejecución normal de empaquetado de Maven se detiene en una prueba fallida, pero un JAR de ayer permanece en target. ¿Qué demuestra ese archivo sobre el código fuente de hoy?
  1. Demuestra que el código fuente de hoy pasó sus pruebas.
  2. Demuestra que el empaquetado ignoró el fallo.
  3. Demuestra únicamente que existe un artefacto anterior.
  4. Demuestra que Maven volvió a compilar el código fuente de ayer.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

¿Te atascas con una pregunta tuya? Ayuda con Tareas

Términos Clave

73 Términos Clave, cada uno con su definición

A

Acceso de paquete
El acceso de paquete permite utilizar un miembro de clase ordinario o un tipo de nivel superior solo desde el paquete exacto que lo declara.
Una clase auxiliar sin modificador público sigue sin estar disponible para una clase de interfaz de un paquete vecino, incluso con una importación.
Acceso protegido
El acceso protegido permite el uso dentro del paquete que declara el miembro y el acceso heredado restringido de subclases de otros paquetes.
Una subclase de otro paquete puede leer su propio campo protegido heredado sin hacer público ese campo.
Agregación
La agregación es una agrupación poco estricta entre el todo y sus partes, mostrada con un rombo vacío, cuyo significado detallado depende del diseño modelado.
Un equipo puede agrupar jugadores que también existen de forma independiente, sin asumir la propiedad de su tiempo de vida.
Agrupación por paquetes
Una agrupación por paquetes es un área con forma de carpeta en un diagrama UML que organiza los recuadros de tipos según el paquete al que pertenecen.
Agrupar por separado las clases de interfaz y de dominio facilita examinar las referencias entre esas áreas.
Aserción
Una aserción en una prueba es una comprobación ejecutable que hace fallar la prueba cuando el resultado observado no cumple la expectativa indicada.
Una aserción de igualdad hace que una tarifa incorrecta provoque un fallo automático en lugar de quedar en la salida impresa.
Aserción de excepción
Una aserción de excepción ejecuta una operación proporcionada y comprueba si lanza el tipo de excepción requerido o un subtipo aceptado.
assertThrows puede verificar que se rechaza un peso de cero y devolver la excepción para comprobar su mensaje.
Asociación
Una asociación es una relación estructural que vincula objetos de distintos tipos y suele implementarse mediante campos que almacenan referencias.
La lista de paquetes de un envío puede dibujarse como una asociación con el nombre de rol parcels en el extremo del paquete.
Atributo
Un atributo es una propiedad con nombre que se muestra en un recuadro de clase UML y que suele corresponder a un campo Java con tipo y visibilidad.
La entrada - weightGrams: int representa un campo entero privado que contiene el peso del paquete.

Á

Ámbito de dependencia
Un ámbito de dependencia controla qué rutas de clases de Maven incluyen una biblioteca y separa los requisitos exclusivos de pruebas de los del programa principal.
El ámbito de prueba hace que JUnit esté disponible en src/test/java sin convertirlo en una dependencia del código principal.

C

Capa
Una capa agrupa clases con un tipo de responsabilidad común y ofrece operaciones a otras partes a través de un límite definido.
Un servicio de paquetes aplica la validación antes de pedir a una capa de almacenamiento que guarde un paquete nuevo.
Capa de acceso a datos
La capa de acceso a datos implementa el guardado y la recuperación de datos persistentes, manteniendo los detalles de almacenamiento separados de las reglas de la aplicación.
Un repositorio de archivos gestiona el formato del archivo, mientras que un servicio de paquetes se ocupa de los pesos válidos.
Capa de presentación
La capa de presentación traduce la interacción del usuario en solicitudes al programa y presenta al usuario los resultados devueltos.
Una interfaz de texto puede dar formato a una tarifa en una frase sin colocar esa frase dentro del código que calcula la tarifa.
Caso límite
Un caso límite utiliza una entrada en el extremo de una regla especificada o junto a él, donde un pequeño cambio puede alterar el comportamiento esperado.
Cuando una franja de tarifa termina en 2000 g, probar exactamente 2000 g revela una comparación incorrectamente estricta que una prueba con 1500 g no detectaría.
Ciclo de dependencias entre paquetes
Un ciclo de dependencias entre paquetes existe cuando una cadena de dependencias entre tipos parte de un paquete y vuelve a él, vinculando sus definiciones en ambas direcciones.
Colocar la interfaz del repositorio junto a los tipos de dominio puede eliminar un ciclo de importaciones entre dominio y datos.
Ciclo de vida de construcción
Un ciclo de vida de construcción es la secuencia ordenada de fases de Maven, en la que solicitar una fase posterior ejecuta las fases anteriores de ese ciclo.
Solicitar package normalmente ejecuta la fase de pruebas antes de producir el artefacto empaquetado del proyecto.
Clase de equivalencia
Una clase de equivalencia en pruebas es un grupo de entradas que la especificación trata de la misma manera y que se representa mediante casos de muestra útiles y límites.
Varios pesos positivos de la misma franja de peso comparten una tarifa, mientras que cero pertenece a una clase de entradas no válidas.
Complemento
Un complemento añade capacidades y configuración de construcción, y suele proporcionar tareas y convenciones para un tipo de proyecto concreto.
El complemento application de Gradle añade una tarea run para la clase principal de paquetes configurada.
Composición
La composición es una asociación entre el todo y sus partes en la que una parte tiene propiedad compuesta exclusiva de un solo todo a la vez; se muestra con un rombo relleno en el todo.
Un pedido y sus líneas pueden utilizar composición cuando eliminar el pedido también elimina esas partes.
Configuración de dependencias
Una configuración de dependencias define dónde está disponible una dependencia de Gradle, como en la implementación principal, la compilación de pruebas o la ejecución de pruebas.
Un lanzador de plataforma puede utilizar testRuntimeOnly porque los archivos fuente de prueba no lo necesitan durante la compilación.
Conflicto de nombres
Un conflicto de nombres ocurre cuando un nombre simple puede referirse a declaraciones que entran en conflicto, lo que exige que el código distinga el tipo previsto.
Un archivo que necesita dos tipos llamados List puede importar uno y utilizar el nombre completamente cualificado del otro.
Contrato de interfaz
Un contrato de interfaz especifica las operaciones que ofrece una implementación y permite a quienes realizan llamadas utilizar el tipo de interfaz sin elegir su implementación.
Un servicio puede llamar a save mediante el contrato del repositorio tanto si el almacenamiento utiliza memoria como un archivo.
Coordenadas del artefacto
Las coordenadas del artefacto identifican un proyecto o biblioteca Maven mediante su grupo, nombre de artefacto y versión para que la construcción pueda resolver el artefacto previsto.
Las coordenadas distinguen una versión de una biblioteca de JUnit de otra sin copiar archivos JAR a mano.

D

Declaración de importación
Una declaración de importación permite que un archivo fuente Java utilice un tipo o miembro estático con un nombre más corto sin cambiar su accesibilidad.
Una clase de interfaz que importa Scanner puede utilizar el nombre simple en la declaración de su campo en lugar de java.util.Scanner.
Declaración de paquete
Una declaración de paquete asigna los tipos de un archivo fuente a un paquete con nombre y aparece antes de sus importaciones y declaraciones de clase.
Escribir package com.example.parcels.domain; hace que ese paquete forme parte del nombre completo de la clase.
Dependencia
Una dependencia es una relación de uso en la que un elemento depende de otro y se dibuja como una línea discontinua con una punta de flecha abierta.
Una operación de tarifa que acepta un parámetro Parcel crea una dependencia de la definición del tipo de paquete.
Dependencia transitiva
Una dependencia transitiva es una biblioteca requerida por otra dependencia, que la herramienta de construcción resuelve junto con el artefacto solicitado directamente.
Declarar la dependencia agregada de Jupiter también incorpora los módulos necesarios para sus funciones de prueba.
Desarrollo guiado por pruebas
El desarrollo guiado por pruebas es un método de desarrollo que utiliza una prueba fallida para guiar un pequeño cambio de comportamiento antes de mejorar la estructura del código.
Un caso fallido de año de siglo guía el siguiente cambio en un comprobador de años bisiestos en lugar de una reescritura extensa.
Diagrama de clases UML
Un diagrama de clases UML modela la estructura estática del software mediante recuadros de tipos, atributos, operaciones y relaciones entre tipos.
Un diagrama de paquetes muestra qué almacena un envío sin seguir el orden de los comandos que introduce un usuario.
Directorio raíz del código fuente
Un directorio raíz del código fuente es la carpeta dentro de la cual comienzan las rutas de paquetes Java, como src/main/java para código de producción en una construcción estándar.
Para parcels.domain.Parcel, la ruta dentro del directorio raíz del código fuente es parcels/domain/Parcel.java.

E

Ensamblaje de objetos
El ensamblaje de objetos es la creación y conexión de objetos que colaboran en un programa, realizada habitualmente cerca del punto de entrada de la aplicación.
Un método main crea un lector y una calculadora, los pasa a un objeto de interfaz e inicia su diálogo.
Entorno de prueba
Un entorno de prueba es el conjunto de objetos y datos preparados para una prueba, que proporciona el estado inicial controlado que necesita su escenario.
Un envío vacío nuevo antes de cada prueba impide que las adiciones de paquetes se transmitan a otro escenario.
Expresión lambda
Una expresión lambda proporciona una pequeña operación como valor para que otro código pueda decidir cuándo y cómo ejecutarla.
La expresión lambda () -> calculator.feeInCents(0) permite que JUnit ejecute la llamada no válida dentro de su aserción.

G

Generalización
La generalización es una relación de subtipo UML dibujada con una línea continua y un triángulo vacío que apunta hacia el tipo más general.
Una línea desde Letter hacia Item se traduce a una declaración Java en la que Letter extiende Item.

H

Herramienta de construcción
Una herramienta de construcción automatiza tareas de proyecto como resolver dependencias, compilar código fuente, ejecutar pruebas y empaquetar artefactos.
Un comando de Maven puede compilar un proyecto de paquetes, ejecutar sus pruebas y producir el JAR resultante.

I

Implementación simplificada para pruebas
Una implementación simplificada para pruebas es una implementación sencilla y funcional de una dependencia utilizada en pruebas que proporciona un comportamiento utilizable con recursos controlados.
Un repositorio en memoria almacena paquetes para pruebas de servicio sin abrir ni modificar un archivo real.
Importación con comodín
Una importación con comodín permite utilizar bajo demanda los tipos accesibles de un paquete, sin poner a disposición los tipos de sus subpaquetes.
Importar 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
Una importación de un solo tipo permite utilizar un tipo accesible por su nombre simple e indica explícitamente qué paquete lo proporciona.
La declaración import java.util.ArrayList; identifica exactamente qué ArrayList utiliza el archivo.
Importación estática
Una importación estática permite que un archivo fuente nombre directamente un miembro estático accesible, omitiendo el nombre de la clase que lo declara.
Las pruebas de JUnit suelen importar estáticamente assertEquals para que cada comprobación se lea como una llamada directa de aserción.
Independencia de las pruebas
La independencia de las pruebas significa que cada prueba proporciona su propio estado requerido y puede pasar o fallar correctamente cuando se ejecuta sola o en cualquier orden.
Una prueba de recuento crea sus propios paquetes en lugar de depender de que un método anterior los añada.
Interfaz de usuario
Una interfaz de usuario es la parte de un programa que acepta acciones del usuario y presenta resultados, como un diálogo de comandos de texto.
Una interfaz de texto para una calculadora de tarifas pide los gramos, lee la respuesta, llama a la calculadora e imprime la tarifa devuelta.
Interfaz pública del paquete
La interfaz pública del paquete es el conjunto de tipos y operaciones accesibles que un paquete ofrece deliberadamente al código externo.
Un paquete de dominio puede exponer una calculadora de envíos mientras oculta la clase auxiliar que almacena sus franjas de peso.
Inyección de dependencias
La inyección de dependencias proporciona a un objeto sus colaboradores desde fuera, lo que permite a quien realiza la llamada seleccionar sus implementaciones o entradas.
Pasar un repositorio al constructor del servicio permite que la prueba elija un almacenamiento controlado.

J

JUnit Jupiter
JUnit Jupiter es el modelo de programación y motor de pruebas de JUnit que proporciona anotaciones, aserciones y extensiones modernas para pruebas Java.
Utilizar @Test de Jupiter mantiene la anotación de prueba coherente con el motor seleccionado por la construcción.
JUnit Platform
JUnit Platform es la base que descubre e inicia los motores de prueba compatibles, incluido el motor de pruebas de Jupiter.
Gradle la selecciona con useJUnitPlatform() cuando las pruebas del proyecto utilizan anotaciones de Jupiter.

L

Lanzador de la herramienta de construcción
Un lanzador de la herramienta de construcción es un programa proporcionado por el proyecto que obtiene y ejecuta la versión de la herramienta elegida por el proyecto para disponer de comandos repetibles.
Un lanzador de Maven incluido en el control de versiones permite a quienes contribuyen ejecutar la versión especificada de Maven con ./mvnw test.
Lista de pruebas
Una lista de pruebas registra los comportamientos y casos que aún deben cubrirse, lo que permite seleccionar un pequeño paso siguiente y añadir descubrimientos según sea necesario.
Una lista de años bisiestos incluye años ordinarios, años divisibles por cuatro y los dos casos de años de siglo.
Lista de versiones de dependencias
Una lista de versiones de dependencias es un conjunto de metadatos que alinea las versiones de módulos relacionados para que una construcción pueda mantener coherente una familia de bibliotecas.
La BOM de JUnit proporciona versiones de módulos compatibles después de seleccionar una versión común.
Lógica de la aplicación
La lógica de la aplicación es el código que implementa las reglas y operaciones de un programa sobre los datos, independientemente de su diálogo con el usuario.
Una calculadora de tarifas elige la franja de tarifa correcta sin pedir datos ni escribir en la consola.

M

Método del ciclo de vida
Un método del ciclo de vida es un método que el marco de pruebas ejecuta en un momento definido, como antes de cada prueba o una vez antes de toda la clase.
@BeforeEach reconstruye los objetos que cambian, mientras que @AfterEach puede eliminar recursos temporales de prueba.
Miembro estático
Un miembro estático pertenece a una clase en lugar de a un objeto individual y se representa subrayando su entrada de atributo u operación en UML.
Subrayar una constante de peso máximo distingue un valor compartido de la clase del peso de cada paquete.
Modificador de acceso
Un modificador de acceso es una palabra clave que controla qué código puede utilizar una declaración, como una clase, un constructor, un campo o un método.
Una clase pública puede tener un constructor al que una clase de interfaz de otro paquete no puede llamar.
Multiplicidad
La multiplicidad indica el número permitido de objetos vinculados en un extremo de una relación para un objeto del extremo opuesto.
Escribir 1..* junto a Parcel exige que cada envío mantenga al menos un paquete vinculado.

N

Navegabilidad
La navegabilidad describe si un objeto puede seguir una relación hacia otro objeto y suele mostrarse con una punta de flecha abierta.
Una flecha del envío al paquete muestra que un envío puede acceder a sus paquetes mediante referencias.
Nombre completamente cualificado
Un nombre completamente cualificado identifica un tipo Java combinando el nombre de su paquete con su nombre simple, eliminando la ambigüedad entre paquetes.
Utilizar java.util.List distingue la interfaz de colección de otro tipo que también se llama List.

O

Operación
Una operación es un comportamiento declarado en un tipo UML, descrito por su nombre, los tipos de sus parámetros, su visibilidad y su tipo de retorno.
La operación + totalWeightGrams(): int describe un método público sin indicar su algoritmo.

P

Paquete
Un paquete es un grupo con nombre de tipos Java que organiza código relacionado, separa los nombres de tipos y proporciona un límite de acceso.
Los paquetes de interfaz y de dominio separados mantienen la gestión de comandos aparte de las clases que calculan las tarifas de los paquetes.
Paquete sin nombre
El paquete sin nombre contiene tipos de archivos fuente sin declaraciones de paquete; los paquetes con nombre no pueden importar esos tipos.
Mover clases auxiliares compartidas a un paquete con nombre permite que las demás partes con nombre de un proyecto hagan referencia a ellas.
Preparar, actuar y comprobar
Preparar, actuar y comprobar es un patrón de escritura de pruebas que separa la preparación de un escenario, la realización de su acción y la comprobación de su resultado.
Crear un envío, añadir un paquete y comprobar su recuento dan a la prueba tres partes claras.
Prueba inestable
Una prueba inestable da resultados inconsistentes sin un cambio relevante en el código, a menudo porque no se controlan el tiempo, el estado compartido o los datos externos.
Una prueba que lee la fecha actual puede cambiar su resultado cuando cambia el calendario durante la noche.
Prueba parametrizada
Una prueba parametrizada ejecuta un método de prueba para varios conjuntos de argumentos proporcionados y registra cada invocación como un caso separado.
Una tabla de pesos y tarifas puede comprobar varios límites sin repetir el cuerpo del método de aserción.
Prueba unitaria
Una prueba unitaria es una comprobación automatizada de una pequeña unidad de código que compara el comportamiento observado con un resultado especificado de forma independiente.
Una prueba de calculadora llama al método de tarifa y comprueba mediante una aserción el número esperado de centavos sin iniciar la interfaz.

R

Realización
La realización es una relación en la que un tipo cumple un contrato de interfaz, mostrada con una línea discontinua y un triángulo vacío.
Un repositorio en memoria realiza la misma interfaz de repositorio que realiza un repositorio basado en archivos.
Refactorización
La refactorización cambia la organización interna del código mientras conserva su comportamiento observable, por ejemplo cambiando el nombre de un método o extrayéndolo.
Mover una clase auxiliar de tarifas a un paquete más adecuado es una refactorización cuando la calculadora sigue dando los mismos resultados.
Regresión
Una regresión es un defecto introducido en un comportamiento que antes funcionaba, a menudo después de un cambio en otra parte del código.
Una prueba existente del límite de tarifa detecta una regresión cuando una comparación cambia de inclusiva a estricta.
Repositorio
Un repositorio ofrece operaciones para almacenar y recuperar objetos de dominio mediante un contrato que puede tener distintas implementaciones.
Una prueba proporciona un repositorio en memoria para evitar depender de un archivo real de datos de paquetes.
Resultado esperado
Un resultado esperado es el resultado o comportamiento requerido por la especificación, determinado independientemente del código que se prueba.
Cuando los paquetes pesados se cobran por bloque de peso comenzado, la tarifa esperada se obtiene contando los bloques, no copiando la salida del método.
Rojo, verde y refactorización
Rojo, verde y refactorización es el ciclo de TDD que consiste en revelar un comportamiento ausente, hacer que pasen todas las pruebas actuales y mejorar la estructura conservando el comportamiento.
Después de que pase el nuevo caso de tarifa, extraer código de cálculo repetido pertenece al paso de refactorización.

S

Separación de responsabilidades
La separación de responsabilidades asigna tareas diferentes a distintas partes de un programa para que cada una pueda entenderse, modificarse y probarse más fácilmente.
Cambiar la redacción de un mensaje de entrada no debería requerir editar el método que calcula la tarifa de un paquete.
Símbolo de visibilidad
Un símbolo de visibilidad es un prefijo UML que indica el acceso: + público, - privado, # protegido o ~ acceso de paquete.
Un campo mostrado con - debe conservar la visibilidad privada cuando el diagrama se traduzca a Java.
Superclase
Una superclase es la clase de la que una subclase hereda miembros y comportamiento; UML coloca el triángulo de generalización en esta clase.
Tanto las cartas como los paquetes pueden heredar comportamiento común de un artículo sin repetirlo en cada recuadro de subclase.
Surefire
Surefire es el complemento de Maven que ejecuta pruebas unitarias durante la fase de pruebas utilizando los marcos de prueba y las reglas de descubrimiento configurados.
Dar a una clase de prueba el nombre ShippingCalculatorTest hace que coincida con un patrón de inclusión predeterminado de Surefire.

T

Tarea de Gradle
Una tarea de Gradle es una unidad de trabajo de construcción, como la compilación o las pruebas, que puede depender de otras tareas y reutilizar resultados sin cambios.
Ejecutar la tarea de construcción también incorpora el trabajo de compilación y pruebas necesario para esa construcción.
Tolerancia
Una tolerancia es una diferencia numérica permitida que utiliza una aserción cuando la igualdad exacta no es adecuada para un cálculo en coma flotante.
Una tolerancia pequeña y justificada acepta una diferencia de redondeo sin aceptar un resultado considerablemente incorrecto.

Sobre esta unidad

Por StudeliInformar de un error

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.

Comenzar