Teste de integração Java explicado com exemplos

Pruebas de integración de Java explicadas con ejemplos

Explore el mundo de las pruebas de integración de Java con nuestra guía completa. Comprender herramientas, procesos y mejores prácticas, complementados con ejemplos prácticos.

Imagem em destaque

A medida que los sistemas de software se vuelven más grandes y complejos, con componentes y servicios interactuando de maneras complejas, las pruebas de integración se han vuelto indispensables. Al validar que todos los componentes y módulos funcionan correctamente cuando se combinan, las pruebas de integración de Java brindan confianza de que el sistema general funcionará según lo previsto.

Con el surgimiento de arquitecturas modulares, microservicios y la implementación automatizada, la verificación temprana de estas interacciones complejas mediante pruebas de integración es ahora una disciplina central. Las pruebas de integración sólidas identifican defectos que surgen de las interacciones de los componentes que las pruebas unitarias por sí solas no pueden detectar. Aprovechar un marco de pruebas de integración de Java puede ayudar a agilizar el proceso al garantizar que todos los módulos y componentes se evalúen minuciosamente.

En el mundo actual de entrega continua y DevOps, donde las iteraciones rápidas y las actualizaciones frecuentes son la norma, las pruebas de integración confiables son imprescindibles para garantizar la calidad y reducir la deuda técnica.

Este artículo explora las herramientas y técnicas para realizar pruebas de integración efectivas en Java. Para gestionar múltiples pruebas de integración de manera eficiente, es común agruparlas en un conjunto de pruebas. Cada conjunto de pruebas normalmente consta de varias clases de prueba, donde cada clase puede representar una característica o componente específico que se está probando. Entonces, ya sea que esté trabajando para una de las principales empresas de servicios de desarrollo de Java o sea un estudiante que busca mejorar sus habilidades de prueba, lo tenemos cubierto.

¿Qué son las pruebas unitarias?

Las pruebas unitarias son un proceso de prueba de software en el que se prueban unidades individuales de código, como métodos, clases y módulos, para ver si funcionan como se espera. Es el primer paso en el ciclo de vida de las pruebas de software. Las pruebas unitarias en Java generalmente se realizan con JUnit . El propósito de las pruebas unitarias es aislar y verificar la exactitud de unidades individuales de su código. Una prueba unitaria fallida puede proporcionar indicaciones tempranas de problemas potenciales, pero las pruebas de integración garantizarán aún más la cohesión y funcionalidad de todo el sistema.

Imagínese el proceso de ensamblar un automóvil. Antes de ensamblar los componentes, cada componente se prueba rigurosamente. Esto es más eficiente y requiere menos tiempo a largo plazo. Ahora imagine si todos los componentes se ensamblaran sin las pruebas adecuadas y luego el automóvil no funcionara. Se necesitará mucho tiempo y esfuerzo para descubrir qué pieza está defectuosa, y mucho más para arreglarla.

Por eso necesitamos pruebas unitarias. Facilita la identificación temprana de errores y ahorra tiempo de desarrollo.

¿Qué son las pruebas de integración de Java?

Las pruebas de integración son un enfoque de prueba de software en el que se acoplan y prueban diferentes módulos. El objetivo es verificar que los módulos funcionen según lo previsto cuando se acoplan. Generalmente se realiza después de las pruebas unitarias y antes de las pruebas del sistema.

Las pruebas de integración son especialmente importantes para aplicaciones que constan de múltiples capas y componentes que se comunican entre sí y con sistemas o servicios externos.

Imagine que está construyendo una computadora personal (PC) desde cero. La PC consta de varios componentes, como la placa base, el procesador, la memoria, los dispositivos de almacenamiento, la tarjeta gráfica, etc. Ha probado todos los componentes previamente. Pero cuando los integras en un sistema, no funcionan. La razón no es que los componentes individuales estén defectuosos, sino más bien porque no son compatibles entre sí. Las pruebas de integración nos ayudan a identificar este tipo de errores.

Diferencias entre pruebas de integración y pruebas unitarias

Alcance: Las pruebas unitarias tienen como objetivo probar las unidades de código comprobables más pequeñas, mientras que las pruebas de integración se centran en probar la interacción de varios componentes de un sistema.

