Cómo probar una aplicación Spring Boot

C

Introducción

Tenga en cuenta: El siguiente artículo estará dedicado a probar las aplicaciones Spring Boot. Se supone que está familiarizado con al menos los conceptos básicos de Java, Maven y Spring Boot (controladores, dependencias, repositorio de bases de datos, etc.).

Hay una falta general de pruebas en la mayoría de las organizaciones. Tal vez incluso su equipo sea uno de esos equipos que tienen buenas intenciones en las pruebas, pero siempre se pospone u olvida a medida que los proyectos avanzan.

¿Por qué las pruebas son tan difíciles de hacer de manera constante? Los beneficios de las pruebas son bien conocidos y, sin embargo, ¿por qué se pasan por alto con tanta frecuencia?

Creo que hay un par de razones por las que las pruebas tienen menor importancia en la mayoría de los equipos. Primero, crear, integrar y mantener pruebas a menudo puede ser difícil. Y en segundo lugar, a menos que sea un ingeniero que haya realizado muchas pruebas y haya visto su importancia y valor, probablemente no lo colocará en un lugar destacado en su lista de prioridades para aprender y formar parte de su proceso de desarrollo.

Afortunadamente, Spring Boot está haciendo que la integración y el trabajo con pruebas sea más fácil que nunca.

Empezando con las pruebas de Spring Boot

Cuando se trata de pruebas, existen varios tipos diferentes de pruebas que puede escribir para ayudar a probar y automatizar el estado de su aplicación. Sin embargo, antes de que podamos comenzar a realizar pruebas, debemos integrar los marcos de prueba.

Con Spring Boot, eso significa que necesitamos agregar un iniciador a las dependencias de nuestro proyecto, para las pruebas solo necesitamos agregar la spring-boot-starter-testdependencia:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>{version}</version>
    <scope>test</scope>
</dependency>

Esta dependencia única lo preparará para la mayoría de sus necesidades de prueba.

JUnit y Hamcrest

El primer marco que integrará el iniciador de pruebas es JUnit .

JUnit ha existido durante mucho tiempo, y si alguna vez ha realizado pruebas unitarias en Java, lo más probable es que haya utilizado este marco antes. Al realizar pruebas unitarias básicas, JUnit y Spring se complementan bien, como verá en algunas demostraciones próximas. Aunque JUnit proporciona cierto soporte de afirmaciones para ayudar a analizar los resultados de las pruebas, Spring Boot también incorpora Hamcrest . Este marco proporciona afirmaciones y coincidencias de resultados de pruebas mejoradas, que cuando se combinan con JUnit le permiten automatizar sus pruebas de principio a fin.

Mockito

El siguiente marco que integra el iniciador de pruebas es Mockito . A veces, al probar, el código que está tratando de probar es una dependencia de otro objeto. A veces, es simplemente un fragmento de código que es difícil de respaldar para una prueba unitaria. En tales casos, usar un marco como Mockito para simular y eliminar esos objetos es la solución. De esta manera, puede continuar con sus pruebas y luego verificar qué se llamó y qué se usó en ese objeto después de ejecutar su prueba.

Herramientas de Spring

Por último, la dependencia del iniciador de prueba extrae las herramientas de prueba de Spring.

Estos incluyen anotaciones, utilidades de prueba y otro soporte de integración de pruebas que permiten trabajar con JUnit, Hamcrest y Mockito dentro del entorno Spring.

Iniciando el proyecto Spring Boot

En el resto de este artículo, configuraremos y trabajaremos con diferentes aspectos de prueba en nuestra aplicación Spring Boot.

En esta sección, vamos a configurar nuestra aplicación y entorno para realizar pruebas. Lo primero que debe suceder es agregar el spring-boot-starter-testa las dependencias de nuestro proyecto.

Solo después de agregarlo, podemos crear una prueba unitaria simple para ver cómo funcionan los conceptos básicos. Luego, vamos a querer cubrir un par de formas diferentes en las que puede ejecutar pruebas dentro de Spring Boot.

Puede crear el proyecto Spring Boot a través de su IDE o generarlo usando Spring Initializr .

