Las pruebas son una parte crucial en cualquier aplicación que vayamos a crear, sin importar la tecnología, siempre es recomendable realizar pruebas automáticas para probar el sistema cuando se implementen nuevos cambios; de esta forma nos ahorramos mucho tiempo ya que, no hay necesidad de realizar muchas de las pruebas de manera manual si no, ejecutando un simple comando.
Las pruebas consisten en probar los componentes de manera individual; en el caso de la aplicación que hemos construido, serían cada uno de los métodos de la API, al igual que cualquier otra dependencia de estos métodos; de esta manera, cuando se ejecutan estas pruebas automatizadas, si la aplicación pasa todas las pruebas, significa que no se encontraron errores, pero, si no pasa las pruebas, significa que hay que hacer cambios a nivel de la aplicación o pruebas implementadas.
Las pruebas ayudan a garantizar que su aplicación funcionará como se espera y a medida que la aplicación vaya creciendo en módulos y complejidad, se puedan implementar nuevas pruebas y adaptar las actuales.
Es importante mencionar que las pruebas no son perfectas, es decir, que, aunque la aplicación pase todas las pruebas no significa que la aplicación está libre de errores, pero sí es un buen indicador inicial de la calidad del software. Además, el código comprobable es generalmente una señal de una buena arquitectura de software.
Las pruebas deben de formar parte del ciclo de desarrollo de la aplicación para garantizar su buen funcionamiento ejecutando las mismas constantemente.
Las pruebas deberían centrarse en probar pequeñas unidades de código de forma aislada o individual.
Por ejemplo, en una aplicación Laravel o una aplicación web en general:
En Laravel, de manera oficial tenemos dos maneras de emplear las pruebas, mediante Pest y PHPUnit.
Las pruebas son la base de las pruebas en Laravel,con las cuales podemos probar de forma aislada los métodos que componen a nuestra aplicación.
PHPUnit es uno de los frameworks para realizar pruebas en PHP y que vienen ya instalado por defecto en Laravel, al ser el que ha formado parte de Laravel por más tiempo, es el que primero vamos a cubrir. Es importante aclarar que para seguir este apartado, debes de haber seleccionado PHPunit como ambiente de testing al momento de crear el proyecto en Laravel.
Al momento de crear un proyecto en Laravel, ya automáticamente también crea una carpeta de tests, con esto, puedes darte cuenta de lo importante que son las pruebas, que aunque no forman parte de desarrollo funcional de una aplicación, si forma parte del ciclo de vida de esta y crear las mismas es evidencia de buenas prácticas en la programación.
Como hemos comentado anteriormente, Laravel a partir de la versión 11, uno de los cambios más importantes es su limpieza de carpetas, quitando archivos y carpetas para fusionarlos con otros o generar bajo demanda otros como en el caso del api.php, pero, puedes ver que la carpeta de tests aún está presente apenas creamos el proyecto, dando una total evidencia de la importancia de la creación de estas pruebas para todas las aplicaciones que desarrollemos.
Por defecto, la carpeta de tests contiene dos carpetas:
Las pruebas unitarias son aquellas que probamos un módulo concreto de la aplicación, como dice su nombre es una unidad, algo que no podemos dividir, por ejemplo, un facade, un modelo, un helper son candidatos de pruebas unitarias, ya que tienen código aislado de la aplicación y no se comunican entre sí como en el caso de los controladores o componentes, estas pruebas se almacenan en tests/Unit.
Mientras que la carpeta de tests/Feature es la empleada para realizar pruebas de integración, como lo son los controladores o aquellos componentes que no son individuales como en el caso anterior si no, en donde ocurren muchas cosas como conexiones la base de datos, emplear helpers, facades o similares, retornas jsons, vistas, etc. Estas pruebas se conocen como pruebas de funciones en donde probamos bloques de códigos más grandes y que usualmente resuelven una respuesta HTTP completa.
Por defecto, ya Laravel viene con algunas pruebas y archivos listos para usar, una de las pruebas de ejemplo es el de ExampleTest.php y que trae el hola mundo para nuestra aplicación.
Independientemente si estás empleando Pest o PHPUnit, la lógica es la misma, lo que cambia es la sintaxis y para ejecutar nuestras pruebas tenemos el comando de:
$ vendor/bin/phpunit
Para PHPUnit, o:
$ vendor/bin/pest
Para Pest, o más fácil:
$ php artisan test
Para cualquiera de los anteriores.
Adicionalmente, puedes crear un archivo .env.testing en la raíz de su proyecto para manejar las configuraciones en ambiente prueba. Este archivo se utilizará en lugar del archivo .env cuando se ejecuten pruebas de Pest y PHPUnit o se ejecuten comandos de Artisan con la opción --env=testing.
Para crear una prueba unitaria, tenemos el siguiente comando:
$ php artisan make:test <ClassTest>
En donde la prueba se colocará en la carpeta de tests/Feature:
O si deseas crear una prueba dentro de la carpeta tests/Unit, puedes usar la opción --unit al ejecutar el comando make:test:
$ php artisan make:test <ClassTest> --unit
Más información en:
https://laravel.com/docs/master/testing
Para que se entienda de una manera menos abstracta el uso de las pruebas, vamos a crear un pequeño ejercicio de operaciones matemáticas antes de empezar a crear pruebas para probar módulos de nuestra aplicación, como serían en caso de los controladores, es decir, el esquema de request/response.
Para estas primeras pruebas, creemos un archivo de operaciones matemáticas como el siguiente:
app\Utils\MathOperations.php
class MathOperations
{
public function add($a, $b) {
return $a + $b;
}
public function subtract($a, $b) {
return $a - $b;
}
public function multiply($a, $b) {
return $a * $b;
}
public function divide($a, $b) {
return $a / $b;
}
}
Ahora vamos a generar nuestro primer archivo para la primera prueba unitaria mediante:
$ php artisan make:test MathOperationsTest --unit
Esto generará un archivo en:
tests/Unit/MathOperationsTest.php
En el cual, creamos unas funciones que permitan probar los métodos anteriores para realizar operaciones matemáticas:
tests/Unit/MathOperationsTest.php
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
// use App\Utils\MathOperations;
class MathOperations
{
public function add($a, $b)
{
return $a + $b;
}
public function subtract($a, $b)
{
return $a - $b;
}
public function multiply($a, $b)
{
return $a * $b;
}
public function divide($a, $b)
{
return $a / $b;
}
}
class MathOperationsTest extends TestCase
{
// public function test_example(): void
// {
// $this->assertTrue(true);
// }
public function testAdd()
{
$mathOperations = new MathOperations();
$result = $mathOperations->add(2, 3);
$this->assertEquals(5, $result);
}
public function testSubtract()
{
$mathOperations = new MathOperations();
$result = $mathOperations->subtract(5, 3);
$this->assertEquals(2, $result);
}
public function testSubtraction()
{
$mathOperations = new MathOperations();
$result = $mathOperations->multiply(5, 3);
$this->assertEquals(15, $result);
}
public function testDivide()
{
$mathOperations = new MathOperations();
$result = $mathOperations->divide(8, 2);
$this->assertEquals(4, $result);
}
}
Para facilitar el ejercicio, copiamos el contenido de MathOperations dentro del archivo unitario.
En este ejemplo, tenemos cuatro métodos de prueba, uno por cada método definido en la clase auxiliar MathOperations que permite probar las operaciones de suma, resta, multiplicación y división respectivamente y con esto podemos apreciar el corazón de las pruebas que es mediante métodos assert o métodos de tipo aserción:
Que no son más que condicionales con los cuales verificamos que se obtengan los resultados esperados, en este ejemplo, se emplea el método assertEquals pero existen varios con un funcionamiento similar e iremos viendo algunos de ellos a lo largo del capítulo.
Puedes ver la inmensa lista completa en:
https://laravel.com/docs/master/http-tests#response-assertions
Aunque no te preocupes por tener que aprenderlos todos, usualmente usamos unos pocos de ellos.
Finalmente, para ejecutar las pruebas unitarias, usamos el comando de:
$ vendor/bin/phpunit
Y deberíamos de ver una salida como la siguiente:
Time: 00:00.850, Memory: 42.50 MB
OK (29 tests, 65 assertions)
Si provocamos algún error en la la clase auxiliar, como sumar dos veces el mismo parámetro, ignorando el otro:
public function add($a, $b)
{
return $a + $a;
}
Y ejecutamos:
$ vendor/bin/phpunit
Veremos una salida como la siguiente:
/MathOperationsTest.php:47
FAILURES!
Tests: 29, Assertions: 65, Failures: 1.
Que indica claramente de que ocurrió un error.
Las pruebas unitarias no son infalibles, ya que, todo depende de las pruebas que ejecutemos, manteniendo el mismo error que provocamos antes, si la prueba fuera como la siguiente:
public function testAdd()
{
$mathOperations = new MathOperations();
$result = $mathOperations->add(2, 2);
$this->assertEquals(4, $result);
}
Las pruebas pasarían:
Time: 00:00.733, Memory: 42.50 MB
OK (29 tests, 65 assertions)
Pero, claramente tenemos un problema en la definición de la clase auxiliar, por lo tanto, las pruebas no son infalibles, son solamente un medio para verificar que no encontramos errores en la aplicación pero no significa de que la aplicación está libre de errores, con esto, podemos tener un entendimiento bsasçio y necesario de cómo funcionan las pruebas unitarias, con este ejemplo, podemos ahora a pasar a probar realmente módulos que conforman la aplicación.
Nuestra aplicación está formada por controladores o similares que son consumidos mediante peticiones HTTP de distintos tipos y es justamente lo que usualmente debemos de probar; las pruebas unitarias están formadas de dos bloques principales, los métodos de aspiración y los métodos http, que en PHPUnit, tenemos un método por cada método HTTP, es decir métodos get, post, put, patch, o delete; para poder enviar peticiones HTTP; debemos de heredar de la clase:
use Tests\TestCase;
Ahora sí vamos a comenzar cerrar todo esto realmente no sería necesario voy a dejar este aquí para que me ubique aquí la carpeta solamente y por aquí vamos a crear nuestra prueba para las categorías Bueno ya tú sabes cómo es la opción de make porque vamos a crear algo ese algo es una test y el nombre va a ser category test bueno puedes colocarle también category Api pero ahorita yo como estoy trabajando en proyectos distintos entonces con category está más que bien para mí pero si tú tienes ahí todo mezclado podría ser mejor category Api o lo colocas dentro de la carpeta de Api como tú quieras entonces Bueno ejecutamos no es una prueba unitaria sino una prueba de integración y aquí fíjate Donde está ubicado aquí estamos en unit y aquí sería la de feature No te preocupes tanto por esto esto, fíjate que ya por aquí tenemos una advertencia:
public function test_example(): void
{
$this->get('/');
}
Esto bueno también lo puedes quitar si quieres para que no genere ruido y por aquí el nombre que le voy a dar sería es y al recuerda que tienen que comenzar con test en bajo para que sea precisamente tomado como una prueba bueno Esto lo voy a dejar ahí Aunque me va a generar aquí un poquito de ruido cuando ejecute las pruebas, pero no importa entonces ejecutamos la prueba y veamos qué pasa y ahorita vuelvo a explicar un poquito el código No te preocupesY mira que por aquí falló algo bueno pasaron cinco ya sabes que es por lo que tenemos acá y fíjate que por aquí nos indica que no sabe qué demonios es el método llamado get que no existe. Entonces esto era lo que te comentaba antes por defecto estamos heredando de php unit framework Test Case que era lo que teníamos acá Cuando tenemos aquí una prueba unitaria pero por aquí estamos es:
use Tests\TestCase;
Haciendo una petición en este caso una ruta que no existe Pero no importa en mi caso al menos devuelve el welcome; el de php unit framework Test Case pero como Laravel es buena gente cuando creamos un feature que él parece que ASUME de que vamos a hacer otro tipo de pruebas en este caso estas pruebas de integración para probar la aplicación en sí y no módulo aislado como sería en este caso que en este caso no sería necesario utilizar este tipo de esquemas es decir emplear métodos de tipo get post ppad y delete porque que son métodos aislados es decir son para probar modelos o helpers o fate o lo que tú quieras entonces ya el automáticamente nos completa bastantes cosas que vamos a ocuparlas Claro está pero bueno de a poco entonces aquí podemos ver que ya esto no nos sirve así que lo tuviéramos que quitar y la que tenemos que heredar es de Test Case fíjate Aquí:
use Tests\TestCase;
Es importante que entiendas las importaciones esto es una importación interna que sería aquí de la carpeta vendor bueno la que tenemos por acá que forman parte del módulo o del proyecto en sí que sería la que tenemos aquí estas importaciones que comienzan con illuminate y toda la cuestión Pero estas ya puedes ver que son locales las cuales indican que está importando de la carpeta de Test que es justamente la que tenemos acá y el que tenemos aquí que es el test Case que er el que te mostraba antes que aquí puedes ver que tenemos código lar Por decirlo de alguna manera tenemos ahí una importación en la cual seguramente se incluyen Ahí todos esos métodos de tipo get post y delete no sé si la estén por aquí bueno no está pero seguramente están en algunos de estos que tenemos aquí entonces es justamente lo que tenemos que hacer esto tiene todo el sentido del mundo ya que por defecto php unit no permite este tipo de integración ya que esto es algo del Laravel, es decir.
Para hacer peticiones tiene todo el sentido del mundo que la gente de Laravel le incluye este tipo de paquetes.
Así que, lo colocamos esta sería la que vamos a emplear ahora y fíjate que ya desapareció el error ahora en mi caso creo que da un 404 porque creo que lo probé ayer y me daba un 404 cu o no Bueno perfecto no da 404 para ver igual puedo venir aquí no debería porque es la de Welcome pero no recuerdo ha sido un 404 entonces Bueno aquí simplemente hizo la petición y no estamos haciendo nada más y es lo que nos está indicando por acá nos dice Bueno perfecto ya todo está bien no tenemos el error anterior pero no estás haciendo ninguna aserción Ya que este es el sentido de las pruebas si es la petición y ya no estamos haciendo nada Entonces cuál sería la aserción precisamente la que tenemos por acá que la gente del L fue muy amable y nos ahorra trabajo sería emplear otro método de tipo aserción fíjate todos los que tenemos los nombres indican bastante pero si empezamos a escribir aquí dice una acer estatus de qué todos van a comenzar con aserción claro Está bueno Esto básicamente verifica el estado o el estatus de la petición.
Recuerda otra vez que por aquí lo podemos ver si venimos aquí más herramientas del desarrollador o control f12 Perdón por aquí en la parte de red cuando recargué vas a ver el código que en este caso es un 404 esto sería todo, la cuestión para que esto luzca bonito pero aquí tenemos un 404 si aquí colocamos Porque para mí esta ruta no existe si para ti existe entonces coloca algo que no exista por ejemplo Esto entonces por aquí pasaría por cualquiera que sea la razón de que quieres probar aquí un 404 que no tiene sentido Pero otra vez Es para presentar un poco todo esto por aquí deberían de pasar todas las pruebas Pero obviamente yo no quiero probar Esto entonces coloco que yo quiero un 200 que sería la respuesta que siempre obtenemos cuando la arabel nos devuelve una vista y aquí puedes ver que falla y el mundo se acabó y todo lo demás aquí indica que bueno lo es típico él devolvió un 404 Pero él esperaba un 200 y obviamente 404 no es igual a 200 aunque sean números pares Entonces el problema es lo mismo de siempre tienes que ver si es problema de la prueba que en este caso es o es otra cosa que la aplicación falló en este caso es obviamente la prueba Entonces por aquí tenemos que colocar lo que queramos probar que en este caso es Api category y al entonces con esto ejecutamos:
public function test_example(): void
{
$response = $this->get('/api/category/all');
dd($response)
$response->assertStatus(404);
}
y Fíjate que la prueba pasó exitosamente ya que esto es justamente lo que devuelve un 200 en En estos casos que tú puedes tener duda de que se está haciendo algo siempre siéntete libre de cambiar momentáneamente el código o bueno la prueba la aserción por algo que que sabes que no va a funcionar por ejemplo un 400 y ves que es lo que te dice aquí te dice que devolví un 200 y tú estás evaluando por un 404 Entonces lo cambiamos y todo el mundo feliz y aquí funciona exitosamente nuevamente.
Entonces es básicamente eso, espero que haya quedado claro cualquier comentario sabes que me lo puedes hacer bien entonces ya aquí finalmente está probando algo pero qué demonios hay ahí Entonces el siguiente tip que te voy a dar es que no dudes nunca en imprimir esto con nuestro mejor amigo que es el dd entonces imprimimos acá para ver qué demonios estamos obteniendo de respuesta:
dd($response)
Fíjate que Bueno aquí nos da mucha información no es precisamente esto que Bueno recuerda aquí yo también que estoy empleando aquí un sniped para digo una extensión en Google Chrome para que luzca bonito pero a la final esto es un JSON y el menos aquí aunque no quede muy claro podemos ver los datos aquí podemos ver que está devolviendo lo que tenemos en nuestra base de datos de desarrollo que bueno Esto lo corregimos luego ya que no es la base de datos que vamos a emplear pero, al menos podemos ver que s está haciendo algo entonces ya con esto nos podemos sentir más seguros de que está probando lo correcto entonces bueno aclarado esto siempre puedes imprimir aquí como te digo la respuesta para que veas esto lo siguiente que tenemos que hacer es probar que creo que lo va a hacer de una vez y luego vamos de a poco algo un poco más interesante que sería la Data es decir ya Nosotros sabemos que esta prueba tiene que devolver todas nuestras categorías entonces en caso de que no nos recordemos qué fue lo que hicimos podemos venir aquí entonces revisamos el método de al aquí tenemos perdón si este es el De Api aquí tenemos Perdón era el de categoría controller por aquí tenemos Aquí está Esto es lo que hacemos un un básicamente ahí puedes ver que es lo mismo entonces lo lógico es emplear lo mismo que estamos utilizando acá ya sea que te guste el get o el al Entonces por aquí podemos obtener las categorías obtenemos todo esto y bueno importamos la categoría dije categoría por los dioses categoría el modelo aquí están bueno Esto lo voy a dejar por aquí como te dije voy a organizarlo un poquito voy a dejar los míos aquí abajo mejor los del framework arriba y el mío aquí y esto lo dejo por ahí bueno Esto lo puedo quitar te lo V a dar aquí de referencia para cuando veas el código fuente y esto lo utilizamos luego bueno bajamos hasta acá otra vez entonces aquí obtenemos todas las categorías podemos ejecutar al menos para saber que no está dando errores, entonces, no dudes nunca cuando estás aprendiendo esto a ejecutar cada rato las pruebas.
Entonces le pasaríamos esto ojo no va a funcionar spoiler y esperamos lo peor como siempre entonces ejecutamos y falló cómo te:
public function test_all(): void
{
Category::factory(10)->create();
$categories = Category::get();
// dd($categories);
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $this->generateTokenAuth()
])
->get('/api/category/all');
// dd($response);
$response->assertStatus(200);
$response->assertJson($categories);
}
Indicaba aquí:
Collection is not callable
Nos está indicando clarito cuál es el problema nos está indicando que una colección no es valido, bueno no es tan clarito pero al menos nos está indicando que el problema es la colección Entonces qué podemos hacer Tenemos que convertir esto a algo que pueda manejar la Laravel, algo un poco curioso porque por aquí sí le podemos indicar a response Json directamente que trabaje con la colección pero en este caso para ese Método en particular al menos al momento en el cual estoy grabando esta clase no se encuentra integrado se esperaría que él haga la traducción Pero bueno Puede que no lo quieran implementar de esa forma en fin por la razón que sea entonces como veíamos antes básicamente las colecciones son los Arrays con vitaminas y Bueno podemos ir hacia atrás entonces aquí tenemos un método que es del Laravel Esto lo puedes emplear en cualquier parte ojo que no es algo aquí de las pruebas pudieras colocarlo aquí y mira que va a ser el mismo resultado claro aquí no tiene sentido porque no vas a hacer operaciones adicionales para nada realmente, entonces cualquier colección de datos entienda esa colección con lo que nos devuelve aquí el Laravel podemos obtener el array y otra vez si tienes dudas con esto podemos imprimirlo por acá imprimimos esto:
public function test_all(): void
{
Category::factory(10)->create();
$categories = Category::get()->toArray();
// dd($categories);
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $this->generateTokenAuth()
])
->get('/api/category/all');
// dd($response);
$response->assertStatus(200);
$response->assertJson($categories);
}
Y fíjate que tenemos es una array y antes teníamos era una bendita colección entonces ejecutamos y aquí tenemos toda este desastre que es una colección tal cual te indico por acá entonces probemos suerte ahora pasándole una array a ver qué pasa si este bendito método lo quiere tomar ejecutamos otra vez y fíjate que ahora sí lo tomo y funciona perfectamente entonces cada vez que quieras trabajar con una colección de datos simplemente conviértelos a una array y los pruebas por acá y eso sería prácticamente todo ya con eso completamos la primera prueba.
Fíjate que funciona correctamente, Esto no es lo que está devolviendo y por lo tanto nos va a dar una bonita bueno excepción se pudiera decir tal cual puedes ver y te voy a indicar que mira, esto es lo que tenemos Y esto es lo que está comparando estás loquito no funciona entonces corrige Tu prueba o corrige la aplicación la misma Bueno lo mismo de siempre ahí tienes que ver cuál es el problema y corregirlo obviamente en este caso obviamente sabemos que esto no es lo que le tenemos que pasar sio sería esto y eso sería prácticamente todo así que guardamos ejecutamos otra vez y todo funciona correctamente Estos son dos simples métodos que tenemos aquí lo siguiente que te digo es que cuando termines la sección O al menos cuando avances un un poco más te recomendaría enormemente que veas documentación en internet veas algunos post y bueno Más o menos tú también veas la experiencia de otras personas o la visión de otras personas yo te muestro la mía Pero tú no tienes que copiarlo tal cual yo lo estoy haciendo seguramente otras personas también realizan otros tipos de métodos de aserción para bueno garantizar de que precisamente estamos verificando algo ya que suponte que no sé aquí tenemos algún otro problema no se me ocurre nada y es por eso que creo que no hace falta hacer otro método de tipo aserción Pero puede que esto esté devolviendo algo extraño entonces a la final esa cosa extraña también la estamos comparando aquí no estamos haciendo nada Algo similar a lo que sucedía por acá con el método de a+ a cuando lo dejamos acá pero entonces siempre te recomendaría eso también otro tip que te doy por aquí Cuando haces un poquito más en todo esto ya entiendas Cómo funciona esto y tengas una visión un poco más amplia de esto te recomendaría que consultes alguna documentación algunos post en internet para que tengas la visión también de otras personas y tú mismo con todo esto básicamente te construyas tu propia forma de hacer las pruebas ya que las pruebas es algo Bastante personal más personal de la implementación de los códigos que ya el arel nos guía un poco de cómo la tenemos que hacer y esto es un poco como te digo más personal para la implementación como vamos a ir viendo poco a poco y a medida de que probemos algunos métodos más complejos que en este caso no tenemos tanto Porque es una aplicación de ejemplo de de un curso pero en el en el entorno real ya serían un poco más complejos seguramente así que pues nada como te digo también como Norma casi eso creo que s está bien realmente no no veo el motivo de por qué no esto siempre es lo primero que deberíamos de probar ya que si esto no existe no tiene sentido hacer ninguna otra prueba es decir suponte que por ejemplo aquí tumbamos la ruta otra vez en la prueba o a nivel del proyecto y ejecutamos lo primero que se tiene que topar es esto ya que no tiene sentido probar algo que no va a devolver un 200 A menos que no sé por cualquiera que se la razón tú configuraste que el código de estado d de alguna petición no devuelve un 200 que otra vez no entendería por qué no tiene sentido Pero esto es lo primero que tenemos que probar ya que es la entrada a todo lo demás si devuelve un 200 perfecto todo va bien entonces seguimos probando en base a lo esperado y Bueno aquí dice que devolvió un 500 por alguna razón y que bueno obviamente no es igual a 200 y ya con eso se detiene la prueba y ya con eso podemos empezar a hacer la corrección ya que si colocá esto aquí al inicio y dejáramos aquí más la prueba bueno la ruta aquí el error sería un poquito más extraño Bueno aquí al menos se devolvió que la ruta no ha sido encontrada al menos es un error bastante agradable pero creo que más directo es que veas aquí directamente el código que está devolviendo en vez de estar preguntándote por esto suponte que es una ruta que estamos aquí Armando nosotros entonces puede ser propenso errores de que no sabes si hay algún problema en la definición de la ruta o es precisamente aquí Entonces esto nos lo quitamos es colocando aquí que lo primero que evalúe es el 200.
- Andrés Cruz
Este material forma parte de mi curso y libro completo; puedes adquirirlos desde el apartado de libros y/o cursos Curso y Libro Laravel 11 con Tailwind Vue 3, introducción a Jetstream Livewire e Inerta desde cero - 2024.
Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter
Acepto recibir anuncios de interes sobre este Blog.
!Cursos desde!
10$
En Udemy
Quedan 3d 13:34!
!Cursos desde!
4$
En Academia
Ver los cursos!Libros desde!
1$
Ver los libros