Complejidad: las pruebas unitarias tienden a ser más simples y más enfocadas, ya que tratan con componentes individuales de forma aislada. Se pueden escribir y ejecutar con relativa facilidad. Las pruebas de integración, por otro lado, son generalmente más complejas debido a la necesidad de coordinar y verificar las interacciones entre múltiples componentes. Requieren un mayor nivel de instalación y configuración para simular con precisión escenarios del mundo real.

Orden de ejecución: generalmente, las pruebas unitarias se realizan antes de las pruebas de integración. Primero debemos comprobar que las unidades individuales funcionan. Sólo entonces podremos integrarlos en módulos más grandes y probar sus relaciones.

Enfoque diferente para las pruebas de integración.

Existen diferentes estrategias para realizar pruebas de integración. Los más comunes son el enfoque del Big Bang y el enfoque incremental (de arriba hacia abajo y de abajo hacia arriba).

Big Bang

En este enfoque, la mayoría de los módulos de software se acoplan y prueban. Es adecuado para sistemas pequeños con menos módulos. En algunos casos, los componentes de un sistema pueden estar estrechamente acoplados o ser altamente interdependientes, lo que hace que la integración incremental sea difícil o poco práctica.

Ventajas del Big Bang

  1. Conveniente para sistemas más pequeños.
  2. Requiere menos tiempo en comparación con otros enfoques.

Desventajas del Big Bang

  1. Sin embargo, con este enfoque es difícil localizar la causa raíz de los defectos encontrados durante las pruebas.
  2. Dado que estás probando la mayoría de los módulos a la vez, es fácil pasar por alto algunas integraciones.
  3. Más difícil de mantener a medida que aumenta la complejidad del proyecto.
  4. Tienes que esperar a que se desarrollen la mayoría de los módulos.

Enfoque de abajo hacia arriba

El enfoque ascendente se centra en desarrollar y probar los módulos independientes de nivel más bajo de un sistema antes de integrarlos en módulos más grandes. Estos módulos se prueban y luego se integran en módulos aún más grandes hasta que todo el sistema esté integrado y probado.

En este enfoque, las pruebas comienzan con los componentes más simples y luego avanzan, agregando y probando componentes de nivel superior hasta que se construye y prueba todo el sistema.

La mayor ventaja de este tipo de pruebas es que no es necesario esperar a que se desarrollen todos los módulos. En su lugar, puede escribir pruebas para aquellas que ya se han creado. La mayor desventaja es que no tienes mucha claridad sobre el comportamiento de tus módulos críticos.

Beneficios

  1. De abajo hacia arriba enfatiza la codificación y las pruebas iniciales, que pueden comenzar tan pronto como se especifica el primer módulo.
  2. Dado que el proceso de prueba se inicia desde el módulo de bajo nivel, hay mucha claridad y es fácil escribir pruebas.
  3. No es necesario conocer los detalles del diseño estructural.
  4. Es más fácil desarrollar condiciones de prueba en general ya que se comienza en el nivel más bajo.

Desventajas

  1. Si el sistema consta de un mayor número de submódulos, este método resulta lento y complejo.
  2. Los desarrolladores no tienen una idea clara sobre el comportamiento de los módulos críticos.

Enfoque de arriba hacia abajo

Las pruebas de integración de Java de arriba hacia abajo son un enfoque de prueba de software en el que el proceso de prueba comienza desde los módulos más críticos del sistema de software y avanza gradualmente hacia los módulos de nivel inferior. Implica probar la integración e interacción entre diferentes componentes del sistema de software de manera jerárquica.

En las pruebas de integración de arriba hacia abajo, los módulos de nivel superior se prueban primero, mientras que los módulos de nivel inferior se reemplazan con resguardos o versiones simuladas que brindan el comportamiento esperado de los módulos de nivel inferior. A medida que avanzan las pruebas, los módulos de nivel inferior se incorporan y prueban gradualmente junto con los módulos de nivel superior.

Beneficios

  1. Los módulos críticos se prueban primero.
  2. Los desarrolladores tienen una idea clara sobre el comportamiento de las funcionalidades críticas de las aplicaciones.
  3. Fácil de detectar problemas en el nivel superior.
  4. Es más fácil aislar errores de interfaz y de transferencia de datos debido a la naturaleza incremental y de arriba hacia abajo del proceso de prueba.