En ambos casos, agregue la webdependencia, que incluye una test-starterdependencia en su proyecto, de lo contrario, tendrá que agregarla manualmente:

pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Al agregarlo manualmente, agregarlo al final del pom.xmlarchivo hará que Maven extraiga todas sus dependencias de recursos de prueba.

Una cosa a tener en cuenta sobre esta dependencia es que incluye el alcance de la prueba <scope>test</scope>. Eso significa que cuando la aplicación está empaquetada y empaquetada para la implementación, se ignoran las dependencias que se declaran con el alcance de la prueba. Las dependencias del alcance de la prueba solo están disponibles cuando se ejecutan en los modos de desarrollo y prueba de Maven.

Ahora que tenemos nuestras bibliotecas de prueba en su lugar, podemos seguir adelante y crear una prueba.

Prueba JUnit

Es la práctica más común que todo el código relacionado con las pruebas vaya a la src/test/javacarpeta. El arquetipo de Maven que generó el proyecto inicialmente incluía una clase de prueba llamada eg DemoApplicationTests– basada en el nombre de su clase principal, en ese mismo paquete.

Ahora solo necesitamos algo para probar.

Definamos un controlador simple en nuestra src/main/javacarpeta:

HomeController:

@RestController
public class HomeController {
    
    @RequestMapping("/")
    public String home() {
        return "Hello World!";
    }
}

Este controlador tiene un solo método, devuelve una cadena, que se ejecuta cuando accedemos a la raíz de nuestra aplicación. Se espera ese tipo de comportamiento de este controlador, pero probémoslo y veamos si se comporta correctamente:

JUnitControllerTest:

public class JUnitControllerTest {

    @Test
    public void testHomeController() {
        HomeController homeController = new HomeController();
        String result = homeController.home();
        assertEquals(result, "Hello World!");
    }
}

assertEqualses un método estático que proviene del org.junit.Assertpaquete, y solo uno de los assertionmétodos utilizados en JUnit:

asertEqualsComprueba si dos tipos u objetos primitivos son iguales.
AsertTrueComprueba si la condición de entrada es verdadera.
aseverarFalsoComprueba si la condición de entrada es falsa.
asertNotNullComprueba si un objeto no es nulo.
asertNullComprueba si un objeto es nulo.
afirmar lo mismoComprueba si dos referencias a objetos apuntan al mismo objeto en la memoria.
assertNotSameComprueba si dos referencias de objeto no apuntan al mismo objeto en la memoria.
assertArrayEqualsComprueba si dos matrices son iguales entre sí.

Comenzamos nuestra prueba instanciando nuestro HomeController. No es necesario depender de la inyección de dependencia para esto. Estamos usando el assertEqualsmétodo para verificar si el valor devuelto por nuestro método coincide con otra cadena.

Esta es una prueba unitaria simple, pero funcional y completa. Hemos integrado los marcos de prueba, creamos una prueba JUnit marcando el método con una @Testanotación, después de lo cual realizamos una afirmación de prueba.

Ahora, se supone que debemos ejecutar la prueba y observar el resultado, y hay varias formas de ejecutar las pruebas:

La primera forma es simplemente hacer clic derecho en toda la prueba o en el nombre de la prueba si desea ejecutar una sola prueba. Luego, seleccione “Ejecutar como JUnit”. Esto comienza la prueba en su IDE:

Si modificamos nuestra prueba y las cadenas ya no coinciden, se nos solicitará un tipo de resultado diferente:

Otra forma en que puede ejecutar las pruebas de su proyecto sería desde la línea de comando o la terminal; si tiene Maven configurado en la línea de comando y está trabajando con Maven, puede ejecutar el comando de prueba de Maven desde la raíz de su proyecto para obtener los mismos resultados:

$ mvn test

Prueba de Mockito

La prueba anterior que creamos fue muy simple. Devolvió texto estático, por lo que fue bastante fácil de probar.

A medida que las aplicaciones crecen en complejidad, no puede simplemente probar unidades de esta manera. El código que está probando puede tener dependencias de otro código administrado por Spring o es difícil de construir mediante una prueba. En ese caso, podemos usar Mockito para ayudarnos a probar.