Desventajas

  1. Requiere el uso de simulaciones, stubs y espías.
  2. Todavía tenemos que esperar por el desarrollo de módulos críticos.

Enfoque mixto (sándwich)

El enfoque mixto o sándwich es la combinación del enfoque ascendente y el enfoque descendente. Normalmente, con este enfoque tiene varias capas, cada una de las cuales se construye utilizando un enfoque de arriba hacia abajo o de abajo hacia arriba.

Pasos involucrados en las pruebas de integración.

Elegir las herramientas y marcos adecuados

Java es un lenguaje de programación popular de alto nivel. Tiene un vasto ecosistema de marcos y bibliotecas para pruebas. Estas son algunas de las herramientas más utilizadas para las pruebas de integración:

  1. JUnit5: un marco de pruebas ampliamente utilizado para Java que se puede utilizar para escribir pruebas unitarias y pruebas de integración.
  2. TestNG: otro marco de prueba popular que proporciona características como ejecución de pruebas paralelas y flexibilidad de configuración de pruebas.
  3. Prueba de Spring Boot: si está trabajando con Spring Boot, este módulo proporciona un amplio soporte para las pruebas de integración de Java, incluida la anotación @SpringBootTest.
  4. Mockito: un poderoso marco de simulación que le permite simular dependencias y concentrarse en probar componentes específicos de forma aislada.
  5. Contenedores de prueba: una biblioteca de Java que le permite definir y ejecutar contenedores Docker para sus dependencias durante las pruebas.

A lo largo de este artículo utilizaremos JUnit5 como nuestro marco de prueba principal. Tendremos uno o dos ejemplos con el marco Spring Boot Test. Estos marcos tienen una sintaxis muy intuitiva, por lo que es fácil de seguir incluso si no estás usando JUnit5.

Entorno de prueba de configuración

Configurar un entorno de prueba es el primer paso para las pruebas de integración de Java. Lo ideal sería configurar una base de datos, simular algunas dependencias y agregar datos de prueba.

Agregar datos de prueba

Antes de comenzar una prueba, puede completar su base de datos utilizando la anotación Junit5 @BeforeEach.

 @DataJpaTest
 clase pública UserRepositoryIntegrationTest {

  @autocableado
  repositorio de usuarios privado repositorio de usuarios;

  @Antes deCada
  setUpTestData público vacío {
    Usuario usuario1 = nuevo Usuario("John Doe", "(correo electrónico protegido)");
    Usuario usuario2 = nuevo Usuario("Jane Smith", "(correo electrónico protegido)");

    userRepository.save(usuario1);    userRepository.save(usuario2);
  }
 }

Simulaciones y talones

La burla y el stubbing le ayudan a aislar sus dependencias o imitar una dependencia que aún no se ha implementado. En el siguiente ejemplo, estamos creando un simulacro para la clase UserRepository.

 importar org.junit.jupiter.api.Test;
 importar estática org.junit.jupiter.api.Assertions.assertEquals;
 importar org.mockito.Mockito.* estático;

 prueba de servicio de usuario de clase pública {

  @Prueba
  prueba pública vacíaCrearUsuario {
    UserRepository userRepository = simulacro(UserRepository.class); 
UserService userService = nuevo UserService(userRepository);
 Usuario usuario = nuevo Usuario("John Doe", "(correo electrónico protegido)");

 cuando(userRepository.save(usuario)).entoncesReturn(true);
 resultado booleano = userService.createUser(usuario);

 verificar (repositorio de usuarios, tiempos (1)). guardar (usuario);
 afirmarEquals(verdadero, resultado);
 }
 }

Configurar la base de datos H2

H2 es una base de datos en memoria que se utiliza a menudo para realizar pruebas, ya que es rápida y liviana. Para configurar H2 para pruebas de integración, puede utilizar la anotación @DataJpaTest proporcionada por Spring Boot. Esta anotación configura una base de datos en memoria y puede usar sus repositorios JPA para interactuar con ella.

 importar org.junit.jupiter.api.Test;
 importar org.springframework.beans.factory.annotation.Autowired;
 importar org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
 importar estática org.junit.jupiter.api.Assertions.assertEquals;

 @DataJpaTest
 clase pública UserRepositoryIntegrationTest {

  @autocableado 
repositorio de usuarios privado repositorio de usuarios;

 @Prueba
 prueba pública vacíaSaveUser {
 Usuario usuario = nuevo Usuario("John", "(correo electrónico protegido)");
 userRepository.save(usuario);
 Usuario guardadoUsuario = userRepository.findByEmail("(correo electrónico protegido)");
 afirmarEquals(usuario.getName, saveUser.getName);
 afirmarEquals(usuario.getEmail, usuarioguardado.getEmail);
 }
 }

Una cosa a tener en cuenta es que es posible que su aplicación no utilice H2 como base de datos principal. En este caso, no está imitando su entorno de producción. Si desea utilizar bases de datos PostgreSQL o MongoDB en su entorno de prueba, debe utilizar contenedores (que se analizan más adelante).

Grabación e informes

Una de las formas más sencillas de registrar sus pruebas es mediante registros. Los registros pueden ayudarle a identificar o replicar rápidamente la causa raíz de cualquier defecto en su aplicación.

A continuación se muestra una configuración de registro sencilla utilizando Logback y SLF4J. Logback nos ayuda a personalizar los mensajes registrados. Podemos proporcionar detalles como fecha, hora, hilo, nivel de registro, seguimiento y más. Para comenzar, cree un archivo logback.xml y agregue la configuración como se muestra a continuación.

 <configuración>
  <appender nombre="STDOUT" >
    <codificador>
      <pattern>%d{HH:mm:ss.SSS} (%thread) %nivel - %msg%n</pattern>
    </codificador>
  </appender>

  <nivel raíz="depurar">
    <appender-ref ref="STDOUT" />
  </raíz>
 </configuración>

Aquí, %d{HH:mm:ss.SSS} imprime la hora (H para horas, m para minutos, s para segundos y S para milisegundos). %thread, %level, %msg y %n imprimen el nombre del hilo, el nivel de registro, el mensaje y la nueva línea respectivamente. El appender creado anteriormente muestra información en la salida estándar. Pero puedes hacer cosas como almacenar los registros en un archivo.

Ahora podemos usar la fachada Logger de SLF4J en nuestro código Java.

 importar org.slf4j.Logger;
 importar org.slf4j.LoggerFactory;

 prueba de ejemplo de clase pública {
  registrador final estático privado
      = LoggerFactory.getLogger(ExampleTest.class);

  público estático vacío principal (argumentos de cadena) {
    logger.info ("Hola desde {}", EjemploTest.class.getSimpleName);
  }
 }

Si lo ejecuta, el resultado será algo como esto.

 14:45:01.260 (principal) INFORMACIÓN: Hola desde EjemploTest

Ejecución de pruebas en contenedores.

Los contenedores Docker son una de las mejores formas de imitar su entorno de producción, ya que en muchos casos su propia aplicación se ejecuta dentro de algunos contenedores remotos. Para estas demostraciones, escribiremos una prueba simple y luego la ejecutaremos dentro de un contenedor acoplable.

 // SampleController.java
 @RestController
 clase pública SampleController {
  @GetMapping("/hola")
  cadena pública decir hola {
    devolver "Hola mundo";
  }
 }

 //AplicaciónMuestra.java 
@SpringBootAplicación
 aplicación de muestra de clase pública {
 público estático vacío principal (argumentos de cadena) {
 SpringApplication.run(SampleApplication.clase, argumentos);
 }
 }