Creemos una nueva clase de prueba dentro src/test/java:

@RestController
@RequestMapping("api/v1/")
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @RequestMapping(value = "user/{id}", method = RequestMethod.GET)
    public User get(@PathVariable Long id) {
        return userRepository.findOne(id);
    }
}

El objetivo de este método es obtener una Userrespuesta, idasí que vamos a probar si hace lo que se supone que debe hacer. Comenzamos creando una instancia de nuestro controlador y luego llamando al get()método:

public class MockitoControllerTest {

    @Test
    public void testGetUserById() {
        UserController userController = new UserController();
        User user = userController.get(1L);
        assertEquals(1l, user.getId().longValue());
    }
}

Pedimos el Usercon el idde 1. Luego, simplemente necesitamos ejecutar una aserción en el objeto devuelto para asegurarnos de que el idsea ​​de hecho igual a 1 como se esperaba.

Si ejecutamos la prueba, observe los resultados:

Nuestra prueba falló con un NullPointerException. Parece que userRepositoryes igual a null.

Esto se debe a que creamos una instancia de UserControllery no usamos Spring para inyectarlo, por lo que todos los objetos inyectados usados ​​por UserController, como userRepository, nunca fueron creados correctamente por Spring.

Este es el problema exacto para el que se crearon los marcos simulados. Al usar Mockito, podemos simular el userRepositorypara que nuestra prueba funcione:

public class MockitoControllerTest {

    @InjectMocks
    private UserController userController;

    @Mock
    private UserRepository userRepository;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testGetUserById() {
        User u = new User();
        u.setId(1l);
        when(userRepository.findOne(1l)).thenReturn(u);

        User user = userController.get(1L);

        verify(userRepository).findOne(1l);

        assertEquals(1l, user.getId().longValue());
    }
}

En lugar de instanciar el UserController, queremos crear una instancia simulada de él. Mockito proporciona una anotación que crea este objeto y lo inyecta en la prueba. Usamos la @InjectMocksanotación, y esto produce un atributo privado llamado userControllerque Mockito está administrando por nosotros.

A continuación, creamos el userRepositoryobjeto simulado y esto corrige nuestro NullPointerExceptioncuando probamos el controlador. Usamos otra anotación de Mockito para eso – @Mock.

A continuación, agregamos el método de configuración que inicializa todos los objetos simulados juntos cuando se ejecuta la prueba. El método anotado con @Beforese ejecuta antes de cada método de prueba. El init()método se ejecuta MockitoAnnotations.initMocks(this)utilizando thisinstancia como argumento. Esto configura nuestras simulaciones antes de cada prueba.

Pasar la thisinstancia hará que Mockito reconozca las @InjectMocksy las @Mocksanotaciones y que deben juntarse.

En este caso, dado que UserControllercontiene un userRepository, el marco Mockito seguirá adelante y lo configurará para nosotros, al igual que lo haría el contenedor Spring mediante la inyección de dependencias.

Puede que le sorprenda el uso de when()en este ejemplo. Es otra staticimportación proporcionada por Mockito. Repasemos esto paso a paso y su propósito quedará claro.

En primer lugar, hemos creado un new Userobjeto y lo hemos puesto iden 1. El when()método nos permite proporcionar el comportamiento de burla real. Esto le está diciendo a Mockito que cuando findOnese llama al método en el repositorio, la devolución Userdebe ser apilada.

Lo que esto significa es que la clase devuelta es falsa con valores de retorno preprogramados y no un objeto devuelto real de la base de datos. Esto nos permite probar la unidad sin tener que conectarnos a la base de datos ni a Spring.

La otra característica útil que ofrece Mockito es la capacidad de verifyque nuestras llamadas a métodos simulados o stubped se utilicen realmente durante el proceso de prueba.

Podemos colocar una verifymarca para ver si el método stubbed realmente se llama, en nuestro caso findOne(). Esta es otra forma en la que puede desarrollar qué tan bien está funcionando su código.