El código anterior crea una API REST simple que devuelve "Hola mundo". Podemos usar Spring Boot Test para probar esta API. Aquí simplemente estamos comprobando si la API devuelve una respuesta o no y el cuerpo de la respuesta debe contener "Hola mundo".

 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
 clase Pruebas de aplicación de muestra {

  @PuertoServidorLocal
  puerto internacional privado;

  @autocableado
  testRestTemplate privado restTemplate;

  @Prueba
  prueba de anulación públicaHelloEndpoint {
    ResponseEntity<String> respuesta = restTemplate.getForEntity(" + puerto + "/hola", String.class);
    afirmarEquals(HttpStatus.OK, respuesta.getStatusCode);
    afirmarEquals("¡Hola mundo!", respuesta.getBody);
  }
 }

Ahora puede crear un Dockerfile y agregar la siguiente configuración. El Dockerfile contiene instrucciones sobre cómo crear su aplicación.

 DE openjdk:17-jre-slim
 DIRTRABAJO /aplicación
 COPIAR destino/sample-application.jar .
 CMD ("java", "-jar", "sample-application.jar")

Ejecute el siguiente comando para compilar e iniciar su contenedor acoplable.

 docker build -t aplicación-de-muestra.
 docker run -d -p 8080:8080 aplicación de muestra

Puede utilizar Docker-Compose para organizar varios contenedores. Un desafío con Docker Compose es cuando ejecuta pruebas de integración; no puede cambiar la configuración durante el tiempo de ejecución. Además, sus contenedores seguirán funcionando incluso si sus pruebas fallan. En su lugar, usaremos una biblioteca llamada testcontainers para iniciar y detener dinámicamente los contenedores.

Contenedores de prueba

Testcontainers es una biblioteca de Java que simplifica el proceso de ejecutar contenedores desechables y aislados para pruebas de integración. Proporciona contenedores livianos y preconfigurados para bases de datos populares (por ejemplo, PostgreSQL, MySQL, Oracle, MongoDB), intermediarios de mensajes (por ejemplo, Kafka, RabbitMQ), servidores web (por ejemplo, Tomcat, Jetty) y más.

Con Testcontainers, puede definir y administrar contenedores en sus pruebas de integración de Java, lo que le permite realizar pruebas con instancias reales de dependencias sin la necesidad de una infraestructura externa. Testcontainers maneja la gestión del ciclo de vida de los contenedores, el aprovisionamiento automático y la integración con marcos de prueba como JUnit o TestNG.

 importar org.junit.jupiter.api.Test;
 importar org.testcontainers.containers.PostgreSQLContainer;
 importar org.testcontainers.junit.jupiter.Container;
 importar org.testcontainers.junit.jupiter.Testcontainers;
 importar org.junit.Assert.* estático;

 @Contenedores de prueba 
clase pública PostgreSQLIntegrationTest {

 @Envase
 PostgreSQLContainer final estático privado<?> postgresContainer = nuevo PostgreSQLContainer<>("postgres:latest")
 .withDatabaseName("db_name")
 .con nombre de usuario ("johndoe")
 .withPassword("contraseña_aleatoria");

 @Prueba
 prueba pública vacíaPostgreSQLContainer {
 afirmarTrue(postgresContainer.isRunning)
 }
 }

Mejores prácticas al escribir pruebas de integración

Comience a escribir pruebas unitarias y de integración desde cero

En el enfoque tradicional en cascada, las tareas se ejecutan de forma secuencial. Las pruebas suelen entrar en juego en las últimas etapas del ciclo de desarrollo. Dado que su aplicación se prueba más tarde, las posibilidades de que los errores pasen desapercibidos y lleguen a producción son bastante altas.

Por el contrario, con el enfoque ágil, empiezas a escribir tus pruebas desde el principio. Garantiza que cada vez que realice un pequeño cambio en su código base, recibirá comentarios inmediatos sobre si sus cambios tienen algún efecto en el código base existente. Si una prueba unitaria falla y se da cuenta de que hay un problema, puede resolverlo inmediatamente antes de que se convierta en un gran problema en etapas posteriores. Esta es la principal ventaja del enfoque ágil, donde escribir pruebas con anticipación proporciona retroalimentación continua, lo que dificulta la introducción de errores en cualquier etapa.

Priorizar pruebas

Las pruebas de integración pueden ser lentas y, en escenarios en los que requieren mucho tiempo y recursos, ejecutarlas repetidamente resulta poco práctico. En estas situaciones, priorizar sus pruebas puede ahorrarle mucho tiempo valioso. Puede priorizar las pruebas en función de factores como el nivel de riesgo asociado con una falla, la complejidad de la funcionalidad que se prueba y el impacto potencial en los usuarios finales o en el sistema en su conjunto.

En Junit5, utilizando clases de prueba, puede agregar etiquetas a sus pruebas y priorizarlas. Aquí hay un ejemplo.

 // Pruebas de muestra.java
 pruebas de muestra de clase pública {
 @Prueba
 @Tag("ALTO")
 prueba de anulación públicaCriticalFunctionalidad {}

 @Prueba
 @Tag("BAJO")
 prueba de vacío públicoLessCriticalFunctionality {}
 }

 // HighPriorityTestSuite.java
 // solo probando las integraciones de alta prioridad
 importar org.junit.platform.suite.api.IncludeTags;
 importar org.junit.platform.suite.api.SelectPackages;
 importar org.junit.platform.suite.api.Suite;

 @Suite
 @IncludeTags("ALTO") 
clase pública HighPriorityTestSuite {}

Imita los entornos de producción lo más fielmente posible.

Cree entornos de prueba que se parezcan lo más posible al entorno de producción. Esto incluye configuraciones, bases de datos, condiciones de red y cualquier dependencia externa similar.

Diseñar casos de prueba para todos los escenarios.

Diseñe casos de prueba y decida el método de prueba adecuado para todos los escenarios. Cree casos de prueba y métodos de prueba que cubran múltiples escenarios y casos extremos para garantizar la máxima cobertura. Pruebe escenarios positivos y realice pruebas de integración negativas para verificar el comportamiento del sistema en diferentes situaciones.

Registre y reporte sus pruebas

Cuando una prueba de integración falla, especialmente en grandes proyectos de software, puede llevar mucho tiempo identificar la causa. Después de una prueba de integración fallida, es beneficioso registrar las ejecuciones de prueba para que pueda identificar fácilmente la causa raíz o reproducir el problema.

Conclusión

En este artículo, exploramos el concepto, los beneficios y varios marcos de pruebas de integración de Java utilizados para las pruebas de integración en Java. También cubrimos cómo utilizar marcos como JUnit5, Spring Boot Test, TestContainers y Logback para escribir pruebas de integración efectivas. Las pruebas de integración son esenciales, por lo que las pruebas de integración desempeñan un papel crucial para garantizar el rendimiento, la calidad y la funcionalidad de nuestras aplicaciones Java. También nos permite comprobar las interacciones y dependencias entre diferentes componentes y capas. Le animamos a explorar más sobre el tema tratado aquí.

Preguntas frecuentes

¿Cómo puedo garantizar la coherencia de los datos de prueba durante las pruebas de integración?

Para las pruebas de integración, es esencial configurar y gestionar datos de prueba coherentes para los distintos componentes. Puede lograr esto utilizando fábricas de datos de prueba, bases de datos de prueba o mecanismos de propagación de datos.

¿Cuáles son los desafíos comunes en las pruebas de integración de Java?

Los desafíos en las pruebas de integración de Java pueden incluir el manejo de dependencias externas, como bases de datos, servicios o API, la gestión de configuraciones del entorno de prueba y garantizar que las pruebas se ejecuten de manera eficiente y repetible.

¿Qué es una “prueba de contrato” en el contexto de las pruebas de integración?

Las pruebas de contrato son una forma de prueba de integración que verifica la compatibilidad y la comunicación entre diferentes servicios o componentes en función de sus contratos definidos (por ejemplo, especificaciones API o formatos de mensajes).

¿Cómo puedo garantizar la calidad del código antes de ejecutar pruebas de integración?

Antes de ejecutar pruebas de integración, es beneficioso realizar un análisis de código estático. Este proceso implica examinar el código del software sin ejecutarlo realmente, con el objetivo de detectar vulnerabilidades, posibles errores y áreas de mejora. El análisis de código estático garantiza que el código cumpla con los estándares de calidad, siga las mejores prácticas y esté libre de errores comunes, lo que sienta una base sólida para las fases de prueba posteriores.

¿Qué herramientas de compilación de Java pueden ayudar a configurar entornos de prueba de integración?

Maven y Gradle son herramientas de compilación de Java que le ayudan a configurar entornos de prueba de integración. Sus complementos y configuraciones administran dependencias y ejecutan conjuntos de pruebas para estandarizar las pruebas entre equipos.

Si te gustó esto, asegúrate de consultar uno de nuestros otros artículos sobre Java:

  • Los pros y los contras del desarrollo de Java
  • Los 10 frameworks Java más populares
  • ¿Para qué se utiliza Java? 8 cosas que puedes crear
  • Los 7 mejores marcos de prueba de Java en 2021
  • Clutch.co nombra a BairesDev como los principales desarrolladores de PHP y Java

Fuente: BairesDev

Regresar al blog

Deja un comentario

Ten en cuenta que los comentarios deben aprobarse antes de que se publiquen.