Si, por alguna razón, el controlador llamó al findOne()método más de una vez, la prueba fallaría instantáneamente y le proporcionaría información que ayudaría a identificar la lógica ofensiva y corregirla con elegancia.

Después de aplicar estos cambios, volver a ejecutar la prueba dará como resultado un hermoso pase verde:

Puede ver que las posibilidades ahora se vuelven infinitas al probar unidades, incluso código complejo. Se necesita algo más de tiempo para configurarlo, pero ahora puede probar controladores, servicios o cualquier otro tipo de objeto, sin tener que ejecutar una prueba de integración que inicie el contenedor Spring.

Las pruebas unitarias con objetos simulados son rápidas, mucho más rápidas que las pruebas de integración.

Matchers de Hamcrest

En la prueba anterior, usamos tanto las aserciones de JUnit para verificar los resultados de la prueba como las de Mockito verifypara asegurarnos de que los objetos simulados se llamaran correctamente. Dicho esto, ¿por qué integrar otro marco para manejar la coincidencia y las afirmaciones de los resultados de las pruebas?

Hamcrest proporciona un enfoque declarativo más legible para afirmar y hacer coincidir los resultados de sus pruebas. Muchos desarrolladores están comenzando a preferir el azúcar sintáctico de Hamcrest sobre los otros métodos de afirmación. Para ver cómo funciona Hamcrest, volvemos a MockitoControllerTestprobar ubicado en la src/test/javacarpeta:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

public class MockitoControllerTest {

    @InjectMocks
    private UserController userController;

    @Mock
    private UserRepository userRepository;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testGetUserById() {
        User u = new User();
        u.setId(1l);
        when(userRepository.findOne(1l)).thenReturn(u);

        User user = userController.get(1L);

        verify(userRepository).findOne(1l);

        //assertEquals(1l, user.getId().longValue());
        assertThat(user.getId(), is(1l));
    }
}

En el testGetUserById()caso de prueba, la prueba se realiza utilizando una aserción JUnit – assertEquals. Comprueba si el valor iddel objeto devuelto coincide con 1 o no.

Podemos cambiar esto con el assertThat()método estático de Hamcrest . Tenga en cuenta que la lógica de la prueba, así como el código que se está probando, permanecen intactos; de hecho, la nueva línea es lógicamente equivalente a la aserción JUnit anterior.

Esto todavía plantea la pregunta, si lógicamente son iguales, ¿por qué incluir otro marco? Comparando estas dos afirmaciones, es evidente que la afirmación de Hamcrest es más legible y menos ofensiva para los ojos. Además, es más fácil ver cuál es el objetivo final de la afirmación de la prueba con solo un vistazo.

La afirmación assertThat()es una simple comparación de igualdad, aunque Hamcrest ofrece muchas opciones y coincidencias además de esta. Incluirlos todos en una tabla sería una locura, así que consulte la documentación oficial de Hamcrest si está interesado en leer sobre ellos.

Pruebas de integración

El último tipo de prueba que cubriremos es el concepto de prueba de integración.

Las pruebas de integración consisten en probar todas las piezas de una aplicación que funcionan juntas como lo harían en un entorno de producción o en vivo. Esto significa que nuestra aplicación esencialmente necesita estar ejecutándose para probarla. Debido a la naturaleza de las pruebas de integración, esto plantea algunos desafíos al crear y ejecutar este tipo de pruebas.

Antes de Spring Boot, existían algunos desafíos que las aplicaciones de Spring tendían a enfrentar.

Problemas de pruebas de integración

Aplicaciones tradicionales de Spring

Los contenedores son difíciles de probar:

Cualquier código que sea parte de su aplicación que se base en el contenedor o la especificación del servlet es difícil de probar porque necesita probar el inicio del contenedor y ejecutar pruebas en él, o necesita simular el contenedor y emularlo en algún Otra manera.

Spring Context debe estar disponible:

Desde Spring Core, Spring Beans y la inyección de dependencia requieren que Spring se ejecute y administre esas piezas en el contexto de su aplicación. Todas las pruebas de integración deben garantizar que se esté ejecutando el contexto de la aplicación Spring.

El inicio de la aplicación / prueba puede ser lento:

Iniciar el contexto de Spring y ejecutar o emular el contenedor puede llevar tiempo en aplicaciones más grandes. Las pruebas de integración se ejecutan naturalmente más lentamente que las pruebas unitarias simples. Puede imaginar que a medida que agrega más y más pruebas de integración, el tiempo de prueba requerido para ejecutarlas todas puede aumentar drásticamente.

El estado de la base de datos debe ser coherente:

Si sus pruebas de integración están modificando la base de datos, o si espera que ciertos datos en la base de datos estén allí para su caso de prueba, entonces puede tener problemas si no puede hacer que su base de datos se mantenga consistente cada vez que ejecuta sus pruebas.

Aplicaciones de Spring Boot

Sin contenedor, más fácil de iniciar la aplicación:

Debido a que las aplicaciones Spring Boot se pueden iniciar como una aplicación Java simple, se elimina la complejidad de lidiar con un contenedor e implementar su aplicación. Por supuesto, Spring Boot todavía tiene un contenedor integrado, pero Spring Boot simplemente hace que iniciar y manejar su aplicación sea mucho más fácil.

Configuración automática de Spring Context:

Las pruebas de integración en Spring Boot aún necesitan tener un contexto Spring. La principal diferencia entre Spring Boot y las aplicaciones tradicionales de Spring es el uso de arrancadores y la configuración automática. Esto facilita un poco el apuntalamiento del contenedor Spring con Spring Boot.

El inicio de la aplicación / prueba puede ser lento:

El inicio de la prueba de integración y el tiempo de ejecución siguen siendo problemas en el entorno Spring Boot. Cuanto más grande sea su aplicación y más componentes Spring tenga, más tiempo llevará iniciar su aplicación.

El estado de la base de datos debe ser coherente:

La consistencia de la base de datos también sigue siendo un problema con las pruebas de Spring Boot.

Con todos estos problemas, las pruebas de integración siguen siendo una de las mejores formas de asegurarse de que su aplicación, en su conjunto, funciona según lo previsto y diseñado.

Cuando se trata de pruebas de integración, las aplicaciones Spring Boot realmente comienzan a brillar sobre las aplicaciones Spring convencionales. Para convertir cualquier prueba JUnit en una prueba de integración adecuada, hay realmente dos cosas básicas que debe hacer.

La primera es que debe anotar sus pruebas con la @RunWithanotación y especificar que desea ejecutarlas con el SpringJUnit4ClassRunner.class.

El segundo es que debe agregar la @SpringApplicationConfigurationanotación y proporcionar su clase Spring Boot principal para su aplicación.

Esta prueba se encargará de probar el UserRepositoryobjeto Spring Data:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Demo.class)
public class UserRepoIntegrationTest {
    @Autowired
    private UserRepository userRepository;

    @Test
    public void testFindAll() {
        List<User> users = userRepository.findAll();
        assertThat(users.size(), is(greaterThanOrEqualTo(0)));
    }
}

Esta prueba le pide al repositorio a todos los usuarios, y luego usa Hamcrest para asegurarse de que la lista de retorno sea mayor o igual a 0. Ahora, cuando se inicie la prueba, se cargará el contexto Spring y Spring inyectará el total userRepositoryen la prueba, tal como lo haría si se ejecutara en una aplicación estándar.

Independientemente del resultado de la prueba, exitoso o no, abra la pestaña de la Consola IDE y debería notar que parece que su aplicación se inició (logotipo de Spring, información, etc.). Esto sucede porque nuestra aplicación realmente comienza con pruebas de integración. Básicamente, cada prueba de integración iniciará su aplicación, y esta es una de las razones por las que las pruebas de integración pueden llevar un tiempo si tiene una aplicación realmente grande o si tiene muchas pruebas.

Puede pensar que ha alcanzado el cenit de las pruebas con Spring Boot, pero hay un área que todavía no cubrimos en absoluto y es la API REST real que exponen sus controladores.

Ahora que a JavaScript, MVC y a las aplicaciones móviles les gusta tener una API REST con la que hablar, en lugar de una página JSP con plantilla, es bueno poder probar esa API real. Eso, a su vez, prueba toda la pila del lado del servidor. Este es el concepto de una prueba de integración web.

Spring proporciona una anotación simple que marca una prueba de integración como una prueba de integración web @WebIntegrationTest. Como de costumbre, dentro de su src/test/javacarpeta cree una clase:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Demo.class)
@WebIntegrationTest
public class UserControllerWebIntegrationTest {

    @Test
    public void testListAll() throws IOException {
        RestTemplate restTemplate = new TestRestTemplate();
        ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:8080/api/v1/users", String.class);

        assertThat(response.getStatusCode(), equalTo(HttpStatus.OK));

        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode responseJson = objectMapper.readTree(response.getBody());

        assertThat(responseJson.isMissingNode(), is(false));
        assertThat(responseJson.toString(), equalTo("[]"));
    }
}

Lo primero que estamos haciendo es crear una plantilla REST: RestTemplatecómo podemos llamar mediante programación a las API y, en este caso, queremos llamar mediante programación a la API de usuario.

La llamada a la API solicita a todos los usuarios del sistema y retiene la respuesta. Aunque codifiqué la URL del servidor aquí con fines de tutorial, esto es algo que podría, y definitivamente debería mover al application.propertiesarchivo, o configurar sus pruebas para que apunte a su entorno de prueba, en lugar de su entorno de desarrollo.

A continuación, ejecutamos una aserción para asegurarnos de obtener una 200 OKdevolución como respuesta; si no, la prueba fallará inmediatamente.

Luego, queremos convertir la respuesta en un objeto JSON real y realizar afirmaciones en él para asegurarnos de que el objeto JSON devuelto esté en un estado que tenga sentido para nuestra aplicación.

Dado que nuestra base de datos en realidad no contiene ningún usuario y, francamente, no existe, lo comprobaremos para asegurarnos de que obtengamos una matriz vacía como nuestra carga útil JSON equalTo("[]").

Nuevamente, estas pruebas son muy costosas de ejecutar, por lo que pueden ser algo que desee configurar solo en un servidor de compilación continua y ejecutarlas cada vez que alguien de su equipo verifique
algo o agregue algo a su repositorio de código.

Conclusión

Dado que Spring Boot integra JUnit, Mockito y Hamcrest, quería cubrir cómo usar estas herramientas dentro de una aplicación Spring Boot. Al igual que la mayoría de los marcos que hemos incluido con nuestros casos de prueba de Spring Boot, puede y debe dedicar un tiempo a examinar cada uno de los marcos por su cuenta, ya que proporcionan herramientas realmente útiles para el desarrollo.

Comenzar a escribir pruebas en sus proyectos realmente no requiere mucho esfuerzo cuando se integra con Spring Boot. Empiece a adquirir el hábito de probar porque definitivamente lo diferenciará de otros desarrolladores. Le ayudará a escribir código de mayor calidad, ya que esencialmente hará una revisión del código de su propio trabajo mientras intenta probarlo.

Como ha visto con los ejemplos que hemos cubierto, hay muchas opciones con las pruebas en una aplicación Spring Boot. Y a pesar de que hemos cubierto ligeramente lo que es posible, debe acostumbrarse a escribir algunas pruebas para parte del código que produce en su aplicación, incluso si todavía está aprendiendo y probando con el código. Cuanto más haga, más fácil será con el tiempo.

Recuerde que Spring Boot tiene que ver con la gestión de dependencias con sus iniciadores. Esos iniciadores a menudo proporcionan una configuración automática que integra los marcos en su aplicación de manera fácil y rápida.

Luego, puede anular y personalizar las necesidades de la aplicación utilizando el application.propertiesarchivo. Spring Boot lo libera de las implementaciones de contenedores al incrustar el contenedor dentro de la aplicación, por lo que ahora puede ejecutar su aplicación Java en cualquier lugar. Esto facilita mucho las implementaciones en la nube o cosas como probar su aplicación.

Asegúrese de tomarse un tiempo y consultar la documentación oficial de prueba de Spring Boot para obtener más información.

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad